@@ -5,10 +5,10 @@ use std::mem::size_of;
55use std:: sync:: { LazyLock , Mutex } ;
66
77use windows_sys:: Win32 :: UI :: Input :: KeyboardAndMouse :: {
8- INPUT , INPUT_0 , INPUT_KEYBOARD , INPUT_MOUSE , KEYBDINPUT , KEYEVENTF_KEYUP , MOUSEEVENTF_HWHEEL ,
9- MOUSEEVENTF_LEFTDOWN , MOUSEEVENTF_LEFTUP , MOUSEEVENTF_MIDDLEDOWN , MOUSEEVENTF_MIDDLEUP ,
10- MOUSEEVENTF_RIGHTDOWN , MOUSEEVENTF_RIGHTUP , MOUSEEVENTF_WHEEL , MOUSEEVENTF_XDOWN ,
11- MOUSEEVENTF_XUP , MOUSEINPUT , SendInput ,
8+ INPUT , INPUT_0 , INPUT_KEYBOARD , INPUT_MOUSE , KEYBDINPUT , KEYEVENTF_EXTENDEDKEY , KEYEVENTF_KEYUP ,
9+ MOUSEEVENTF_HWHEEL , MOUSEEVENTF_LEFTDOWN , MOUSEEVENTF_LEFTUP , MOUSEEVENTF_MIDDLEDOWN ,
10+ MOUSEEVENTF_MIDDLEUP , MOUSEEVENTF_RIGHTDOWN , MOUSEEVENTF_RIGHTUP , MOUSEEVENTF_WHEEL ,
11+ MOUSEEVENTF_XDOWN , MOUSEEVENTF_XUP , MOUSEINPUT , SendInput ,
1212} ;
1313
1414use openlogi_core:: binding:: {
@@ -120,7 +120,7 @@ fn parse_shortcut(text: &str) -> KeyCombo {
120120fn press_shortcut ( shortcut : Shortcut ) {
121121 match combo ( shortcut) {
122122 Ok ( combo) => post_custom_shortcut ( & combo) ,
123- Err ( vk) => post_key ( vk, & [ ] ) ,
123+ Err ( vk) => post_key ( vk, & [ ] , false ) ,
124124 }
125125}
126126
@@ -129,16 +129,16 @@ fn press_shortcut(shortcut: Shortcut) {
129129/// synthesis (see the comment below) and is skipped.
130130fn dispatch_native ( native : NativeAction ) {
131131 match native {
132- NativeAction :: MissionControl | NativeAction :: AppExpose => post_key ( VK_TAB , & [ VK_LWIN ] ) ,
133- NativeAction :: PreviousDesktop => post_key ( VK_LEFT , & [ VK_LWIN , VK_CONTROL ] ) ,
134- NativeAction :: NextDesktop => post_key ( VK_RIGHT , & [ VK_LWIN , VK_CONTROL ] ) ,
135- NativeAction :: ShowDesktop => post_key ( VK_D , & [ VK_LWIN ] ) ,
136- NativeAction :: LaunchpadShow => post_key ( VK_LWIN , & [ ] ) ,
137- NativeAction :: LockScreen => post_key ( VK_L , & [ VK_LWIN ] ) ,
132+ NativeAction :: MissionControl | NativeAction :: AppExpose => post_key ( VK_TAB , & [ VK_LWIN ] , false ) ,
133+ NativeAction :: PreviousDesktop => post_key ( VK_LEFT , & [ VK_LWIN , VK_CONTROL ] , false ) ,
134+ NativeAction :: NextDesktop => post_key ( VK_RIGHT , & [ VK_LWIN , VK_CONTROL ] , false ) ,
135+ NativeAction :: ShowDesktop => post_key ( VK_D , & [ VK_LWIN ] , false ) ,
136+ NativeAction :: LaunchpadShow => post_key ( VK_LWIN , & [ ] , false ) ,
137+ NativeAction :: LockScreen => post_key ( VK_L , & [ VK_LWIN ] , false ) ,
138138 // Win+Shift+S opens the snip overlay, which serves both full-screen
139139 // and region capture on Windows.
140140 NativeAction :: Screenshot | NativeAction :: CaptureRegion => {
141- post_key ( VK_S , & [ VK_LWIN , VK_SHIFT ] ) ;
141+ post_key ( VK_S , & [ VK_LWIN , VK_SHIFT ] , false ) ;
142142 }
143143 // Suspending reliably needs `SetSuspendState` (powrprof.dll), which
144144 // hibernates instead when hibernation is enabled — no clean win from
@@ -151,12 +151,12 @@ fn dispatch_native(native: NativeAction) {
151151
152152fn dispatch_media ( key : MediaKey ) {
153153 match key {
154- MediaKey :: PlayPause => post_key ( VK_MEDIA_PLAY_PAUSE , & [ ] ) ,
155- MediaKey :: NextTrack => post_key ( VK_MEDIA_NEXT_TRACK , & [ ] ) ,
156- MediaKey :: PrevTrack => post_key ( VK_MEDIA_PREV_TRACK , & [ ] ) ,
157- MediaKey :: VolumeUp => post_key ( VK_VOLUME_UP , & [ ] ) ,
158- MediaKey :: VolumeDown => post_key ( VK_VOLUME_DOWN , & [ ] ) ,
159- MediaKey :: Mute => post_key ( VK_VOLUME_MUTE , & [ ] ) ,
154+ MediaKey :: PlayPause => post_key ( VK_MEDIA_PLAY_PAUSE , & [ ] , false ) ,
155+ MediaKey :: NextTrack => post_key ( VK_MEDIA_NEXT_TRACK , & [ ] , false ) ,
156+ MediaKey :: PrevTrack => post_key ( VK_MEDIA_PREV_TRACK , & [ ] , false ) ,
157+ MediaKey :: VolumeUp => post_key ( VK_VOLUME_UP , & [ ] , false ) ,
158+ MediaKey :: VolumeDown => post_key ( VK_VOLUME_DOWN , & [ ] , false ) ,
159+ MediaKey :: Mute => post_key ( VK_VOLUME_MUTE , & [ ] , false ) ,
160160 }
161161}
162162
@@ -215,15 +215,15 @@ fn post_click(button: MouseButton) {
215215 send_inputs ( & [ mouse_input ( down, data) , mouse_input ( up, data) ] ) ;
216216}
217217
218- fn post_key ( vk : u16 , modifiers : & [ u16 ] ) {
218+ fn post_key ( vk : u16 , modifiers : & [ u16 ] , extended : bool ) {
219219 let mut inputs = Vec :: with_capacity ( modifiers. len ( ) * 2 + 2 ) ;
220220 for modifier in modifiers {
221- inputs. push ( key_input ( * modifier, false ) ) ;
221+ inputs. push ( key_input ( * modifier, false , false ) ) ;
222222 }
223- inputs. push ( key_input ( vk, false ) ) ;
224- inputs. push ( key_input ( vk, true ) ) ;
223+ inputs. push ( key_input ( vk, false , extended ) ) ;
224+ inputs. push ( key_input ( vk, true , extended ) ) ;
225225 for modifier in modifiers. iter ( ) . rev ( ) {
226- inputs. push ( key_input ( * modifier, true ) ) ;
226+ inputs. push ( key_input ( * modifier, true , false ) ) ;
227227 }
228228 send_inputs ( & inputs) ;
229229}
@@ -269,16 +269,21 @@ pub(super) fn post_scroll(delta: ScrollDelta) {
269269}
270270
271271fn post_custom_shortcut ( combo : & KeyCombo ) {
272- let Some ( vk) = super :: hid_usage_to_windows ( combo. key ( ) . code ( ) ) else {
272+ let usage = combo. key ( ) . code ( ) ;
273+ let Some ( vk) = super :: hid_usage_to_windows ( usage) else {
273274 tracing:: warn!(
274- usage = combo . key ( ) . code ( ) ,
275+ usage,
275276 chord = %combo. rendered_label( ) ,
276277 "CustomShortcut key has no Windows mapping yet; press ignored"
277278 ) ;
278279 return ;
279280 } ;
280281
281- post_key ( vk, & combo_modifiers ( combo) ) ;
282+ post_key (
283+ vk,
284+ & combo_modifiers ( combo) ,
285+ super :: hid_usage_extended_key ( usage) ,
286+ ) ;
282287}
283288
284289fn combo_modifiers ( combo : & KeyCombo ) -> Vec < u16 > {
@@ -305,27 +310,42 @@ pub(super) fn hold_keys(keys: &[HeldKey], phase: KeyPhase) {
305310 . filter_map ( |key| held_virtual_key ( * key) )
306311 . collect ( ) ;
307312 let key_up = phase == KeyPhase :: Up ;
308- let mut inputs: Vec < _ > = keys. iter ( ) . map ( |key| key_input ( * key, key_up) ) . collect ( ) ;
313+ let mut inputs: Vec < _ > = keys
314+ . iter ( )
315+ . map ( |key| key_input ( key. vk , key_up, key. extended ) )
316+ . collect ( ) ;
309317 if key_up {
310318 inputs. reverse ( ) ;
311319 }
312320 send_inputs ( & inputs) ;
313321}
314322
315- fn held_virtual_key ( key : HeldKey ) -> Option < u16 > {
323+ struct VirtualKey {
324+ vk : u16 ,
325+ extended : bool ,
326+ }
327+
328+ fn held_virtual_key ( key : HeldKey ) -> Option < VirtualKey > {
316329 match key {
317- HeldKey :: Control => Some ( VK_CONTROL ) ,
318- HeldKey :: Shift => Some ( VK_SHIFT ) ,
319- HeldKey :: Alt => Some ( VK_MENU ) ,
330+ HeldKey :: Control => Some ( VirtualKey {
331+ vk : VK_CONTROL ,
332+ extended : false ,
333+ } ) ,
334+ HeldKey :: Shift => Some ( VirtualKey {
335+ vk : VK_SHIFT ,
336+ extended : false ,
337+ } ) ,
338+ HeldKey :: Alt => Some ( VirtualKey {
339+ vk : VK_MENU ,
340+ extended : false ,
341+ } ) ,
320342 HeldKey :: Key ( usage) => {
321- let key = super :: hid_usage_to_windows ( usage. code ( ) ) ;
322- if key. is_none ( ) {
323- tracing:: warn!(
324- usage = usage. code( ) ,
325- "held shortcut usage has no Windows mapping — edge ignored"
326- ) ;
327- }
328- key
343+ let code = usage. code ( ) ;
344+ let vk = super :: hid_usage_to_windows ( code) ?;
345+ Some ( VirtualKey {
346+ vk,
347+ extended : super :: hid_usage_extended_key ( code) ,
348+ } )
329349 }
330350 }
331351}
@@ -353,11 +373,14 @@ fn send_inputs(inputs: &[INPUT]) {
353373 }
354374}
355375
356- fn key_input ( vk : u16 , key_up : bool ) -> INPUT {
376+ fn key_input ( vk : u16 , key_up : bool , extended : bool ) -> INPUT {
357377 let mut flags = 0 ;
358378 if key_up {
359379 flags |= KEYEVENTF_KEYUP ;
360380 }
381+ if extended {
382+ flags |= KEYEVENTF_EXTENDEDKEY ;
383+ }
361384 INPUT {
362385 r#type : INPUT_KEYBOARD ,
363386 Anonymous : INPUT_0 {
0 commit comments