Background/Problem Statement:
I’ve recently returned to playing Foxhole, an MMO war game that involves lots of repetitive clicking, e.g., Shift+clicking ten times to gather supplies at mines, or clicking-and-holding for minutes at a time to construct buildings and vehicles. I’m looking for the easiest way to do on Linux what AutoHotkey does for Windows users.
Potential Solutions:
I’m using Bazzite Linux, Gnome, Wayland. I know how to set custom shortcuts in Gnome settings (Settings > Keyboard > Keyboard Shortcuts). The thing is I don’t want these custom shortcuts to be always active, so I’m considering writing a pair of executable scripts that would enable me to quickly & easily start listening for the macro shortcuts at the beginning of my gaming session and stop listening when I’m done gaming.
So my workflow would be as follows:
- At the beginning of my gaming session, I’ll double click the first script to initiate something like
ydotoold
. This script will also contain two or three lines ofgsettings set
commands to create custom keyboard shortcuts, which triggerydotool
commands to emulate mouse clicks. For Windows users, the AHK version of this would be simply double clicking to start the AHK script. - At the end of my gaming session, since I don’t want to leave these custom shortcuts out there when I’m not playing, I will double click the second script, which stops
ydotoold
and removes the two or three custom keyboard shortcuts for the mouse clicking macros. This is where Windows users simply close AHK.
While I’m comfortable with the command line, this approach of managing Gnome settings through the command line would be new territory for me, and it will require a decent amount of trial and error. Before I proceed, I’d like to know if there’s a more straightforward way to achieve this.
Has anyone else accomplished something similar, and if so, would you be kind enough to share how you did it? Thanks!
EDIT: Adding links to some documentation I’ve found in my research so far. I haven’t set anything up yet, but I figure this might help others trying to do something similar.
- How to set custom keyboard shortcuts from terminal? - Ask Ubuntu forum post from March 2015
- ydotool on GitHub
- Hawck on GitHub - appears to be an alternative to
ydotool
Create a virtual mouse and use keyboard events tracked with python and add it as a service in systemd if you want to always keep it around
I also use my own solution for this in order to add middle mouse scrolling on everything turned on and off with a keybind
Create a virtual mouse and use keyboard events tracked with python
Where can I learn more about this? I searched the internet and found a post on that other site that shall not be named mentioning a tool called libclicker, is this what you mean?
I don’t remember what I used I’ll get back to you when I find it
I went on a journey to do something very similar, I remap keypad buttons to various other inputs using a python script running as a service. My original post is here, and my eventual solution is in the comments. My post has some links to other solutions that I tried, but ultimately I’m happy using a custom python script. That may not fit your need, but maybe it will help a little!
I was going to ask if you’ve heard of AntiMicroX, then I saw in your other post that you’d already tried that. Thanks for the idea to use a Python script, I’ll have to look into that.
Happy to help! For me, python was the way to get everything I wanted, instead of almost what I needed. In my opinion, the python-evdev documentation is really helpful, and should be able to get you most of the way to what you need. For what it’s worth, based on my experience with AutoHotKey on Windows, you should be able to recreate anything you had before and more with python.
Which distro do you use? I’m on Bazzite, which means I’m unable to add my user to the
input
group as suggested at the top of the Quick Start page.EDIT: Your other post says you’re running Debian 12. Did you have to add yourself to the
input
group in order to see your devices as the Quick Start page suggests?Edit: Once you “grab” your input device with python-evdev (
dev.grab()
), the input will be absorbed until it is un-grabed (dev.ungrab()
). If you grab your only keyboard input, you’ll be stuck and will need a secondary keyboard to get unstuck.I have a bad habit of speculating too much, I’m gonna try to stick to just what I did in case I’m remembering some of the why details incorrectly. I’ll use the details from my device, anywhere you see “Azeron LTD Azeron Keypad”, “16d0”, or “1103”, you need to replace the values with your device-
Start by finding the info for the input device you want to monitor:
cat /proc/bus/input/devices | more
This should result in a list of input devices with various details, I used the ‘Name’ to identify mine:
N: Name="Azeron LTD Azeron Keypad"
When you have found the device, save the vendor ID and product ID for the next step:
I: Bus=0003 Vendor=16d0 Product=1103 Version=0111 N: Name="Azeron LTD Azeron Keypad"
Add a udev rule so that you can read the input from the device, and another for python-evdev to create a virtual device. I use link_priority 71 (as seen in the file name). The rule I’m using to let the virtual device be created could be better - this is something you might want to research more for a permanent solution, but this rule can be removed later if you just wanted to test with it:
sudo nano /etc/udev/rules.d/71-azeron-uaccess.rules
Write the file contents:
#Access to read from "Azeron LTD Azeron Keypad" SUBSYSTEMS=="usb", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="1103", TAG+="uaccess" #Access for python-evdev to create a new device KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess", MODE="0660"
Restart udev:
sudo udevadm control --reload-rules && sudo udevadm trigger
At this point, you should have access to the device from python-evdev and also be able to create a virtual device with python-evdev. I don’t know if it will help, but I figured I can add a bit of my code here:
import evdev, subprocess, re from evdev import UInput, InputDevice, ecodes from select import select virtual_device_name = 'python-mouse-device' input_mouse = '/dev/input/' input_keyboard = '/dev/input/' # These Regex's may need to be updated per-device. These match # my Azeron Cyro Mouse and Key Inputs. input_keyboard_regex = "^.*Azeron_LTD_Azeron_Keypad_2053388B5942.*event-kbd.*" input_mouse_regex = "^.*Azeron_LTD_Azeron_Keypad_2053388B5942.*event-mouse.*" # Get the path to the input devices. These are dynamic, and can change on system reboot. devices_by_id = subprocess.run(["ls", "-l", "/dev/input/by-id"], encoding='utf-8', stdout=subprocess.PIPE) devices_by_id = devices_by_id.stdout.split('\n') for device_id in devices_by_id: device_match = re.search(input_mouse_regex, device_id) if device_match: input_mouse = input_mouse + re.search("event[0-9]{1,}", device_match.group()).group() device_match = re.search(input_keyboard_regex, device_id) if device_match: input_keyboard = input_keyboard + re.search("event[0-9]{1,}", device_match.group()).group() if input_mouse == '/dev/input/' or not input_mouse: sys.exit("Mouse not found") if input_keyboard == '/dev/input/' or not input_keyboard: sys.exit("Keyboard not found") #Create the virtual mouse ui = UInput.from_device(InputDevice(input_mouse), name=virtual_device_name) try: #Define the devices devices = map(InputDevice, (input_keyboard, input_mouse)) devices = {dev.fd: dev for dev in devices} #for dev in devices.values(): print(dev) #Grab the devices to block their native input for dev in devices.values(): dev.grab() #Read and handle events from the devices and translate it to the virtual device r, w, x = select(devices, [], []) for fd in r: for event in devices[fd].read(): if event.type == ecodes.EV_MSC and event.code == 4 and event.value == 458767: #remap or add a script to perform #Add more if / elif as needed for different keys or key combinations #If you didn't remap the key, pass the input through as normal ui.write(event.type, event.code, event.value) finally: for dev in devices.values(): dev.ungrab()
The code above I run as a service, but my explanation is getting a bit long-winded already, so if any of this ends up being helpful, and you actually do want to run it as a background service, if you need help doing that let me know!
This looks promising https://www.baeldung.com/linux/mouse-events-input-event-interface
You’d have to write some service that runs in the background monitoring for the hotkey that starts the macro.
I’d probably do it in python or another real language other than bash at that point
Thanks for the pointer to that Baeldung blog post. I’ve seen references to
evdev
andlibevdev
on previous adventures in configuring game pads and HOTAS setups for games that don’t support them natively. Also, fair point about python vs bash—I’ve edited the post to be language agnostic.If you have a keyboard running qmk or something you may be able to just write a macro for that. I’m not super familiar with it though
qmk
I had to look that up to remember what it was. I don’t think my keyboard is supported, nonetheless here’s the website in case anyone else out there is interested in learning more.
What brand of preipherals do you have? Razer software (Synapse) can map Razer peripheral keys to anything you like, including macros. I’m willing to bet several other manufacturers have the same feature.