Write correct contents in correct folder for gtk3 settings without lxsession
[lxde/lxappearance.git] / src / lxappearance.c
1 /*
2 * lxappearance.c
3 *
4 * Copyright 2010 PCMan <pcman.tw@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "lxappearance.h"
27
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30
31 #include <X11/X.h>
32 #include <X11/Xatom.h>
33 #include <X11/Xlib.h>
34 #include <gdk/gdkx.h>
35 #include <string.h>
36
37 #include <dbus/dbus.h>
38
39 #include "widget-theme.h"
40 #include "color-scheme.h"
41 #include "icon-theme.h"
42 #include "cursor-theme.h"
43 #include "font.h"
44 #include "other.h"
45 #include "plugin.h"
46
47 LXAppearance app = {0};
48
49 Atom lxsession_atom = 0;
50 static const char* lxsession_name = NULL;
51
52 /* Dbus functions Copy from lxsession-logout
53 TODO Create a library fro this ?
54 */
55
56 static gboolean check_lxde_dbus()
57 {
58 DBusError error;
59 dbus_error_init(&error);
60 DBusConnection * connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
61 if (connection == NULL)
62 {
63 g_warning(G_STRLOC ": Failed to connect to the session message bus: %s", error.message);
64 dbus_error_free(&error);
65 return FALSE;
66 }
67
68 DBusMessage * test = dbus_message_new_method_call (
69 "org.lxde.SessionManager",
70 "/org/lxde/SessionManager",
71 "org.lxde.SessionManager",
72 "Logout");
73
74 if (dbus_message_has_interface(test, "org.lxde.SessionManager"))
75 {
76 return TRUE;
77 }
78 else
79 {
80 return FALSE;
81 }
82 }
83
84 static void check_lxsession()
85 {
86 lxsession_atom = XInternAtom( GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), "_LXSESSION", True );
87 if( lxsession_atom != None )
88 {
89 XGrabServer( GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) );
90 if( XGetSelectionOwner( GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), lxsession_atom ) )
91 {
92 app.use_lxsession = TRUE;
93 lxsession_name = g_getenv("DESKTOP_SESSION");
94 }
95 XUngrabServer( GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) );
96 }
97
98 /* Check Lxsession also with dbus */
99 if (check_lxde_dbus())
100 {
101 app.use_lxsession = TRUE;
102 lxsession_name = g_getenv("DESKTOP_SESSION");
103 }
104
105 }
106
107 static GOptionEntry option_entries[] =
108 {
109 { NULL }
110 };
111
112 static void save_cursor_theme_name()
113 {
114 char* dir_path;
115 if(!app.cursor_theme || !g_strcmp0(app.cursor_theme, "default"))
116 return;
117
118 dir_path = g_build_filename(g_get_home_dir(), ".icons/default", NULL);
119 if(0 == g_mkdir_with_parents(dir_path, 0700))
120 {
121 char* index_theme = g_build_filename(dir_path, "index.theme", NULL);
122 char* content = g_strdup_printf(
123 "# This file is written by LXAppearance. Do not edit."
124 "[Icon Theme]\n"
125 "Name=Default\n"
126 "Comment=Default Cursor Theme\n"
127 "Inherits=%s\n", app.cursor_theme ? app.cursor_theme : "");
128 g_file_set_contents(index_theme, content, -1, NULL);
129 g_free(content);
130 g_free(index_theme);
131 }
132 g_free(dir_path);
133
134 /*
135 dir_path = g_build_filename(g_get_home_dir(), ".Xdefaults", NULL);
136 Xcursor.theme: name
137 Xcursor.size: [size]
138 g_file_set_contents(dir_path, "", -1, NULL);
139 g_free(dir_path);
140 */
141 }
142
143 static void reload_all_programs()
144 {
145 #if GTK_CHECK_VERSION(3, 0, 0)
146
147 /* TODO Port this to something else than gdk_event_send_clientmessage_toall */
148
149 #else
150 GdkEventClient event;
151 event.type = GDK_CLIENT_EVENT;
152 event.send_event = TRUE;
153 event.window = NULL;
154
155 if( app.use_lxsession )
156 {
157 event.message_type = gdk_atom_intern_static_string("_LXSESSION");
158 event.data.b[0] = 0; /* LXS_RELOAD */
159 }
160 else
161 {
162 /* if( icon_only )
163 event.message_type = gdk_atom_intern("_GTK_LOAD_ICONTHEMES", FALSE);
164 */
165 event.message_type = gdk_atom_intern("_GTK_READ_RCFILES", FALSE);
166 }
167 event.data_format = 8;
168 gdk_event_send_clientmessage_toall((GdkEvent *)&event);
169 #endif
170 }
171
172 static const char* bool2str(gboolean val)
173 {
174 return val ? "TRUE" : "FALSE";
175 }
176
177
178 static void lxappearance_save_gtkrc()
179 {
180 static const char* tb_styles[]={
181 "GTK_TOOLBAR_ICONS",
182 "GTK_TOOLBAR_TEXT",
183 "GTK_TOOLBAR_BOTH",
184 "GTK_TOOLBAR_BOTH_HORIZ"
185 };
186 static const char* tb_icon_sizes[]={
187 "GTK_ICON_SIZE_INVALID",
188 "GTK_ICON_SIZE_MENU",
189 "GTK_ICON_SIZE_SMALL_TOOLBAR",
190 "GTK_ICON_SIZE_LARGE_TOOLBAR",
191 "GTK_ICON_SIZE_BUTTON",
192 "GTK_ICON_SIZE_DND",
193 "GTK_ICON_SIZE_DIALOG"
194 };
195
196 char* file_path = g_build_filename(g_get_home_dir(), ".gtkrc-2.0", NULL);
197 GString* content = g_string_sized_new(512);
198 g_string_append(content,
199 "# DO NOT EDIT! This file will be overwritten by LXAppearance.\n"
200 "# Any customization should be done in ~/.gtkrc-2.0.mine instead.\n\n");
201 if(app.widget_theme)
202 g_string_append_printf(content,
203 "gtk-theme-name=\"%s\"\n", app.widget_theme);
204 if(app.icon_theme)
205 g_string_append_printf(content,
206 "gtk-icon-theme-name=\"%s\"\n", app.icon_theme);
207 if(app.default_font)
208 g_string_append_printf(content,
209 "gtk-font-name=\"%s\"\n", app.default_font);
210 if(app.cursor_theme)
211 g_string_append_printf(content,
212 "gtk-cursor-theme-name=\"%s\"\n", app.cursor_theme);
213 save_cursor_theme_name();
214
215 g_string_append_printf(content,
216 "gtk-cursor-theme-size=%d\n"
217 "gtk-toolbar-style=%s\n"
218 "gtk-toolbar-icon-size=%s\n"
219 "gtk-button-images=%d\n"
220 "gtk-menu-images=%d\n"
221 #if GTK_CHECK_VERSION(2, 14, 0)
222 "gtk-enable-event-sounds=%d\n"
223 "gtk-enable-input-feedback-sounds=%d\n"
224 #endif
225 "gtk-xft-antialias=%d\n"
226 "gtk-xft-hinting=%d\n"
227
228 , app.cursor_theme_size,
229 tb_styles[app.toolbar_style],
230 tb_icon_sizes[app.toolbar_icon_size],
231 app.button_images ? 1 : 0,
232 app.menu_images ? 1 : 0,
233 #if GTK_CHECK_VERSION(2, 14, 0)
234 app.enable_event_sound ? 1 : 0,
235 app.enable_input_feedback ? 1 : 0,
236 #endif
237 app.enable_antialising ? 1 : 0,
238 app.enable_hinting ? 1 : 0
239 );
240
241 if(app.hinting_style)
242 g_string_append_printf(content,
243 "gtk-xft-hintstyle=\"%s\"\n", app.hinting_style);
244
245 if(app.font_rgba)
246 g_string_append_printf(content,
247 "gtk-xft-rgba=\"%s\"\n", app.font_rgba);
248
249 if(app.color_scheme)
250 {
251 char* escaped = g_strescape(app.color_scheme, NULL);
252 g_string_append_printf(content,
253 "gtk-color-scheme=\"%s\"\n",
254 escaped);
255 g_free(escaped);
256 }
257
258 g_string_append_printf(content,
259 "include \"%s/.gtkrc-2.0.mine\"\n",
260 g_get_home_dir());
261
262 g_file_set_contents(file_path, content->str, content->len, NULL);
263
264 /* Save also in GTK3 folder
265 Content shold be different from the gtk2 one
266 */
267 GString* content_gtk3 = g_string_sized_new(512);
268 char* file_path_gtk3 = g_build_filename(g_get_user_config_dir(), "gtk-3.0", NULL);
269 char* file_path_settings = g_build_filename(g_get_user_config_dir(), "gtk-3.0", "settings.ini", NULL);
270
271 if (!g_file_test(file_path_gtk3, G_FILE_TEST_IS_DIR))
272 {
273 g_mkdir_with_parents(file_path_gtk3, 0755);
274 }
275
276
277 g_string_append(content_gtk3, "[Settings] \n");
278
279 if(app.widget_theme)
280 g_string_append_printf(content_gtk3,
281 "gtk-theme-name=%s\n", app.widget_theme);
282 if(app.icon_theme)
283 g_string_append_printf(content_gtk3,
284 "gtk-icon-theme-name=%s\n", app.icon_theme);
285 if(app.default_font)
286 g_string_append_printf(content_gtk3,
287 "gtk-font-name=%s\n", app.default_font);
288 if(app.cursor_theme)
289 g_string_append_printf(content_gtk3,
290 "gtk-cursor-theme-name=%s\n", app.cursor_theme);
291 save_cursor_theme_name();
292
293 g_string_append_printf(content_gtk3,
294 "gtk-cursor-theme-size=%d\n"
295 "gtk-toolbar-style=%s\n"
296 "gtk-toolbar-icon-size=%s\n"
297 "gtk-button-images=%d\n"
298 "gtk-menu-images=%d\n"
299 #if GTK_CHECK_VERSION(2, 14, 0)
300 "gtk-enable-event-sounds=%d\n"
301 "gtk-enable-input-feedback-sounds=%d\n"
302 #endif
303 "gtk-xft-antialias=%d\n"
304 "gtk-xft-hinting=%d\n"
305
306 , app.cursor_theme_size,
307 tb_styles[app.toolbar_style],
308 tb_icon_sizes[app.toolbar_icon_size],
309 app.button_images ? 1 : 0,
310 app.menu_images ? 1 : 0,
311 #if GTK_CHECK_VERSION(2, 14, 0)
312 app.enable_event_sound ? 1 : 0,
313 app.enable_input_feedback ? 1 : 0,
314 #endif
315 app.enable_antialising ? 1 : 0,
316 app.enable_hinting ? 1 : 0
317 );
318
319 if(app.hinting_style)
320 g_string_append_printf(content_gtk3,
321 "gtk-xft-hintstyle=%s\n", app.hinting_style);
322
323 if(app.font_rgba)
324 g_string_append_printf(content_gtk3,
325 "gtk-xft-rgba=%s\n", app.font_rgba);
326
327 if(app.color_scheme)
328 {
329 char* escaped = g_strescape(app.color_scheme, NULL);
330 g_string_append_printf(content_gtk3,
331 "gtk-color-scheme=%s\n",
332 escaped);
333 g_free(escaped);
334 }
335
336 g_file_set_contents(file_path_settings, content_gtk3->str, content_gtk3->len, NULL);
337
338 g_string_free(content, TRUE);
339 g_string_free(content_gtk3, TRUE);
340 g_free(file_path);
341 }
342
343 static void lxappearance_save_lxsession()
344 {
345 char* rel_path = g_strconcat("lxsession/", lxsession_name, "/desktop.conf", NULL);
346 char* user_config_file = g_build_filename(g_get_user_config_dir(), rel_path, NULL);
347 char* buf;
348 int len;
349 GKeyFile* kf = g_key_file_new();
350
351 if(!g_key_file_load_from_file(kf, user_config_file, G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, NULL))
352 {
353 /* the user config file doesn't exist, create its parent dir */
354 len = strlen(user_config_file) - strlen("/desktop.conf");
355 user_config_file[len] = '\0';
356 g_debug("user_config_file = %s", user_config_file);
357 g_mkdir_with_parents(user_config_file, 0700);
358 user_config_file[len] = '/';
359
360 g_key_file_load_from_dirs(kf, rel_path, (const char**)g_get_system_config_dirs(), NULL,
361 G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
362 }
363
364 g_free(rel_path);
365
366 g_key_file_set_string( kf, "GTK", "sNet/ThemeName", app.widget_theme );
367 g_key_file_set_string( kf, "GTK", "sGtk/FontName", app.default_font );
368
369 g_key_file_set_string( kf, "GTK", "sGtk/ColorScheme", app.color_scheme ? app.color_scheme : "" );
370
371 g_key_file_set_string( kf, "GTK", "sNet/IconThemeName", app.icon_theme );
372
373 g_key_file_set_string( kf, "GTK", "sGtk/CursorThemeName", app.cursor_theme );
374 g_key_file_set_integer( kf, "GTK", "iGtk/CursorThemeSize", app.cursor_theme_size );
375 save_cursor_theme_name();
376
377 g_key_file_set_integer( kf, "GTK", "iGtk/ToolbarStyle", app.toolbar_style );
378 g_key_file_set_integer( kf, "GTK", "iGtk/ToolbarIconSize", app.toolbar_icon_size );
379
380 g_key_file_set_integer( kf, "GTK", "iGtk/ToolbarStyle", app.toolbar_style );
381 g_key_file_set_integer( kf, "GTK", "iGtk/ToolbarIconSize", app.toolbar_icon_size );
382
383 g_key_file_set_integer( kf, "GTK", "iGtk/ButtonImages", app.button_images );
384 g_key_file_set_integer( kf, "GTK", "iGtk/MenuImages", app.menu_images );
385
386 #if GTK_CHECK_VERSION(2, 14, 0)
387 /* "Net/SoundThemeName\0" "gtk-sound-theme-name\0" */
388 g_key_file_set_integer( kf, "GTK", "iNet/EnableEventSounds", app.enable_event_sound);
389 g_key_file_set_integer( kf, "GTK", "iNet/EnableInputFeedbackSounds", app.enable_input_feedback);
390 #endif
391 g_key_file_set_integer( kf, "GTK", "iXft/Antialias", app.enable_antialising);
392 g_key_file_set_integer( kf, "GTK", "iXft/Hinting", app.enable_hinting);
393 g_key_file_set_string( kf, "GTK", "sXft/HintStyle", app.hinting_style);
394 g_key_file_set_string( kf, "GTK", "sXft/RGBA", app.font_rgba);
395
396 buf = g_key_file_to_data( kf, &len, NULL );
397 g_key_file_free(kf);
398
399 g_file_set_contents(user_config_file, buf, len, NULL);
400 g_free(buf);
401 g_free(user_config_file);
402 }
403
404 static void on_dlg_response(GtkDialog* dlg, int res, gpointer user_data)
405 {
406 switch(res)
407 {
408 case GTK_RESPONSE_APPLY:
409
410 if(app.use_lxsession)
411 lxappearance_save_lxsession();
412 else
413 lxappearance_save_gtkrc();
414
415 reload_all_programs();
416
417 app.changed = FALSE;
418 gtk_dialog_set_response_sensitive(GTK_DIALOG(app.dlg), GTK_RESPONSE_APPLY, FALSE);
419 break;
420 case 1: /* about dialog */
421 {
422 GtkBuilder* b = gtk_builder_new();
423 if(gtk_builder_add_from_file(b, PACKAGE_UI_DIR "/about.ui", NULL))
424 {
425 GtkWidget* dlg = GTK_WIDGET(gtk_builder_get_object(b, "dlg"));
426 gtk_dialog_run(GTK_DIALOG(dlg));
427 gtk_widget_destroy(dlg);
428 }
429 g_object_unref(b);
430 }
431 break;
432 default:
433 gtk_main_quit();
434 }
435 }
436
437 static void settings_init()
438 {
439 GtkSettings* settings = gtk_settings_get_default();
440 g_object_get(settings,
441 "gtk-theme-name", &app.widget_theme,
442 "gtk-font-name", &app.default_font,
443 "gtk-icon-theme-name", &app.icon_theme,
444 "gtk-cursor-theme-name", &app.cursor_theme,
445 "gtk-cursor-theme-size", &app.cursor_theme_size,
446 "gtk-toolbar-style", &app.toolbar_style,
447 "gtk-toolbar-icon-size", &app.toolbar_icon_size,
448 "gtk-button-images", &app.button_images,
449 "gtk-menu-images", &app.menu_images,
450 #if GTK_CHECK_VERSION(2, 14, 0)
451 "gtk-enable-event-sounds", &app.enable_event_sound,
452 "gtk-enable-input-feedback-sounds", &app.enable_input_feedback,
453 #endif
454 "gtk-xft-antialias", &app.enable_antialising,
455 "gtk-xft-hinting", &app.enable_hinting,
456 "gtk-xft-hintstyle", &app.hinting_style,
457 "gtk-xft-rgba", &app.font_rgba,
458 NULL);
459 /* try to figure out cursor theme used. */
460 if(!app.cursor_theme || g_strcmp0(app.cursor_theme, "default") == 0)
461 {
462 /* get the real theme name from default. */
463 GKeyFile* kf = g_key_file_new();
464 char* fpath = g_build_filename(g_get_home_dir(), ".icons/default/index.theme", NULL);
465 gboolean ret = g_key_file_load_from_file(kf, fpath, 0, NULL);
466 g_free(fpath);
467
468 if(!ret)
469 ret = g_key_file_load_from_data_dirs(kf, "icons/default/index.theme", NULL, 0, NULL);
470
471 if(ret)
472 {
473 g_free(app.cursor_theme);
474 app.cursor_theme = g_key_file_get_string(kf, "Icon Theme", "Inherits", NULL);
475 g_debug("cursor theme name: %s", app.cursor_theme);
476 }
477 g_key_file_free(kf);
478 }
479
480 app.color_scheme_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
481 /* try to load custom color scheme if available */
482 if(app.use_lxsession)
483 {
484 char* rel_path = g_strconcat("lxsession/", lxsession_name, "/desktop.conf", NULL);
485 char* user_config_file = g_build_filename(g_get_user_config_dir(), rel_path, NULL);
486 GKeyFile* kf = g_key_file_new();
487 if(g_key_file_load_from_file(kf, user_config_file, 0, NULL))
488 app.color_scheme = g_key_file_get_string(kf, "GTK", "sGtk/ColorScheme", NULL);
489 else if(g_key_file_load_from_dirs(kf, rel_path, (const char**)g_get_system_config_dirs(), NULL, 0, NULL))
490 app.color_scheme = g_key_file_get_string(kf, "GTK", "sGtk/ColorScheme", NULL);
491 g_key_file_free(kf);
492 g_free(rel_path);
493 g_free(user_config_file);
494
495 if(app.color_scheme)
496 {
497 if(*app.color_scheme)
498 color_scheme_str_to_hash(app.color_scheme_hash, app.color_scheme);
499 else
500 {
501 g_free(app.color_scheme);
502 app.color_scheme = NULL;
503 }
504 }
505 }
506 else
507 {
508 char* gtkrc_file = g_build_filename(g_get_home_dir(), ".gtkrc-2.0", NULL);
509 gtkrc_file_get_color_scheme(gtkrc_file, app.color_scheme_hash);
510 g_free(gtkrc_file);
511 if(g_hash_table_size(app.color_scheme_hash) > 0)
512 app.color_scheme = color_scheme_hash_to_str(app.color_scheme_hash);
513 }
514 }
515
516 int main(int argc, char** argv)
517 {
518 GError* err = NULL;
519 GtkBuilder* b;
520
521 /* gettext support */
522 #ifdef ENABLE_NLS
523 bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
524 bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
525 textdomain ( GETTEXT_PACKAGE );
526 #endif
527
528 g_thread_init(NULL);
529 /* initialize GTK+ and parse the command line arguments */
530 if( G_UNLIKELY( ! gtk_init_with_args( &argc, &argv, "", option_entries, GETTEXT_PACKAGE, &err ) ) )
531 {
532 g_print( "Error: %s\n", err->message );
533 return 1;
534 }
535
536 app.abi_version = LXAPPEARANCE_ABI_VERSION;
537
538 /* check if we're under LXSession */
539 check_lxsession();
540
541 /* create GUI here */
542 b = gtk_builder_new();
543 if(!gtk_builder_add_from_file(b, PACKAGE_UI_DIR "/lxappearance.ui", NULL))
544 return 1;
545
546 /* NOTE: GUI must be created prior to loading settings from GtkSettings object.
547 * Some properties of GtkSettings class are installed by some other gtk classes.
548 * For example, "gtk-menu-images" property is actually installed by initialization
549 * of GtkImageMenuItem class. If we load the GUI first, then when the menu items
550 * are created, this property gets correctly registered. So later it can be read. */
551
552 /* load config values */
553 settings_init();
554
555 app.dlg = GTK_WIDGET(gtk_builder_get_object(b, "dlg"));
556
557 widget_theme_init(b);
558 color_scheme_init(b);
559 icon_theme_init(b);
560 cursor_theme_init(b);
561 font_init(b);
562 other_init(b);
563 /* the page for window manager plugins */
564 app.wm_page = GTK_WIDGET(gtk_builder_get_object(b, "wm_page"));
565
566 plugins_init(b);
567
568 g_signal_connect(app.dlg, "response", G_CALLBACK(on_dlg_response), NULL);
569
570 gtk_window_present(GTK_WINDOW(app.dlg));
571 g_object_unref(b);
572
573 gtk_main();
574
575 plugins_finalize();
576
577 return 0;
578 }
579
580 void lxappearance_changed()
581 {
582 if(!app.changed)
583 {
584 app.changed = TRUE;
585 gtk_dialog_set_response_sensitive(GTK_DIALOG(app.dlg), GTK_RESPONSE_APPLY, TRUE);
586 }
587 }
588
589 void on_check_button_toggled(GtkToggleButton* btn, gpointer user_data)
590 {
591 gboolean* val = (gboolean*)user_data;
592 gboolean new_val = gtk_toggle_button_get_active(btn);
593 if(new_val != *val)
594 {
595 *val = new_val;
596 lxappearance_changed();
597 }
598 }