Enable build support by adding .onedev-buildspec.yml
bots Loading last commit info...
.editorconfig
.gitignore
LICENSE
README.md
bot
README.md

libinput Bot

bot is a simple Zsh script framework that uses a Zsh coprocess to read key input from libinput record and trigger user-defined functions upon certain combinations being pressed. It can be used for anything requiring global keyboard shortcuts to trigger actions, under any system configured to use libinput devices.

Why?

bot was created so that I could trigger keyboard macros for games running under Wayland. Most macro systems that were around when bot was created only worked under Xorg, or were too annoying to work with.

However, bot is not very performant and clunky to work with in its own ways. But on the upside, that makes it is very simple, and as such is easy to modify and extend. bot does not have any logic itself to determine when to run actions, it only keeps track of key state, so there is a lot of flexibility in writing scripts for it.

Should I use bot?

If you need a quick and easy way to perform actions when a keyboard or mouse button is pressed, held, or released, and need decent control on when effects stop and start, sure. But if you need precise timing, out-of-box support for checking the position of the mouse, or don't use libinput, this script is not for you.

Do note most desktop environments come with global keyboard shortcuts, like KDE and Gnome. Check your system's documentation.

Usage

The bot script first loads sub-scripts (bots) from the bots/ directory as specified on the command line as arguments. For example, if you have two files bots/bot1 and bots/bot2, and run the command bot bot1 bot2, both files will be dot-sourced and added to the array of enabled bots (bot_enabled).

Creating your own bot

Since no bots except for the example clicker are included by default with this distribution, you need to create your own if you want any more advanced effects.

Defining hooks

By itself, bot only keeps track of the state of keys and the bots registered on startup. To actually act on this information, each bot should add a function name or other evalable command to the bot_hooks array.

Each item in this array is evaluated repeatedly in a loop, without arguments. This loop runs in the main thread, while the libinput events are handled in the coprocess, so you don't have to worry about waiting on event input, and you can still do things when no events are being sent.

For example, if you were to write the below script to bots/example and run bot example, you would get "hello world" repeatedly printed to standard output:

function example {
    echo 'hello world'
}
bot_hooks+=example

You can technically add any valid Zsh command list to the array, though this is not supported. It would be best to use function names. But to play the devil's advocate, this unfortunately works:

myscript='echo hello world'
bot_hooks+=$myscript

Running hooks

When going through the bot_hooks array to run hooks every cycle, bot will look in the bot_enabled array to see if the hook is there. If it isn't, the hook is not run. Bots that are specified on the command line are also added to the bot_enabled array, so adding a hook named after your bot will have that hook be enabled and run automatically. If your bot has a different name, you should enable it manually by adding it to the array when your script is sourced. Naturally, this also means that renaming bots can result in their hooks not being enabled automatically, without also renaming the hook.

Do note that the arguments of bot are added to the bot_enabled array even if there is no matching filename. This is on purpose, so that you can modify bots by checking the contents of bot_enabled for specific items.

Writing a bot

Each bot is expected to add a list of key constants to the bot_keys array to enable the key state to be tracked. For example, to add the F1 key to the list:

bot_keys+=(KEY_F1)

When libinput record sends a key event, the key in question (hereafter $k) is checked against the array. If a key matches, the following arrays are updated:

  • $s[$k]: The current state of the key.
  • $c[$k]: The count of loops since the key was pressed.
  • $t[$k]: A toggle that gets flipped every time the key is pressed.

Aside from the $s, $c, and $t arrays, there are a few functions available that you can use to quickly check the state of keys:

  • key_pressed
  • key_released
  • key_toggled_on
  • key_toggled_off

Each of these functions accepts two arguments: the key to check, and a count after which the key will be considered actice again. Let's look at an example, with the comments explaining when each block is processed:

if {key_pressed KEY_H} {
    # Happens once on initial press of the H key.
}
if {key_pressed KEY_J 3000} {
    # Happens once on initial press of the J key.
    # This block will run again after 3000 cycles if the key is still pressed.

    # It is up to the hook to reset the counter for the key. Set it to 0 to
    # show that you've handled the event:
    c[KEY_J]=0
    # If you don't reset the cycle to 0, this block will run every loop after
    # the counter hits 3000. This is an intentional feature.
}
if {key_toggled_on KEY_K} {
    # This block will run once on initial press of K, and K is now "on".
    # Pressing K again will turn the key "off", and won't run this block.
    # Pressing the key again will toggle it back on and run this block again.
}
if {key_toggled_on KEY_L 3000} {
    # L is toggled on for the initial press, and this block will run.
    # After 3000 cycles, it will run again, and will continue to do so until
    # the key is toggled back off with another press.
    # As with key_pressed, make sure to reset the counter, with same reasoning:
    c[KEY_L]=0
}

The key_released and key_toggle_off functions work exactly the same as key_pressed and key_toggle_on, except they trigger when the key is released or the key toggled off, respectively. Do note that keys are not tracked until the first press, so key_toggle_off will not return success until the key has already been pressed twice to toggle it on then off.

Performing automated inputs

It is expected that bots will want to send key and mouse input with ydotool. To help make this process easier, bot attempts to start ydotool with systemd --user by default. Look at the included clicker script for an example of sending input with ydotool, specifically mouse clicks.

If you don't need ydotool, or don't use systemd service management, feel free to comment that line out of the script. If you're on Xorg, you can use xdotool instead, which doesn't need a daemon or service.

Of course, you don't even need to send inputs at all if you don't want to—while that is what bot was created to do, it is purpose-agnostic and you can use whatever tool or run whatever script or program you want as part of your bot.

Known issues

  • Timing: One reason I never mention time when discussing repeat actions is because bot does not keep track of time, only the count of cycles the main loop has gone through. Large, demanding scripts will slow down loop execution, as will loading many bots at once. Future versions of this project may refactor this system or add an additional system to track actual time elapsed.

License

This project is licensed under the Apache License, Version 2.0 (the "License"); you may not use it except in compliance with the License. A copy of the License is made available in the LICENSE file. You may also obtain a copy of the license at the Apache website.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Please wait...
Page is in error, reload to recover