@@ -2216,19 +2216,53 @@ xkb_state_key_get_consumed_mods(struct xkb_state *state, xkb_keycode_t kc)
22162216 * bloated for *clients* applications.
22172217 */
22182218struct xkb_state_machine {
2219+ /** Internal state */
22192220 struct xkb_state state ;
2221+
2222+ /** Extra event API-specific state data */
2223+ struct state_machine_data {
2224+ /** Configuration */
2225+ struct state_machine_config {
2226+ /** Shortcuts tweak */
2227+ struct state_machine_shortcuts_config {
2228+ /** Modifier mask to trigger tweak */
2229+ xkb_mod_mask_t mask ;
2230+ /** Target layouts targets */
2231+ xkb_layout_index_t * targets ;
2232+ } shortcuts ;
2233+ } config ;
2234+ /** State */
2235+ struct {
2236+ /** The “real” base group, before shortcuts tweak */
2237+ int32_t base_group ;
2238+ } state ;
2239+ } extra ;
22202240};
22212241
22222242struct xkb_state_machine_options {
2243+ /** Accessibility flags */
22232244 enum xkb_state_accessibility_flags a11y_affect ;
22242245 enum xkb_state_accessibility_flags a11y_flags ;
2246+
2247+ /** Shortcuts tweak */
2248+ struct xkb_shortcuts_config_options {
2249+ /** Modifier mask to trigger tweak */
2250+ xkb_mod_mask_t mask ;
2251+ /** Target layouts targets */
2252+ darray (xkb_layout_index_t ) targets ;
2253+ } shortcuts ;
2254+
22252255 struct xkb_context * ctx ;
22262256};
22272257
22282258#define state_machine_options_new (context ) {\
22292259 .a11y_affect = XKB_STATE_A11Y_NO_FLAGS, \
22302260 .a11y_flags = XKB_STATE_A11Y_NO_FLAGS, \
2231- .ctx = (context) \
2261+ .shortcuts = { \
2262+ .mask = 0, \
2263+ .targets = darray_new(), \
2264+ }, \
2265+ .ctx = (context), \
22322266}
22332267
22342268/* Default state options */
@@ -2253,6 +2287,8 @@ xkb_state_machine_options_destroy(struct xkb_state_machine_options *options)
22532287{
22542288 if (options == NULL )
22552289 return ;
2290+
2291+ darray_free (options -> shortcuts .targets );
22562292 xkb_context_unref (options -> ctx );
22572293 free (options );
22582294}
@@ -2282,6 +2318,110 @@ xkb_state_machine_options_update_a11y_flags(
22822318 return 0 ;
22832319}
22842320
2321+ int
2322+ xkb_state_machine_options_shortcuts_update_mods (
2323+ struct xkb_state_machine_options * restrict options ,
2324+ xkb_mod_mask_t affect , xkb_mod_mask_t mask
2325+ )
2326+ {
2327+ options -> shortcuts .mask &= ~affect ;
2328+ options -> shortcuts .mask |= (mask & affect );
2329+ return 0 ;
2330+ }
2331+
2332+ static int
2333+ state_machine_set_shortcuts_reference_layout (
2334+ struct xkb_shortcuts_config_options * restrict options ,
2335+ xkb_layout_index_t source , xkb_layout_index_t target
2336+ )
2337+ {
2338+ if (source >= XKB_MAX_GROUPS || target >= XKB_MAX_GROUPS )
2339+ return 1 ;
2340+
2341+ if (target == source ) {
2342+ /* Skip default setting */
2343+ return 0 ;
2344+ }
2345+
2346+ /* Resize array & initialize new entries, if relevant */
2347+ if (source >= darray_size (options -> targets )) {
2348+ xkb_layout_index_t new = darray_size (options -> targets );
2349+ darray_resize (options -> targets , source + 1 );
2350+ for (; new < source ; new ++ )
2351+ darray_item (options -> targets , new ) =
2352+ XKB_LAYOUT_INVALID ;
2353+ }
2354+
2355+ darray_item (options -> targets , source ) = (source == target )
2356+ ? XKB_LAYOUT_INVALID
2357+ : target ;
2358+ return 0 ;
2359+ }
2360+
2361+ int
2362+ xkb_state_machine_options_shortcuts_set_mapping (
2363+ struct xkb_state_machine_options * restrict options ,
2364+ xkb_layout_index_t source , xkb_layout_index_t target
2365+ )
2366+ {
2367+ return state_machine_set_shortcuts_reference_layout (& options -> shortcuts ,
2368+ source , target );
2369+ }
2370+
2371+ static bool
2372+ state_machine_set_shortcuts (struct xkb_state_machine * sm ,
2373+ const struct xkb_shortcuts_config_options * options )
2374+ {
2375+ struct xkb_keymap * const restrict keymap = sm -> state .keymap ;
2376+
2377+ /* Consider only defined layouts */
2378+ xkb_layout_index_t count = MIN (
2379+ keymap -> num_groups ,
2380+ (xkb_layout_index_t ) darray_size (options -> targets )
2381+ );
2382+ /* Drop layout entries with default setting or invalid group */
2383+ static_assert (XKB_LAYOUT_INVALID > XKB_MAX_GROUPS , "" );
2384+ while (count > 1 ) {
2385+ if (darray_item (options -> targets , count - 1 ) <
2386+ keymap -> num_groups )
2387+ break ;
2388+ count -- ;
2389+ }
2390+ if (!count )
2391+ return true;
2392+
2393+ xkb_mod_mask_t mask = options -> mask ;
2394+ if (mask ) {
2395+ /* Sanitize mask */
2396+ mask &= (UINT64_C (1 ) << xkb_keymap_num_mods (keymap )) - 1 ;
2397+ } else {
2398+ /* Default mask */
2399+ // TODO
2400+ }
2401+ if (!mask )
2402+ return true;
2403+
2404+ xkb_layout_index_t * const targets = calloc (keymap -> num_groups ,
2405+ sizeof (* targets ));
2406+ if (!targets )
2407+ return false;
2408+
2409+ for (xkb_layout_index_t l = 0 ; l < count ; l ++ ) {
2410+ /* Sanitize layouts targets */
2411+ targets [l ] = (darray_item (options -> targets , l ) <
2412+ keymap -> num_groups )
2413+ ? darray_item (options -> targets , l )
2414+ : XKB_LAYOUT_INVALID ;
2415+ }
2416+ for (xkb_layout_index_t l = count ; l < keymap -> num_groups ; l ++ ) {
2417+ targets [l ] = XKB_LAYOUT_INVALID ;
2418+ }
2419+
2420+ sm -> extra .config .shortcuts .mask = mask ;
2421+ sm -> extra .config .shortcuts .targets = targets ;
2422+ return true;
2423+ }
2424+
22852425struct xkb_state_machine *
22862426xkb_state_machine_new (struct xkb_keymap * keymap ,
22872427 const struct xkb_state_machine_options * options )
@@ -2295,7 +2435,19 @@ xkb_state_machine_new(struct xkb_keymap *keymap,
22952435
22962436 xkb_state_init (& sm -> state , keymap ,
22972437 options -> a11y_affect , options -> a11y_flags );
2438+ sm -> extra .state .base_group = (int32_t ) XKB_LAYOUT_INVALID ;
2439+
2440+ /* Shortcuts */
2441+ if (!darray_empty (options -> shortcuts .targets )) {
2442+ if (!state_machine_set_shortcuts (sm , & options -> shortcuts ))
2443+ goto error ;
2444+ }
2445+
22982446 return sm ;
2447+
2448+ error :
2449+ xkb_state_machine_unref (sm );
2450+ return NULL ;
22992451}
23002452
23012453struct xkb_state_machine *
@@ -2314,6 +2466,7 @@ xkb_state_machine_unref(struct xkb_state_machine *sm)
23142466 return ;
23152467
23162468 xkb_state_destroy (& sm -> state );
2469+ free (sm -> extra .config .shortcuts .targets );
23172470 free (sm );
23182471}
23192472
@@ -2337,6 +2490,8 @@ xkb_state_machine_update_controls(struct xkb_state_machine *sm,
23372490 & sm -> state , events , affect , controls
23382491 );
23392492
2493+ // FIXME: shortcut tweak
2494+
23402495 if (changed ) {
23412496 /* Create event only if some component actually changed */
23422497 darray_append (events -> queue , (struct xkb_event ) {
@@ -2379,6 +2534,8 @@ xkb_state_machine_update_latched_locked(struct xkb_state_machine *sm,
23792534 locked_layout
23802535 );
23812536
2537+ // FIXME: shortcut tweak
2538+
23822539 if (changed ) {
23832540 /* Create event only if some component actually changed */
23842541 darray_append (events -> queue , (struct xkb_event ) {
@@ -2402,12 +2559,18 @@ xkb_state_machine_update_key(struct xkb_state_machine *sm,
24022559 events -> next = 0 ;
24032560
24042561 struct xkb_state * restrict const state = & sm -> state ;
2562+ struct state_machine_data * restrict const extra = & sm -> extra ;
24052563 const struct xkb_key * const key = XkbKey (state -> keymap , kc );
24062564 if (key == NULL )
24072565 return 0 ;
24082566
24092567 const struct state_components prev_components = state -> components ;
24102568
2569+ if (extra -> state .base_group != (int32_t ) XKB_LAYOUT_INVALID ) {
2570+ /* Restore the real base group, but not the effective group */
2571+ state -> components .base_group = extra -> state .base_group ;
2572+ }
2573+
24112574 state -> set_mods = 0 ;
24122575 state -> clear_mods = 0 ;
24132576
@@ -2439,6 +2602,25 @@ xkb_state_machine_update_key(struct xkb_state_machine *sm,
24392602
24402603 xkb_state_update_derived (state );
24412604
2605+ bool used_shortcuts_tweak = false;
2606+ if (extra -> config .shortcuts .targets &&
2607+ (state -> components .mods & extra -> config .shortcuts .mask ) &&
2608+ (extra -> config .shortcuts .targets [state -> components .group ] !=
2609+ XKB_LAYOUT_INVALID )) {
2610+ /* Activate shortcuts tweak */
2611+ extra -> state .base_group = state -> components .base_group ;
2612+ state -> components .base_group
2613+ = (int32_t ) extra -> config .shortcuts .targets [state -> components .group ]
2614+ - state -> components .latched_group
2615+ - state -> components .locked_group ;
2616+ // TODO: could we avoid recomputing everything?
2617+ xkb_state_update_derived (state );
2618+ used_shortcuts_tweak = true;
2619+ } else {
2620+ /* Deactivate shortcuts tweak */
2621+ extra -> state .base_group = (int32_t ) XKB_LAYOUT_INVALID ;
2622+ }
2623+
24422624 const enum xkb_state_component changed =
24432625 get_state_component_changes (& prev_components , & state -> components );
24442626
@@ -2454,6 +2636,8 @@ xkb_state_machine_update_key(struct xkb_state_machine *sm,
24542636 : XKB_EVENT_TYPE_KEY_UP ,
24552637 .keycode = kc
24562638 });
2639+ } else if (used_shortcuts_tweak ) {
2640+ // FIXME: we need to prepend the tweak
24572641 }
24582642
24592643 if (changed ) {
0 commit comments