/* -*- mode: c; c-basic-offset: 2 -*- * * This is kiba, a useless but fun GNOME dock. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "akamaru.h" #define KIBA_GCONF_PATH "/apps/kiba" #define PIXMAP_PATH "/usr/share/pixmaps" #define ICON_SIZE 48 GQuark kiba_error_quark (void) { static GQuark q = 0; if (q == 0) q = g_quark_from_static_string ("kiba-quark"); return q; } #define KIBA_ERROR kiba_error_quark () typedef struct _KibaDock KibaDock; typedef struct _KibaDockClass KibaDockClass; typedef struct _KibaLauncher KibaLauncher; typedef enum { KIBA_DOCK_POSITION_LEFT_EDGE_TOP, KIBA_DOCK_POSITION_LEFT_EDGE_CENTER, KIBA_DOCK_POSITION_LEFT_EDGE_BOTTOM, KIBA_DOCK_POSITION_RIGHT_EDGE_TOP, KIBA_DOCK_POSITION_RIGHT_EDGE_CENTER, KIBA_DOCK_POSITION_RIGHT_EDGE_BOTTOM, KIBA_DOCK_POSITION_TOP_EDGE_LEFT, KIBA_DOCK_POSITION_TOP_EDGE_CENTER, KIBA_DOCK_POSITION_TOP_EDGE_RIGHT, KIBA_DOCK_POSITION_BOTTOM_EDGE_LEFT, KIBA_DOCK_POSITION_BOTTOM_EDGE_CENTER, KIBA_DOCK_POSITION_BOTTOM_EDGE_RIGHT } KibaDockPosition; struct _KibaDock { GtkWidget widget; KibaDockPosition position; Model model; Anchor *anchor; Anchor *mouse_anchor; GdkRectangle geometry; int spacing; int num_launchers; GList *launchers; int drag_offset_x; int drag_offset_y; int drag_distance; KibaLauncher *dragging_launcher; int dx, dy; gboolean dragging_dock; GConfClient *gconf_client; }; struct _KibaDockClass { GtkWidgetClass parent_class; }; struct _KibaLauncher { GdkWindow *window; GdkPixbuf *pixbuf; KibaDock *dock; Object *object; char *name; char *exec; char *comment; char *icon; gboolean prelight; }; #define KIBA_TYPE_DOCK (kiba_dock_get_type ()) #define KIBA_DOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), KIBA_TYPE_DOCK, KibaDock)) #define KIBA_DOCK_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), KIBA_DOCK, KibaDockClass)) #define KIBA_IS_DOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), KIBA_TYPE_DOCK)) #define KIBA_IS_DOCK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), KIBA_TYPE_DOCK)) #define KIBA_DOCK_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS ((obj), KIBA_TYPE_DOCK, KibaDockClass)) static void kiba_dock_realize (GtkWidget *widget); static void kiba_dock_show (GtkWidget *widget); static void kiba_dock_map (GtkWidget *widget); static gboolean kiba_dock_expose_event (GtkWidget *clock, GdkEventExpose *event); static gboolean kiba_dock_button_press_event (GtkWidget *widget, GdkEventButton *event); static gboolean kiba_dock_button_release_event (GtkWidget *widget, GdkEventButton *event); static gboolean kiba_dock_motion_notify_event (GtkWidget *widget, GdkEventMotion *event); static gboolean kiba_dock_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event); static gboolean kiba_dock_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event); static void kiba_dock_drag_data_received (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time); static KibaLauncher *kiba_launcher_new (KibaDock *dock, const char *gconf_path); static void kiba_launcher_create_window (KibaLauncher *launcher); static void kiba_dock_add_launcher (KibaDock *dock, KibaLauncher *launcher, int x, int y); static gint timeout_callback (gpointer data); G_DEFINE_TYPE (KibaDock, kiba_dock, GTK_TYPE_WIDGET) typedef struct { const char *name; GtkOrientation orientation; int halignment; int valignment; GdkPoint anchor; } KibaDockLayoutData; static const KibaDockLayoutData kiba_dock_layout_data[] = { { "left-edge-top", GTK_ORIENTATION_VERTICAL, 0, 0, { 0, 1 } }, { "left-edge-center", GTK_ORIENTATION_VERTICAL, 0, 1, { 0, 2 } }, { "left-edge-bottom", GTK_ORIENTATION_VERTICAL, 0, 2, { 0, 3 } }, { "right-edge-top", GTK_ORIENTATION_VERTICAL, 2, 0, { 4, 1 } }, { "right-edge-center", GTK_ORIENTATION_VERTICAL, 2, 1, { 4, 2 } }, { "right-edge-bottom", GTK_ORIENTATION_VERTICAL, 2, 2, { 4, 3 } }, { "top-edge-left", GTK_ORIENTATION_HORIZONTAL, 0, 0, { 1, 0 } }, { "top-edge-center", GTK_ORIENTATION_HORIZONTAL, 1, 0, { 2, 0 } }, { "top-edge-right", GTK_ORIENTATION_HORIZONTAL, 2, 0, { 3, 0 } }, { "bottom-edge-left", GTK_ORIENTATION_HORIZONTAL, 0, 2, { 1, 4 } }, { "bottom-edge-center", GTK_ORIENTATION_HORIZONTAL, 1, 2, { 2, 4 } }, { "bottom-edge-right", GTK_ORIENTATION_HORIZONTAL, 2, 2, { 3, 4 } } }; static void kiba_dock_class_init (KibaDockClass *class) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); widget_class->realize = kiba_dock_realize; widget_class->show = kiba_dock_show; widget_class->map = kiba_dock_map; widget_class->expose_event = kiba_dock_expose_event; widget_class->drag_data_received = kiba_dock_drag_data_received; widget_class->button_press_event = kiba_dock_button_press_event; widget_class->button_release_event = kiba_dock_button_release_event; widget_class->motion_notify_event = kiba_dock_motion_notify_event; widget_class->enter_notify_event = kiba_dock_enter_notify_event; widget_class->leave_notify_event = kiba_dock_leave_notify_event; } static void kiba_dock_init (KibaDock *dock) { GdkScreen *screen; Object *object; static const GtkTargetEntry targets[] = { { "text/plain", 0, 0 } }; GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (dock), GTK_NO_WINDOW); GTK_WIDGET_SET_FLAGS (GTK_WIDGET (dock), GTK_TOPLEVEL); GTK_PRIVATE_SET_FLAG (GTK_WIDGET (dock), GTK_ANCHORED); screen = gdk_screen_get_default (); gdk_screen_get_monitor_geometry(screen, 0, &dock->geometry); dock->position = KIBA_DOCK_POSITION_BOTTOM_EDGE_CENTER; dock->spacing = ICON_SIZE; model_init (&dock->model); dock->model.constrain_iterations = 1; model_add_enclosing_rectangle (&dock->model, ICON_SIZE / 2, ICON_SIZE / 2, dock->geometry.width - ICON_SIZE / 2, dock->geometry.height - ICON_SIZE / 2); object = model_add_object (&dock->model, 0, 0, 0, NULL); dock->anchor = model_add_anchor (&dock->model, object, 0, 0 ); dock->mouse_anchor = model_add_anchor (&dock->model, NULL, 0, 0); gtk_drag_dest_set (GTK_WIDGET (dock), GTK_DEST_DEFAULT_ALL, targets, G_N_ELEMENTS (targets), GDK_ACTION_MOVE | GDK_ACTION_COPY); gtk_drag_dest_add_uri_targets (GTK_WIDGET (dock)); g_timeout_add (20, timeout_callback, dock); } GtkWidget* kiba_dock_new (GConfClient *gconf_client) { GtkWidget *dock = g_object_new (KIBA_TYPE_DOCK, NULL); KIBA_DOCK (dock)->gconf_client = gconf_client; return dock; } static KibaLauncher * kiba_dock_get_launcher_for_window (KibaDock *dock, GdkWindow *window) { GList *l; for (l = dock->launchers; l != NULL; l = l->next) { KibaLauncher *launcher = l->data; if (launcher->window == window) return launcher; } return NULL; } static void kiba_dock_paint (KibaDock *dock) { GtkWidget *widget = GTK_WIDGET (dock); cairo_pattern_t *gradient; cairo_t *cr; const int margin = 1, radius = 5; int x0, y0, x1, y1; const KibaDockLayoutData *d; d = &kiba_dock_layout_data[dock->position]; x0 = (d->halignment == 0 ? -radius : margin); y0 = (d->valignment == 0 ? -radius : margin);; x1 = widget->allocation.width - (d->halignment == 2 ? -radius : margin); y1 = widget->allocation.height - (d->valignment == 2 ? -radius : margin); cr = gdk_cairo_create (widget->window); cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); cairo_paint (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_arc (cr, x0 + radius, y1 - radius, radius, M_PI / 2, M_PI); cairo_line_to (cr, x0, y0 + radius); cairo_arc (cr, x0 + radius, y0 + radius, radius, M_PI, 3 * M_PI / 2); cairo_line_to (cr, x1 - radius, y0); cairo_arc (cr, x1 - radius, y0 + radius, radius, 3 * M_PI / 2, 2 * M_PI); cairo_line_to (cr, x1, y1 - radius); cairo_arc (cr, x1 - radius, y1 - radius, radius, 0, M_PI / 2); cairo_close_path (cr); gradient = cairo_pattern_create_linear ((x0 + x1) / 2 - 1, y0, (x0 + x1) / 2 + 1, y1); cairo_pattern_add_color_stop_rgba (gradient, 0, 1, 1, 1, 0.4); cairo_pattern_add_color_stop_rgba (gradient, 1, 1, 1, 1, 0.6); cairo_set_source (cr, gradient); cairo_fill_preserve (cr); cairo_set_source_rgba (cr, 1, 1, 1, 1); cairo_set_line_width (cr, 1); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_stroke (cr); cairo_destroy (cr); } static void kiba_launcher_paint (KibaLauncher *launcher) { cairo_t *cr; cr = gdk_cairo_create (launcher->window); gdk_cairo_set_source_pixbuf (cr, launcher->pixbuf, 0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); if (launcher->prelight) { cairo_set_source_rgba (cr, 1, 1, 1, 0.3); cairo_set_operator (cr, CAIRO_OPERATOR_ATOP); cairo_paint (cr); } cairo_destroy (cr); } static gboolean kiba_dock_expose_event (GtkWidget *widget, GdkEventExpose *event) { KibaDock *dock = KIBA_DOCK (widget); KibaLauncher *launcher; if (event->window == widget->window) kiba_dock_paint (dock); else { launcher = kiba_dock_get_launcher_for_window (dock, event->window); if (launcher != NULL) kiba_launcher_paint (launcher); } return TRUE; } static void kiba_dock_realize (GtkWidget *widget) { KibaDock *dock = KIBA_DOCK (widget); GdkScreen *screen; GdkWindowAttr attributes; gint attributes_mask; GList *l; g_return_if_fail (KIBA_IS_DOCK (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); screen = gdk_screen_get_default (); attributes.window_type = GDK_WINDOW_TEMP; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gdk_screen_get_rgba_visual (screen); attributes.colormap = gdk_screen_get_rgba_colormap (screen); attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y; if (attributes.visual != NULL && attributes.colormap != NULL) attributes_mask |= GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (gdk_screen_get_root_window (screen), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, dock); for (l = dock->launchers; l != NULL; l = l->next) { KibaLauncher *launcher = l->data; kiba_launcher_create_window (launcher); } } static void kiba_dock_layout (KibaDock *dock) { const KibaDockLayoutData *d; GtkAllocation allocation; const int cap_size = 20; d = &kiba_dock_layout_data[dock->position]; if (d->orientation == GTK_ORIENTATION_VERTICAL) { allocation.width = 2 * ICON_SIZE / 3; allocation.height = dock->num_launchers * dock->spacing + (d->valignment == 0 ? 2 * cap_size : cap_size); } else { allocation.width = dock->num_launchers * dock->spacing + (d->halignment == 0 ? 2 * cap_size : cap_size); allocation.height = 2 * ICON_SIZE / 3; } allocation.x = d->halignment * (dock->geometry.width - allocation.width) / 2; allocation.y = d->valignment * (dock->geometry.height - allocation.height) / 2; dock->anchor->x = allocation.x + allocation.width / 2; dock->anchor->y = allocation.y + allocation.height / 2; if (d->orientation == GTK_ORIENTATION_VERTICAL) { dock->model.gravity.x = (d->halignment - 1) * 50; dock->model.gravity.y = 0; } else { dock->model.gravity.x = 0; dock->model.gravity.y = (d->valignment - 1) * 50; } gtk_widget_size_allocate (GTK_WIDGET (dock), &allocation); gtk_widget_queue_draw (GTK_WIDGET (dock)); } static void kiba_dock_set_position (KibaDock *dock, KibaDockPosition position) { dock->position = position; kiba_dock_layout (dock); } static void kiba_dock_show (GtkWidget *widget) { KibaDock *dock = KIBA_DOCK (widget); GTK_WIDGET_SET_FLAGS (widget, GTK_VISIBLE); if (!GTK_WIDGET_REALIZED (widget)) { kiba_dock_layout (dock); gtk_widget_realize (widget); } gtk_widget_map (widget); } static void kiba_dock_map (GtkWidget *widget) { KibaDock *dock = KIBA_DOCK (widget); GList *l; GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED); gdk_window_show (widget->window); for (l = dock->launchers; l != NULL; l = l->next) { KibaLauncher *launcher = l->data; gdk_window_show (launcher->window); } } static gint timeout_callback (gpointer data) { KibaDock *dock = data; GList *l; int i; for (l = dock->launchers; l != NULL; l = l->next) { KibaLauncher *launcher = l->data; gdk_window_move (launcher->window, launcher->object->position.x + 0.5 - ICON_SIZE / 2, launcher->object->position.y + 0.5 - ICON_SIZE / 2); } for (i = 0; i < 6; i++) model_step (&dock->model, 0.01); return TRUE; } typedef struct { KibaDock *dock; Object *object; } AddSpacerClosure; static void add_spacer (Object *object, void *data) { AddSpacerClosure *closure = data; /* Skip the anchor object. */ if (object != closure->dock->anchor->object && object != closure->object) model_add_spacer (&closure->dock->model, closure->object, object, closure->dock->spacing); } static void kiba_dock_add_launcher (KibaDock *dock, KibaLauncher *launcher, int x, int y) { AddSpacerClosure closure; closure.dock = dock; closure.object = model_add_object (&dock->model, x, y, 12, NULL); model_add_spring (&dock->model, dock->anchor->object, closure.object, 0); model_for_each_object (&dock->model, add_spacer, &closure); dock->launchers = g_list_prepend (dock->launchers, launcher); dock->num_launchers++; launcher->object = closure.object; launcher->dock = dock; if (GTK_WIDGET_REALIZED (GTK_WIDGET (dock))) kiba_launcher_create_window (launcher); if (GTK_WIDGET_MAPPED (GTK_WIDGET (dock))) gdk_window_show (launcher->window); kiba_dock_layout (dock); } static gboolean kiba_dock_button_press_event (GtkWidget *widget, GdkEventButton *event) { KibaDock *dock = KIBA_DOCK (widget); KibaLauncher *launcher; dock->drag_offset_x = event->x; dock->drag_offset_y = event->y; dock->drag_distance = 0; if (widget->window == event->window) { dock->dragging_dock = TRUE; return TRUE; } launcher = kiba_dock_get_launcher_for_window (dock, event->window); if (launcher == NULL) return FALSE; dock->dragging_launcher = launcher; dock->mouse_anchor->x = launcher->object->position.x; dock->mouse_anchor->y = launcher->object->position.y; dock->mouse_anchor->object = launcher->object; return TRUE; } static gboolean kiba_launcher_exec (gpointer data) { KibaLauncher *launcher = data; int argc; char **argv, **p, **q; GError *error = NULL; const KibaDockLayoutData *d; /* FIXME: Support drag and drop on launchers with %U and %F */ if (!g_shell_parse_argv (launcher->exec, &argc, &argv, &error)) { g_error ("failed to parse '%s': %s\n", launcher->exec, error->message); g_error_free (error); return FALSE; } for (p = argv, q = argv; *p != NULL; p++) { if (*p[0] == '%' || strlen (*p) == 2) switch (*p[1]) { case 'U': case 'u': case 'F': case 'f': default: g_free (*p); break; } else *q++ = *p; } *q++ = NULL; /* Bounce the icon. */ d = &kiba_dock_layout_data[launcher->dock->position]; if (d->orientation == GTK_ORIENTATION_VERTICAL) launcher->object->previous_position.x += (d->halignment - 1) * g_random_double_range (5, 10); else launcher->object->previous_position.y += (d->valignment - 1) * g_random_double_range (5, 10); g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error); if (error != NULL) g_error ("failed to launch %s: %s\n", argv[0], error->message); g_strfreev (argv); return FALSE; } static void kiba_launcher_queue_draw (KibaLauncher *launcher) { GdkRectangle invalid_rect; invalid_rect.x = 0; invalid_rect.y = 0; invalid_rect.width = gdk_pixbuf_get_width (launcher->pixbuf); invalid_rect.height = gdk_pixbuf_get_height (launcher->pixbuf); gdk_window_invalidate_rect (launcher->window, &invalid_rect, TRUE); } static gboolean kiba_dock_button_release_event (GtkWidget *widget, GdkEventButton *event) { KibaDock *dock = KIBA_DOCK (widget); if (dock->dragging_dock) { dock->dragging_dock = FALSE; return TRUE; } if (dock->dragging_launcher && dock->drag_distance <= 1) g_idle_add (kiba_launcher_exec, dock->dragging_launcher); else { dock->dragging_launcher->object->previous_position.x -= dock->dx / 2; dock->dragging_launcher->object->previous_position.y -= dock->dy / 2; } dock->dragging_launcher = NULL; dock->mouse_anchor->object = NULL; return TRUE; } static int kiba_dock_find_closest_position (KibaDock *dock, int x, int y) { int i, min_d, d, position; int dx, dy; min_d = INT_MAX; for (i = 0; i < G_N_ELEMENTS (kiba_dock_layout_data); i++) { dx = x - dock->geometry.width * kiba_dock_layout_data[i].anchor.x / 4; dy = y - dock->geometry.height * kiba_dock_layout_data[i].anchor.y / 4; d = dx * dx + dy * dy; if (d < min_d) { min_d = d; position = i; } } return position; } static gboolean kiba_dock_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { KibaDock *dock = KIBA_DOCK (widget); GdkModifierType state; int x, y, new_x, new_y, dx, dy, position; gdk_window_get_pointer (gdk_get_default_root_window(), &x, &y, &state); if (dock->dragging_dock) { position = kiba_dock_find_closest_position (dock, x, y); gconf_client_set_string (dock->gconf_client, KIBA_GCONF_PATH "/options/position", kiba_dock_layout_data[position].name, NULL); return TRUE; } new_x = x - dock->drag_offset_x + ICON_SIZE / 2; new_y = y - dock->drag_offset_y + ICON_SIZE / 2; dx = new_x - dock->mouse_anchor->x; dy = new_y - dock->mouse_anchor->y; dock->dx = dx; dock->dy = dy; dock->mouse_anchor->x = CLAMP (new_x, ICON_SIZE / 2, dock->geometry.width - ICON_SIZE /2 ); dock->mouse_anchor->y = CLAMP (new_y, ICON_SIZE / 2, dock->geometry.height - ICON_SIZE / 2); dock->drag_distance += sqrt (dx * dx + dy * dy); return TRUE; } static gboolean kiba_dock_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event) { KibaDock *dock = KIBA_DOCK (widget); KibaLauncher *launcher; launcher = kiba_dock_get_launcher_for_window (dock, event->window); if (launcher == NULL) return FALSE; launcher->prelight = TRUE; kiba_launcher_queue_draw (launcher); return TRUE; } static gboolean kiba_dock_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event) { KibaDock *dock = KIBA_DOCK (widget); KibaLauncher *launcher; launcher = kiba_dock_get_launcher_for_window (dock, event->window); if (launcher == NULL) return FALSE; launcher->prelight = FALSE; kiba_launcher_queue_draw (launcher); return TRUE; } static void parse_desktop_file (KibaLauncher *launcher, const char *uri, GError **error) { static const char file_uri_prefix[] = "file://"; static const char desktop_file_suffix[] = ".desktop"; static const char desktop_entry_group[] = "Desktop Entry"; static const struct { const char *name; size_t offset; } fields[] = { { "Icon", G_STRUCT_OFFSET (KibaLauncher, icon) }, { "Exec", G_STRUCT_OFFSET (KibaLauncher, exec) }, { "Name", G_STRUCT_OFFSET (KibaLauncher, name) }, { "Comment", G_STRUCT_OFFSET (KibaLauncher, comment) }, }; GKeyFile *file; const char *filename ; char *s; int i; if (!g_str_has_suffix (uri, desktop_file_suffix)) { g_set_error (error, KIBA_ERROR, 0, "%s is not a .desktop file", uri); return; } if (g_str_has_prefix (uri, file_uri_prefix)) filename = uri + strlen (file_uri_prefix); else filename = uri; file = g_key_file_new (); if (!g_key_file_load_from_file (file, filename, 0, error)) return; for (i = 0; i < G_N_ELEMENTS (fields); i++) { s = g_key_file_get_value (file, desktop_entry_group, fields[i].name, error); if (s == NULL) return; G_STRUCT_MEMBER (char *, launcher, fields[i].offset) = s; } } static gboolean load_icon (KibaLauncher *launcher, GError **error) { char *path; char *suffix; launcher->pixbuf = gdk_pixbuf_new_from_file_at_size (launcher->icon, 48, -1, error); if (launcher->pixbuf != NULL) return TRUE; g_clear_error (error); path = g_strdup_printf (PIXMAP_PATH "/%s", launcher->icon); launcher->pixbuf = gdk_pixbuf_new_from_file_at_size (path, 48, -1, error); g_free (path); if (launcher->pixbuf != NULL) return TRUE; g_clear_error (error); launcher->pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(), launcher->icon, 48, 0, error); if (launcher->pixbuf != NULL) return TRUE; suffix = strstr (launcher->icon, ".png"); if (suffix == NULL) return FALSE; *suffix = '\0'; g_clear_error (error); launcher->pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(), launcher->icon, 48, 0, error); return launcher->pixbuf != NULL; } static void kiba_launcher_create_window (KibaLauncher *launcher) { GdkWindowAttr attributes; gint attributes_mask; GdkScreen *screen = gdk_screen_get_default (); attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gdk_screen_get_rgba_visual (screen); attributes.colormap = gdk_screen_get_rgba_colormap (screen); attributes.window_type = GDK_WINDOW_TEMP; attributes.x = launcher->object->position.x; attributes.y = launcher->object->position.y; attributes.width = gdk_pixbuf_get_width (launcher->pixbuf); attributes.height = gdk_pixbuf_get_height (launcher->pixbuf); attributes.event_mask = GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y; if (attributes.visual != NULL && attributes.colormap != NULL) attributes_mask |= GDK_WA_VISUAL | GDK_WA_COLORMAP; launcher->window = gdk_window_new (gdk_screen_get_root_window (screen), &attributes, attributes_mask); gdk_window_set_user_data (launcher->window, launcher->dock); } static KibaLauncher * kiba_launcher_new (KibaDock *dock, const char *gconf_path) { KibaLauncher *launcher; GError *error = NULL; char *desktop_file; char *key; launcher = g_new0 (KibaLauncher, 1); key = g_strdup_printf ("%s/file", gconf_path); desktop_file = gconf_client_get_string (dock->gconf_client, key, &error); g_free (key); if (error != NULL) { printf ("error getting desktop file path for launcher: %s\n", error->message); g_error_free (error); g_free (launcher); return NULL; } parse_desktop_file (launcher, desktop_file, &error); free (desktop_file); if (error != NULL) { printf ("error parsing desktop file: %s\n", error->message); g_error_free (error); g_free (launcher); return NULL; } load_icon (launcher, &error); if (error != NULL) { printf ("error creating launcher window: %s\n", error->message); g_error_free (error); g_free (launcher); return NULL; } return launcher; } static void kiba_dock_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time) { KibaDock *dock = KIBA_DOCK (widget); GError *error = NULL; char *key; gtk_drag_finish (drag_context, TRUE, FALSE, time); g_strstrip ((char *) selection_data->data); printf ("got desktop file: %s\n", selection_data->data); key = g_strdup_printf (KIBA_GCONF_PATH "/launchers/%x/file", time); gconf_client_set_string (dock->gconf_client, key, (char *) selection_data->data, &error); if (error != NULL) { printf ("failed to set gconf key %s: %s\n", key, error->message); g_error_free (error); } if (error == NULL) { gconf_engine_associate_schema (dock->gconf_client->engine, key, "/schemas" KIBA_GCONF_PATH "/launchers/file", &error); if (error != NULL) { printf ("failed to set schema for %s: %s\n", key, error->message); g_error_free (error); } } g_free (key); } static void set_position_from_string (KibaDock *dock, const char *string) { int i; for (i = 0; i < G_N_ELEMENTS (kiba_dock_layout_data); i++) { if (strcmp (kiba_dock_layout_data[i].name, string) == 0) { kiba_dock_set_position (dock, i); return; } } printf ("invalid position\n"); } static void set_option (KibaDock *dock, GConfEntry *entry) { const char *value; char *key; int i; struct { const char *name; size_t offset; } options[] = { { "elasticity", G_STRUCT_OFFSET (Model, elasticity) }, { "friction", G_STRUCT_OFFSET (Model, friction) }, { "k", G_STRUCT_OFFSET (Model, k) } }; if (strcmp (entry->key, KIBA_GCONF_PATH "/options/position") == 0 && entry->value != NULL && entry->value->type == GCONF_VALUE_STRING) { value = gconf_value_get_string (entry->value); set_position_from_string (dock, value); } else for (i = 0; i < G_N_ELEMENTS (options); i++) { key = g_strdup_printf (KIBA_GCONF_PATH "/options/%s", options[i].name); if (strcmp (entry->key, key) == 0 && entry->value != NULL && entry->value->type == GCONF_VALUE_FLOAT) { G_STRUCT_MEMBER (double, &dock->model, options[i].offset) = gconf_value_get_float (entry->value); } g_free (key); } } static void options_changed_callback(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data) { KibaDock *dock; dock = KIBA_DOCK (user_data); set_option (dock, entry); } static void launchers_changed_callback(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data) { KibaDock *dock; KibaLauncher *launcher; char *path, *last_slash; dock = KIBA_DOCK (user_data); if (!g_str_has_suffix (entry->key, "/file")) { printf ("ignoring gconf event for %s\n", entry->key); return; } path = g_strdup (entry->key); last_slash = strrchr (path, '/'); *last_slash = '\0'; launcher = kiba_launcher_new (dock, path); g_free (path); if (launcher == NULL) return; kiba_dock_add_launcher (dock, launcher, g_random_int_range (0, dock->geometry.width), g_random_int_range (0, dock->geometry.height)); } static void init_from_gconf (KibaDock *dock, GConfClient *gconf_client, GError **error) { GSList *launchers, *options, *l; KibaLauncher *launcher; launchers = gconf_client_all_dirs (gconf_client, KIBA_GCONF_PATH "/launchers", error); if (*error != NULL) return; for (l = launchers; l != NULL; l = l->next) { char *path = l->data; launcher = kiba_launcher_new (dock, path); if (launcher == NULL) continue; kiba_dock_add_launcher (dock, launcher, g_random_int_range (0, dock->geometry.width), g_random_int_range (0, dock->geometry.height)); } options = gconf_client_all_entries (gconf_client, KIBA_GCONF_PATH "/options", error); if (*error != NULL) return; for (l = options; l != NULL; l = l->next) { GConfEntry *entry = l->data; set_option (dock, entry); } } int main (int argc, char *argv[]) { GtkWidget *dock; GConfClient *gconf_client; GError *error = NULL; gtk_init (&argc, &argv); gconf_client = gconf_client_get_default (); dock = kiba_dock_new (gconf_client); gconf_client_add_dir (gconf_client, KIBA_GCONF_PATH, GCONF_CLIENT_PRELOAD_NONE, &error); gconf_client_notify_add(gconf_client, KIBA_GCONF_PATH "/options", options_changed_callback, dock, NULL, &error); if (error != NULL) { g_error ("Failed to listen for " KIBA_GCONF_PATH "/options notifications: %s", error->message); exit (1); } gconf_client_notify_add(gconf_client, KIBA_GCONF_PATH "/launchers", launchers_changed_callback, dock, NULL, &error); if (error != NULL) { g_error ("Failed to listen for " KIBA_GCONF_PATH "/launchers notifications: %s", error->message); exit (1); } init_from_gconf (KIBA_DOCK (dock), gconf_client, &error); if (error != NULL) { g_error ("Failed to read config from gconf: %s", error->message); exit (1); } gtk_widget_show (dock); gtk_main (); return 0; }