1 #include "e.h"
2 #include "e_mod_main.h"
3 #include "e_mod_parse.h"
4 #include "gadget/xkbswitch.h"
5
6 /* GADCON */
7 static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style);
8 static void _gc_shutdown(E_Gadcon_Client *gcc);
9 static void _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient);
10 static const char *_gc_label(const E_Gadcon_Client_Class *client_class);
11 static const char *_gc_id_new(const E_Gadcon_Client_Class *client_class EINA_UNUSED);
12 static Evas_Object *_gc_icon(const E_Gadcon_Client_Class *client_class, Evas *evas);
13
14 /* EVENTS */
15 static Eina_Bool _xkb_changed_state(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
16 static void _e_xkb_cb_mouse_down(void *data, Evas *evas, Evas_Object *obj, void *event);
17 static void _e_xkb_cb_menu_configure(void *data, E_Menu *mn, E_Menu_Item *mi EINA_UNUSED);
18 static void _e_xkb_cb_lmenu_post(void *data, E_Menu *menu EINA_UNUSED);
19 static void _e_xkb_cb_lmenu_set(void *data, E_Menu *mn EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED);
20
21 /* Static variables
22 * The static variables specific to the current code unit.
23 */
24
25 /* GADGET INSTANCE */
26
27 typedef struct _Instance
28 {
29 E_Gadcon_Client *gcc;
30
31 Evas_Object *o_xkbswitch;
32 Evas_Object *o_xkbflag;
33 E_Config_XKB_Layout *layout;
34 unsigned int lmenu_timestamp;
35 Ecore_Timer *lmenu_timer;
36
37 E_Menu *lmenu;
38 } Instance;
39
40 /* LIST OF INSTANCES */
41 static Eina_List *instances = NULL;
42
43 /* Global variables
44 * Global variables shared across the module.
45 */
46
47 /* CONFIG STRUCTURE */
48 Xkb _xkb = { NULL, NULL, NULL };
49
50 static Ecore_Event_Handler *xkb_change_handle = NULL;
51
52 static const E_Gadcon_Client_Class _gc_class =
53 {
54 GADCON_CLIENT_CLASS_VERSION,
55 "xkbswitch",
56 {
57 _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, NULL
58 },
59 E_GADCON_CLIENT_STYLE_PLAIN
60 };
61
62 E_API E_Module_Api e_modapi =
63 {
64 E_MODULE_API_VERSION,
65 "XKB Switcher"
66 };
67
68 /* Module initializer
69 * Initializes the configuration file, checks its versions, populates
70 * menus, finds the rules file, initializes gadget icon.
71 */
72 E_API void *
e_modapi_init(E_Module * m)73 e_modapi_init(E_Module *m)
74 {
75 /* Menus and dialogs */
76 e_configure_registry_category_add("keyboard_and_mouse", 80, _("Input"),
77 NULL, "preferences-behavior");
78 e_configure_registry_item_add("keyboard_and_mouse/xkbswitch", 110,
79 _("Keyboard"), NULL,
80 "preferences-desktop-keyboard",
81 _xkb_cfg_dialog);
82 e_modapi_gadget_init(m);
83
84 _xkb.module = m;
85 xkb_change_handle = ecore_event_handler_add(E_EVENT_XKB_CHANGED, _xkb_changed_state, NULL);
86 /* Gadcon */
87 e_gadcon_provider_register(&_gc_class);
88 return m;
89 }
90
91 /* Module shutdown
92 * Called when the module gets unloaded. Deregisters the menu state
93 * and frees up the config.
94 */
95 E_API int
e_modapi_shutdown(E_Module * m)96 e_modapi_shutdown(E_Module *m)
97 {
98 e_configure_registry_item_del("keyboard_and_mouse/xkbswitch");
99 e_configure_registry_category_del("keyboard_and_mouse");
100
101 e_modapi_gadget_shutdown(m);
102
103 if (_xkb.evh) ecore_event_handler_del(_xkb.evh);
104 if (_xkb.cfd) e_object_del(E_OBJECT(_xkb.cfd));
105 _xkb.cfd = NULL;
106 _xkb.module = NULL;
107 ecore_event_handler_del(xkb_change_handle);
108 e_gadcon_provider_unregister(&_gc_class);
109
110 return 1;
111 }
112
113 /* Module state save
114 * Used to save the configuration file.
115 */
116 E_API int
e_modapi_save(E_Module * m)117 e_modapi_save(E_Module *m)
118 {
119 e_modapi_gadget_save(m);
120
121 return 1;
122 }
123
124 /* Updates icons on all available xkbswitch gadgets to reflect the
125 * current layout state.
126 */
127 void
_xkb_update_icon(int cur_group)128 _xkb_update_icon(int cur_group)
129 {
130 Instance *inst;
131 Eina_List *l;
132 E_Config_XKB_Layout *cl;
133
134 EINA_SAFETY_ON_NULL_RETURN(e_config->xkb.used_layouts);
135 //INF("ui: %d", cur_group);
136 cl = eina_list_nth(e_config->xkb.used_layouts, cur_group);
137 EINA_SAFETY_ON_NULL_RETURN(cl);
138 if (!e_config_xkb_layout_eq(cl, e_config->xkb.current_layout))
139 {
140 e_config_xkb_layout_free(e_config->xkb.current_layout);
141 e_config->xkb.current_layout = e_config_xkb_layout_dup(cl);
142 }
143
144 if (e_config->xkb.only_label)
145 {
146 EINA_LIST_FOREACH(instances, l, inst)
147 {
148 if (!e_config_xkb_layout_eq(e_config->xkb.current_layout, inst->layout))
149 inst->layout = e_config_xkb_layout_dup(e_config->xkb.current_layout);
150 E_FREE_FUNC(inst->o_xkbflag, evas_object_del);
151 e_theme_edje_object_set(inst->o_xkbswitch,
152 "base/theme/modules/xkbswitch",
153 "e/modules/xkbswitch/noflag");
154 edje_object_part_text_set(inst->o_xkbswitch,
155 "e.text.label", cl->name);
156 }
157 }
158 else
159 {
160 EINA_LIST_FOREACH(instances, l, inst)
161 {
162 if (!e_config_xkb_layout_eq(e_config->xkb.current_layout, inst->layout))
163 inst->layout = e_config_xkb_layout_dup(e_config->xkb.current_layout);
164 if (!inst->o_xkbflag)
165 inst->o_xkbflag = e_icon_add(inst->gcc->gadcon->evas);
166 e_theme_edje_object_set(inst->o_xkbswitch,
167 "base/theme/modules/xkbswitch",
168 "e/modules/xkbswitch/main");
169 e_xkb_e_icon_flag_setup(inst->o_xkbflag, cl->name);
170 edje_object_part_swallow(inst->o_xkbswitch, "e.swallow.flag",
171 inst->o_xkbflag);
172 edje_object_part_text_set(inst->o_xkbswitch, "e.text.label",
173 e_xkb_layout_name_reduce(cl->name));
174 }
175 }
176 }
177
178 /* LOCAL STATIC FUNCTIONS */
179
180 static E_Gadcon_Client *
_gc_init(E_Gadcon * gc,const char * gcname,const char * id,const char * style)181 _gc_init(E_Gadcon *gc, const char *gcname, const char *id, const char *style)
182 {
183 Instance *inst;
184
185 /* The instance */
186 inst = E_NEW(Instance, 1);
187 /* The gadget */
188 inst->o_xkbswitch = edje_object_add(gc->evas);
189 inst->layout = e_config_xkb_layout_dup(e_xkb_layout_get());
190 if (e_config->xkb.only_label || (!inst->layout))
191 e_theme_edje_object_set(inst->o_xkbswitch,
192 "base/theme/modules/xkbswitch",
193 "e/modules/xkbswitch/noflag");
194 else
195 e_theme_edje_object_set(inst->o_xkbswitch,
196 "base/theme/modules/xkbswitch",
197 "e/modules/xkbswitch/main");
198 edje_object_part_text_set(inst->o_xkbswitch, "e.text.label",
199 inst->layout ? e_xkb_layout_name_reduce(inst->layout->name) : _("NONE"));
200 /* The gadcon client */
201 inst->gcc = e_gadcon_client_new(gc, gcname, id, style, inst->o_xkbswitch);
202 inst->gcc->data = inst;
203 /* The flag icon */
204 if (inst->layout && (!e_config->xkb.only_label))
205 {
206 inst->o_xkbflag = e_icon_add(gc->evas);
207 e_xkb_e_icon_flag_setup(inst->o_xkbflag, inst->layout->name);
208 /* The icon is part of the gadget. */
209 edje_object_part_swallow(inst->o_xkbswitch, "e.swallow.flag",
210 inst->o_xkbflag);
211 }
212 else inst->o_xkbflag = NULL;
213 /* e_config->xkb.used_layout */
214 /* Hook some menus */
215 evas_object_event_callback_add(inst->o_xkbswitch, EVAS_CALLBACK_MOUSE_DOWN,
216 _e_xkb_cb_mouse_down, inst);
217 /* Make the list know about the instance */
218 instances = eina_list_append(instances, inst);
219
220 return inst->gcc;
221 }
222
223 static void
_gc_shutdown(E_Gadcon_Client * gcc)224 _gc_shutdown(E_Gadcon_Client *gcc)
225 {
226 Instance *inst;
227
228 if (!(inst = gcc->data)) return;
229 instances = eina_list_remove(instances, inst);
230
231 if (inst->lmenu)
232 {
233 e_menu_post_deactivate_callback_set(inst->lmenu, NULL, NULL);
234 e_object_del(E_OBJECT(inst->lmenu));
235 inst->lmenu = NULL;
236 }
237 if (inst->o_xkbswitch)
238 {
239 evas_object_event_callback_del(inst->o_xkbswitch,
240 EVAS_CALLBACK_MOUSE_DOWN,
241 _e_xkb_cb_mouse_down);
242 evas_object_del(inst->o_xkbswitch);
243 evas_object_del(inst->o_xkbflag);
244 }
245 e_config_xkb_layout_free(inst->layout);
246 E_FREE(inst);
247 }
248
249 static void
_gc_orient(E_Gadcon_Client * gcc,E_Gadcon_Orient orient EINA_UNUSED)250 _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED)
251 {
252 e_gadcon_client_aspect_set(gcc, 16, 16);
253 e_gadcon_client_min_size_set(gcc, 16, 16);
254 }
255
256 static const char *
_gc_label(const E_Gadcon_Client_Class * client_class EINA_UNUSED)257 _gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
258 {
259 return _("Keyboard");
260 }
261
262 static const char *
_gc_id_new(const E_Gadcon_Client_Class * client_class)263 _gc_id_new(const E_Gadcon_Client_Class *client_class)
264 {
265 static char buf[4096];
266
267 snprintf(buf, sizeof(buf), "%s.%d", client_class->name,
268 eina_list_count(instances) + 1);
269 return buf;
270 }
271
272 static Evas_Object *
_gc_icon(const E_Gadcon_Client_Class * client_class EINA_UNUSED,Evas * evas)273 _gc_icon(const E_Gadcon_Client_Class *client_class EINA_UNUSED, Evas *evas)
274 {
275 Evas_Object *o;
276 char buf[PATH_MAX];
277
278 snprintf(buf, sizeof(buf), "%s/e-module-xkbswitch.edj", _xkb.module->dir);
279 o = edje_object_add(evas);
280 edje_object_file_set(o, buf, "icon");
281 return o;
282 }
283
284 static Eina_Bool
_xkb_changed_state(void * data EINA_UNUSED,int type EINA_UNUSED,void * event EINA_UNUSED)285 _xkb_changed_state(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
286 {
287 _xkb_update_icon(e_config->xkb.cur_group);
288 return ECORE_CALLBACK_PASS_ON;
289 }
290
291 static Eina_Bool
_e_xkb_cb_lmenu(void * data)292 _e_xkb_cb_lmenu(void *data)
293 {
294 Instance *inst;
295
296 Evas_Coord x, y, w, h;
297 int cx, cy, dir;
298 E_Menu_Item *mi;
299
300 inst = data;
301 inst->lmenu_timer = NULL;
302
303 /* Coordinates and sizing */
304 evas_object_geometry_get(inst->o_xkbswitch, &x, &y, &w, &h);
305 e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &cx, &cy,
306 NULL, NULL);
307 x += cx;
308 y += cy;
309
310 if (!inst->lmenu) inst->lmenu = e_menu_new();
311
312 mi = e_menu_item_new(inst->lmenu);
313
314 e_menu_item_label_set(mi, _("Settings"));
315 e_util_menu_item_theme_icon_set(mi, "preferences-system");
316 e_menu_item_callback_set(mi, _e_xkb_cb_menu_configure, NULL);
317
318 if (!e_config->xkb.dont_touch_my_damn_keyboard)
319 {
320 E_Config_XKB_Layout *cl, *cur;
321 Eina_List *l;
322 char buf[4096];
323
324 mi = e_menu_item_new(inst->lmenu);
325 e_menu_item_separator_set(mi, 1);
326 cur = e_xkb_layout_get();
327
328 /* Append all the layouts */
329 EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl)
330 {
331 const char *name = cl->name;
332
333 mi = e_menu_item_new(inst->lmenu);
334
335 e_menu_item_radio_set(mi, 1);
336 e_menu_item_radio_group_set(mi, 1);
337 if (e_config_xkb_layout_eq(cur, cl))
338 e_menu_item_toggle_set(mi, 1);
339 e_xkb_flag_file_get(buf, sizeof(buf), name);
340 e_menu_item_icon_file_set(mi, buf);
341 if (cl->variant)
342 snprintf(buf, sizeof(buf), "%s (%s, %s)", cl->name, cl->model, cl->variant);
343 else
344 snprintf(buf, sizeof(buf), "%s (%s)", cl->name, cl->model);
345 e_menu_item_label_set(mi, buf);
346 e_menu_item_callback_set(mi, _e_xkb_cb_lmenu_set, cl);
347 }
348 }
349
350 /* Deactivate callback */
351 e_menu_post_deactivate_callback_set(inst->lmenu,
352 _e_xkb_cb_lmenu_post, inst);
353 /* Proper menu orientation */
354 switch (inst->gcc->gadcon->orient)
355 {
356 case E_GADCON_ORIENT_TOP:
357 dir = E_MENU_POP_DIRECTION_DOWN;
358 break;
359
360 case E_GADCON_ORIENT_BOTTOM:
361 dir = E_MENU_POP_DIRECTION_UP;
362 break;
363
364 case E_GADCON_ORIENT_LEFT:
365 dir = E_MENU_POP_DIRECTION_RIGHT;
366 break;
367
368 case E_GADCON_ORIENT_RIGHT:
369 dir = E_MENU_POP_DIRECTION_LEFT;
370 break;
371
372 case E_GADCON_ORIENT_CORNER_TL:
373 dir = E_MENU_POP_DIRECTION_DOWN;
374 break;
375
376 case E_GADCON_ORIENT_CORNER_TR:
377 dir = E_MENU_POP_DIRECTION_DOWN;
378 break;
379
380 case E_GADCON_ORIENT_CORNER_BL:
381 dir = E_MENU_POP_DIRECTION_UP;
382 break;
383
384 case E_GADCON_ORIENT_CORNER_BR:
385 dir = E_MENU_POP_DIRECTION_UP;
386 break;
387
388 case E_GADCON_ORIENT_CORNER_LT:
389 dir = E_MENU_POP_DIRECTION_RIGHT;
390 break;
391
392 case E_GADCON_ORIENT_CORNER_RT:
393 dir = E_MENU_POP_DIRECTION_LEFT;
394 break;
395
396 case E_GADCON_ORIENT_CORNER_LB:
397 dir = E_MENU_POP_DIRECTION_RIGHT;
398 break;
399
400 case E_GADCON_ORIENT_CORNER_RB:
401 dir = E_MENU_POP_DIRECTION_LEFT;
402 break;
403
404 case E_GADCON_ORIENT_FLOAT:
405 case E_GADCON_ORIENT_HORIZ:
406 case E_GADCON_ORIENT_VERT:
407 default:
408 dir = E_MENU_POP_DIRECTION_AUTO;
409 break;
410 }
411
412 e_gadcon_locked_set(inst->gcc->gadcon, 1);
413
414 /* We display not relatively to the gadget, but similarly to
415 * the start menu - thus the need for direction etc.
416 */
417 e_menu_activate_mouse(inst->lmenu,
418 e_zone_current_get(),
419 x, y, w, h, dir, inst->lmenu_timestamp);
420
421 return ECORE_CALLBACK_CANCEL;
422 }
423
424 static void
_e_xkb_cb_mouse_down(void * data,Evas * evas EINA_UNUSED,Evas_Object * obj EINA_UNUSED,void * event)425 _e_xkb_cb_mouse_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
426 {
427 Evas_Event_Mouse_Down *ev = event;
428 Instance *inst = data;
429 E_Menu *m;
430
431 if (!inst) return;
432
433 if (ev->button == 3) /* Right-click utility menu */
434 {
435 int x, y;
436 E_Menu_Item *mi;
437
438 /* The menu and menu item */
439 m = e_menu_new();
440 mi = e_menu_item_new(m);
441 /* Menu item specifics */
442 e_menu_item_label_set(mi, _("Settings"));
443 e_util_menu_item_theme_icon_set(mi, "preferences-system");
444 e_menu_item_callback_set(mi, _e_xkb_cb_menu_configure, NULL);
445 /* Append into the util menu */
446 m = e_gadcon_client_util_menu_items_append(inst->gcc,
447 m, 0);
448 /* Coords */
449 e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y,
450 NULL, NULL);
451 /* Activate - we show the menu relative to the gadget */
452 e_menu_activate_mouse(m,
453 e_zone_current_get(),
454 (x + ev->output.x), (y + ev->output.y), 1, 1,
455 E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
456 evas_event_feed_mouse_up(inst->gcc->gadcon->evas, ev->button,
457 EVAS_BUTTON_NONE, ev->timestamp, NULL);
458 }
459
460 else if ((ev->button == 2) /* Middle click */
461 ||
462 ((ev->button == 1) && (ev->flags & EVAS_BUTTON_DOUBLE_CLICK)) /* double Left-click */
463 )
464 {
465 if (inst->lmenu_timer)
466 {
467 ecore_timer_del(inst->lmenu_timer);
468 inst->lmenu_timer = NULL;
469 }
470 e_xkb_layout_next();
471 }
472 else if ((ev->button == 1) && (!inst->lmenu)) /* Left-click layout menu */
473 {
474 if (!inst->lmenu_timer)
475 {
476 inst->lmenu_timestamp = ev->timestamp;
477 #ifdef HAVE_WAYLAND_ONLY
478 inst->lmenu_timer = ecore_timer_add(0.25,
479 _e_xkb_cb_lmenu, inst);
480 #else
481 inst->lmenu_timer = ecore_timer_add(ecore_x_double_click_time_get(),
482 _e_xkb_cb_lmenu, inst);
483 #endif
484
485 }
486 }
487 }
488
489 static void
_e_xkb_cb_lmenu_post(void * data,E_Menu * menu EINA_UNUSED)490 _e_xkb_cb_lmenu_post(void *data, E_Menu *menu EINA_UNUSED)
491 {
492 Instance *inst = data;
493
494 if (!inst) return;
495 e_gadcon_locked_set(inst->gcc->gadcon, 0);
496 inst->lmenu = NULL;
497 }
498
499 static void
_e_xkb_cb_menu_configure(void * data EINA_UNUSED,E_Menu * mn EINA_UNUSED,E_Menu_Item * mi EINA_UNUSED)500 _e_xkb_cb_menu_configure(void *data EINA_UNUSED, E_Menu *mn EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
501 {
502 if (_xkb.cfd) return;
503 _xkb_cfg_dialog(NULL, NULL);
504 }
505
506 static void
_e_xkb_cb_lmenu_set(void * data,E_Menu * mn EINA_UNUSED,E_Menu_Item * mi EINA_UNUSED)507 _e_xkb_cb_lmenu_set(void *data, E_Menu *mn EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
508 {
509 Eina_List *l;
510 int cur_group = -1, grp = -1;
511 E_Config_XKB_Layout *cl2, *cl = data;
512
513 EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl2)
514 {
515 grp++;
516 if (cl2 == cl) cur_group = grp;
517 }
518 if (cur_group == -1) return;
519 if (e_config_xkb_layout_eq(cl, e_xkb_layout_get())) return;
520 e_xkb_layout_set(cl);
521 e_config_xkb_layout_free(e_config->xkb.sel_layout);
522 e_config->xkb.sel_layout = e_config_xkb_layout_dup(cl);
523 if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
524 #ifdef HAVE_WAYLAND
525 e_comp_wl_input_keymap_index_set(cur_group);
526 #else
527 (void)cur_group;
528 #endif
529 }
530
531