1 /* TODO
2    check http://www.pvv.org/~mariusbu/proposal.html
3    for advances in cross toolkit settings */
4 
5 #include <e.h>
6 //#include <X11/Xlib.h>
7 //#include <X11/Xmd.h>            /* For CARD16 */
8 
9 // define here to avoid needing x includes directly.
10 #define C16                 unsigned short
11 #define C32                 unsigned int
12 
13 #define RETRY_TIMEOUT       2.0
14 
15 #define SETTING_TYPE_INT    0
16 #define SETTING_TYPE_STRING 1
17 #define SETTING_TYPE_COLOR  2
18 
19 #define OFFSET_ADD(n) ((n + 4 - 1) & (~(4 - 1)))
20 
21 typedef struct _Settings_Manager Settings_Manager;
22 typedef struct _Setting         Setting;
23 
24 struct _Settings_Manager
25 {
26    Ecore_X_Window selection;
27    Ecore_Timer   *timer_retry;
28    unsigned long  serial;
29    Ecore_X_Atom   _atom_xsettings_screen;
30    Eina_Bool enabled E_BITFIELD;
31 };
32 
33 struct _Setting
34 {
35    unsigned short type;
36 
37    const char    *name;
38 
39    struct
40      {
41       const char *value;
42      } s;
43    struct
44      {
45       int value;
46      } i;
47    struct
48      {
49       unsigned short red, green, blue, alpha;
50      } c;
51 
52    unsigned long length;
53    unsigned long last_change;
54 };
55 
56 static void _e_xsettings_apply(Settings_Manager *sm);
57 
58 static Ecore_X_Atom _atom_manager = 0;
59 static Ecore_X_Atom _atom_xsettings = 0;
60 static Ecore_X_Atom _atom_gtk_iconthemes = 0;
61 static Ecore_X_Atom _atom_gtk_rcfiles = 0;
62 static Settings_Manager *manager = NULL;
63 static Eina_List *settings = NULL;
64 static Eina_Bool running = EINA_FALSE;
65 static Eio_File *eio_op = NULL;
66 static Eina_Bool setting = EINA_FALSE;
67 static Eina_Bool reset = EINA_FALSE;
68 static const char _setting_icon_theme_name[] = "Net/IconThemeName";
69 static const char _setting_theme_name[] = "Net/ThemeName";
70 static const char _setting_font_name[] = "Gtk/FontName";
71 #if 0
72 static const char _setting_xft_dpi[] = "Xft/DPI";
73 #endif
74 static const char *_setting_theme = NULL;
75 
76 static void _e_xsettings_done_cb(void *data, Eio_File *handler, const Eina_Stat *stat);
77 
78 static Ecore_X_Atom
_e_xsettings_atom_screen_get(int screen_num)79 _e_xsettings_atom_screen_get(int screen_num)
80 {
81    char buf[32];
82 
83    snprintf(buf, sizeof(buf), "_XSETTINGS_S%d", screen_num);
84    return ecore_x_atom_get(buf);
85 }
86 
87 static Eina_Bool
_e_xsettings_selection_owner_set(void)88 _e_xsettings_selection_owner_set(void)
89 {
90    Ecore_X_Atom atom;
91    Ecore_X_Window cur_selection;
92    Eina_Bool ret;
93 
94    atom = _e_xsettings_atom_screen_get(0);
95    ecore_x_selection_owner_set(e_comp->cm_selection, atom,
96                                ecore_x_current_time_get());
97    ecore_x_sync();
98    cur_selection = ecore_x_selection_owner_get(atom);
99 
100    ret = (cur_selection == e_comp->cm_selection);
101    if (!ret)
102      ERR("XSETTINGS: tried to set selection to %#x, but got %#x",
103          (unsigned int)e_comp->cm_selection, cur_selection);
104 
105    return ret;
106 }
107 
108 static void
_e_xsettings_deactivate(Settings_Manager * sm)109 _e_xsettings_deactivate(Settings_Manager *sm)
110 {
111    Ecore_X_Atom atom;
112 
113    atom = _e_xsettings_atom_screen_get(0);
114    ecore_x_selection_owner_set(0, atom, ecore_x_current_time_get());
115    ecore_x_sync();
116    sm->enabled = 0;
117 }
118 
119 static Eina_Bool
_e_xsettings_activate(Settings_Manager * sm)120 _e_xsettings_activate(Settings_Manager *sm)
121 {
122    Ecore_X_Atom atom;
123    Ecore_X_Window old_win;
124 
125    if (sm->enabled) return 1;
126 
127    atom = _e_xsettings_atom_screen_get(0);
128    old_win = ecore_x_selection_owner_get(atom);
129    if (old_win != 0) return 0;
130 
131    if (!_e_xsettings_selection_owner_set())
132      return 0;
133 
134    ecore_x_client_message32_send(e_comp->root, _atom_manager,
135                                  ECORE_X_EVENT_MASK_WINDOW_CONFIGURE,
136                                  ecore_x_current_time_get(), atom,
137                                  e_comp->cm_selection, 0, 0);
138 
139    if (settings) _e_xsettings_apply(sm);
140    sm->enabled = 1;
141 
142    return 1;
143 }
144 
145 static Eina_Bool
_e_xsettings_activate_retry(void * data)146 _e_xsettings_activate_retry(void *data)
147 {
148    Settings_Manager *sm = data;
149    Eina_Bool ret;
150 
151    INF("XSETTINGS: reactivate...");
152    ret = _e_xsettings_activate(sm);
153    if (ret)
154      INF("XSETTINGS: activate success!");
155    else
156      ERR("XSETTINGS: activate failure! retrying in %0.1f seconds", RETRY_TIMEOUT);
157 
158    if (!ret)
159      return ECORE_CALLBACK_RENEW;
160 
161    sm->timer_retry = NULL;
162    return ECORE_CALLBACK_CANCEL;
163 }
164 
165 static void
_e_xsettings_retry(Settings_Manager * sm)166 _e_xsettings_retry(Settings_Manager *sm)
167 {
168    if (sm->timer_retry) return;
169    sm->timer_retry = ecore_timer_loop_add
170      (RETRY_TIMEOUT, _e_xsettings_activate_retry, sm);
171 }
172 
173 static void
_e_xsettings_string_set(const char * name,const char * value)174 _e_xsettings_string_set(const char *name, const char *value)
175 {
176    Setting *s;
177    Eina_List *l;
178 
179    if (!name) return;
180    if (name == _setting_theme_name)
181      e_config->xsettings.net_theme_name_detected = value;
182    name = eina_stringshare_add(name);
183 
184    EINA_LIST_FOREACH(settings, l, s)
185      {
186         if (s->type != SETTING_TYPE_STRING) continue;
187         if (s->name == name) break;
188      }
189    if (!value)
190      {
191         if (!s) return;
192         DBG("remove %s\n", name);
193         eina_stringshare_del(name);
194         eina_stringshare_del(s->name);
195         eina_stringshare_del(s->s.value);
196         settings = eina_list_remove(settings, s);
197         E_FREE(s);
198         return;
199      }
200    if (s)
201      {
202         DBG("update %s %s\n", name, value);
203         eina_stringshare_del(name);
204         eina_stringshare_replace(&s->s.value, value);
205      }
206    else
207      {
208         DBG("add %s %s\n", name, value);
209         s = E_NEW(Setting, 1);
210         s->type = SETTING_TYPE_STRING;
211         s->name = name;
212         s->s.value = eina_stringshare_add(value);
213         settings = eina_list_append(settings, s);
214      }
215 
216    /* type + pad + name-len + last-change-serial + str_len */
217    s->length = 12;
218    s->length += OFFSET_ADD(strlen(name));
219    s->length += OFFSET_ADD(strlen(value));
220    s->last_change = ecore_x_current_time_get();
221 }
222 
223 #if 0
224 static void
225 _e_xsettings_int_set(const char *name, int value, Eina_Bool set)
226 {
227    Setting *s;
228    Eina_List *l;
229 
230    if (!name) return;
231    name = eina_stringshare_add(name);
232 
233    EINA_LIST_FOREACH(settings, l, s)
234      {
235         if (s->type != SETTING_TYPE_INT) continue;
236         if (s->name == name) break;
237      }
238    if (!set)
239      {
240         if (!s) return;
241         DBG("remove %s\n", name);
242         eina_stringshare_del(name);
243         eina_stringshare_del(s->name);
244         settings = eina_list_remove(settings, s);
245         E_FREE(s);
246         return;
247      }
248    if (s)
249      {
250         DBG("update %s %d\n", name, value);
251         eina_stringshare_del(name);
252         s->i.value = value;
253      }
254    else
255      {
256         DBG("add %s %d\n", name, value);
257         s = E_NEW(Setting, 1);
258         s->type = SETTING_TYPE_INT;
259         s->name = name;
260         s->i.value = value;
261         settings = eina_list_append(settings, s);
262      }
263 
264    // type + pad + name-len + last-change-serial + value
265    s->length = 12;
266    s->length += OFFSET_ADD(strlen(name));
267 }
268 
269 #endif
270 
271 static unsigned char *
_e_xsettings_copy(unsigned char * buffer,Setting * s)272 _e_xsettings_copy(unsigned char *buffer, Setting *s)
273 {
274    size_t str_len;
275    size_t len;
276    C16 tmp16;
277    C32 tmp32;
278 
279    buffer[0] = s->type;
280    buffer[1] = 0;
281    buffer += 2;
282 
283    str_len = strlen(s->name);
284    tmp16 = str_len;
285    memcpy(buffer, &tmp16, sizeof(C16));
286    buffer += 2;
287 
288    memcpy(buffer, s->name, str_len);
289    buffer += str_len;
290 
291    len = OFFSET_ADD(str_len) - str_len;
292    memset(buffer, 0, len);
293    buffer += len;
294 
295    tmp32 = s->last_change;
296    memcpy(buffer, &tmp32, sizeof(C32));
297    buffer += 4;
298 
299    switch (s->type)
300      {
301       case SETTING_TYPE_INT:
302         tmp32 = s->i.value;
303         memcpy(buffer, &tmp32, sizeof(C32));
304         buffer += 4;
305         break;
306 
307       case SETTING_TYPE_STRING:
308         str_len = strlen(s->s.value);
309         tmp32 = str_len;
310         memcpy(buffer, &tmp32, sizeof(C32));
311         buffer += 4;
312 
313         memcpy(buffer, s->s.value, str_len);
314         buffer += str_len;
315 
316         len = OFFSET_ADD(str_len) - str_len;
317         memset(buffer, 0, len);
318         buffer += len;
319         break;
320 
321       case SETTING_TYPE_COLOR:
322         tmp16 = s->c.red;
323         memcpy(buffer, &tmp16, sizeof(C16));
324         buffer += 2;
325         tmp16 = s->c.green;
326         memcpy(buffer, &tmp16, sizeof(C16));
327         buffer += 2;
328         tmp16 = s->c.blue;
329         memcpy(buffer, &tmp16, sizeof(C16));
330         buffer += 2;
331         tmp16 = s->c.alpha;
332         memcpy(buffer, &tmp16, sizeof(C16));
333         buffer += 2;
334         break;
335      }
336 
337    return buffer;
338 }
339 
340 static void
_e_xsettings_apply(Settings_Manager * sm)341 _e_xsettings_apply(Settings_Manager *sm)
342 {
343    unsigned char *data;
344    unsigned char *pos;
345    size_t len = 12;
346    Setting *s;
347    Eina_List *l;
348    C32 tmp32;
349 
350    EINA_LIST_FOREACH(settings, l, s)
351      len += s->length;
352 
353    pos = data = calloc(1, len);
354    if (!data) return;
355 
356 #if (defined __BYTE_ORDER && __BYTE_ORDER == __LITTLE_ENDIAN) || (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
357    *pos = 0; //LSBFirst
358 #else
359    *pos = 1; //MSBFirst
360 #endif
361 
362    pos += 4;
363    tmp32 = sm->serial++;
364    memcpy(pos, &tmp32, sizeof(C32));
365    pos += 4;
366    tmp32 = eina_list_count(settings);
367    memcpy(pos, &tmp32, sizeof(C32));
368    pos += 4;
369 
370    EINA_LIST_FOREACH(settings, l, s)
371      pos = _e_xsettings_copy(pos, s);
372 
373    ecore_x_window_prop_property_set(e_comp->cm_selection,
374                                     _atom_xsettings,
375                                     _atom_xsettings,
376                                     8, data, len);
377    free(data);
378 }
379 
380 static void
_e_xsettings_update(void)381 _e_xsettings_update(void)
382 {
383    if (e_comp->cm_selection) _e_xsettings_apply(manager);
384 }
385 
386 static void
_e_xsettings_gtk_icon_update(void)387 _e_xsettings_gtk_icon_update(void)
388 {
389    const Eina_List *l;
390    E_Client *ec;
391 
392    EINA_LIST_FOREACH(e_comp->clients, l, ec)
393      if (ec->icccm.state)
394        ecore_x_client_message8_send(e_client_util_win_get(ec),
395                                     _atom_gtk_iconthemes, NULL, 0);
396 }
397 
398 static void
_e_xsettings_gtk_rcfiles_update(void)399 _e_xsettings_gtk_rcfiles_update(void)
400 {
401    const Eina_List *l;
402    E_Client *ec;
403 
404    EINA_LIST_FOREACH(e_comp->clients, l, ec)
405      if (ec->icccm.state)
406        ecore_x_client_message8_send(e_client_util_win_get(ec),
407                                     _atom_gtk_rcfiles, NULL, 0);
408 }
409 
410 static void
_e_xsettings_icon_theme_set(void)411 _e_xsettings_icon_theme_set(void)
412 {
413    if (e_config->xsettings.match_e17_icon_theme)
414      {
415         _e_xsettings_string_set(_setting_icon_theme_name,
416                                 e_config->icon_theme);
417         return;
418      }
419 
420    if (e_config->xsettings.net_icon_theme_name)
421      {
422         _e_xsettings_string_set(_setting_icon_theme_name,
423                                 e_config->xsettings.net_icon_theme_name);
424         return;
425      }
426 
427    _e_xsettings_string_set(_setting_icon_theme_name, NULL);
428 }
429 
430 static void
_e_xsettings_error_cb(void * data,Eio_File * handler EINA_UNUSED,int error EINA_UNUSED)431 _e_xsettings_error_cb(void *data, Eio_File *handler EINA_UNUSED, int error EINA_UNUSED)
432 {
433    Eina_List *l = data;
434    if (reset || setting)
435      {
436         char buf[PATH_MAX];
437         if (reset || (!l)) l = efreet_data_dirs_get();
438         else if (l)
439           l = l->next;
440         reset = EINA_FALSE;
441         if (l)
442           {
443              snprintf(buf, sizeof(buf), "%s/themes/%s",
444                       (char *)eina_list_data_get(l), _setting_theme);
445              eio_op = eio_file_direct_stat(buf, _e_xsettings_done_cb,
446                                            _e_xsettings_error_cb, l);
447              return;
448           }
449      }
450    eio_op = NULL;
451    setting = EINA_FALSE;
452    _setting_theme = NULL;
453 
454    if (e_config->xsettings.net_theme_name)
455      _e_xsettings_string_set(_setting_theme_name,
456                              e_config->xsettings.net_theme_name);
457    else
458      _e_xsettings_string_set(_setting_theme_name, NULL);
459    _e_xsettings_update();
460 }
461 
462 static void
_e_xsettings_done_cb(void * data EINA_UNUSED,Eio_File * handler EINA_UNUSED,const Eina_Stat * estat EINA_UNUSED)463 _e_xsettings_done_cb(void *data EINA_UNUSED, Eio_File *handler EINA_UNUSED, const Eina_Stat *estat EINA_UNUSED)
464 {
465    if (reset)
466      {
467         /* should not happen */
468         _e_xsettings_error_cb(NULL, NULL, 0);
469         return;
470      }
471    _e_xsettings_string_set(_setting_theme_name, _setting_theme);
472    _setting_theme = NULL;
473    eio_op = NULL;
474    setting = EINA_FALSE;
475    _e_xsettings_update();
476 }
477 
478 static void
_e_xsettings_theme_set(void)479 _e_xsettings_theme_set(void)
480 {
481    if (e_config->xsettings.match_e17_theme)
482      {
483         const char *file;
484 
485         file = e_theme_edje_file_get(NULL, "e/desktop/background");
486         if (file)
487           {
488              if ((_setting_theme = edje_file_data_get(file, "gtk-theme")))
489                {
490                   char buf[PATH_MAX];
491 
492                   e_user_homedir_snprintf(buf, sizeof(buf),
493                                           ".themes/%s", _setting_theme);
494                   eio_op = eio_file_direct_stat(buf, _e_xsettings_done_cb,
495                                                 _e_xsettings_error_cb, NULL);
496                   setting = EINA_TRUE;
497                   return;
498                }
499           }
500      }
501 
502    if (e_config->xsettings.net_theme_name)
503      {
504         _e_xsettings_string_set(_setting_theme_name,
505                                 e_config->xsettings.net_theme_name);
506         return;
507      }
508 
509    _e_xsettings_string_set(_setting_theme_name, NULL);
510 }
511 
512 static void
_e_xsettings_font_set(void)513 _e_xsettings_font_set(void)
514 {
515    E_Font_Default *efd;
516    E_Font_Properties *efp;
517 
518    efd = e_font_default_get("application");
519 
520    if (efd && efd->font)
521      {
522         efp = e_font_fontconfig_name_parse(efd->font);
523         if (efp->name)
524           {
525              Eina_Strbuf *buf;
526              Eina_List *l;
527              int size = efd->size;
528              char size_buf[12];
529              const char *p;
530 
531              /* TODO better way to convert evas font sizes? */
532              if (!size) size = 12;
533              else if (size < 0) size /= -10;
534              else if (size < 5) size = 5;
535              else if (size > 25) size = 25;
536 
537              /* Convert from pixels to point. */
538              snprintf(size_buf, sizeof(size_buf), "%1.1f", (float) size * 0.75);
539 
540              buf = eina_strbuf_new();
541              eina_strbuf_append(buf, efp->name);
542              eina_strbuf_append_char(buf, ' ');
543              EINA_LIST_FOREACH(efp->styles, l, p)
544                {
545                   eina_strbuf_append(buf, p);
546                   eina_strbuf_append_char(buf, ' ');
547                }
548              eina_strbuf_append(buf, size_buf);
549              _e_xsettings_string_set(_setting_font_name,
550                                      eina_strbuf_string_get(buf));
551              eina_strbuf_free(buf);
552              e_font_properties_free(efp);
553              return;
554           }
555 
556         e_font_properties_free(efp);
557      }
558 
559    _e_xsettings_string_set(_setting_font_name, NULL);
560 }
561 
562 #if 0
563 static void
564 _e_xsettings_xft_set(void)
565 {
566    if (e_config->scale.use_dpi)
567      _e_xsettings_int_set(_setting_xft_dpi,
568                           e_config->scale.base_dpi, EINA_TRUE);
569    else
570      _e_xsettings_int_set(_setting_xft_dpi, 0, EINA_FALSE);
571 }
572 
573 #endif
574 
575 static void
_e_xsettings_cursor_path_set(void)576 _e_xsettings_cursor_path_set(void)
577 {
578    struct stat st;
579    char buf[PATH_MAX], env[PATH_MAX + PATH_MAX + 100], *path;
580 
581    e_user_homedir_concat_static(buf, ".icons");
582 
583    if (stat(buf, &st)) return;
584    path = getenv("XCURSOR_PATH");
585    if (path)
586      {
587         if (strstr(path, buf)) return;
588         snprintf(env, sizeof(env), "%s:%s", buf, path);
589         path = env;
590      }
591    else
592      {
593         snprintf(env, sizeof(env), "%s:%s", buf, "/usr/share/icons");
594         path = env;
595      }
596    e_env_set("XCURSOR_PATH", path);
597 }
598 
599 static void
_e_xsettings_start(void)600 _e_xsettings_start(void)
601 {
602    if (running) return;
603 
604    _e_xsettings_theme_set();
605    _e_xsettings_icon_theme_set();
606    _e_xsettings_font_set();
607    _e_xsettings_cursor_path_set();
608 
609    manager = E_NEW(Settings_Manager, 1);
610 
611    if (!_e_xsettings_activate(manager))
612      _e_xsettings_retry(manager);
613 
614    running = EINA_TRUE;
615 }
616 
617 static void
_e_xsettings_stop(void)618 _e_xsettings_stop(void)
619 {
620    Setting *s;
621 
622    if (!running) return;
623 
624    if (manager->timer_retry)
625      ecore_timer_del(manager->timer_retry);
626 
627    if ((!stopping) && (!x_fatal))
628      _e_xsettings_deactivate(manager);
629 
630    E_FREE(manager);
631 
632    EINA_LIST_FREE(settings, s)
633      {
634         if (s->name) eina_stringshare_del(s->name);
635         if (s->s.value) eina_stringshare_del(s->s.value);
636         E_FREE(s);
637      }
638 
639    running = EINA_FALSE;
640 }
641 
642 EINTERN int
e_xsettings_init(void)643 e_xsettings_init(void)
644 {
645    _atom_manager = ecore_x_atom_get("MANAGER");
646    _atom_xsettings = ecore_x_atom_get("_XSETTINGS_SETTINGS");
647    _atom_gtk_iconthemes = ecore_x_atom_get("_GTK_LOAD_ICONTHEMES");
648    _atom_gtk_rcfiles = ecore_x_atom_get("_GTK_READ_RCFILES");
649 
650    if (e_config->xsettings.enabled)
651      {
652         _e_xsettings_start();
653         if (!getenv("E_RESTART"))
654           _e_xsettings_gtk_rcfiles_update();
655      }
656 
657    return 1;
658 }
659 
660 EINTERN int
e_xsettings_shutdown(void)661 e_xsettings_shutdown(void)
662 {
663    _e_xsettings_stop();
664    if (eio_op) eio_file_cancel(eio_op);
665    eio_op = NULL;
666    setting = EINA_FALSE;
667 
668    return 1;
669 }
670 
671 E_API void
e_xsettings_config_update(void)672 e_xsettings_config_update(void)
673 {
674    if (!_atom_manager) return;
675    setting = EINA_FALSE;
676    if (eio_op) eio_file_cancel(eio_op);
677    if (!e_config->xsettings.enabled)
678      {
679         _e_xsettings_stop();
680         return;
681      }
682 
683    if (!running)
684      {
685         _e_xsettings_start();
686      }
687    else
688      {
689         _e_xsettings_theme_set();
690         _e_xsettings_icon_theme_set();
691         _e_xsettings_font_set();
692         _e_xsettings_update();
693         _e_xsettings_gtk_icon_update();
694         reset = EINA_TRUE;
695      }
696 }
697 
698