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