Skip to content

Commit 8acee4d

Browse files
Ericky Dos Santosclaude
andcommitted
feat: add the special action protocol client
Carries the device's special key — the HUMAIN button — from the compositor to a window. The compositor owns the gesture, because the key is usually a modifier it also needs for its own chords, and sends the resolved meaning rather than the key. `window::register_special_action(id, is_default)` registers a window, and gestures arrive as `Event::SpecialAction`: `Activate` for a tap (focus your input), `HoldStart`/`HoldEnd` around a hold (push-to-talk), `Cancel` when the gesture is abandoned and anything captured should be discarded. `is_default` also makes the window the fallback receiver, used whenever no registered surface is focused. Also bumps winit to 18bb0187, which carries the client bindings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent a311adf commit 8acee4d

8 files changed

Lines changed: 143 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/src/event.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::focus;
88
use crate::input_method;
99
use crate::keyboard;
1010
use crate::mouse;
11+
use crate::special_action;
1112
use crate::surface_visibility;
1213
use crate::touch;
1314
use crate::window;
@@ -35,6 +36,9 @@ pub enum Event {
3536
/// An input method event
3637
InputMethod(input_method::Event),
3738

39+
/// The device's special key was used while this surface was the receiver
40+
SpecialAction(special_action::Event),
41+
3842
/// An auto-hide visibility event from the compositor
3943
AutoHide(auto_hide::Event),
4044

core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub mod mouse;
2929
pub mod overlay;
3030
pub mod padding;
3131
pub mod renderer;
32+
pub mod special_action;
3233
pub mod surface_visibility;
3334
pub mod svg;
3435
pub mod text;

core/src/special_action.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! The device's special key, resolved by the compositor.
2+
//!
3+
//! The key — the HUMAIN button — is usually bound to a modifier the compositor
4+
//! also needs for its own chords, so only the compositor can tell a tap from
5+
//! the start of one. It resolves the gesture and sends the meaning rather than
6+
//! the key. Register a window with
7+
//! [`register_special_action`](crate::window::register_special_action) to
8+
//! receive these.
9+
10+
/// A resolved gesture on the special key.
11+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12+
pub enum Event {
13+
/// Tapped. Focus the text input; no voice is involved.
14+
Activate,
15+
/// A hold began. Start capturing audio.
16+
HoldStart,
17+
/// The hold ended. Stop capturing and process what was captured.
18+
HoldEnd,
19+
/// The gesture was abandoned. Discard any capture rather than process it.
20+
Cancel,
21+
}

runtime/src/window.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,13 @@ pub enum Action {
253253

254254
/// Enable or disable blur behind the window at runtime.
255255
SetBlur(Id, bool),
256+
257+
/// Register the window to receive the device's special key.
258+
/// Parameters are (window_id, is_default_receiver).
259+
RegisterSpecialAction(Id, bool),
260+
261+
/// Stop receiving the device's special key.
262+
UnregisterSpecialAction(Id),
256263
}
257264

258265
/// A window managed by iced.
@@ -332,6 +339,35 @@ pub fn close_requests() -> Subscription<Id> {
332339
})
333340
}
334341

342+
/// Registers the window to receive the device's special key.
343+
///
344+
/// The compositor resolves the gesture and delivers it as
345+
/// [`Event::SpecialAction`](crate::core::Event::SpecialAction): a tap asks the
346+
/// window to focus its input, a hold brackets push-to-talk.
347+
///
348+
/// When `is_default_receiver` the window also becomes the fallback, used
349+
/// whenever no registered surface is focused. Only one fallback exists at a
350+
/// time; registering a second replaces the first.
351+
///
352+
/// ## Platform-specific
353+
/// - **COSMIC/Wayland:** Uses the `zcosmic_special_action_v1` protocol.
354+
/// - **Other platforms:** No effect.
355+
pub fn register_special_action<T>(id: Id, is_default_receiver: bool) -> Task<T> {
356+
task::effect(crate::Action::Window(Action::RegisterSpecialAction(
357+
id,
358+
is_default_receiver,
359+
)))
360+
}
361+
362+
/// Stops the window receiving the device's special key.
363+
///
364+
/// ## Platform-specific
365+
/// - **COSMIC/Wayland:** Uses the `zcosmic_special_action_v1` protocol.
366+
/// - **Other platforms:** No effect.
367+
pub fn unregister_special_action<T>(id: Id) -> Task<T> {
368+
task::effect(crate::Action::Window(Action::UnregisterSpecialAction(id)))
369+
}
370+
335371
/// Opens a new window with the given [`Settings`]; producing the [`Id`]
336372
/// of the new window on completion.
337373
pub fn open(settings: Settings) -> (Id, Task<Id>) {

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,11 @@ pub mod touch {
636636
pub use crate::core::touch::{Event, Finger};
637637
}
638638

639+
pub mod special_action {
640+
//! Listen and react to the device's special key.
641+
pub use crate::core::special_action::Event;
642+
}
643+
639644
pub mod auto_hide {
640645
//! Listen and react to auto-hide visibility events from the compositor.
641646
pub use crate::core::auto_hide::Event;

winit/src/conversion.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use crate::core::input_method;
66
use crate::core::keyboard;
77
use crate::core::mouse;
8+
use crate::core::special_action;
89
use crate::core::theme;
910
use crate::core::touch;
1011
use crate::core::window;
@@ -310,6 +311,9 @@ pub fn window_event(
310311
Some(Event::Window(window::Event::Rescaled(scale_factor as f32)))
311312
}
312313
WindowEvent::Dnd(dnd_event) => Some(Event::Dnd(dnd_window_event(dnd_event))),
314+
WindowEvent::SpecialAction(action) => {
315+
Some(Event::SpecialAction(special_action_event(action)))
316+
}
313317
_ => None,
314318
}
315319
}
@@ -1153,6 +1157,28 @@ fn is_private_use(c: char) -> bool {
11531157
('\u{E000}'..='\u{F8FF}').contains(&c)
11541158
}
11551159

1160+
/// Converts a winit special action event into an iced one.
1161+
#[cfg(all(
1162+
feature = "wayland",
1163+
any(
1164+
target_os = "linux",
1165+
target_os = "dragonfly",
1166+
target_os = "freebsd",
1167+
target_os = "netbsd",
1168+
target_os = "openbsd",
1169+
)
1170+
))]
1171+
fn special_action_event(event: winit::event::SpecialActionEvent) -> special_action::Event {
1172+
use winit::event::SpecialActionEvent;
1173+
1174+
match event {
1175+
SpecialActionEvent::Activate => special_action::Event::Activate,
1176+
SpecialActionEvent::HoldStart => special_action::Event::HoldStart,
1177+
SpecialActionEvent::HoldEnd => special_action::Event::HoldEnd,
1178+
SpecialActionEvent::Cancel => special_action::Event::Cancel,
1179+
}
1180+
}
1181+
11561182
/// Converts a winit DnD window event into an iced DnD event.
11571183
fn dnd_window_event(event: winit::event::DndWindowEvent) -> crate::core::dnd::Event {
11581184
use crate::core::dnd::{DndAction, Event as DndEvent, SourceEvent};

winit/src/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3017,6 +3017,42 @@ fn run_action<'a, P, C>(
30173017
window.raw.set_blur(blur);
30183018
}
30193019
}
3020+
window::Action::RegisterSpecialAction(id, is_default) => {
3021+
#[cfg(all(
3022+
feature = "wayland",
3023+
any(
3024+
target_os = "linux",
3025+
target_os = "dragonfly",
3026+
target_os = "freebsd",
3027+
target_os = "netbsd",
3028+
target_os = "openbsd",
3029+
)
3030+
))]
3031+
{
3032+
use winit::platform::wayland::WindowExtWayland;
3033+
if let Some(window) = window_manager.get_mut(id) {
3034+
let _ = window.raw.register_special_action(is_default);
3035+
}
3036+
}
3037+
}
3038+
window::Action::UnregisterSpecialAction(id) => {
3039+
#[cfg(all(
3040+
feature = "wayland",
3041+
any(
3042+
target_os = "linux",
3043+
target_os = "dragonfly",
3044+
target_os = "freebsd",
3045+
target_os = "netbsd",
3046+
target_os = "openbsd",
3047+
)
3048+
))]
3049+
{
3050+
use winit::platform::wayland::WindowExtWayland;
3051+
if let Some(window) = window_manager.get_mut(id) {
3052+
let _ = window.raw.unregister_special_action();
3053+
}
3054+
}
3055+
}
30203056
},
30213057
Action::System(action) => match action {
30223058
system::Action::GetInformation(_channel) => {

0 commit comments

Comments
 (0)