@@ -4,7 +4,11 @@ use glib::clone;
44use gtk:: prelude:: * ;
55use gtk:: subclass:: prelude:: * ;
66use once_cell:: sync:: Lazy ;
7- use std:: { cell:: RefCell , collections:: HashMap } ;
7+ use regex:: Regex ;
8+ use std:: {
9+ cell:: { Cell , RefCell } ,
10+ collections:: HashMap ,
11+ } ;
812
913use crate :: Keyboard ;
1014use backend:: DerefCell ;
@@ -28,10 +32,23 @@ pub static SCANCODE_LABELS: Lazy<HashMap<String, String>> = Lazy::new(|| {
2832 labels
2933} ) ;
3034
35+ fn parse_mod_tap ( name : & str ) -> Option < ( & str , & str ) > {
36+ let mt_re = Regex :: new ( "MT\\ (([^()]+), ([^()]+)\\ )" ) . unwrap ( ) ;
37+ mt_re. captures ( name) . map ( |captures| {
38+ let mod_name = captures. get ( 1 ) . unwrap ( ) . as_str ( ) ;
39+ let kc_name = captures. get ( 2 ) . unwrap ( ) . as_str ( ) ;
40+ ( mod_name, kc_name)
41+ } )
42+ }
43+
3144#[ derive( Default ) ]
3245pub struct PickerInner {
3346 group_box : DerefCell < PickerGroupBox > ,
3447 keyboard : RefCell < Option < Keyboard > > ,
48+ mod_tap_box : DerefCell < gtk:: Box > ,
49+ mod_tap_check : DerefCell < gtk:: CheckButton > ,
50+ mod_tap_mods : DerefCell < gtk:: ComboBoxText > ,
51+ mod_tap_signal_blocked : Cell < bool > ,
3552}
3653
3754#[ glib:: object_subclass]
@@ -52,13 +69,50 @@ impl ObjectImpl for PickerInner {
5269 } ) ) ;
5370 } ;
5471
72+ let mod_tap_mods = cascade ! {
73+ gtk:: ComboBoxText :: new( ) ;
74+ ..append( Some ( "LEFT_CTRL" ) , "Left Ctrl" ) ;
75+ ..append( Some ( "LEFT_SHIFT" ) , "Left Shift" ) ;
76+ ..append( Some ( "LEFT_ALT" ) , "Left Alt" ) ;
77+ ..append( Some ( "LEFT_SUPER" ) , "Left Super" ) ;
78+ ..append( Some ( "RIGHT_CTRL" ) , "Right Ctrl" ) ;
79+ ..append( Some ( "RIGHT_SHIFT" ) , "Right Shift" ) ;
80+ ..append( Some ( "RIGHT_ALT" ) , "Right Alt" ) ;
81+ ..append( Some ( "RIGHT_SUPER" ) , "Right Super" ) ;
82+ ..set_active_id( Some ( "LEFT_CTRL" ) ) ;
83+ ..connect_property_active_id_notify( clone!( @weak picker => move |_| {
84+ picker. mod_tap_updated( ) ;
85+ } ) ) ;
86+ } ;
87+
88+ let mod_tap_check = cascade ! {
89+ gtk:: CheckButton :: with_label( "Mod-Tap" ) ;
90+ ..bind_property( "active" , & mod_tap_mods, "sensitive" ) . flags( glib:: BindingFlags :: SYNC_CREATE ) . build( ) ;
91+ ..connect_toggled( clone!( @weak picker => move |_| {
92+ picker. update_key_visibility( ) ;
93+ picker. mod_tap_updated( ) ;
94+ } ) ) ;
95+ } ;
96+
97+ let mod_tap_box = cascade ! {
98+ gtk:: Box :: new( gtk:: Orientation :: Horizontal , 8 ) ;
99+ ..add( & mod_tap_check) ;
100+ ..add( & mod_tap_mods) ;
101+ } ;
102+
55103 cascade ! {
56104 picker;
105+ ..set_spacing( 18 ) ;
106+ ..set_orientation( gtk:: Orientation :: Vertical ) ;
57107 ..add( & group_box) ;
108+ ..add( & mod_tap_box) ;
58109 ..show_all( ) ;
59110 } ;
60111
61112 self . group_box . set ( group_box) ;
113+ self . mod_tap_box . set ( mod_tap_box) ;
114+ self . mod_tap_check . set ( mod_tap_check) ;
115+ self . mod_tap_mods . set ( mod_tap_mods) ;
62116 }
63117}
64118
@@ -82,49 +136,134 @@ impl Picker {
82136 PickerInner :: from_instance ( self )
83137 }
84138
139+ fn update_key_visibility ( & self ) {
140+ let kb = match self . keyboard ( ) {
141+ Some ( kb) => kb,
142+ None => return ,
143+ } ;
144+ let is_mod_tap =
145+ self . inner ( ) . mod_tap_box . get_visible ( ) && self . inner ( ) . mod_tap_check . get_active ( ) ;
146+ self . inner ( ) . group_box . set_key_visibility ( |name| {
147+ // Check that scancode is available for the keyboard
148+ let visible = kb. has_scancode ( name) ;
149+ let sensitive = !is_mod_tap || kb. layout ( ) . scancode_from_name ( name) . unwrap_or ( 0 ) < 256 ;
150+ ( visible, sensitive)
151+ } ) ;
152+ }
153+
85154 pub ( crate ) fn set_keyboard ( & self , keyboard : Option < Keyboard > ) {
86155 if let Some ( old_kb) = & * self . inner ( ) . keyboard . borrow ( ) {
87156 old_kb. set_picker ( None ) ;
88157 }
89158
90159 if let Some ( kb) = & keyboard {
91- // Check that scancode is available for the keyboard
92- self . inner ( ) . group_box . set_key_visibility ( |name| {
93- let visible = kb. has_scancode ( name) ;
94- let sensitive = true ;
95- ( visible, sensitive)
96- } ) ;
97160 kb. set_picker ( Some ( & self ) ) ;
161+
162+ self . inner ( )
163+ . mod_tap_box
164+ . set_visible ( kb. layout ( ) . meta . has_mod_tap ) ;
98165 }
99166
100167 * self . inner ( ) . keyboard . borrow_mut ( ) = keyboard;
168+ self . update_key_visibility ( ) ;
101169 }
102170
103- pub ( crate ) fn set_selected ( & self , scancode_names : Vec < String > ) {
171+ pub ( crate ) fn set_selected ( & self , mut scancode_names : Vec < String > ) {
172+ self . inner ( ) . mod_tap_signal_blocked . set ( true ) ;
173+
174+ self . inner ( ) . mod_tap_box . set_sensitive ( false ) ;
175+ self . inner ( ) . mod_tap_check . set_active ( false ) ;
176+ self . inner ( ) . mod_tap_mods . set_active_id ( Some ( "LEFT_CTRL" ) ) ;
177+
178+ if scancode_names. len ( ) == 1 {
179+ self . inner ( ) . mod_tap_box . set_sensitive ( true ) ;
180+ if let Some ( ( mod_name, _) ) = parse_mod_tap ( & scancode_names[ 0 ] ) {
181+ self . inner ( ) . mod_tap_check . set_active ( true ) ;
182+ self . inner ( ) . mod_tap_mods . set_active_id ( Some ( mod_name) ) ;
183+ }
184+ }
185+
186+ self . inner ( ) . mod_tap_signal_blocked . set ( false ) ;
187+
188+ for i in scancode_names. iter_mut ( ) {
189+ if let Some ( ( _, kc_name) ) = parse_mod_tap ( & i) {
190+ * i = kc_name. to_string ( ) ;
191+ }
192+ }
193+
104194 self . inner ( ) . group_box . set_selected ( scancode_names) ;
105195 }
106196
107- fn key_pressed ( & self , name : String ) {
108- let kb = match self . inner ( ) . keyboard . borrow ( ) . clone ( ) {
197+ fn mod_ ( & self ) -> Option < String > {
198+ if self . inner ( ) . mod_tap_box . get_visible ( ) && self . inner ( ) . mod_tap_check . get_active ( ) {
199+ Some ( self . inner ( ) . mod_tap_mods . get_active_id ( ) ?. into ( ) )
200+ } else {
201+ None
202+ }
203+ }
204+
205+ fn keyboard ( & self ) -> Option < Keyboard > {
206+ self . inner ( ) . keyboard . borrow ( ) . clone ( )
207+ }
208+
209+ fn key_pressed ( & self , mut name : String ) {
210+ let kb = match self . keyboard ( ) {
109211 Some ( kb) => kb,
110- None => {
111- return ;
112- }
212+ None => return ,
113213 } ;
114214 let layer = kb. layer ( ) ;
115215
216+ if let Some ( mod_) = self . mod_ ( ) {
217+ name = format ! ( "MT({}, {})" , mod_, name) ;
218+ }
219+
116220 info ! ( "Clicked {} layer {:?}" , name, layer) ;
117221 if let Some ( layer) = layer {
118222 let futures = FuturesUnordered :: new ( ) ;
119- for i in kb. selected ( ) . iter ( ) {
120- let i = * i;
223+ for i in kb. selected ( ) . iter ( ) . copied ( ) {
121224 futures. push ( clone ! ( @strong kb, @strong name => async move {
122225 kb. keymap_set( i, layer, & name) . await ;
123226 } ) ) ;
124227 }
125228 glib:: MainContext :: default ( ) . spawn_local ( async { futures. collect :: < ( ) > ( ) . await } ) ;
126229 }
127230 }
231+
232+ fn mod_tap_updated ( & self ) {
233+ if self . inner ( ) . mod_tap_signal_blocked . get ( ) {
234+ return ;
235+ }
236+
237+ let kb = match self . keyboard ( ) {
238+ Some ( kb) => kb,
239+ None => return ,
240+ } ;
241+ let layer = kb. layer ( ) ;
242+
243+ if let Some ( layer) = layer {
244+ let futures = FuturesUnordered :: new ( ) ;
245+ for i in kb. selected ( ) . iter ( ) . copied ( ) {
246+ if let Some ( ( _, scancode) ) = & kb. board ( ) . keys ( ) [ i] . get_scancode ( layer) {
247+ let kc_name = if let Some ( ( _, kc_name) ) = parse_mod_tap ( scancode) {
248+ kc_name
249+ } else {
250+ scancode
251+ } ;
252+
253+ let name = if let Some ( mod_name) = self . mod_ ( ) {
254+ format ! ( "MT({}, {})" , mod_name, kc_name)
255+ } else {
256+ kc_name. to_string ( )
257+ } ;
258+
259+ futures. push ( clone ! ( @strong kb, @strong name => async move {
260+ kb. keymap_set( i, layer, & name) . await ;
261+ } ) ) ;
262+ }
263+ }
264+ glib:: MainContext :: default ( ) . spawn_local ( async { futures. collect :: < ( ) > ( ) . await } ) ;
265+ }
266+ }
128267}
129268
130269#[ cfg( test) ]
0 commit comments