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