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