Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added functionality for multiple devices and fixed stop record option #1

Merged
merged 6 commits into from
Jan 12, 2025

Conversation

CookieTheory
Copy link
Contributor

Added functionality for multiple devices:

Keystrokes is amazing, but I did want an ability to record multiple devices at once (keyboard and mouse, for example). As libinput record, the one being used by keystrokes has the ability to record multiple devices I decided to implement that functionality.
Instead of a string containing a device name, this would now contain an array of device names for recording. In given parameters, checks are made that there is an element (gt 0) and that it is not empty or starts with a dash (next option). If it's greater but is another option/empty, it shifts and breaks out of the loop. Otherwise, it runs the same logic of finding the device but adds it to an array instead of initializing a string. Error message also changed to ask for "at least one device" in the -D option.

SIGINT instead of SIGTERM

To stop recording, it should use SIGINT instead of SIGTERM, as SIGTERM will truncate the file. The issue becomes apparent when recording multiple devices. Signal kills the process of recording and writing to the file instantly, and it ends up truncating and usually only finishes writing for one device. SIGINT lets the process clean up and close the file, successfully writing everything it needs to before shutting down. I left SIGTERM in stop replay as it doesn't need to finish a process and we want it just to stop.

This does not affect previous scripts and works normally with just one device. Everything was tested with ydotool in Wayland and should work the same in X11.

Readme and help could be updated to clarify that the -D option supports multiple devices instead of one recording device but that I will leave up to the owner.

@Darukutsu
Copy link
Owner

Darukutsu commented Jan 11, 2025

Thank you for pull request however there are few minor/major problems.

First of all, I decided to use POSIX for portability, since I'm not very familiar with all shells which are freely available to download and their similarities. Although we can speculate that libinput is not probably supported on such systems which run archaic shells there are always people who run unexpected things. Not me and you, but maybe somebody in future will. Thus try to rework your PR to support POSIX shell, shouldn't be that hard, I will add shellcheck actions to check for this automatically for future PRs.

Secondly which is more concerning is that while it works using mirror replay I can't make it work with simple replay due to how parsing works. I'm planning to add mouse support sometime in future, but that will need to rework parsing.

Also do you get similar warning when running it on wayland? Just wondering if this is x11 issue or not.

Warning: device is missing recorded udev property: MOUSE_DPI=1000@167
/dev/input/event25: Logitech M705

@Darukutsu
Copy link
Owner

Darukutsu commented Jan 11, 2025

If unsure here's link to great read. I added workflow actions.

You don't need to implement new parsing if you don't want, just fix for posix and I will merge it with note in readme or help to describe current state of work.

@CookieTheory
Copy link
Contributor Author

Thank you for response, I will rework it to be POSIX compliant.
Secondly, I have noticed the issue with simple replay. My main use of the program was in mirror replay (consistent mouse movements for example) so I just thought it was wayland issue as it has issues with most other macro programs or plainly won't run them. This one was most plug-and-play I have found and that's why I love it.

For last warning, I have not experienced it, so probably X11 issue. I have no issue recording mouse and mouse movements, as well as keyboard and mouse movements at the same time. Example of keyboard+mouse usage in a game:

untitled2.mp4

Video 2xFF, cut and lower resolution due to size limitations but shows working demo of both devices replicated. There is start delay on replay, but I think that is due to my swhkd config and/or due to swhkd itself. One thing I also noticed in your commits, you have added tail to pick last device from the list. My list contains first device as one that should be recorded, not the last so that could also come into consideration/be added as an issue. It could be Wayland/X11 difference or just device specific differences, I avoided it by just using /dev/input/event* instead of names, easier for me.

@Darukutsu
Copy link
Owner

Darukutsu commented Jan 11, 2025

looks cool, I forgot we could use it in games.

There is start delay on replay, but I think that is due to my swhkd config and/or due to swhkd itself.

I literally use libinput function for mirroring. I don't experience delay on my side

One thing I also noticed in your commits, you have added tail to pick last device from the list. My list contains first device as one that should be recorded, not the last so that could also come into consideration/be added as an issue.

Read the commit message why i did it this way. You can specify device name via "real name", this way you can have script that will behave same on reboots, because /dev/input/event* changes everytime...

Small edit note:
How would it work if game doesn't reset your mouse position to original state. I just tried mouse replay in wm and realized unless you put mouse back to original position where you started recording, it will repeat those moves from which ever position cursor starts in, thus resulting in undesired behaviour.

@CookieTheory
Copy link
Contributor Author

CookieTheory commented Jan 11, 2025

looks cool, I literally forgot we could use it in games.

Yup, I'm intending to use it for benchmarks. I was looking for both Wayland and X11 solution and stumbled on this.

There is start delay on replay, but I think that is due to my swhkd config and/or due to swhkd itself.

I literally use libinput function for mirroring. As soon as libinput record is initiated it starts recording with the timeout at start how long does it take you to press first key/move mouse.

That's why I mentioned it could be due to swhkd or just due to games/Wayland, through terminal it runs immediately, but there is like 10 seconds delay as also seen in the video by in game counter (as soon as I set my crosshair i pressed the bind at 1:51, but macro starts at 1:39), accounting for my delay from record bind to first key it's still 10 seconds too much. Either swhkd takes too long to pick up signals when in-game, or game/Wayland/ydotool don't start fast enough.

Read the commit message why i did it this way. You can specify device name via "real name", this way you can have script that will behave same on reboots, because /dev/input/event* changes everytime...

For me /dev/input/event* doesn't change on reboots, and I have issues with "real name" in libinput replay as my keyboard contains character outside of iso8859-1 encoding (.̊ ). Another reason why your program is better than just running macros through libinput.

Traceback (most recent call last):
  File "/usr/lib/libinput/libinput-replay", line 406, in <module>
    main()
    ~~~~^^
  File "/usr/lib/libinput/libinput-replay", line 393, in main
    loop(args, y)
    ~~~~^^^^^^^^^
  File "/usr/lib/libinput/libinput-replay", line 260, in loop
    uinput = create(d)
  File "/usr/lib/libinput/libinput-replay", line 115, in create
    d.name = fetch(evdev, "name")
    ^^^^^^
  File "/usr/lib/python3.13/site-packages/libevdev/device.py", line 253, in name
    self._libevdev.name = name
    ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/site-packages/libevdev/_clib.py", line 416, in name
    return self._set_name(self._ctx, name.encode("iso8859-1"))
                                     ~~~~~~~~~~~^^^^^^^^^^^^^
UnicodeEncodeError: 'latin-1' codec can't encode character '\u030a' in position 27: ordinal not in range(256)

How would it work if game doesn't reset your mouse position to original state. I just tried mouse replay in wm and realized unless you put mouse back to original position where you started recording, it will repeat those moves from which ever position cursor starts in, thus resulting in undesired behaviour.

For games, like FPS games, mouse is always centered and moves through interpolation of change_x - center_x, takes that change, moves the camera matrix and returns mouse to center, so relative positions work great for 3D games. I looked into absolute positions through libinput, but that choice is only for touch inputs. Mouse pointers work differently and only support relative position. To implement absolute positions it would probably take for example a macro at the start of the recording to put mouse in one of display corners by putting massive relative position change, and add that relative position change at the start of replay macro file (but there is always an issue of mouse sensitivity).

@Darukutsu
Copy link
Owner

Darukutsu commented Jan 11, 2025

For me /dev/input/event* doesn't change on reboots

weird or maybe my system is :D

I have issues with "real name" in libinput replay as my keyboard contains character outside of iso8859-1 encoding (.̊ ).

I think whatever name libinput list-kernel-devices prints should working if put into "quotes" , and if not you don't need to specify whole name since i use grep for matching... for example "Corne K" should be enough to match "Corne Keyboard".

Regarding error I have no idea maybe you could share macro file so i try to reproduce it, but before posting it check if there isn't any security private related information in it.

Regarding the delay I mentioned in my previous edit, I was trippin' it should replay immediately, so something definitely happens on your side, you could check /tmp/your.macro file if it isn't adding it and if not then I have no idea. Also share your dotfile(swhkd conf)

Recording starting position is definitely doable, I will think about it.

@CookieTheory
Copy link
Contributor Author

I think whatever name libinput list-kernel-devices prints should working if put into "quotes" , and if not you don't need to specify whole name since i use grep for matching... for example "Corne K" should be enough to match "Corne Keyboard".

I do get that and love it, but that's why I mentioned tail and fetching last event in libinput event list. for me list-kernel-devices is as follows:

/dev/input/event0:      Power Button
/dev/input/event1:      Power Button
/dev/input/event2:      GAMDIAS Technology Co.,Ltd.̊ GAME KEYBOARD
/dev/input/event3:      GAMDIAS Technology Co.,Ltd.̊ GAME KEYBOARD Keyboard
/dev/input/event4:      USB Gaming Mouse
/dev/input/event5:      USB Gaming Mouse
/dev/input/event6:      USB Gaming Mouse Consumer Control
/dev/input/event7:      USB Gaming Mouse
/dev/input/event8:      PC Speaker
/dev/input/event9:      Eee PC WMI hotkeys
/dev/input/event10:     HD-Audio Generic Rear Mic
/dev/input/event11:     HD-Audio Generic Front Mic
/dev/input/event12:     HDA NVidia HDMI/DP,pcm=3
/dev/input/event13:     HDA NVidia HDMI/DP,pcm=7
/dev/input/event14:     HDA NVidia HDMI/DP,pcm=8
/dev/input/event15:     HD-Audio Generic Line
/dev/input/event16:     HD-Audio Generic Line Out
/dev/input/event17:     HDA NVidia HDMI/DP,pcm=9
/dev/input/event18:     HD-Audio Generic Front Headphone
/dev/input/event19:     ydotoold virtual device
/dev/input/event20:     ydotoold virtual device

Both /dev/input/event4 for mouse and /dev/input/event2 for keyboard is the actual event I want to pick up, additional ones I don't know where they came from. Both keyboard and mouse I want to fetch first, but for you it's the last, that's why you added tail in def792d commit. We can't really ask the user which one because most likely scenario is scripts so maybe with this merge of multiple devices it could record all instances? Extra ones are duds anyway.

Anyways, with latest commit I think I made my side POSIX compliant, arrays are not being used anymore and it's back to string.

@CookieTheory
Copy link
Contributor Author

# swhkdrc

# File to track macro mode state

# Force stop macro
super + shift + q
    pkexec keystrokes -s && \
    notify-send -t 3000 --hint int:transient:1 "swhkd" "Macro forcestopped"

# Play mirrored macro
super + shift + m
    ydotool key Escape; \
    notify-send --hint int:transient:1 "swhkd" "Playing mirrored macro"; \
    pkexec keystrokes -m -p -y /tmp/.ydotool_socket;

# Record macro
super+ shift + r
    ydotool key Escape; \
    notify-send --hint int:transient:1 "swhkd" "Recording macro"; \
    pkexec keystrokes -r -D /dev/input/event4 /dev/input/event2;

Forgot to add, you asked me earlier, this is my config for swhdk, runs normally on desktop, but there is replay delay in games. Probably due to games themselves or the way compositor is acting. Doesn't matter anyways, it's just that one specific scenario.

@Darukutsu
Copy link
Owner

Both /dev/input/event4 for mouse and /dev/input/event2 for keyboard is the actual event I want to pick up, additional ones I don't know where they came from. Both keyboard and mouse I want to fetch first, but for you it's the last, that's why you added tail in def792d commit. We can't really ask the user which one because most likely scenario is scripts so maybe with this merge of multiple devices it could record all instances? Extra ones are duds anyway.

I thought it is rational that when you plug new device(with same name) it will be desired one to record, but now I understand that maybe on system boot up you might have plugged 3 devices with same name thus script won't work as expected if desire is to record only first. However when recording multiple devices using name specifier can also result in undesired results. Let's say you want to use device name "USB Gaming Mouse" for event4 in your script all the time but it might change on second boot and suddenly became event5 while other "USB Gaming Mouse" will became event4.

For now safest choice is to specify each device I want to use using /dev/input/evdev* and check if that evdev correlates with device we think it does.

In my case I can record both device names since bluetooth input get's auto-disabled by Corne itself.
$ libinput list-kernel-devices
/dev/input/event23:	Corne Keyboard
/dev/input/event24:	Corne Mouse
/dev/input/event25:	ZMK Project Corne Keyboard
/dev/input/event26:	ZMK Project Corne Mouse

$ libinput list-kernel-devices --hid
- name:   'ZMK Project Corne'
  id:     '1d50:615e'
  driver: 'hid-generic'
  hidraw: ['/dev/hidraw5']
  evdev:  ['/dev/input/event25', '/dev/input/event26']
  
- name:   'Corne'
  id:     '1d50:615e'
  driver: 'hid-generic'
  hidraw: ['/dev/hidraw4']
  evdev:  ['/dev/input/event23', '/dev/input/event24']
  
 $ libinput list-devices
 Device:           ZMK Project Corne Keyboard
Kernel:           /dev/input/event25
Group:            8
Seat:             seat0, default
Capabilities:     keyboard pointer 
Tap-to-click:     n/a
Tap-and-drag:     n/a
Tap drag lock:    n/a
Left-handed:      n/a
Nat.scrolling:    disabled
Middle emulation: n/a
Calibration:      n/a
Scroll methods:   none
Click methods:    none
Disable-w-typing: n/a
Disable-w-trackpointing: n/a
Accel profiles:   n/a
Rotation:         0.0

Device:           ZMK Project Corne Mouse
Kernel:           /dev/input/event26
Group:            8
Seat:             seat0, default
Capabilities:     pointer 
Tap-to-click:     n/a
Tap-and-drag:     n/a
Tap drag lock:    n/a
Left-handed:      disabled
Nat.scrolling:    disabled
Middle emulation: disabled
Calibration:      n/a
Scroll methods:   button
Click methods:    none
Disable-w-typing: n/a
Disable-w-trackpointing: n/a
Accel profiles:   flat *adaptive custom
Rotation:         0.0

Device:           Corne Keyboard
Kernel:           /dev/input/event23
Group:            15
Seat:             seat0, default
Capabilities:     keyboard pointer 
Tap-to-click:     n/a
Tap-and-drag:     n/a
Tap drag lock:    n/a
Left-handed:      n/a
Nat.scrolling:    disabled
Middle emulation: n/a
Calibration:      n/a
Scroll methods:   none
Click methods:    none
Disable-w-typing: n/a
Disable-w-trackpointing: n/a
Accel profiles:   n/a
Rotation:         0.0

Device:           Corne Mouse
Kernel:           /dev/input/event24
Group:            15
Seat:             seat0, default
Capabilities:     pointer 
Tap-to-click:     n/a
Tap-and-drag:     n/a
Tap drag lock:    n/a
Left-handed:      disabled
Nat.scrolling:    disabled
Middle emulation: disabled
Calibration:      n/a
Scroll methods:   button
Click methods:    none
Disable-w-typing: n/a
Disable-w-trackpointing: n/a
Accel profiles:   flat *adaptive custom
Rotation:         0.0

Looks ok, I see this pr generated lots of questions and issues we need to fix. Will open issues for those to keep track of them. I will edit readme and add some information to help regarding this.

@CookieTheory
Copy link
Contributor Author

The rest in checker are infos, not errors/warnings. Line 60 is intentional because device names should split, lines 111 and 137 are from master unchanged. Should i just put everything under shellcheck disable?

@Darukutsu
Copy link
Owner

Darukutsu commented Jan 12, 2025

no worries I'm on it your pr is ready to merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants