{"id":20563286,"url":"https://github.com/banchouboo/lucky","last_synced_at":"2025-06-11T05:05:39.367Z","repository":{"id":65838682,"uuid":"597911986","full_name":"BanchouBoo/lucky","owner":"BanchouBoo","description":"Lua-configured input daemon for X","archived":false,"fork":false,"pushed_at":"2024-02-02T20:09:31.000Z","size":28,"stargazers_count":53,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T14:57:03.502Z","etag":null,"topics":["daemon","hotkey","hotkey-daemon","hotkey-daemons","hotkeys","keybind","keybinding","keybindings","keyboard-shortcuts","lua","mouse","x","x11","xorg"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BanchouBoo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-02-06T01:12:39.000Z","updated_at":"2025-03-10T15:18:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"66fdfc41-d12b-4dd6-b98d-4ffce25724e9","html_url":"https://github.com/BanchouBoo/lucky","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BanchouBoo%2Flucky","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BanchouBoo%2Flucky/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BanchouBoo%2Flucky/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BanchouBoo%2Flucky/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BanchouBoo","download_url":"https://codeload.github.com/BanchouBoo/lucky/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BanchouBoo%2Flucky/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259204801,"owners_count":22821159,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["daemon","hotkey","hotkey-daemon","hotkey-daemons","hotkeys","keybind","keybinding","keybindings","keyboard-shortcuts","lua","mouse","x","x11","xorg"],"created_at":"2024-11-16T04:17:27.448Z","updated_at":"2025-06-11T05:05:39.320Z","avatar_url":"https://github.com/BanchouBoo.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lucky\n`lucky` is an input daemon for Xorg configured with a lua script.\n\nTo get a quick overview of what can be done, see the [examples](#examples).\n\n## build\nNote: requires Zig master, you can download builds at https://ziglang.org/download\n\nDependencies:\n- `xcb`\n- `xcb-keysyms`\n\nTo install, run `zig build -p \u003cprefix\u003e` where `\u003cprefix\u003e` is the directory that contains the `bin` folder you want it to install to.\n\nSo to install it to the system you could do:\n```sh\nsudo zig build -p /usr/local\n```\nand to install it for just the current user you could do:\n```sh\nzig build -p ~/.local\n```\n\nYou may also define a different [build mode](https://ziglang.org/documentation/master/#Build-Mode) using `-Doptimize=\u003cmode\u003e`, the default is `ReleaseFast`\n\n## features\n- Bind `press` and `release` functions to keys and mouse buttons\n- Bind `motion` and window `enter` and `exit` functions to mouse buttons\n    - `motion_resolution` option to determine how often to read motion events. Defaults to 5 so motion binds don't overwhelm your system (i.e. every 5th motion event will call the binding's `motion` callback).\n- Optional filter function to make a binding only active in specific contexts, if the filter returns false the key will be passed through to other applications\n- Various API functions to make complex, contextual bindings possible\n\n## cli flags\n- `-c`, `--config` - file path to load for the config, defaults to `XDG_CONFIG_HOME/lucky/config.lua`\n\n## api\n- `lucky.bind(bind_string, { ... })` - bind a modifier+key/mouse button combination\n- `lucky.cmd(command, arg1, arg2, ...)` - run a command directly, with function argument being a new argument to the command\n- `lucky.shell(command_string)` - runs `command_string` under the system's shell, useful if you want to use subshells, pipelines, file redirects, etc. which are not available when running a command through `cmd`\n- `lucky.is_root(window_id)` - returns true if `window_id` is the root window\n- `lucky.get_root()` - returns the ID of the root window (based on the currently focused window)\n- `lucky.get_parent_window(window_id)` - return the ID of the immediate non-root parent of `window_id`, if no parent exist return `window_id`\n- `lucky.get_top_level_window(window_id)` - return the ID of the top level non-root parent of `window_id`, if no parents exist return `window_id`\n- `lucky.get_focused_window()` - return the ID of the currently focused window\n- `lucky.get_geometry(window_id)` - returns a table containing the `x`, `y`, `width`, `height`, and `border_width` of `window_id`\n- `lucky.get_title(window_id)` - returns the title of `window_id`\n- `lucky.get_class(window_id)` - returns the class of `window_id`\n- `lucky.get_instance(window_id)` - returns the instance of `window_id`\n- `lucky.reload()` - reload your config\n\n## examples\n### launch a terminal\n```lua\nlucky.bind('super Return', {\n    press = function(window_id)\n        lucky.cmd(os.getenv('TERMINAL') or 'xterm')\n    end\n})\n```\n\n### bind super 1-9 to switch tag in dwm\n```lua\nfor i=1,9 do\n    lucky.bind('super ' .. tostring(i), {\n        press = function()\n            lucky.cmd('dwmc', 'viewex', tostring(i - 1))\n        end,\n    })\nend\n```\n\n### bind a key for a specific window\n```lua\n-- when Discord is focused, this keybinding will be active\nlucky.bind('alt Return', {\n    filter = function(wid)\n        return lucky.get_class(wid) == \"discord\"\n    end,\n    press = function()\n        lucky.cmd('notify-send', 'Discord is the current focused window')\n    end\n})\n\n-- when Discord is not focused, this keybinding will be active\nlucky.bind('alt Return', {\n    press = function()\n        lucky.cmd('notify-send', 'Discord is NOT the current focused window')\n    end\n})\n```\n\n### reload config\n```lua\nlucky.bind('super minus', {\n    press = function(window_id)\n        lucky.reload()\n    end\n})\n```\n\n### open clipboard in mpv\n```lua \nlucky.bind('super p', {\n    press = function(window_id)\n        lucky.shell('mpv --force-window=immediate \"$(xclip -o -sel clip)\"')\n    end\n})\n```\n\n### horizontal mouse position as a tag switcher when holding `super alt mouse_left`\n```lua\nfunction pointer_to_tag(x, y, window_id)\n    local max_x = lucky.get_geometry(lucky.get_root()).width - 1\n    local percent = x / max_x\n    local tag = math.floor(percent * 9) -- 9 tags\n    lucky.cmd('dwmc', 'viewex', tostring(tag))\nend\n\nlucky.bind(\"super alt mouse_left\", {\n    press = pointer_to_tag,\n    motion = pointer_to_tag\n})\n```\n\n### vertical mouse as a volume slider if you press the left mouse button on the left edge of the screen\n```lua\nfunction volume_slider(x, y, wid)\n    local max_y = lucky.get_geometry(lucky.get_root()).height - 1\n    local percent = math.floor(((max_y - y) / max_y) * 100)\n    lucky.cmd('pactl', 'set-sink-volume', '@DEFAULT_SINK@', tostring(percent) .. '%')\nend\n\nlucky.bind(\"mouse_left\", {\n    filter = function(x, y, wid)\n        return x == 0\n    end,\n\n    press = volume_slider,\n    motion = volume_slider\n})\n```\n\n## future plans\n- [ ] MAN PAGE!!!!!\n- [ ] Pass arbitrary data on startup, so you can load thinsg dynamically in the config\n    - For example, you could pass in the window manager for loading wm-specific keybinds, if you use multiple window managers\n- [ ] Ability to send arbitrary inputs through the lua API, so keys can be rebound in certain contexts\n- [ ] Make key repeat detectable\n- [ ] Plan9-esque Mouse chording\n    - I already know internally how to implement this, just need to work out the specifics of how it'll work for the user-side of things\n- [ ] Expose more information to the binding callbacks, possibly shove it all in a table instead of individual function arguments\n    - Mouse position for key presses\n    - Mouse positions relative to the window, not just global position\n    - Mouse movement relative to the last motion event for motion callback\n- [ ] Look into what's possible with the X input extension\n    - In particular, I'd like to implement things like touchpad and touchscreen gestures. Maybe stuff with drawing tablet input as well (pressure, for example).\n    - I have a general idea of how to do stuff with this but I don't have devices to test with which makes it rather difficult to build support for these things\n- [ ] Chording system\n    - Could perhaps work by giving a table to a function instead, which has keys to functions or more tables for nested chords\n    - Callback users can implement when updating the chord state, they could use this information to build their own whichkey-like system\n- [ ] Allow users to replay a matched keybinding to other clients\n    - Consider gutting the filter callback if this is implemented, since it lets you effectively do the same thing (the filter function might be a more comfortable for the common use case though, so maybe it'd still be worth keeping)\n- [ ] Allow users to implement named labels for bindings. This would allow for things like enabling/disabling bindings via their label, or removing the binding entirely.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbanchouboo%2Flucky","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbanchouboo%2Flucky","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbanchouboo%2Flucky/lists"}