#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2023 JELOS (https://github.com/JustEnoughLinuxOS)
#               2021-present pkegg

. /etc/profile

set -e
set -o pipefail

### Summary
#   This script listens to input events and takes actions.
###

### Enable logging
case $(get_setting system.loglevel) in
  verbose)
    DEBUG=true
  ;;
  *)
    DEBUG=false
  ;;
esac

### Define matching values from evtest, but allow them to be overridden in system.cfg.
KEY_VOLUME_UP=$(get_setting key.volume.up)
if [ -z "${KEY_VOLUME_UP}" ]
then
  KEY_VOLUME_UP='KEY_VOLUME*UP'
fi

KEY_VOLUME_DOWN=$(get_setting key.volume.down)
if [ -z "${KEY_VOLUME_DOWN}" ]
then
  KEY_VOLUME_DOWN='KEY_VOLUME*DOWN'
fi

FUNCTION_VOLUME_UP_EVENT='*('${KEY_VOLUME_UP}'), value 1'
FUNCTION_VOLUME_DOWN_EVENT='*('${KEY_VOLUME_DOWN}'), value 1'

### Define matching function hotkeys from controller values, but allow them to be overridden in system.cfg.
KEYA_MODIFIER=$(get_setting key.function.a)
if [ -z "${KEYA_MODIFIER}" ]
then
  KEYA_MODIFIER="${DEVICE_FUNC_KEYA_MODIFIER}"
fi

KEYB_MODIFIER=$(get_setting key.function.b)
if [ -z "${KEYB_MODIFIER}" ]
then
  KEYB_MODIFIER="${DEVICE_FUNC_KEYB_MODIFIER}"
fi

FUNCTION_MODIFIER_A_EVENT='*('${KEYA_MODIFIER}'), value *'
FUNCTION_MODIFIER_B_EVENT='*('${KEYB_MODIFIER}'), value *'

BTN_HOTKEY_A_MODIFIER=$(get_setting key.hotkey.a)
if [ -z "${BTN_HOTKEY_A_MODIFIER}" ]
then
  BTN_HOTKEY_A_MODIFIER="BTN_TL"
fi

BTN_HOTKEY_B_MODIFIER=$(get_setting key.hotkey.b)
if [ -z "${BTN_HOTKEY_B_MODIFIER}" ]
then
  BTN_HOTKEY_B_MODIFIER="BTN_SELECT"
fi

BTN_HOTKEY_C_MODIFIER=$(get_setting key.hotkey.c)
if [ -z "${BTN_HOTKEY_C_MODIFIER}" ]
then
  BTN_HOTKEY_C_MODIFIER="BTN_START"
fi

HOTKEY_A_PRESSED=false
HOTKEY_B_PRESSED=false
HOTKEY_C_PRESSED=false

FUNCTION_HOTKEY_A_EVENT='*('${BTN_HOTKEY_A_MODIFIER}'), value *'
FUNCTION_HOTKEY_B_EVENT='*('${BTN_HOTKEY_B_MODIFIER}'), value *'
FUNCTION_HOTKEY_C_EVENT='*('${BTN_HOTKEY_C_MODIFIER}'), value *'

### Define static values for dpad buttons and as a hat.
FUNCTION_HOTKEY_DPAD_UP_EVENT="*(BTN_DPAD_UP), value 1*"
FUNCTION_HOTKEY_DPAD_DOWN_EVENT="*(BTN_DPAD_DOWN), value 1*"
FUNCTION_HOTKEY_DPAD_RIGHT_EVENT="*(BTN_DPAD_RIGHT), value 1*"
FUNCTION_HOTKEY_DPAD_LEFT_EVENT="*(BTN_DPAD_LEFT), value 1*"

FUNCTION_HOTKEY_HAT_UP_EVENT="*(ABS_HAT0Y), value -1*"
FUNCTION_HOTKEY_HAT_DOWN_EVENT="*(ABS_HAT0Y), value 1*"
FUNCTION_HOTKEY_HAT_RIGHT_EVENT="*(ABS_HAT0X), value 1*"
FUNCTION_HOTKEY_HAT_LEFT_EVENT="*(ABS_HAT0X), value -1*"

DPAD_EVENTS=$(get_setting key.dpad.events)
if [ -z "${DPAD_EVENTS}" ] || \
   [ "${DPAD_EVENTS}" = "0" ]
then
  DPAD_EVENTS=false
else
  DPAD_EVENTS=true
fi

CONTROLLER_DISCONNECTED="*error reading: No such device"
DEVICE_DISCONNECTED="*error reading: No such device"

### Matches if a button was pressed (1), released (0) or held down (2)
PRESS='value [1-9]'
RELEASE='value 0'

### Function buttons should be defined as not pressed for later matching.
FN_A_PRESSED=false
FN_B_PRESSED=false

### Allow actions to be overriden in system.cfg
FN_A_ACTION_UP="$(get_setting key.function.a.up)"
FN_A_ACTION_DOWN="$(get_setting key.function.a.down)"

FN_B_ACTION_UP="$(get_setting key.function.b.up)"
FN_B_ACTION_DOWN="$(get_setting key.function.b.down)"

FN_AB_ACTION_UP="$(get_setting key.function.ab.up)"
FN_AB_ACTION_DOWN="$(get_setting key.function.ab.down)"

### Set sane defaults to manage volume, brightness, wireless on/off, and led on/off.
if [ -z "${FN_A_ACTION_UP}" ]
then
  FN_A_ACTION_UP="brightness up"
fi

if [ -z "${FN_A_ACTION_DOWN}" ]
then
  FN_A_ACTION_DOWN="brightness down"
fi

if [ -z "${FN_B_ACTION_UP}" ]
then
  FN_B_ACTION_UP="ledcontrol"
fi

if [ -z "${FN_B_ACTION_DOWN}" ]
then
  FN_B_ACTION_DOWN="ledcontrol poweroff"
fi

if [ -z "${FN_AB_ACTION_UP}" ]
then
  FN_AB_ACTION_UP="wifictl enable"
fi

if [ -z "${FN_AB_ACTION_DOWN}" ]
then
  FN_AB_ACTION_DOWN="wifictl disable"
fi

### Simple functions to execute button actions, including global kill.
execute_kill() {
  if [ -e "/tmp/.process-kill-data" ]
  then
    TO_KILL="$(cat /tmp/.process-kill-data)"
    ${DEBUG} && log $0 "Execute: killall ${TO_KILL}"
    killall ${TO_KILL} 2>/dev/null
  fi
}

execute_action() {
    ${DEBUG} && log $0 "${1}|${FN_A_PRESSED}|${FN_B_PRESSED}|${DPAD_UP_PRESSED}|${DPAD_DOWN_PRESSED}|${DPAD_RIGHT_PRESSED}|${DPAD_LEFT_PRESSED}"
    if [ "${FN_A_PRESSED}" = true ] && \
       [ "${FN_B_PRESSED}" = true ]
    then
        ${DEBUG} && log $0 "Executing FN_AB action"
        case ${1} in
            up)
                ${DEBUG} && log $0 "FN_AB (${FN_AB_ACTION_UP})"
                ${FN_AB_ACTION_UP}
            ;;
            down)
                ${DEBUG} && log $0 "FN_AB (${FN_AB_ACTION_DOWN})"
                ${FN_AB_ACTION_DOWN}
            ;;
        esac
    elif [ "${FN_A_PRESSED}" = true ] && \
         [ "${FN_B_PRESSED}" = false ]
    then
        ${DEBUG} && log $0 "Executing FN_A action"
        case ${1} in
            up)
                ${DEBUG} && log $0 "FN_A (${FN_A_ACTION_UP})"
                ${FN_A_ACTION_UP}
            ;;
            down)
                ${DEBUG} && log $0 "FN_A (${FN_A_ACTION_DOWN})"
                ${FN_A_ACTION_DOWN}
            ;;
        esac
    elif [ "${FN_A_PRESSED}" = false ] && \
         [ "${FN_B_PRESSED}" = true ]
    then
        ${DEBUG} && log $0 "Executing FN_B action"
        case ${1} in
            up)
                ${DEBUG} && log $0 "FN_B (${FN_B_ACTION_UP})"
                ${FN_B_ACTION_UP}
            ;;
            down)
                ${DEBUG} && log $0 "FN_B (${FN_B_ACTION_DOWN})"
                ${FN_B_ACTION_DOWN}
            ;;
        esac
    else
        volume ${1}
    fi
}

### Search the system for useful devices to monitor for inputs.
get_devices() {
  KJDEVS=false
  FOUNDKEYS=false
  FOUNDJOY=false
  RETRY=5
  while [ ${KJDEVS} = false ]
  do
    # Detect input devices automatically
    for DEV in /dev/input/ev*
    do
      unset SUPPORTS
      SUPPORTS=$(udevadm info ${DEV} | awk '/ID_INPUT_KEY=|ID_INPUT_JOYSTICK=/ {print $2}')
      if [ -n "${SUPPORTS}" ]
      then
        DEVICE=$(udevadm info ${DEV} | awk 'BEGIN {FS="="} /DEVNAME=/ {print $2}')
        INPUT_DEVICES+=("${DEVICE}")
        if [[ "${SUPPORTS}" =~ ID_INPUT_KEY ]]
        then
          ${DEBUG} && log $0 "Found Keyboard: ${DEVICE}"
          FOUNDKEYS=true
        elif [[ "${SUPPORTS}" =~ ID_INPUT_JOYSTICK ]]
        then
          ${DEBUG} && log $0 "Found Joystick: ${DEVICE}"
          FOUNDJOY=true
        fi
      fi
    done
    if [ "${FOUNDKEYS}" = "true" ] &&
       [ "${FOUNDJOY}" = "true" ]
    then
      ${DEBUG} && log $0 "Found all of the needed devices."
      KJDEVS=true
      break
    fi
    if [ "${RETRY}" -ge 5 ]
    then
      ${DEBUG} && log $0 "Did not find all of the needed devices, but that may be OK.  Breaking."
      break
    else
      RETRY=$(( ${RETRY} + 1 ))
    fi
    sleep 1
  done
}

get_devices

# If the input devices change, it may be a new controller
# so handle it here.
mkcontroller 2>/dev/null ||:


### Go into a cpu friendly loop that idles until a key is pressed.  Take action when a known pattern of keys are pressed together.
(
   for INPUT_DEVICE in ${INPUT_DEVICES[@]}
   do
     evtest "${INPUT_DEVICE}" 2>&1 &
   done
   wait
) | while read line; do
    case ${line} in
        (${CONTROLLER_DISCONNECTED})
        ${DEBUG} && log $0 "Reloading due to ${CONTROLLER_DEVICE} reattach..."
        get_devices
        ;;
        (${DEVICE_DISCONNECTED})
        ${DEBUG} && log $0 "Reloading due to ${DEVICE} reattach..."
        get_devices
        ;;
        (${FUNCTION_MODIFIER_A_EVENT})
           if [[ "${line}" =~ ${PRESS} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_MODIFIER_A_EVENT}: Pressed"
              FN_A_PRESSED=true
           elif [[ "${line}" =~ ${RELEASE} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_MODIFIER_A_EVENT}: Released"
              FN_A_PRESSED=false
           fi
        ;;
        (${FUNCTION_MODIFIER_B_EVENT})
           if [[ "${line}" =~ ${PRESS} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_MODIFIER_B_EVENT}: Pressed"
              FN_B_PRESSED=true
           elif [[ "${line}" =~ ${RELEASE} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_MODIFIER_B_EVENT}: Released"
              FN_B_PRESSED=false
           fi
        ;;
        (${FUNCTION_VOLUME_UP_EVENT})
            execute_action up
        ;;
        (${FUNCTION_VOLUME_DOWN_EVENT})
            execute_action down
        ;;
    esac

    case ${line} in
        (${FUNCTION_HOTKEY_A_EVENT})
           if [[ "${line}" =~ ${PRESS} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_HOTKEY_A_EVENT}: Pressed"
              HOTKEY_A_PRESSED=true
           elif [[ "${line}" =~ ${RELEASE} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_HOTKEY_A_EVENT}: Released"
              HOTKEY_A_PRESSED=false
           fi
        ;;
        (${FUNCTION_HOTKEY_B_EVENT})
           if [[ "${line}" =~ ${PRESS} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_HOTKEY_B_EVENT}: Pressed"
              HOTKEY_B_PRESSED=true
              if [ "${HOTKEY_A_PRESSED}" = true ] && \
                 [ "${HOTKEY_C_PRESSED}" = true ]
              then
                execute_kill
              fi
           elif [[ "${line}" =~ ${RELEASE} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_HOTKEY_B_EVENT}: Released"
              HOTKEY_B_PRESSED=false
           fi
        ;;
        (${FUNCTION_HOTKEY_C_EVENT})
           if [[ "${line}" =~ ${PRESS} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_HOTKEY_C_EVENT}: Pressed"
              HOTKEY_C_PRESSED=true
              if [ "${HOTKEY_A_PRESSED}" = true ] && \
                 [ "${HOTKEY_B_PRESSED}" = true ]
              then
                execute_kill
              fi
           elif [[ "${line}" =~ ${RELEASE} ]]
           then
              ${DEBUG} && log $0 "${FUNCTION_HOTKEY_C_EVENT}: Released"
              HOTKEY_C_PRESSED=false
           fi
        ;;
        (${FUNCTION_HOTKEY_DPAD_UP_EVENT}|${FUNCTION_HOTKEY_HAT_UP_EVENT})
            if [ "${FN_A_PRESSED}" = true ] && \
               [ "${DPAD_EVENTS}" = true ]
            then
              ${DEBUG} && log $0 "${FUNCTION_DPAD_UP_EVENT}${FUNCTION_HOTKEY_HAT_UP_EVENT}: Volume Up"
              volume up
            fi
        ;;
        (${FUNCTION_HOTKEY_DPAD_DOWN_EVENT}|${FUNCTION_HOTKEY_HAT_DOWN_EVENT})
            if [ "${FN_A_PRESSED}" = true ] && \
               [ "${DPAD_EVENTS}" = true ]
            then
              ${DEBUG} && log $0 "${FUNCTION_DPAD_DOWN_EVENT}${FUNCTION_HOTKEY_HAT_DOWN_EVENT}: Volume Down"
              volume down
            fi
        ;;
        (${FUNCTION_HOTKEY_DPAD_RIGHT_EVENT}|${FUNCTION_HOTKEY_HAT_RIGHT_EVENT})
            if [ "${FN_A_PRESSED}" = true ] && \
               [ "${DPAD_EVENTS}" = true ]
            then
              ${DEBUG} && log $0 "${FUNCTION_DPAD_RIGHT_EVENT}${FUNCTION_HOTKEY_HAT_RIGHT_EVENT}: Brightness Up"
              brightness up
            fi
        ;;
        (${FUNCTION_HOTKEY_DPAD_LEFT_EVENT}|${FUNCTION_HOTKEY_HAT_LEFT_EVENT})
            if [ "${FN_A_PRESSED}" = true ] && \
               [ "${DPAD_EVENTS}" = true ]
            then
              ${DEBUG} && log $0 "${FUNCTION_DPAD_LEFT_EVENT}${FUNCTION_HOTKEY_HAT_LEFT_EVENT}: Brightness Down"
              brightness down
            fi
        ;;
    esac
done
