Skip to content

keybinds: Per-device submaps - #16018

Open
slopesque wants to merge 15 commits into
hyprwm:mainfrom
slopesque:feat/per-device-submaps
Open

slopesque wants to merge 15 commits into
hyprwm:mainfrom
slopesque:feat/per-device-submaps

Conversation

@slopesque

@slopesque slopesque commented Aug 26, 2026 •

Copy link
Copy Markdown

Closes #15640

Describe your PR, what does it fix/add?

An implementation for per-device submap definitions. This PR thus proposes :

  • A new optional parameter to hl.define_submap to accept metadata similar to what we do with hl.bind. So far, only the device key is handled.
  • An architecture change promoting submaps to have their own data structure, stored alongside Keybinds::CBinds in Keybinds::CRegister.

Just like bindings, if the submap has no device mapped to it, it becomes global. Otherwise, the submap will be set if and only if the last device used to press a key (in practice, the one that has the key pressed to trigger hl.dsp.submap()) is mapped by the submap.

Below is an example code snippet that can be added to an Hyprland test configuration to test the feature :

local mainMod = "ALT"

hl.on("keybinds.submap",
    function(submap)
        text = nil

        if submap == "" then
            text = "Returning to default"
        else
            text = "Enabling submap " .. submap
        end

        hl.notification.create({ text = text, timeout = 2500 })
    end
)

hl.bind(mainMod .. " + M", hl.dsp.submap("test"))

hl.define_submap("test",
    function()
        hl.bind(mainMod .. " + P",
            function()
                hl.notification.create({ text = "A special notification", timeout = 2000 })
            end
        )

        hl.bind(mainMod .. " + M", hl.dsp.submap("reset"))
    end,
    {
        device = {
            inclusive = true,
            list = { "wl_keyboard" }  -- or whatever is returned when running `hyprctl devices`
        }
    }
)

Is there anything you want to mention? (unchecked code, possible bugs, found problems, breaking compatibility, etc.)

Keybinds::CSubset Data Structure

Right now (and if I understand correctly), we handle binds directly using two strings in Keybinds::SBindMetadata : submap and submapReset. When we want to check the submaps, we look for them in CBind objects directly (ex: Keybinds::CRegistry::hasSubmap ).

As we now allow metadata values for submaps which can be lead to evolve along with Hyprland's features, such an approach may not scale well on the long run. In order to handle device mappings, the PR thus introduces a dedicated data structure Keybinds::CSubset for submaps. The idea is to store them along with Keybinds::CBinds in the keybinds registry, where they are also reset when the keybinds are reset.

m_lastDevice in Config::Actions::CActionState

Changes I saved as commit ddb4f88cbce700360f79f2bd2f1bf4dc49465604 gathers everything related to my current approach to pass on the current input device to the submap match search in Config::Actions::setSubmap : so far, I added a SP<IHID> m_lastDevice field to the global config action state, which I read during setSubmap .

I think there might be way, way better ways to do this other than straight up tracking the last device in the global action state. That being siad, I'm still very new to the codebase and this is the first option I have found. I kept this commit to at least have a functional first result, but please do tell if they are more efficient ways to pass around the input device across Keybinds callbacks.

AI Usage

As it is my first time working in Hyprland, I only relied on an AI model (Claude Sonnet in this case) for analysis purposes, in order to grasp an initial understanding of Hyprland's global structure and the project filesystem. Beyond that, I no longer used AI and favored understanding Hyprland's modules by reading the source code, testing its behavior using a debugger and checking out the documentation for the Lua C API. Moreover, I haven't used any AI while working on the feature itself.

Is it ready for merging, or does it need work?

The API and backend logic is ready and tested. The only missing part from the original feature request is to remove the global lock on submaps to allow multiple ones being active at the same time. Whether we implement it here or in a second PR is up to discussion.

The architecture change regarding CSubmap should also be approved before merging. If there is no problem with it, then we can change the existing logic to align it with the new API.

@slopesque
slopesque marked this pull request as ready for review August 26, 2026 16:44
@slopesque
slopesque marked this pull request as draft August 26, 2026 16:44
@github-actions
github-actions Bot requested a review from vaxerski August 26, 2026 16:52
@slopesque slopesque changed the title Per-device submaps keybinds: Per-device submaps Aug 28, 2026
@slopesque
slopesque marked this pull request as ready for review August 28, 2026 17:26
const int optsIdx = fnIdx + 1;
Keybinds::SSubmapArgs args;

if (lua_gettop(L) >= optsIdx) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we are reusing this (Also for binds) it would make sense to unify those parsers to produce some shared CDeviceList from lua (CDeviceList::fromLua() or sumn)

uint32_t m_lastMouseCode = 0; // last mouse button code, 0 if last was keyboard
uint32_t m_timeLastMs = 0; // timestamp of last key/mouse event
std::string m_currentSubmap = ""; // current keybind submap name
SP<IHID> m_lastDevice; // device used to press last keycode

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't lock the device

Comment thread tests/keybinds/Submap.cpp Outdated
};

// testing with actual devices requires being able to instantiate them
// virtually :(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and you can, check the plugin in hyprtester. You can test it with integration tests

@slopesque
slopesque force-pushed the feat/per-device-submaps branch 4 times, most recently from 1c5ad97 to 4c31a90 Compare September 1, 2026 23:58
@slopesque
slopesque force-pushed the feat/per-device-submaps branch from ed27c07 to 349e0a6 Compare October 1, 2026 07:54
* CSubmapContext interface has been pruned to only follow actual use
  cases by other components
* CSubmapContext::find now search by submap name and device
* added custom uniset submap action. it resets the context and only add
  the specified submap to the context
* updated multiSubmap to match new behavior on dedicated test submaps
@slopesque
slopesque force-pushed the feat/per-device-submaps branch from 349e0a6 to a771309 Compare October 20, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-device submaps

2 participants