diff options
Diffstat (limited to 'xsettings')
-rw-r--r-- | xsettings/Makefile | 18 | ||||
-rw-r--r-- | xsettings/client-simple.c | 123 | ||||
-rw-r--r-- | xsettings/client.c | 245 | ||||
-rw-r--r-- | xsettings/gtk-utils.c | 119 | ||||
-rw-r--r-- | xsettings/gtk-utils.h | 12 | ||||
-rw-r--r-- | xsettings/manager.c | 277 | ||||
-rw-r--r-- | xsettings/settings-proposal.txt | 313 | ||||
-rw-r--r-- | xsettings/xsettings-client.c | 512 | ||||
-rw-r--r-- | xsettings/xsettings-client.h | 46 | ||||
-rw-r--r-- | xsettings/xsettings-common.c | 239 | ||||
-rw-r--r-- | xsettings/xsettings-common.h | 88 | ||||
-rw-r--r-- | xsettings/xsettings-manager.c | 394 | ||||
-rw-r--r-- | xsettings/xsettings-manager.h | 46 |
13 files changed, 2432 insertions, 0 deletions
diff --git a/xsettings/Makefile b/xsettings/Makefile new file mode 100644 index 0000000..cc126f9 --- /dev/null +++ b/xsettings/Makefile @@ -0,0 +1,18 @@ +CFLAGS= -Wall -g `gtk-config --cflags` -g -Wall +LIBS=`gtk-config --libs` + +manager_objects = manager.o gtk-utils.o xsettings-common.o xsettings-manager.o + +client_objects = client.o xsettings-common.o xsettings-client.o + +all: xsettings-manager xsettings-client + +xsettings-manager: $(manager_objects) + $(CC) -o $@ $(manager_objects) $(LIBS) + +xsettings-client: $(client_objects) + $(CC) -o $@ $(client_objects) $(LIBS) + +clean: + rm -f *.o xsettings-manager xsettings-client + diff --git a/xsettings/client-simple.c b/xsettings/client-simple.c new file mode 100644 index 0000000..85dff05 --- /dev/null +++ b/xsettings/client-simple.c @@ -0,0 +1,123 @@ +#include <stdio.h> +#include <stdlib.h> + +#include <X11/Xlib.h> + +#include "xsettings-client.h" + +static void +usage (void) +{ + fprintf (stderr, "Usage: xsettings-client [-display DISPLAY]\n"); + exit (1); +} + +static void +dump_setting (XSettingsSetting *setting) +{ + switch (setting->type) + { + case XSETTINGS_TYPE_INT: + printf (" INT: %d\n", setting->data.v_int); + break; + case XSETTINGS_TYPE_STRING: + printf (" STRING: %s\n", setting->data.v_string); + break; + case XSETTINGS_TYPE_COLOR: + printf (" COLOR: (%#4x, %#4x, %#4x, %#4x)\n", + setting->data.v_color.red, + setting->data.v_color.green, + setting->data.v_color.blue, + setting->data.v_color.alpha); + break; + } +} + +static void +notify_cb (const char *name, + XSettingsAction action, + XSettingsSetting *setting, + void *data) +{ + switch (action) + { + case XSETTINGS_ACTION_NEW: + printf ("%s: new\n", name); + dump_setting (setting); + break; + case XSETTINGS_ACTION_CHANGED: + printf ("%s: new\n", name); + dump_setting (setting); + break; + case XSETTINGS_ACTION_DELETED: + printf ("%s: deleted\n", name); + break; + } +} + +static void +watch_cb (Window window, + Bool is_start, + long mask, + void *cb_data) +{ + if (is_start) + printf ("Starting watch on %#lx with mask %#lx\n", window, mask); + else + printf ("Stopping watch on %#lx \n", window); +} + +int +main (int argc, char **argv) +{ + Display *display; + const char *display_str = NULL; + XSettingsClient *client; + int i; + + for (i = 1; i < argc; i++) + { + if (strcmp (argv[i], "-display") == 0) + { + if (i + 1 == argc) + usage(); + else + { + display_str = argv[i + 1]; + i++; + } + } + else + usage (); + } + + display = XOpenDisplay (display_str); + if (!display) + { + fprintf (stderr, "Cannot open display '%s'\n", + XDisplayName (display_str)); + exit (1); + } + + client = xsettings_client_new (display, DefaultScreen (display), + notify_cb, watch_cb, NULL); + if (!client) + { + fprintf (stderr, "Cannot create client\n"); + exit (1); + } + + while (1) + { + XEvent xev; + + XNextEvent (display, &xev); + + xsettings_client_process_event (client, &xev); + } + + xsettings_client_destroy (client); + XCloseDisplay (display); + + return 0; +} diff --git a/xsettings/client.c b/xsettings/client.c new file mode 100644 index 0000000..6419ea8 --- /dev/null +++ b/xsettings/client.c @@ -0,0 +1,245 @@ +#include <stdio.h> +#include <stdlib.h> + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "gtk-utils.h" +#include "xsettings-client.h" + +enum { + NAME = 0, + TYPE = 1, + VALUE = 2, + SERIAL = 3 +}; + +XSettingsClient *client; +GtkWidget *settings_clist; + +void +terminate_cb (void *data) +{ + gboolean *terminated = data; + + g_print ("Releasing the selection and exiting\n"); + + *terminated = TRUE; + gtk_main_quit (); +} + +static void +create_gui (void) +{ + char *titles[] = { + "Name", "Type", "Value", "Serial" + }; + + GtkWidget *scrolled_window; + GtkWidget *dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL); + GtkWidget *vbox, *bbox, *separator, *table, *button; + + gtk_window_set_title (GTK_WINDOW (dialog), "Sample XSETTINGS Client"); + gtk_window_set_default_size (GTK_WINDOW (dialog), -1, 300); + + gtk_signal_connect (GTK_OBJECT (dialog), "destroy", + GTK_SIGNAL_FUNC (gtk_main_quit), NULL); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (dialog), vbox); + + bbox = gtk_hbutton_box_new (); + gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); + + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END); + gtk_container_set_border_width (GTK_CONTAINER (bbox), 5); + + separator = gtk_hseparator_new (); + gtk_box_pack_end (GTK_BOX (vbox), separator, FALSE, FALSE, 0); + + table = gtk_table_new (3, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + settings_clist = gtk_clist_new_with_titles (4, titles); + gtk_container_add (GTK_CONTAINER (scrolled_window), settings_clist); + gtk_clist_set_selection_mode (GTK_CLIST (settings_clist), GTK_SELECTION_BROWSE); + gtk_clist_set_sort_column (GTK_CLIST (settings_clist), 1); + gtk_clist_set_auto_sort (GTK_CLIST (settings_clist), TRUE); + + gtk_clist_set_column_width (GTK_CLIST (settings_clist), NAME, 200); + gtk_clist_set_column_width (GTK_CLIST (settings_clist), TYPE, 50); + gtk_clist_set_column_width (GTK_CLIST (settings_clist), VALUE, 100); + gtk_clist_set_column_width (GTK_CLIST (settings_clist), SERIAL, 50); + + gtk_table_attach (GTK_TABLE (table), scrolled_window, + 1, 2, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, + 0, 0); + + gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0); + + button = gtk_button_new_with_label ("Close"); + gtk_box_pack_end (GTK_BOX (bbox), button, FALSE, FALSE, 0); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_main_quit), NULL); + + gtk_widget_show_all (dialog); +} + +static int +find_row (const char *name) +{ + GList *tmp_list = GTK_CLIST (settings_clist)->row_list; + int index = 0; + + while (tmp_list) + { + GtkCListRow *row = GTK_CLIST_ROW (tmp_list); + + if (strcmp (row->data, name) == 0) + return index; + + tmp_list = tmp_list->next; + index++; + } + + return -1; +} + +static void +update_row (int row, + XSettingsSetting *setting) +{ + char buffer[256]; + GtkStyle *style; + const char *type; + + if (setting->type != XSETTINGS_TYPE_COLOR) + gtk_clist_set_cell_style (GTK_CLIST (settings_clist), + row, VALUE, NULL); + + switch (setting->type) + { + case XSETTINGS_TYPE_INT: + type = "INT"; + sprintf (buffer, "%d", setting->data.v_int); + gtk_clist_set_text (GTK_CLIST (settings_clist), row, VALUE, buffer); + break; + case XSETTINGS_TYPE_STRING: + type = "STRING"; + gtk_clist_set_text (GTK_CLIST (settings_clist), row, VALUE, setting->data.v_string); + break; + case XSETTINGS_TYPE_COLOR: + type = "COLOR"; + gtk_clist_set_text (GTK_CLIST (settings_clist), row, VALUE, ""); + style = gtk_style_copy (settings_clist->style); + style->base[GTK_STATE_NORMAL].red = setting->data.v_color.red; + style->base[GTK_STATE_NORMAL].green = setting->data.v_color.green; + style->base[GTK_STATE_NORMAL].blue = setting->data.v_color.blue; + style->bg[GTK_STATE_SELECTED].red = setting->data.v_color.red; + style->bg[GTK_STATE_SELECTED].green = setting->data.v_color.green; + style->bg[GTK_STATE_SELECTED].blue = setting->data.v_color.blue; + gtk_clist_set_cell_style (GTK_CLIST (settings_clist), + row, VALUE, style); + gtk_style_unref (style); + break; + default: + g_assert_not_reached (); + } + + gtk_clist_set_text (GTK_CLIST (settings_clist), row, TYPE, type); + + sprintf (buffer, "%ld", setting->last_change_serial); + gtk_clist_set_text (GTK_CLIST (settings_clist), row, SERIAL, buffer); +} + +static void +notify_cb (const char *name, + XSettingsAction action, + XSettingsSetting *setting, + void *data) +{ + int row; + char *text[4]; + + switch (action) + { + case XSETTINGS_ACTION_NEW: + text[NAME] = (char *)name; + text[TYPE] = text[VALUE] = text[SERIAL] = ""; + row = gtk_clist_insert (GTK_CLIST (settings_clist), 0, text); + gtk_clist_set_row_data_full (GTK_CLIST (settings_clist), row, + g_strdup (name), (GDestroyNotify)g_free); + update_row (row, setting); + break; + case XSETTINGS_ACTION_CHANGED: + row = find_row (name); + update_row (row, setting); + break; + case XSETTINGS_ACTION_DELETED: + row = find_row (name); + gtk_clist_remove (GTK_CLIST (settings_clist), row); + break; + } +} + +GdkFilterReturn +client_event_filter (GdkXEvent *xevent, + GdkEvent *event, + gpointer data) +{ + if (xsettings_client_process_event (client, (XEvent *)xevent)) + return GDK_FILTER_REMOVE; + else + return GDK_FILTER_CONTINUE; +} + +static void +watch_cb (Window window, + Bool is_start, + long mask, + void *cb_data) +{ + GdkWindow *gdkwin; + + if (is_start) + printf ("Starting watch on %#lx with mask %#lx\n", window, mask); + else + printf ("Stopping watch on %#lx \n", window); + + gdkwin = gdk_window_lookup (window); + if (is_start) + gdk_window_add_filter (gdkwin, client_event_filter, NULL); + else + gdk_window_remove_filter (gdkwin, client_event_filter, NULL); +} + +int +main (int argc, char **argv) +{ + gtk_init (&argc, &argv); + + create_gui (); + + client = xsettings_client_new (gdk_display, DefaultScreen (gdk_display), + notify_cb, watch_cb, NULL); + if (!client) + { + fprintf (stderr, "Could not create client!"); + exit (1); + } + + gtk_main (); + + xsettings_client_destroy (client); + + return 0; +} diff --git a/xsettings/gtk-utils.c b/xsettings/gtk-utils.c new file mode 100644 index 0000000..8575853 --- /dev/null +++ b/xsettings/gtk-utils.c @@ -0,0 +1,119 @@ +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <gdk/gdkx.h> + +#include "gtk-utils.h" + +static void +msgbox_yes_cb (GtkWidget *widget, gboolean *result) +{ + *result = TRUE; + gtk_object_destroy (GTK_OBJECT (gtk_widget_get_toplevel (widget))); +} + +static gboolean +msgbox_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + if (event->keyval == GDK_Escape) + { + gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event"); + gtk_object_destroy (GTK_OBJECT (widget)); + return TRUE; + } + + return FALSE; +} + +gboolean +msgbox_run (GtkWindow *parent, + const char *message, + const char *yes_button, + const char *no_button, + gint default_index) +{ + gboolean result = FALSE; + GtkWidget *dialog; + GtkWidget *button; + GtkWidget *label; + GtkWidget *vbox; + GtkWidget *button_box; + GtkWidget *separator; + + g_return_val_if_fail (message != NULL, FALSE); + g_return_val_if_fail (default_index >= 0 && default_index <= 1, FALSE); + + /* Create a dialog + */ + dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + if (parent) + gtk_window_set_transient_for (GTK_WINDOW (dialog), parent); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + + /* Quit our recursive main loop when the dialog is destroyed. + */ + gtk_signal_connect (GTK_OBJECT (dialog), "destroy", + GTK_SIGNAL_FUNC (gtk_main_quit), NULL); + + /* Catch Escape key presses and have them destroy the dialog + */ + gtk_signal_connect (GTK_OBJECT (dialog), "key_press_event", + GTK_SIGNAL_FUNC (msgbox_key_press_cb), NULL); + + /* Fill in the contents of the widget + */ + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (dialog), vbox); + + label = gtk_label_new (message); + gtk_misc_set_padding (GTK_MISC (label), 12, 12); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + + separator = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0); + + button_box = gtk_hbutton_box_new (); + gtk_box_pack_start (GTK_BOX (vbox), button_box, FALSE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (button_box), 8); + + + /* When Yes is clicked, call the msgbox_yes_cb + * This sets the result variable and destroys the dialog + */ + if (yes_button) + { + button = gtk_button_new_with_label (yes_button); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_container_add (GTK_CONTAINER (button_box), button); + + if (default_index == 0) + gtk_widget_grab_default (button); + + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (msgbox_yes_cb), &result); + } + + /* When No is clicked, destroy the dialog + */ + if (no_button) + { + button = gtk_button_new_with_label (no_button); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_container_add (GTK_CONTAINER (button_box), button); + + if (default_index == 1) + gtk_widget_grab_default (button); + + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_object_destroy), GTK_OBJECT (dialog)); + } + + gtk_widget_show_all (dialog); + + /* Run a recursive main loop until a button is clicked + * or the user destroys the dialog through the window mananger */ + gtk_main (); + + return result; +} diff --git a/xsettings/gtk-utils.h b/xsettings/gtk-utils.h new file mode 100644 index 0000000..4496834 --- /dev/null +++ b/xsettings/gtk-utils.h @@ -0,0 +1,12 @@ +#ifndef GTK_UTILS_H +#define GTK_UTILS_H + +#include <gtk/gtkwindow.h> + +gboolean msgbox_run (GtkWindow *parent, + const char *message, + const char *yes_button, + const char *no_button, + gint default_index); + +#endif /* GTK_UTILS_H */ diff --git a/xsettings/manager.c b/xsettings/manager.c new file mode 100644 index 0000000..f02a134 --- /dev/null +++ b/xsettings/manager.c @@ -0,0 +1,277 @@ +#include <stdlib.h> +#include <stdio.h> + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "gtk-utils.h" +#include "xsettings-manager.h" + +XSettingsManager *manager; + +void +terminate_cb (void *data) +{ + gboolean *terminated = data; + + g_print ("Releasing the selection and exiting\n"); + + *terminated = TRUE; + gtk_main_quit (); +} + +static GtkWidget * +create_label (const char *text) +{ + GtkWidget *label = gtk_label_new (text); + gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5); + + return label; +} + +typedef struct +{ + GtkWidget *entry; + GtkAdjustment *adjustment; + GtkWidget *colorsel; + GtkWidget *swatch; + GtkRcStyle *rc_style; +} GUI; + +static void +sync_values (GtkWidget *dummy, GUI *gui) +{ + XSettingsColor color; + + xsettings_manager_set_string (manager, "Net/UserName", gtk_entry_get_text (GTK_ENTRY (gui->entry))); + xsettings_manager_set_int (manager, "Net/DoubleClickTime", gui->adjustment->value); + + color.red = gui->rc_style->bg[GTK_STATE_NORMAL].red; + color.green = gui->rc_style->bg[GTK_STATE_NORMAL].green; + color.blue = gui->rc_style->bg[GTK_STATE_NORMAL].blue; + color.alpha = 0xffff; + + xsettings_manager_set_color (manager, "Net/Background/Normal", &color); + + xsettings_manager_notify (manager); +} + +static void +show_colorsel (GtkWidget *widget, GUI *gui) +{ + gdouble color[4]; + + color[0] = (gdouble)gui->rc_style->bg[GTK_STATE_NORMAL].red / 0xffff; + color[1] = (gdouble)gui->rc_style->bg[GTK_STATE_NORMAL].green / 0xffff; + color[2] = (gdouble)gui->rc_style->bg[GTK_STATE_NORMAL].blue / 0xffff; + + gtk_color_selection_set_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (gui->colorsel)->colorsel), + color); + gtk_widget_show (gui->colorsel); +} + +static void +colorsel_ok (GtkWidget *widget, GUI *gui) +{ + gdouble color[4]; + + gtk_color_selection_get_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (gui->colorsel)->colorsel), + color); + + gtk_rc_style_unref (gui->rc_style); + + gui->rc_style = gtk_rc_style_new (); + gui->rc_style->bg[GTK_STATE_NORMAL].red = color[0] * 0xffff; + gui->rc_style->bg[GTK_STATE_NORMAL].green = color[1] * 0xffff; + gui->rc_style->bg[GTK_STATE_NORMAL].blue = color[2] * 0xffff; + gui->rc_style->bg[GTK_STATE_PRELIGHT] = gui->rc_style->bg[GTK_STATE_NORMAL]; + + gui->rc_style->color_flags[GTK_STATE_NORMAL] = GTK_RC_BG; + gui->rc_style->color_flags[GTK_STATE_PRELIGHT] = GTK_RC_BG; + + gtk_widget_modify_style (gui->swatch, gui->rc_style); + sync_values (NULL, gui); + + gtk_widget_hide (gui->colorsel); +} + +static gboolean +close_hides (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + gtk_widget_hide (widget); + return TRUE; +} + +static void +create_gui (void) +{ + GtkWidget *dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL); + GtkWidget *vbox, *bbox, *separator; + GtkWidget *button, *scale; + GtkWidget *table, *frame, *alignment; + GUI *gui = g_new (GUI, 1); + + gtk_window_set_title (GTK_WINDOW (dialog), "Sample XSETTINGS Manager"); + gtk_window_set_default_size (GTK_WINDOW (dialog), 500, 300); + + gtk_signal_connect (GTK_OBJECT (dialog), "destroy", + GTK_SIGNAL_FUNC (gtk_main_quit), NULL); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (dialog), vbox); + + bbox = gtk_hbutton_box_new (); + gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); + + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END); + gtk_container_set_border_width (GTK_CONTAINER (bbox), 5); + + separator = gtk_hseparator_new (); + gtk_box_pack_end (GTK_BOX (vbox), separator, FALSE, FALSE, 0); + + table = gtk_table_new (3, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0); + + + gtk_table_attach (GTK_TABLE (table), create_label ("Net/DoubleClickTime"), + 0, 1, 0, 1, + GTK_FILL, 0, + 0, 0); + + gui->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (300., 0., 1000., 10., 100, 0.)); + gtk_signal_connect (GTK_OBJECT (gui->adjustment), "value_changed", + GTK_SIGNAL_FUNC (sync_values), gui); + + scale = gtk_hscale_new (gui->adjustment); + gtk_table_attach (GTK_TABLE (table), scale, + 1, 2, 0, 1, + GTK_FILL | GTK_EXPAND, 0, + 0, 0); + + + gtk_table_attach (GTK_TABLE (table), create_label ("Net/UserName"), + 0, 1, 1, 2, + GTK_FILL, 0, + 0, 0); + gui->entry = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), gui->entry, + 1, 2, 1, 2, + GTK_FILL | GTK_EXPAND, 0, + 0, 0); + + gtk_entry_set_text (GTK_ENTRY (gui->entry), "John Doe"); + gtk_signal_connect (GTK_OBJECT (gui->entry), "changed", + GTK_SIGNAL_FUNC (sync_values), gui); + + gtk_table_attach (GTK_TABLE (table), create_label ("Net/Background/Normal"), + 0, 1, 2, 3, + GTK_FILL, 0, + 0, 0); + + alignment = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_table_attach (GTK_TABLE (table), alignment, + 1, 2, 2, 3, + GTK_FILL | GTK_EXPAND, 0, + 0, 0); + + button = gtk_button_new (); + gtk_container_add (GTK_CONTAINER (alignment), button); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT); + gtk_container_add (GTK_CONTAINER (button), frame); + + gui->swatch = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (frame), gui->swatch); + gtk_widget_set_usize (gui->swatch, 80, 15); + + gui->rc_style = gtk_rc_style_new (); + gui->rc_style->bg[GTK_STATE_NORMAL].red = 0xcccc; + gui->rc_style->bg[GTK_STATE_NORMAL].green = 0xcccc; + gui->rc_style->bg[GTK_STATE_NORMAL].blue = 0xffff; + gui->rc_style->bg[GTK_STATE_PRELIGHT] = gui->rc_style->bg[GTK_STATE_NORMAL]; + + gui->rc_style->color_flags[GTK_STATE_NORMAL] = GTK_RC_BG; + gui->rc_style->color_flags[GTK_STATE_PRELIGHT] = GTK_RC_BG; + + gtk_widget_modify_style (gui->swatch, gui->rc_style); + + gui->colorsel = gtk_color_selection_dialog_new ("Net/Background/Normal"); + gtk_signal_connect (GTK_OBJECT (gui->colorsel), "delete_event", + GTK_SIGNAL_FUNC (close_hides), NULL); + + gtk_signal_connect (GTK_OBJECT (GTK_COLOR_SELECTION_DIALOG (gui->colorsel)->ok_button), + "clicked", + GTK_SIGNAL_FUNC (colorsel_ok), gui); + gtk_signal_connect_object (GTK_OBJECT (GTK_COLOR_SELECTION_DIALOG (gui->colorsel)->cancel_button), + "clicked", + GTK_SIGNAL_FUNC (gtk_widget_hide), + GTK_OBJECT (gui->colorsel)); + gtk_widget_hide (GTK_COLOR_SELECTION_DIALOG (gui->colorsel)->help_button); + + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (show_colorsel), gui); + + button = gtk_button_new_with_label ("Close"); + gtk_box_pack_end (GTK_BOX (bbox), button, FALSE, FALSE, 0); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_main_quit), NULL); + + sync_values (NULL, gui); + + gtk_widget_show_all (dialog); +} + +GdkFilterReturn +manager_event_filter (GdkXEvent *xevent, + GdkEvent *event, + gpointer data) +{ + if (xsettings_manager_process_event (manager, (XEvent *)xevent)) + return GDK_FILTER_REMOVE; + else + return GDK_FILTER_CONTINUE; +} + +int +main (int argc, char **argv) +{ + gboolean terminated = FALSE; + + gtk_init (&argc, &argv); + + if (xsettings_manager_check_running (gdk_display, DefaultScreen (gdk_display))) + terminated = !msgbox_run (NULL, + "XSETTINGS manager already detected for screen\n" + "Replace existing manager?", + "Replace", "Exit", 1); + + if (!terminated) + { + manager = xsettings_manager_new (gdk_display, DefaultScreen (gdk_display), + terminate_cb, &terminated); + if (!manager) + { + fprintf (stderr, "Could not create manager!"); + exit (1); + } + } + + gdk_window_add_filter (NULL, manager_event_filter, NULL); + + if (!terminated) + { + create_gui (); + gtk_main (); + } + + xsettings_manager_destroy (manager); + + return 0; +} diff --git a/xsettings/settings-proposal.txt b/xsettings/settings-proposal.txt new file mode 100644 index 0000000..233b5d4 --- /dev/null +++ b/xsettings/settings-proposal.txt @@ -0,0 +1,313 @@ +XSettings - cross toolkit configuration +======================================= + +Goals +===== + +The intent of this specification is to specify a mechanism to +allow the configuration of settings such as double click timeout, +drag-and-drop threshold, and default foreground and background colors +for all applications running within a desktop. The mechanism should: + + - allow for instant updates to be propagated across all applications at + runtime + - it should perform well, even for remote applications. + +It is not intended: + + - for the storage of application-specific data + - to be able to store large amounts of data + - to store complex data types (other than as strings) + + +Existing systems +================ + +The existing systems in this area are the Xrm database, and various +other configuration database systems, mostly specific to particular +desktops. (The kde configuration system, gnome-config, GConf, +libproplist, etc.) + +The Xrm database is tempting to use for this purpose since it is +very well established and has a universally deployed existing +implementation. However, it has various defects, that make it +not suitable for this purpose. + + - The Xrm database merges information from various sources - the + ~/.Xdefaults file on the root window, and a property on the root + window. This means that to programatically configure the Xrm + database in response to a GUI config tool is difficult and + unreliable. + + - The Xrm database was not designed for on-the-fly changing of + settings. + + - The Xrm database stores all information in a single text + property on the root window. This makes it difficult to + determine what settings have changed; it is necessary to parse + the property and do string comparisons. + + Additionally, most systems have a large amount of application + specific information in the Xrm database, which further slows + down notification of changes. + +Other configuration databases are more designed for this +task. However, they are sophisticated systems that are not easily +reimplementable. Also, picking one would mean difficulties +integrating with other desktops that use different systems. + +It is our contention that a system designed specifically for +configuration of a small number of settings being changed at +runtime can: + + - Be easier to bridge onto each systems native configuration + mechanism. + + - Be easier to implement in whatever language/library combination + people want to use. + + - Be more efficient. + +Than using a more general existing system. + + +Overview +======== + +The XSettings mechanism does not specify the manner in which settings +are stored or changed. Instead, it is a mechanism for clients to +interact with the _settings_manager_, which uses desktop-specific +mechanisms for storing and tracking changes to settings. + +The settings manager maintains an unmapped window on which all +settings are stored, the _settings_window_. This window is the owner +of the _XSETTINGS_S[N] resource. ([N] is replaced by the value of the +screen - for instances, _XSETTINGS_S0.) + +The values of the settings are stored in a single property on this +window, the _XSETTINGS_SETTINGS + + +Client behavior +=============== + +On startup, each client that should identify the settings window by +calling XGetSelectionOwner() for the _XSETTINGS_S[N] selection and select +for notification on the settings window by calling XSelectInput() +with a mask of StructureNotifyMask|PropertyChangeMask. + +To prevent race conditions a client MUST grab the +server while performing these operations using XGrabServer(). + +Clients + +If there is no owner of the _XSETTINGS_S[N] selection, the client can +determine when an owner is established by listening for client +messages sent to root window of the screen with type MANAGER. (See +section 2.8, Manager Selections of the ICCCM.) The format of this +message is: + + event-mask: StructureNotify + event: ClientMessage + type: MANAGER + format: 32 + data[0]: timestamp + data[1]: _XSETTINGS_S[N] (atom) + data[2]: New owner of the selection + data[3]: 0 (reserved) + +The client can then proceed to read contents of the _XSETTINGS_SETTINGS +property from the settings window and interpret according to +the information in the "_XSETTINGS_SETTINGS Format" section of this +document. + +Clients must trap X errors when reading the _XSETTING_SETTINGS property +because it is possible that the selection window may be destroyed +at any time. + +When the client is notified that the settings window has been +destroyed or discovers that the selection window has been destroyed, +it should reset all settings to their default values and then proceed +as on initial startup. [ See rational section ] + +When a client receives a PropertyChangeNotify event for the window +it should reread the _XSETTING_SETTINGS property. It can use +the 'serial' field to tell what fields have been changed. The +client must parse the entire property and read in all new values +before taking action on changed settings such as notifying listeners +for those settings to avoid using a mix of old and new data. + + +_XSETTINGS_SETTINGS Format +========================== + +The _XSETTINGS_SETTINGS property is of form 8 and type _XSETTINGS_SETTINGS. +The contents are a + + 1 CARD8 byte-order + 3 unused + 4 CARD32 SERIAL + 4 CARD32 N_SETTINGS + +Followed by N_SETTINGS settings records, which have a header: + + 1 SETTING_TYPE type + 1 unused + 2 n name-len + n STRING8 name + P unused, p=pad(n) + 4 CARD32 last-change-serial + +Where SETTING_TYPE is + + 0 XSettingsTypeInteger + 1 XSettingsTypeString + 2 XSettingsTypeColor + +followed by the body. If TYPE = XSettingsTypeString the body is: + + 4 n value-len + n STRING8 value + P unused, p=pad(n) + +If TYPE == XSettingsTypeInteger, then the body is: + + 4 INT32 value + +If TYPE == XSettingsTypeColor, then the body is: + + 2 CARD16 red + 2 CARD16 blue + 2 CARD16 green + 2 CARD16 alpha + +If the setting does not need the alpha field, it should +be set to 65535. + + +Setting names must be confined to the ascii characters: + + 'A'-'Z' 'a'-'z' '0'-'9' '_' and '/' + +With the additional restrictions that '/' cannot appear in +the leading or trailing position, that two occurences of +'/' cannot be consecutive, and that the first character +of the name, and and the first character after a slash +cannot be one of '0'-'9'. Names may not be empty. + +So, + + "GTK/colors/background0" + "_background" + "_111" + +are legitimate names, while + + "/" + "_background/" + "GTK//colors" + "" +Are not legitimate names. + +The names, types, contents, and default values of standard settings +will be separately agreed upon. + +Names beginning with 'Net/' and case variants of that string are +reserved and must not be used without prior agreement. + + +The 'serial' field and and the 'last-change-serial' field of the each +settings record can be used to tell which settings have changed since +the last time a client retrieved the _XSETTINGS_SETTINGS property. Each +time the client retrieves the contents of the _XSETTINGS_SETTINGS +property it should store the contents of the 'serial' field. When +it next retrieves the property, any settings whose 'last-change-serial' +is greater than the stored value. + +(Careful clients will make provisions for wrap-around of the serial +field. This is, however, not expected to happen in practice.) + + +Settings Manager behavior +========================= + +The _XSETTING_S[N] selection is managed as a manager selection +according to section 2.8 of the ICCCM and the handling of the +selections window, the _XSETTING_S[N] window and MANAGER client +messages must conform to that specification. + +The settings manager changes the contents of the _XSETTINGS_SETTINGS +property of the root window whenever the source it derives them +from changes, taking care to increment the 'serial' field at each +increment and set the 'last-change-serial' fields appropriately. + + +Rational and discussion +======================= + +The reasons why an existing configuration mechanism, and in particular, +the Xrm database, was not used is discussed above. Various other +design decisions are discussed below: + + +Why aren't the contents of the property stored in XML? + +The data format is designed to be space efficient and to be easily +and efficiently parsed with minimal code. These are not things +XML does well. Flexibility of structure, things that XML +does well are not needed here. If needed, XML can be used for +the contents of individual settings. + + +Why is the settings property screen specific? + +While most settings are expected to be shared across all screens +of a display, some settings, such as font sizes will be screen +specific, and it is considered easier to let the settings manager +propagate shared resources across screens then to have both +screen-specific and screen-independent resources. + + +Why does there need to be a "settings manager" process running? + +Having a process always present as the owner of the _XSETTING +selection ensures that there are no race conditions and is simpler +than trying to create a locking mechanism that can work without a +persistant process. + +It is also expected that to properly handle notification of changes to +the stored properties most desktops will want a process running to +watch for changes in any case. In cases of tight resource usage, the +settings manager can be combined with some other function, such +as the window manager or session manager. + + +Why use a single property for all settings? + +Using a single property has several advantages. First, retrieving +all settings takes only a single round-trip to the server instead +of a round-trip for each settings. Second, it means that when +multiple settings can be changed at once, only a single notification +is received by clients, and clients will see interrelated properties +changed in an atomic fashion. + + +Why is the _XSETTINGS_SETTINGS property stored in the endianess +of the manager instead of a neutral endianness? + +This conforms to the way many other X mechanisms work. The main reason +for doing it this way is to save conversions for the common case when +the client and manager are on machines of the same endianness. + + +When the settings manager exits, why should clients reset the +values to the default settings instead of keeping the current +settings? + +Resetting the settings to the default values is preferred to +maintaining the current values because it makes sure that all programs +on the desktop have consistent values for settings whether they were +started before or after the settings manager exited. It is not +expected that changes in the current settings manager will occur +very often. + diff --git a/xsettings/xsettings-client.c b/xsettings/xsettings-client.c new file mode 100644 index 0000000..7e1e356 --- /dev/null +++ b/xsettings/xsettings-client.c @@ -0,0 +1,512 @@ +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> + +#include <X11/Xlib.h> +#include <X11/Xmd.h> /* For CARD16 */ + +#include "xsettings-client.h" + +struct _XSettingsClient +{ + Display *display; + int screen; + XSettingsNotifyFunc notify; + XSettingsWatchFunc watch; + void *cb_data; + + Window manager_window; + Atom manager_atom; + Atom selection_atom; + Atom xsettings_atom; + + XSettingsList *settings; +}; + +static void +notify_changes (XSettingsClient *client, + XSettingsList *old_list) +{ + XSettingsList *old_iter = old_list; + XSettingsList *new_iter = client->settings; + + if (!client->notify) + return; + + while (old_iter || new_iter) + { + int cmp; + + if (old_iter && new_iter) + cmp = strcmp (old_iter->setting->name, new_iter->setting->name); + else if (old_iter) + cmp = -1; + else + cmp = 1; + + if (cmp < 0) + { + client->notify (old_iter->setting->name, + XSETTINGS_ACTION_DELETED, + NULL, + client->cb_data); + } + else if (cmp == 0) + { + if (!xsettings_setting_equal (old_iter->setting, + new_iter->setting)) + client->notify (old_iter->setting->name, + XSETTINGS_ACTION_CHANGED, + new_iter->setting, + client->cb_data); + } + else + { + client->notify (new_iter->setting->name, + XSETTINGS_ACTION_NEW, + new_iter->setting, + client->cb_data); + } + + if (old_iter) + old_iter = old_iter->next; + if (new_iter) + new_iter = new_iter->next; + } +} + +static int +ignore_errors (Display *display, XErrorEvent *event) +{ + return True; +} + +static char local_byte_order = '\0'; + +#define BYTES_LEFT(buffer) ((buffer)->data + (buffer)->len - (buffer)->pos) + +static XSettingsResult +fetch_card16 (XSettingsBuffer *buffer, + CARD16 *result) +{ + CARD16 x; + + if (BYTES_LEFT (buffer) < 2) + return XSETTINGS_ACCESS; + + x = *(CARD16 *)buffer->pos; + buffer->pos += 2; + + if (buffer->byte_order == local_byte_order) + *result = x; + else + *result = (x << 8) | (x >> 8); + + return XSETTINGS_SUCCESS; +} + +static XSettingsResult +fetch_ushort (XSettingsBuffer *buffer, + unsigned short *result) +{ + CARD16 x; + XSettingsResult r; + + r = fetch_card16 (buffer, &x); + if (r == XSETTINGS_SUCCESS) + *result = x; + + return r; +} + +static XSettingsResult +fetch_card32 (XSettingsBuffer *buffer, + CARD32 *result) +{ + CARD32 x; + + if (BYTES_LEFT (buffer) < 4) + return XSETTINGS_ACCESS; + + x = *(CARD32 *)buffer->pos; + buffer->pos += 4; + + if (buffer->byte_order == local_byte_order) + *result = x; + else + *result = (x << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | (x >> 24); + + return XSETTINGS_SUCCESS; +} + +static XSettingsResult +fetch_card8 (XSettingsBuffer *buffer, + CARD8 *result) +{ + if (BYTES_LEFT (buffer) < 1) + return XSETTINGS_ACCESS; + + *result = *(CARD32 *)buffer->pos; + buffer->pos += 1; + + return XSETTINGS_SUCCESS; +} + +#define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1))) + +static XSettingsList * +parse_settings (unsigned char *data, + size_t len) +{ + XSettingsBuffer buffer; + XSettingsResult result = XSETTINGS_SUCCESS; + XSettingsList *settings = NULL; + CARD32 serial; + CARD32 n_entries; + CARD32 i; + XSettingsSetting *setting; + + local_byte_order = xsettings_byte_order (); + + buffer.pos = buffer.data = data; + buffer.len = len; + + result = fetch_card8 (&buffer, &buffer.byte_order); + if (buffer.byte_order != MSBFirst && + buffer.byte_order != LSBFirst) + { + fprintf (stderr, "Invalid byte order in XSETTINGS property\n"); + result = XSETTINGS_FAILED; + goto out; + } + + buffer.pos += 3; + + result = fetch_card32 (&buffer, &serial); + if (result != XSETTINGS_SUCCESS) + goto out; + + result = fetch_card32 (&buffer, &n_entries); + if (result != XSETTINGS_SUCCESS) + goto out; + + for (i = 0; i < n_entries; i++) + { + CARD8 type; + CARD16 name_len; + CARD32 v_int; + size_t pad_len; + + result = fetch_card8 (&buffer, &type); + if (result != XSETTINGS_SUCCESS) + goto out; + + buffer.pos += 1; + + result = fetch_card16 (&buffer, &name_len); + if (result != XSETTINGS_SUCCESS) + goto out; + + pad_len = XSETTINGS_PAD(name_len, 4); + if (BYTES_LEFT (&buffer) < pad_len) + { + result = XSETTINGS_ACCESS; + goto out; + } + + setting = malloc (sizeof *setting); + if (!setting) + { + result = XSETTINGS_NO_MEM; + goto out; + } + setting->type = XSETTINGS_TYPE_INT; /* No allocated memory */ + + setting->name = malloc (name_len + 1); + if (!setting->name) + { + result = XSETTINGS_NO_MEM; + goto out; + } + + memcpy (setting->name, buffer.pos, name_len); + setting->name[name_len] = '\0'; + buffer.pos += pad_len; + + result = fetch_card32 (&buffer, &v_int); + if (result != XSETTINGS_SUCCESS) + goto out; + setting->last_change_serial = v_int; + + switch (type) + { + case XSETTINGS_TYPE_INT: + result = fetch_card32 (&buffer, &v_int); + if (result != XSETTINGS_SUCCESS) + goto out; + + setting->data.v_int = (INT32)v_int; + break; + case XSETTINGS_TYPE_STRING: + result = fetch_card32 (&buffer, &v_int); + if (result != XSETTINGS_SUCCESS) + goto out; + + pad_len = XSETTINGS_PAD (v_int, 4); + if (v_int + 1 == 0 || /* Guard against wrap-around */ + BYTES_LEFT (&buffer) < pad_len) + { + result = XSETTINGS_ACCESS; + goto out; + } + + setting->data.v_string = malloc (v_int + 1); + if (!setting->data.v_string) + { + result = XSETTINGS_NO_MEM; + goto out; + } + + memcpy (setting->data.v_string, buffer.pos, v_int); + setting->data.v_string[v_int] = '\0'; + buffer.pos += pad_len; + + break; + case XSETTINGS_TYPE_COLOR: + result = fetch_ushort (&buffer, &setting->data.v_color.red); + if (result != XSETTINGS_SUCCESS) + goto out; + result = fetch_ushort (&buffer, &setting->data.v_color.green); + if (result != XSETTINGS_SUCCESS) + goto out; + result = fetch_ushort (&buffer, &setting->data.v_color.blue); + if (result != XSETTINGS_SUCCESS) + goto out; + result = fetch_ushort (&buffer, &setting->data.v_color.alpha); + if (result != XSETTINGS_SUCCESS) + goto out; + + break; + default: + /* Quietly ignore unknown types */ + break; + } + + setting->type = type; + + result = xsettings_list_insert (&settings, setting); + if (result != XSETTINGS_SUCCESS) + goto out; + + setting = NULL; + } + + out: + + if (result != XSETTINGS_SUCCESS) + { + switch (result) + { + case XSETTINGS_NO_MEM: + fprintf(stderr, "Out of memory reading XSETTINGS property\n"); + break; + case XSETTINGS_ACCESS: + fprintf(stderr, "Invalid XSETTINGS property (read off end)\n"); + break; + case XSETTINGS_DUPLICATE_ENTRY: + fprintf (stderr, "Duplicate XSETTINGS entry for '%s'\n", setting->name); + case XSETTINGS_FAILED: + case XSETTINGS_SUCCESS: + case XSETTINGS_NO_ENTRY: + break; + } + + if (setting) + xsettings_setting_free (setting); + + xsettings_list_free (settings); + settings = NULL; + + } + + return settings; +} + +static void +read_settings (XSettingsClient *client) +{ + Atom type; + int format; + unsigned long n_items; + unsigned long bytes_after; + unsigned char *data; + int result; + + int (*old_handler) (Display *, XErrorEvent *); + + XSettingsList *old_list = client->settings; + + client->settings = NULL; + + old_handler = XSetErrorHandler (ignore_errors); + result = XGetWindowProperty (client->display, client->manager_window, + client->xsettings_atom, 0, LONG_MAX, + False, client->xsettings_atom, + &type, &format, &n_items, &bytes_after, &data); + XSetErrorHandler (old_handler); + + if (result == Success && type == client->xsettings_atom) + { + if (format != 8) + { + fprintf (stderr, "Invalid format for XSETTINGS property %d", format); + } + else + client->settings = parse_settings (data, n_items); + + XFree (data); + } + + notify_changes (client, old_list); + xsettings_list_free (old_list); +} + +static void +add_events (Display *display, + Window window, + long mask) +{ + XWindowAttributes attr; + + XGetWindowAttributes (display, window, &attr); + XSelectInput (display, window, attr.your_event_mask | mask); +} + +static void +check_manager_window (XSettingsClient *client) +{ + if (client->manager_window && client->watch) + client->watch (client->manager_window, False, 0, client->cb_data); + + XGrabServer (client->display); + + client->manager_window = XGetSelectionOwner (client->display, + client->selection_atom); + if (client->manager_window) + XSelectInput (client->display, client->manager_window, + PropertyChangeMask | StructureNotifyMask); + + XUngrabServer (client->display); + XFlush (client->display); + + if (client->manager_window && client->watch) + client->watch (client->manager_window, True, + PropertyChangeMask | StructureNotifyMask, + client->cb_data); + + read_settings (client); +} + +XSettingsClient * +xsettings_client_new (Display *display, + int screen, + XSettingsNotifyFunc notify, + XSettingsWatchFunc watch, + void *cb_data) +{ + XSettingsClient *client; + char buffer[256]; + + client = malloc (sizeof *client); + if (!client) + return NULL; + + client->display = display; + client->screen = screen; + client->notify = notify; + client->watch = watch; + client->cb_data = cb_data; + + client->manager_window = None; + client->settings = NULL; + + sprintf(buffer, "_XSETTINGS_S%d", screen); + client->selection_atom = XInternAtom (display, buffer, False); + client->xsettings_atom = XInternAtom (display, "_XSETTINGS_SETTINGS", False); + client->manager_atom = XInternAtom (display, "MANAGER", False); + + /* Select on StructureNotify so we get MANAGER events + */ + add_events (display, RootWindow (display, screen), StructureNotifyMask); + + if (client->watch) + client->watch (RootWindow (display, screen), True, StructureNotifyMask, + client->cb_data); + + check_manager_window (client); + + return client; +} + +void +xsettings_client_destroy (XSettingsClient *client) +{ + if (client->watch) + client->watch (RootWindow (client->display, client->screen), + False, 0, client->cb_data); + if (client->manager_window && client->watch) + client->watch (client->manager_window, False, 0, client->cb_data); + + xsettings_list_free (client->settings); + free (client); +} + +XSettingsResult +xsettings_client_get_setting (XSettingsClient *client, + const char *name, + XSettingsSetting **setting) +{ + XSettingsSetting *search = xsettings_list_lookup (client->settings, name); + if (search) + { + *setting = xsettings_setting_copy (search); + return *setting ? XSETTINGS_SUCCESS : XSETTINGS_NO_MEM; + } + else + return XSETTINGS_NO_ENTRY; +} + +Bool +xsettings_client_process_event (XSettingsClient *client, + XEvent *xev) +{ + /* The checks here will not unlikely cause us to reread + * the properties from the manager window a number of + * times when the manager changes from A->B. But manager changes + * are going to be pretty rare. + */ + if (xev->xany.window == RootWindow (client->display, client->screen)) + { + if (xev->xany.type == ClientMessage && + xev->xclient.message_type == client->manager_atom) + { + check_manager_window (client); + return True; + } + } + else if (xev->xany.window == client->manager_window) + { + if (xev->xany.type == DestroyNotify) + { + check_manager_window (client); + return True; + } + else if (xev->xany.type == PropertyNotify) + { + read_settings (client); + return True; + } + } + + return False; +} diff --git a/xsettings/xsettings-client.h b/xsettings/xsettings-client.h new file mode 100644 index 0000000..480d646 --- /dev/null +++ b/xsettings/xsettings-client.h @@ -0,0 +1,46 @@ +#ifndef XSETTINGS_CLIENT_H +#define XSETTINGS_CLIENT_H + +#include <X11/Xlib.h> +#include "xsettings-common.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _XSettingsClient XSettingsClient; + +typedef enum +{ + XSETTINGS_ACTION_NEW, + XSETTINGS_ACTION_CHANGED, + XSETTINGS_ACTION_DELETED, +} XSettingsAction; + +typedef void (*XSettingsNotifyFunc) (const char *name, + XSettingsAction action, + XSettingsSetting *setting, + void *cb_data); +typedef void (*XSettingsWatchFunc) (Window window, + Bool is_start, + long mask, + void *cb_data); + +XSettingsClient *xsettings_client_new (Display *display, + int screen, + XSettingsNotifyFunc notify, + XSettingsWatchFunc watch, + void *cb_data); +void xsettings_client_destroy (XSettingsClient *client); +Bool xsettings_client_process_event (XSettingsClient *client, + XEvent *xev); + +XSettingsResult xsettings_client_get_setting (XSettingsClient *client, + const char *name, + XSettingsSetting **setting); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* XSETTINGS_CLIENT_H */ diff --git a/xsettings/xsettings-common.c b/xsettings/xsettings-common.c new file mode 100644 index 0000000..7529f97 --- /dev/null +++ b/xsettings/xsettings-common.c @@ -0,0 +1,239 @@ +#include "string.h" +#include "stdlib.h" + +#include <X11/Xlib.h> +#include <X11/Xmd.h> /* For CARD32 */ + +#include "xsettings-common.h" + +XSettingsSetting * +xsettings_setting_copy (XSettingsSetting *setting) +{ + XSettingsSetting *result; + size_t str_len; + + result = malloc (sizeof *result); + if (!result) + return NULL; + + str_len = strlen (setting->name); + result->name = malloc (str_len + 1); + if (!result->name) + goto err; + + memcpy (result->name, setting->name, str_len + 1); + + result->type = setting->type; + + switch (setting->type) + { + case XSETTINGS_TYPE_INT: + result->data.v_int = setting->data.v_int; + break; + case XSETTINGS_TYPE_COLOR: + result->data.v_color = setting->data.v_color; + break; + case XSETTINGS_TYPE_STRING: + str_len = strlen (setting->data.v_string); + result->data.v_string = malloc (str_len + 1); + if (!result->data.v_string) + goto err; + + memcpy (result->data.v_string, setting->data.v_string, str_len + 1); + break; + } + + result->last_change_serial = setting->last_change_serial; + + return result; + + err: + if (result->name) + free (result->name); + free (result); + + return NULL; +} + +XSettingsList * +xsettings_list_copy (XSettingsList *list) +{ + XSettingsList *new = NULL; + XSettingsList *old_iter = list; + XSettingsList *new_iter = NULL; + + while (old_iter) + { + XSettingsList *new_node; + + new_node = malloc (sizeof *new_node); + if (!new_node) + goto error; + + new_node->setting = xsettings_setting_copy (old_iter->setting); + if (!new_node->setting) + { + free (new_node); + goto error; + } + + if (new_iter) + new_iter->next = new_node; + else + new = new_node; + + new_iter = new_node; + + old_iter = old_iter->next; + } + + return new; + + error: + xsettings_list_free (new); + return NULL; +} + +int +xsettings_setting_equal (XSettingsSetting *setting_a, + XSettingsSetting *setting_b) +{ + if (setting_a->type != setting_b->type) + return 0; + + if (strcmp (setting_a->name, setting_b->name) != 0) + return 0; + + switch (setting_a->type) + { + case XSETTINGS_TYPE_INT: + return setting_a->data.v_int == setting_b->data.v_int; + case XSETTINGS_TYPE_COLOR: + return (setting_a->data.v_color.red == setting_b->data.v_color.red && + setting_a->data.v_color.green == setting_b->data.v_color.green && + setting_a->data.v_color.blue == setting_b->data.v_color.blue && + setting_a->data.v_color.alpha == setting_b->data.v_color.alpha); + case XSETTINGS_TYPE_STRING: + return strcmp (setting_a->data.v_string, setting_b->data.v_string) == 0; + } + + return 0; +} + +void +xsettings_setting_free (XSettingsSetting *setting) +{ + if (setting->type == XSETTINGS_TYPE_STRING) + free (setting->data.v_string); + + free (setting); +} + +void +xsettings_list_free (XSettingsList *list) +{ + while (list) + { + XSettingsList *next = list->next; + + xsettings_setting_free (list->setting); + free (list); + + list = next; + } +} + +XSettingsResult +xsettings_list_insert (XSettingsList **list, + XSettingsSetting *setting) +{ + XSettingsList *node; + XSettingsList *iter; + XSettingsList *last = NULL; + + node = malloc (sizeof *node); + if (!node) + return XSETTINGS_NO_MEM; + node->setting = setting; + + iter = *list; + while (iter) + { + int cmp = strcmp (setting->name, iter->setting->name); + + if (cmp < 0) + break; + else if (cmp == 0) + { + free (node); + return XSETTINGS_DUPLICATE_ENTRY; + } + + last = iter; + iter = iter->next; + } + + if (last) + last->next = node; + else + *list = node; + + node->next = iter; + + return XSETTINGS_SUCCESS; +} + +XSettingsResult +xsettings_list_delete (XSettingsList **list, + const char *name) +{ + XSettingsList *iter; + XSettingsList *last = NULL; + + iter = *list; + while (iter) + { + if (strcmp (name, iter->setting->name) == 0) + { + if (last) + last->next = iter->next; + else + *list = iter->next; + + xsettings_setting_free (iter->setting); + free (iter); + + return XSETTINGS_SUCCESS; + } + + last = iter; + iter = iter->next; + } + + return XSETTINGS_FAILED; +} + +XSettingsSetting * +xsettings_list_lookup (XSettingsList *list, + const char *name) +{ + XSettingsList *iter; + + iter = list; + while (iter) + { + if (strcmp (name, iter->setting->name) == 0) + return iter->setting; + + iter = iter->next; + } + + return NULL; +} + +char +xsettings_byte_order (void) +{ + CARD32 myint = 0x01020304; + return (*(char *)&myint == 1) ? MSBFirst : LSBFirst; +} diff --git a/xsettings/xsettings-common.h b/xsettings/xsettings-common.h new file mode 100644 index 0000000..2551db0 --- /dev/null +++ b/xsettings/xsettings-common.h @@ -0,0 +1,88 @@ +#ifndef XSETTINGS_COMMON_H +#define XSETTINGS_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _XSettingsBuffer XSettingsBuffer; +typedef struct _XSettingsColor XSettingsColor; +typedef struct _XSettingsList XSettingsList; +typedef struct _XSettingsSetting XSettingsSetting; + +/* Types of settings possible. Enum values correspond to + * protocol values. + */ +typedef enum +{ + XSETTINGS_TYPE_INT = 0, + XSETTINGS_TYPE_STRING = 1, + XSETTINGS_TYPE_COLOR = 2 +} XSettingsType; + +typedef enum +{ + XSETTINGS_SUCCESS, + XSETTINGS_NO_MEM, + XSETTINGS_ACCESS, + XSETTINGS_FAILED, + XSETTINGS_NO_ENTRY, + XSETTINGS_DUPLICATE_ENTRY +} XSettingsResult; + +struct _XSettingsBuffer +{ + char byte_order; + size_t len; + unsigned char *data; + unsigned char *pos; +}; + +struct _XSettingsColor +{ + unsigned short red, green, blue, alpha; +}; + +struct _XSettingsList +{ + XSettingsSetting *setting; + XSettingsList *next; +}; + +struct _XSettingsSetting +{ + char *name; + XSettingsType type; + + union { + int v_int; + char *v_string; + XSettingsColor v_color; + } data; + + unsigned long last_change_serial; +}; + +XSettingsSetting *xsettings_setting_copy (XSettingsSetting *setting); +void xsettings_setting_free (XSettingsSetting *setting); +int xsettings_setting_equal (XSettingsSetting *setting_a, + XSettingsSetting *setting_b); + +void xsettings_list_free (XSettingsList *list); +XSettingsList *xsettings_list_copy (XSettingsList *list); +XSettingsResult xsettings_list_insert (XSettingsList **list, + XSettingsSetting *setting); +XSettingsSetting *xsettings_list_lookup (XSettingsList *list, + const char *name); +XSettingsResult xsettings_list_delete (XSettingsList **list, + const char *name); + +char xsettings_byte_order (void); + +#define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1))) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* XSETTINGS_COMMON_H */ diff --git a/xsettings/xsettings-manager.c b/xsettings/xsettings-manager.c new file mode 100644 index 0000000..2741c97 --- /dev/null +++ b/xsettings/xsettings-manager.c @@ -0,0 +1,394 @@ +#include <stdio.h> +#include <stdlib.h> + +#include <X11/Xmd.h> /* For CARD16 */ + +#include "xsettings-manager.h" + +struct _XSettingsManager +{ + Display *display; + int screen; + + Window window; + Atom manager_atom; + Atom selection_atom; + Atom xsettings_atom; + + XSettingsTerminateFunc terminate; + void *cb_data; + + XSettingsList *settings; + unsigned long serial; +}; + +XSettingsList *settings; + +typedef struct +{ + Window window; + Atom timestamp_prop_atom; +} TimeStampInfo; + +static Bool +timestamp_predicate (Display *display, + XEvent *xevent, + XPointer arg) +{ + TimeStampInfo *info = (TimeStampInfo *)arg; + + if (xevent->type == PropertyNotify && + xevent->xproperty.window == info->window && + xevent->xproperty.atom == info->timestamp_prop_atom) + return True; + + return False; +} + +/** + * get_server_time: + * @display: display from which to get the time + * @window: a #Window, used for communication with the server. + * The window must have PropertyChangeMask in its + * events mask or a hang will result. + * + * Routine to get the current X server time stamp. + * + * Return value: the time stamp. + **/ +static Time +get_server_time (Display *display, + Window window) +{ + unsigned char c = 'a'; + XEvent xevent; + TimeStampInfo info; + + info.timestamp_prop_atom = XInternAtom (display, "_TIMESTAMP_PROP", False); + info.window = window; + + XChangeProperty (display, window, + info.timestamp_prop_atom, info.timestamp_prop_atom, + 8, PropModeReplace, &c, 1); + + XIfEvent (display, &xevent, + timestamp_predicate, (XPointer)&info); + + return xevent.xproperty.time; +} + +Bool +xsettings_manager_check_running (Display *display, + int screen) +{ + char buffer[256]; + Atom selection_atom; + + sprintf(buffer, "_XSETTINGS_S%d", screen); + selection_atom = XInternAtom (display, buffer, False); + + if (XGetSelectionOwner (display, selection_atom)) + return True; + else + return False; +} + +XSettingsManager * +xsettings_manager_new (Display *display, + int screen, + XSettingsTerminateFunc terminate, + void *cb_data) +{ + XSettingsManager *manager; + Time timestamp; + XClientMessageEvent xev; + + char buffer[256]; + + manager = malloc (sizeof *manager); + if (!manager) + return NULL; + + manager->display = display; + manager->screen = screen; + + sprintf(buffer, "_XSETTINGS_S%d", screen); + manager->selection_atom = XInternAtom (display, buffer, False); + manager->xsettings_atom = XInternAtom (display, "_XSETTINGS_SETTINGS", False); + manager->manager_atom = XInternAtom (display, "MANAGER", False); + + manager->terminate = terminate; + manager->cb_data = cb_data; + + manager->settings = NULL; + manager->serial = 0; + + manager->window = XCreateSimpleWindow (display, + RootWindow (display, screen), + 0, 0, 10, 10, 0, + WhitePixel (display, screen), + WhitePixel (display, screen)); + + XSelectInput (display, manager->window, PropertyChangeMask); + timestamp = get_server_time (display, manager->window); + + XSetSelectionOwner (display, manager->selection_atom, + manager->window, timestamp); + + /* Check to see if we managed to claim the selection. If not, + * we treat it as if we got it then immediately lost it + */ + + if (XGetSelectionOwner (display, manager->selection_atom) == + manager->window) + { + xev.type = ClientMessage; + xev.window = RootWindow (display, screen); + xev.message_type = manager->manager_atom; + xev.format = 32; + xev.data.l[0] = timestamp; + xev.data.l[1] = manager->selection_atom; + xev.data.l[2] = manager->window; + xev.data.l[3] = 0; /* manager specific data */ + xev.data.l[4] = 0; /* manager specific data */ + + XSendEvent (display, RootWindow (display, screen), + False, StructureNotifyMask, (XEvent *)&xev); + } + else + { + manager->terminate (manager->cb_data); + } + + return manager; +} + +void +xsettings_manager_destroy (XSettingsManager *manager) +{ + XDestroyWindow (manager->display, manager->window); + + xsettings_list_free (manager->settings); + free (manager); +} + +Window +xsettings_manager_get_window (XSettingsManager *manager) +{ + return manager->window; +} + +Bool +xsettings_manager_process_event (XSettingsManager *manager, + XEvent *xev) +{ + if (xev->xany.window == manager->window && + xev->xany.type == SelectionClear && + xev->xselectionclear.selection == manager->selection_atom) + { + manager->terminate (manager->cb_data); + return True; + } + + return False; +} + +XSettingsResult +xsettings_manager_set_setting (XSettingsManager *manager, + XSettingsSetting *setting) +{ + XSettingsSetting *old_setting = xsettings_list_lookup (settings, setting->name); + XSettingsSetting *new_setting; + XSettingsResult result; + + if (old_setting) + { + if (xsettings_setting_equal (old_setting, setting)) + return XSETTINGS_SUCCESS; + + xsettings_list_delete (&settings, setting->name); + } + + new_setting = xsettings_setting_copy (setting); + if (!new_setting) + return XSETTINGS_NO_MEM; + + new_setting->last_change_serial = manager->serial; + + result = xsettings_list_insert (&settings, new_setting); + + if (result != XSETTINGS_SUCCESS) + xsettings_setting_free (new_setting); + + return result; +} + +XSettingsResult +xsettings_manager_set_int (XSettingsManager *manager, + const char *name, + int value) +{ + XSettingsSetting setting; + + setting.name = (char *)name; + setting.type = XSETTINGS_TYPE_INT; + setting.data.v_int = value; + + return xsettings_manager_set_setting (manager, &setting); +} + +XSettingsResult +xsettings_manager_set_string (XSettingsManager *manager, + const char *name, + const char *value) +{ + XSettingsSetting setting; + + setting.name = (char *)name; + setting.type = XSETTINGS_TYPE_STRING; + setting.data.v_string = (char *)value; + + return xsettings_manager_set_setting (manager, &setting); +} + +XSettingsResult +xsettings_manager_set_color (XSettingsManager *manager, + const char *name, + XSettingsColor *value) +{ + XSettingsSetting setting; + + setting.name = (char *)name; + setting.type = XSETTINGS_TYPE_COLOR; + setting.data.v_color = *value; + + return xsettings_manager_set_setting (manager, &setting); +} + +size_t +setting_length (XSettingsSetting *setting) +{ + size_t length = 8; /* type + pad + name-len + last-change-serial */ + length += XSETTINGS_PAD (strlen (setting->name), 4); + + switch (setting->type) + { + case XSETTINGS_TYPE_INT: + length += 4; + break; + case XSETTINGS_TYPE_STRING: + length += 4 + XSETTINGS_PAD (strlen (setting->data.v_string), 4); + break; + case XSETTINGS_TYPE_COLOR: + length += 8; + break; + } + + return length; +} + +void +setting_store (XSettingsSetting *setting, + XSettingsBuffer *buffer) +{ + size_t string_len; + size_t length; + + *(buffer->pos++) = setting->type; + *(buffer->pos++) = 0; + + string_len = strlen (setting->name); + *(CARD16 *)(buffer->pos) = string_len; + buffer->pos += 2; + + length = XSETTINGS_PAD (string_len, 4); + memcpy (buffer->pos, setting->name, string_len); + length -= string_len; + buffer->pos += string_len; + + while (length > 0) + { + *(buffer->pos++) = 0; + length--; + } + + *(CARD32 *)(buffer->pos) = setting->last_change_serial; + buffer->pos += 4; + + switch (setting->type) + { + case XSETTINGS_TYPE_INT: + *(CARD32 *)(buffer->pos) = setting->data.v_int; + buffer->pos += 4; + break; + case XSETTINGS_TYPE_STRING: + string_len = strlen (setting->data.v_string); + *(CARD32 *)(buffer->pos) = string_len; + buffer->pos += 4; + + length = XSETTINGS_PAD (string_len, 4); + memcpy (buffer->pos, setting->data.v_string, string_len); + length -= string_len; + buffer->pos += string_len; + + while (length > 0) + { + *(buffer->pos++) = 0; + length--; + } + break; + case XSETTINGS_TYPE_COLOR: + *(CARD16 *)(buffer->pos) = setting->data.v_color.red; + *(CARD16 *)(buffer->pos + 2) = setting->data.v_color.green; + *(CARD16 *)(buffer->pos + 4) = setting->data.v_color.blue; + *(CARD16 *)(buffer->pos + 6) = setting->data.v_color.alpha; + buffer->pos += 8; + break; + } +} + +XSettingsResult +xsettings_manager_notify (XSettingsManager *manager) +{ + XSettingsBuffer buffer; + XSettingsList *iter; + int n_settings = 0; + + buffer.len = 12; /* byte-order + pad + SERIAL + N_SETTINGS */ + + iter = settings; + while (iter) + { + buffer.len += setting_length (iter->setting); + n_settings++; + iter = iter->next; + } + + buffer.data = buffer.pos = malloc (buffer.len); + if (!buffer.data) + return XSETTINGS_NO_MEM; + + *buffer.pos = xsettings_byte_order (); + + buffer.pos += 4; + *(CARD32 *)buffer.pos = manager->serial++; + buffer.pos += 4; + *(CARD32 *)buffer.pos = n_settings; + buffer.pos += 4; + + iter = settings; + while (iter) + { + setting_store (iter->setting, &buffer); + iter = iter->next; + } + + XChangeProperty (manager->display, manager->window, + manager->xsettings_atom, manager->xsettings_atom, + 8, PropModeReplace, buffer.data, buffer.len); + + free (buffer.data); + + return XSETTINGS_SUCCESS; +} + diff --git a/xsettings/xsettings-manager.h b/xsettings/xsettings-manager.h new file mode 100644 index 0000000..cf73858 --- /dev/null +++ b/xsettings/xsettings-manager.h @@ -0,0 +1,46 @@ +#ifndef XSETTINGS_MANAGER_H +#define XSETTINGS_MANAGER_H + +#include <X11/Xlib.h> +#include "xsettings-common.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _XSettingsManager XSettingsManager; + +typedef void (*XSettingsTerminateFunc) (void *cb_data); + +Bool xsettings_manager_check_running (Display *display, + int screen); + +XSettingsManager *xsettings_manager_new (Display *display, + int screen, + XSettingsTerminateFunc terminate, + void *cb_data); + +void xsettings_manager_destroy (XSettingsManager *manager); +Window xsettings_manager_get_window (XSettingsManager *manager); +Bool xsettings_manager_process_event (XSettingsManager *manager, + XEvent *xev); + +XSettingsResult xsettings_manager_set_setting (XSettingsManager *manager, + XSettingsSetting *setting); +XSettingsResult xsettings_manager_set_int (XSettingsManager *manager, + const char *name, + int value); +XSettingsResult xsettings_manager_set_string (XSettingsManager *manager, + const char *name, + const char *value); +XSettingsResult xsettings_manager_set_color (XSettingsManager *manager, + const char *name, + XSettingsColor *value); +XSettingsResult xsettings_manager_notify (XSettingsManager *manager); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* XSETTINGS_MANAGER_H */ |