Ao1Pointblank / xinput-mouse-keybinds

simple shell script to make application-specific mouse button actions possible
0 stars 0 forks source link

This whole project was stupid and clunky. I revamped it. #1

Open Ao1Pointblank opened 4 months ago

Ao1Pointblank commented 4 months ago

The Problem

while i am proud of the old button10.sh script's ability to restart itself via a lock file, making updating binds very easy, and of the method of detecting when a button10 press was made, via a while loop and xinput --query-state, the whole thing was being done the wrong way.

let me explain some a downside: the loop itself is really bad for performance. since these are binds mainly needed for gaming, they must be responsive. being held up by a 0.1sec delay simply is not fast enough and results in button mashing that most mouse dpi buttons maybe are not intended to handle (could be a skill issue lol)

the whole script is simply unnecessarily complex for something being run every 0.1sec.

The solution

there are already very robust tools for handling custom keybinds. (xbindkeys and its gui config tool, xbindkeys-config) using these, i am able to get instantaneous input from the dpi button.

How to implement

Ao1Pointblank commented 3 months ago

i have discovered that for whatever reason, X11 cannot capture the WM_CLASS of certain windows that capture the cursor, i.e, a lot of games.

relevant error message: xprop: error: Can't grab the mouse.

this revamped script doesn't work with some windows. i have tried with wmctrl, xdotool, xwininfo, xprop, etc etc etc... somehow the old version of this script DID work with those windows, and i think it has something to do with the while-loop and sleep delay?

if i run the loop while true; do xprop -id "$(xdotool getactivewindow)" WM_CLASS; sleep 1; done, it does work with these cursor-grabbing windows so perhaps instead of polling mouse state every millisecond i can instead use the benefits of xbindkey but also use a while loop to circumvent the weird x11 goofiness.

Ao1Pointblank commented 3 months ago

WOW! i am EXTRA stupid! my stubbornness to not learn any python has cost me many hours on this project!!!

apparently there is a super easy way of doing all this without any xbindkeys shenanigans, in python:

import subprocess
from evdev import InputDevice, ecodes

#path to the input device (your mouse with DPI button)
device_path = '/dev/input/event2'

device = InputDevice(device_path)

#define the event code for the desired button (BTN_FORWARD in my case, aka button 10 according to Xev)
button_event_code = ecodes.BTN_FORWARD

#define the commands for different window classes
commands = {
    "discord": "xdotool key 'alt+shift+Up'",                       # DISCORD navigate to unread
    "vesktop": "xdotool key 'alt+shift+Up'",                       # VESKTOP navigate to unread
    "PortalWars-Linux-Shipping": "xdotool key 'f+h'",              # SPLITGATE pick up weapon and use spray
    "steam_app_782330": "xdotool key 'g+f'; xdotool key 'g'",      # DOOMETERNAL icebomb (and switch back to frag)
    "steam_app_553850": "xdotool key 'b'",                         # HELLDIVERS2 free keybind
    "steam_app_1240440": "xdotool key 'g'",                        # HALOINFINITE
    "Audacious": "bash -c '/home/pointblank/.local/share/nemo/scripts/💿\ Music/🎼\ Edit\ Current\ Audacious\ Opus'",
    "Nemo-desktop": "sleep 0.2; dbus-send --session --dest=org.Cinnamon --type=method_call --print-reply /org/Cinnamon org.Cinnamon.ShowOverview 1> /dev/null",
    "cube2_client": "play -q ~/.sounds/save_open.ogg"
}

#listen for events
for event in device.read_loop():
    if event.type == ecodes.EV_KEY:
        if event.code == button_event_code:
            if event.value == 1:  #check for button press event
                #execute xdotool command to get the active window ID
                xdotool_process = subprocess.Popen(["xdotool", "getactivewindow"], stdout=subprocess.PIPE)
                #get the output of the xdotool command
                active_window_id, _ = xdotool_process.communicate()
                #decode the output to a string
                active_window_id = active_window_id.decode("utf-8").strip()

                #execute xprop with the active window ID to get the window class
                xprop_process = subprocess.Popen(["xprop", "-id", active_window_id, "WM_CLASS"], stdout=subprocess.PIPE)
                #get the output of xprop
                xprop_output, _ = xprop_process.communicate()
                #decode the output to a string
                xprop_output = xprop_output.decode("utf-8").strip()

                #parse the output to extract the window class
                window_class = xprop_output.split('"')[3]

                #execute the corresponding command based on the window class
                if window_class in commands:
                    command = commands[window_class]
                    print(f"Window Detected: {window_class}. Executing command:")
                    print(command)
                    subprocess.Popen(["/bin/bash", "-c", command])
                else:
                    print(f"No command defined for window class: {window_class}")