2
* This file is a part of the Cairo-Dock project
4
* Copyright : (C) see the 'copyright' file.
5
* E-mail : see the 'copyright' file.
7
* This program is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU General Public License
9
* as published by the Free Software Foundation; either version 3
10
* of the License, or (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21
// gcc `pkg-config --cflags --libs glib-2.0 dbus-glib-1 gtk+-2.0` -o xsettings -ggdb xsettings.c
28
#include <math.h> // ceil
31
#include <X11/Xatom.h>
32
#include <X11/Xutil.h>
39
#include <dbus/dbus-glib.h>
40
#include <dbus/dbus-glib-bindings.h>
46
GObjectClass parent_class;
47
} dbusMainObjectClass;
48
gboolean xsettings_set (dbusMainObject *pDbusObject, const gchar *cSettingName, GValue *pValue, GError **error);
50
#include "xsettings-dbus-spec.h"
52
static Display *dpy = NULL;
53
static int serial = 1;
54
static Atom selection_atom;
55
static Atom xsettings_atom;
56
static Window window = 0;
57
static GHashTable *pSettings = NULL;
59
static dbusMainObject *pMainObject = NULL;
82
static int _xerror_handler (Display * pDisplay, XErrorEvent *pXError)
84
g_warning ("Error (%d, %d, %d) during an X request on %ld", pXError->error_code, pXError->request_code, pXError->minor_code, pXError->resourceid);
89
static inline _insert_setting (const gchar *cName, guint iType)
91
Setting *pSetting = g_new0 (Setting, 1);
92
pSetting->type = iType;
93
g_hash_table_insert (pSettings, g_strdup (cName), pSetting);
96
static void register_settings (void)
98
pSettings = g_hash_table_new (g_str_hash,
101
_insert_setting ("Net/ThemeName", G_TYPE_STRING);
102
_insert_setting ("Net/IconThemeName", G_TYPE_STRING);
103
_insert_setting ("Net/SoundThemeName", G_TYPE_STRING);
104
_insert_setting ("Net/EnableEventSounds", G_TYPE_BOOLEAN);
105
_insert_setting ("Net/EnableInputFeedbackSounds", G_TYPE_BOOLEAN);
106
_insert_setting ("Gtk/FontName", G_TYPE_STRING);
107
_insert_setting ("Gtk/CursorThemeName", G_TYPE_STRING);
115
#define pad(n) ((4 - ((n)%4)) % 4)
116
static void _compute_buffer_size (const gchar *cName, Setting *pSetting, int *data)
118
if (pSetting->iSerial == 0) // ignore settings that have never been defined.
121
g_print ("%s (%s)\n", __func__, cName);
122
pSetting->iSize = 1 + 1 + 2 + 4; // type + unused + name_len + serial
123
int value_len, value_pad;
124
switch (pSetting->type)
132
if (pSetting->cValue == NULL)
134
value_len = strlen (pSetting->cValue);
135
value_pad = pad (value_len);
136
pSetting->iSize += 4; // for value_len
139
value_len = 8; // 4 shorts.
145
pSetting->value_len = value_len;
146
pSetting->value_pad = value_pad;
147
pSetting->iSize += value_len + value_pad;
149
int name_len = strlen (cName);
150
int name_pad = pad (name_len);
151
pSetting->name_len = name_len;
152
pSetting->name_pad = name_pad;
154
pSetting->iSize += name_len + name_pad;
156
data[1] += pSetting->iSize;
157
g_print (" %d / %d / %d\n", pSetting->name_len+pSetting->name_pad, pSetting->value_len+pSetting->value_pad, pSetting->iSize);
160
static void _build_buffer (const gchar *cName, Setting *pSetting, guchar **data)
162
if (pSetting->iSerial == 0) // ignore settings that have never been defined.
167
switch (pSetting->type)
174
*buf = SETTING_STRING;
177
*buf = SETTING_COLOR;
186
*(CARD16*)buf = pSetting->name_len;
189
memcpy (buf, cName, pSetting->name_len);
190
buf += pSetting->name_len + pSetting->name_pad;
191
// last_change_serial
192
*(CARD32*)buf = pSetting->iSerial;
195
switch (pSetting->type)
199
*(CARD32*)buf = pSetting->iValue;
203
*(CARD32*)buf = pSetting->value_len;
205
memcpy (buf, pSetting->cValue, pSetting->value_len);
210
for (i = 0; i < 4; i ++)
212
((CARD16*)buf)[i] = pSetting->color[i];
219
buf += pSetting->value_len + pSetting->value_pad;
224
static void _update_settings_on_X (void)
226
// compute the buffer size for the settings that have been defined.
227
int data[2] = {0, 0}; // nb settings, buffer size.
228
data[1] = 12; // header
229
g_hash_table_foreach (pSettings, (GHFunc)_compute_buffer_size, data);
230
int iNbSettings = data[0];
232
g_print (" nb settings: %d; iSize: %d\n", iNbSettings, iSize);
233
g_return_if_fail (iSize != 0);
235
// build the buffer to send to X.
236
guchar *buffer = g_new0 (gchar, iSize);
237
guchar *pos = buffer;
239
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
248
*(CARD32 *)pos = serial;
251
*(CARD32 *)pos = iNbSettings;
254
g_hash_table_foreach (pSettings, (GHFunc)_build_buffer, &pos);
255
g_print (" %d/%d bytes filled\n", pos - buffer, iSize);
257
// now set these settings on our window.
258
XChangeProperty (dpy,
262
8, PropModeReplace, buffer, iSize);
268
////////////////////////
269
/// SETTINGS STORAGE ///
270
////////////////////////
272
static void _store_setting (const gchar *cSettingName, Setting *pSetting)
274
gchar *cConfFilePath = g_strdup_printf ("%s/.config/xsettings.conf", g_getenv ("HOME"));
275
GKeyFile *pKeyFile = g_key_file_new ();
276
GError *error = NULL;
277
g_key_file_load_from_file (pKeyFile, cConfFilePath, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error);
280
g_warning ("couldn't parse %s : %s", cConfFilePath, error->message);
281
g_error_free (error);
285
gchar *str = strchr (cSettingName, '/');
289
const gchar *cGroupName = cSettingName;
290
const gchar *cKeyName = str + 1;
292
switch (pSetting->type)
295
g_key_file_set_integer (pKeyFile, cGroupName, cKeyName, pSetting->iValue);
298
g_key_file_set_boolean (pKeyFile, cGroupName, cKeyName, pSetting->iValue);
301
g_key_file_set_string (pKeyFile, cGroupName, cKeyName, pSetting->cValue);
307
for (i = 0; i < 4; i ++)
308
color[i] = pSetting->color[i];
309
g_key_file_set_integer_list (pKeyFile, cGroupName, cKeyName, color, 4);
317
gchar *cContent = g_key_file_to_data (pKeyFile, &length, &error);
320
g_warning ("can't write settings: %s", error->message);
321
g_error_free (error);
324
g_return_if_fail (cContent != NULL && *cContent != '\0');
326
g_file_set_contents (cConfFilePath, cContent, length, &error);
329
g_warning ("can't write settings into %s: %s", cConfFilePath, error->message);
330
g_error_free (error);
335
g_free (cConfFilePath);
338
static void _load_stored_settings (void)
340
gchar *cConfFilePath = g_strdup_printf ("%s/.config/xsettings.conf", g_getenv ("HOME"));
341
if (! g_file_test (cConfFilePath, G_FILE_TEST_EXISTS))
343
/// copy from /usr/share...
346
GKeyFile *pKeyFile = g_key_file_new ();
347
GError *error = NULL;
348
g_key_file_load_from_file (pKeyFile, cConfFilePath, 0, &error);
351
g_warning ("couldn't parse %s : %s", cConfFilePath, error->message);
354
g_free (cConfFilePath);
356
// store the default settings.
358
gchar **pGroupList = g_key_file_get_groups (pKeyFile, &length);
359
g_return_if_fail (pGroupList != NULL);
363
const gchar *cGroupName, *cKeyName;
366
for (i = 0; pGroupList[i] != NULL; i ++)
368
cGroupName = pGroupList[i];
370
// get the keys of the group.
372
pKeyList = g_key_file_get_keys (pKeyFile, cGroupName, NULL, NULL);
373
g_return_if_fail (pKeyList != NULL);
375
for (j = 0; pKeyList[j] != NULL; j ++)
377
cKeyName = pKeyList[j];
379
cSettingName = g_strdup_printf ("%s/%s", cGroupName, cKeyName);
380
g_print (" + %s\n", cSettingName);
382
pSetting = g_hash_table_lookup (pSettings, cSettingName);
383
if (! pSetting) // unknown setting, guess its type from its value.
385
Setting *pSetting = g_new0 (Setting, 1);
386
gchar *cValue = g_key_file_get_string (pKeyFile, cGroupName, cKeyName, &error);
387
if (error != NULL || cValue == NULL) // it can be empty though.
389
g_warning ("wrong property %s: %s", cSettingName, error ? error->message : "empty value");
390
if (error) g_error_free (error);
392
g_free (cSettingName);
395
if (g_ascii_isdigit (*cValue) || *cValue == '-')
397
if (strchr (cValue, ';') != NULL) // a color is a list of 4 short
399
pSetting->type = G_TYPE_UINT64;
400
gchar **rgba = g_strsplit (cValue, ";", -1);
402
for (i = 0; i < 4 && rgba[i] != NULL; i ++)
404
pSetting->color[i] = atoi(rgba[i]);
410
pSetting->type = G_TYPE_INT;
411
pSetting->iValue = atoi (cValue);
414
else if (strcmp (cValue, "true") == 0 || strcmp (cValue, "false") == 0)
416
pSetting->type = G_TYPE_BOOLEAN;
417
pSetting->iValue = (*cValue == 't');
421
pSetting->type = G_TYPE_STRING;
422
pSetting->cValue = cValue;
424
g_print (" + unknown setting of type 'string' (%s)\n",pSetting->cValue );
426
pSetting->iSerial = serial;
427
g_hash_table_insert (pSettings, cSettingName, pSetting);
433
switch (pSetting->type)
436
pSetting->iValue = g_key_file_get_boolean (pKeyFile, cGroupName, cKeyName, &error); // 0 or 1
439
pSetting->iValue = g_key_file_get_integer (pKeyFile, cGroupName, cKeyName, &error);
442
pSetting->cValue = g_key_file_get_string (pKeyFile, cGroupName, cKeyName, &error);
446
int *rgba = g_key_file_get_integer_list (pKeyFile, cGroupName, cKeyName, &length, &error);
448
for (i = 0; i < 4 && i < length; i ++)
450
pSetting->color[i] = rgba[i];
456
g_warning ("bad property type for %s", cSettingName);
457
g_free (cSettingName);
463
g_warning ("wrong property %s: %s", cSettingName, error->message);
464
g_error_free (error);
466
g_free (cSettingName);
470
pSetting->iSerial = serial;
471
g_free (cSettingName);
473
g_strfreev (pKeyList);
475
g_strfreev (pGroupList);
477
g_key_file_free (pKeyFile);
481
//////////////////////
482
/// DBUS INTERFACE ///
483
//////////////////////
485
G_DEFINE_TYPE(dbusMainObject, xsettings, G_TYPE_OBJECT);
487
gboolean xsettings_set (dbusMainObject *pDbusObject, const gchar *cSettingName, GValue *pValue, GError **error)
489
// update the setting with the given value.
490
g_print ("%s (%s)\n", __func__, cSettingName);
491
Setting *pSetting = g_hash_table_lookup (pSettings, cSettingName);
492
g_return_val_if_fail (pSetting != NULL, FALSE); // let's ignore unexisting settings.
494
switch (pSetting->type)
497
if (! G_VALUE_HOLDS_BOOLEAN (pValue))
499
pSetting->iValue = g_value_get_boolean (pValue);
502
if (! G_VALUE_HOLDS_INT (pValue))
504
pSetting->iValue = g_value_get_int (pValue);
507
if (! G_VALUE_HOLDS_STRING (pValue))
509
g_free (pSetting->cValue);
510
pSetting->cValue = g_strdup (g_value_get_string (pValue));
514
if (! G_VALUE_HOLDS_BOXED (pValue))
516
GArray *a = g_value_get_boxed (pValue); /// TODO: check that Python actually makes a GArray.
518
for (i = 0; i < 4 && i < a->len; i ++)
520
g_print ("color %d: %d\n", i, g_array_index (a, int, i));
521
pSetting->color[i] = g_array_index (a, int, i);
523
/*gchar **rgba = g_strsplit (cValue, ";", -1);
525
for (i = 0; i < 4 && rgba[i] != NULL; i ++)
527
pSetting->color[i] = atoi(rgba[i]);
535
pSetting->iSerial = serial;
537
// store the new setting value in the conf file.
538
_store_setting (cSettingName, pSetting);
540
// update the X settings.
541
_update_settings_on_X ();
547
static void xsettings_class_init(dbusMainObjectClass *klass)
551
static void xsettings_init (dbusMainObject *pDbusObject)
553
dbus_g_object_type_install_info(xsettings_get_type(), &dbus_glib_xsettings_object_info);
555
GError *error = NULL;
556
DBusGConnection *connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
559
g_warning ("%s\n", error->message);
560
g_error_free (error);
564
dbus_g_connection_register_g_object (connection, "/Manager", G_OBJECT(pDbusObject));
568
static void _register_dbus_interface (void)
571
GError *error = NULL;
572
DBusGConnection *pConnection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
575
g_warning ("Dbus error: %s", error->message);
576
g_error_free (error);
581
DBusGProxy *pSessionProxy = dbus_g_proxy_new_for_name (
585
DBUS_INTERFACE_DBUS);
588
org_freedesktop_DBus_request_name (pSessionProxy, "org.cairodock.xsettings", 0, &request_ret, &error);
591
g_warning ("Dbus error: %s", error->message);
592
g_error_free (error);
597
pMainObject = g_object_new (xsettings_get_type(), NULL);
611
Atom timestamp_prop_atom;
615
timestamp_predicate (Display *display,
619
TimeStampInfo *info = (TimeStampInfo *)arg;
621
if (xevent->type == PropertyNotify &&
622
xevent->xproperty.window == info->window &&
623
xevent->xproperty.atom == info->timestamp_prop_atom)
629
int main (int argc, char** argv)
631
// init the X session.
632
gdk_init(&argc, &argv);
633
XSetErrorHandler (_xerror_handler);
634
GdkDisplay *gdpy = gdk_display_get_default();
635
dpy = GDK_DISPLAY_XDISPLAY(gdpy);
636
//dpy = XOpenDisplay (NULL);
637
g_return_val_if_fail (dpy != NULL, 1);
639
int screen = DefaultScreen (dpy);
641
Window root = RootWindow (dpy, screen);
643
XSetErrorHandler (_xerror_handler);
645
gchar *xsetting_sn = g_strdup_printf ("_XSETTINGS_S%d", DefaultScreen(dpy));
646
selection_atom = XInternAtom (dpy, xsetting_sn, False);
647
g_free (xsetting_sn);
648
xsettings_atom = XInternAtom (dpy, "_XSETTINGS_SETTINGS", False) ;
650
// create an client (an invisible small X window).
651
window = XCreateSimpleWindow (dpy,
654
WhitePixel (dpy, screen),
655
WhitePixel (dpy, screen));
656
g_return_val_if_fail (window != 0, 1);
658
// acquire the XSETTINGS selection.
659
int time = CurrentTime;
660
XSetSelectionOwner (dpy, selection_atom, window, time);
662
// check that we could acquire it.
663
if (XGetSelectionOwner (dpy, selection_atom) != window)
665
g_warning ("Couldn't acquire the selection, another XSettings Manager is probably running.");
669
// notify all the other clients that we are here.
670
XClientMessageEvent xev;
672
xev.type = ClientMessage;
674
xev.message_type = XInternAtom (dpy, "MANAGER", False);
676
xev.data.l[0] = time;
677
xev.data.l[1] = selection_atom;
678
xev.data.l[2] = window;
682
XSendEvent (dpy, root, False, StructureNotifyMask, (XEvent *)&xev);
684
// register the known settings
685
register_settings ();
687
// init the settings with their default values.
688
_load_stored_settings ();
690
// update the XSettings property with the settings.
691
_update_settings_on_X ();
694
_register_dbus_interface ();
696
// enter the main loop.
697
GMainLoop *loop = g_main_loop_new (NULL, FALSE);
698
g_main_loop_run (loop);