Skip to content

Rebase Physical Keys Shortcuts - #12570

Open
LeonMatthes wants to merge 21 commits into
slint-ui:masterfrom
LeonMatthes:physical-keys-shortcuts-v2
Open

Rebase Physical Keys Shortcuts#12570
LeonMatthes wants to merge 21 commits into
slint-ui:masterfrom
LeonMatthes:physical-keys-shortcuts-v2

Conversation

@LeonMatthes

@LeonMatthes LeonMatthes commented Jul 21, 2026

Copy link
Copy Markdown
Member

This PR rebases #11474 by @amirHdev on to the current master.

It also closes the backend gap so that now all backends support physical key bindings 🥳

Closes #11474
Closes #11247

amirHdev and others added 13 commits July 22, 2026 10:07
Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
Native menu bars interpret shortcuts as logical keys. Emit a compiler warning for @physical-keys() in MenuItem shortcuts, log a one-time runtime warning in the winit+muda backend, and map supported physical codes to a best-effort logical key.

Add parser/resolver syntax tests for invalid @physical-keys(), and update the physical-keys runtime test to reuse testing backend helpers.
Replace WindowEvent::Key { event_type, event } with WindowEvent::Key(WindowKeyEvent) where WindowKeyEvent bundles the existing KeyEvent struct plus the event type. This keeps the public key event payload consistent with the rest of the runtime and avoids duplicating fields.

Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
After the rebase, some imports were unresolved and some API shapes had
changed, which needed adjustment.
Also improve physical_key_codes documentation
@LeonMatthes
LeonMatthes force-pushed the physical-keys-shortcuts-v2 branch from 2630a9a to faeae5c Compare July 22, 2026 08:48
}
```

Physical key names reuse familiar `Key` names such as `A`, `Digit1`, `BackQuote`, or `LeftArrow`, but they refer to a physical key location instead of logical text. Today these names refer to positions on a U.S. keyboard layout.

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.

Is that really just today and a contract that we might break in the future, or will it remain that way?

Generally, I think the use-case and the explanation what the mapping is should come first in the docs, before explaining the syntax. (see style guide, "Write actionable" and "Offer direct advice")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The keys we're adding now will remain to be based on the position of a U.S. keyboard layout.
However, if we ever add keys that just don't exist on a U.S. layout, those of course won't refer to U.S. layout positions.
Or if we ever add things like "PowerButton"/"VolumeUp/Down" and so on. Those aren't really on the U.S. keyboard.
So this is a bit defensively phrased. Should I still remove it?

(Moved the paragraph above the code example).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

As an additional note on docs/discoverability here: The PR currently does not introduce a PhysicalKey namespace and Rust enum, like we have for Key.
I think this would be valuable to have as well, but we can/should do that as a follow-up PR so that this PR doesn't become stale again.
(#12212 also depends on this PR and would then be unblocked).

@tronical tronical left a comment

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.

Looks generally good. I feel we're discussing details ;)


### Physical Key Bindings

If you want to match a physical key position instead of the character produced on the current keyboard layout, use `@physical-keys(..)`.

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.

Suggest to flip it around: "Use @physical-keys(..) to match ..." . Also saves you the "want" :)

Could you include an example (use-case, not code) as well, perhaps? (games?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done (Added WASD as example)

If you want to match a physical key position instead of the character produced on the current keyboard layout, use `@physical-keys(..)`.

Physical key names reuse familiar `Key` names such as `A`, `Digit1`, `BackQuote`, or `LeftArrow`, but they refer to a physical key location instead of logical text.
Today these names refer to positions on a U.S. keyboard layout.

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.

After yesterday's discussion I feel even more that "Today" should be omitted.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Okay, and reworded it to make it shorter.

/// This uses the same names as `@physical-keys(...)`, for example `A`,
/// `Digit1`, or `LeftArrow`. It is empty when the backend doesn't provide
/// physical key information.
physical_key: SharedString,

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.

A question, just for my understanding: The name suggests that it's a single key, but the field is a string. Would you use a char in a perfect world?

@LeonMatthes LeonMatthes Jul 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

For performance we could just use an int. This is just an identifier for a physical key, we should not even be thinking in terms of "characters" here really.

The reason this is a string is so that you can easily name your own keys. Like if you have your own embedded device which has a physical key to "start the reactor", you could just have a "StartReactor" key.
At the moment, this is not supported yet, but that is a limitation we should lift in the future so backends can implement their own custom physical keys.

This is somewhat nicer to do with a string than some kind of integer, but an integer would definitely be more performant. So maybe we should talk about this again during API review.
Also, it matches what the Keys type already stores, so we don't have to make that a tagged union.

}

#[inline(never)]
fn compile_keys_literal(expr: &Expression) -> TokenStream {

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.

This looks like an unnecessary re-indentation? :)

@LeonMatthes LeonMatthes Jul 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The indentation didn't change, the type of the function argument changed ;)

(The previous function was weirdly unpacking the expression enum for no reason).

Comment thread internal/compiler/passes/lower_menus.rs Outdated
&& let Some(binding) = element.borrow().bindings.get("shortcut")
&& matches!(&binding.borrow().expression, Expression::Keys(k) if k.is_physical)
{
diag.push_warning(

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.

Wouldn't it be better to make this an error?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hm, yes, probably better to make it an error, we can always relax it later.

In theory they do actually work in menus due to our fallback logic, but we cannot show them in the MenuItem correctly (they show up as and will match like logical keys and will also match the physical key).

This error also doesn't prevent you from sneaking a physical key onto a MenuItem shortcut through a property binding. So it's not a perfect defense.

Comment thread internal/compiler/passes/resolving.rs Outdated
};
}

thread_local! {

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.

Instead of "duplicating" this in each thread, wouldn't it be better to put this behind a LazyLock?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hm, our compiler currently only runs in one thread, but sure, we can make that a lazylock for when olivier finishes the LLR interpreter :)

Comment thread internal/core/platform.rs
/// Always reported as [`Accepted`](crate::api::WindowEventDispatchResult::Accepted).
PointerExited,
/// A key was pressed or released.
Key(WindowKeyEvent),

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.

I suggest to split out making the new key event public into a separate PR. Reduces the surface of this and makes it easier to discuss/review just the API. I don't think it's needed strictly for this functionality, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, I just ported the backends to use this 🙈
They would all have to use InternalKeyEvent then for physical keys support.

Can we just decide during API review whether we want it public or not and add a PR to make it private again if needed?

static WARN_ONCE: std::sync::Once = std::sync::Once::new();
WARN_ONCE.call_once(|| {
i_slint_core::debug_log!(
"Warning: Physical keys used in menu shortcuts are interpreted as logical keys based on a US keyboard layout. Use @keys(...) for menu shortcuts."

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.

Isn't this caught at compile time? (especially if it were an error)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You could still set this via a property from Rust, so this is a last layer of defense.


As another example, `@keys(Control + Plus)` is equivalent to `@keys(Control + Shift? + "+")`.

### Physical Key Bindings

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.

Perhaps here it would also be worthwhile to mention the menu limitation? (or is that overdoing it?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added that onto the docs of the MenuItem.shortcut property now. I think it's unnecessary here, but definitely makes sense there.

@LeonMatthes
LeonMatthes marked this pull request as ready for review July 24, 2026 08:39
@LeonMatthes
LeonMatthes requested a review from tronical July 24, 2026 08:39
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.

Support Physical Keys in keys type

3 participants