diff options
Diffstat (limited to 'dock.c')
-rw-r--r-- | dock.c | 564 |
1 files changed, 403 insertions, 161 deletions
@@ -1,4 +1,6 @@ /* -*- mode: c; c-basic-offset: 2 -*- + * + * This is kiba, a useless but fun GNOME dock. */ #include <gtk/gtk.h> @@ -9,69 +11,72 @@ #include <string.h> #include <sys/time.h> #include <math.h> -#include <librsvg/rsvg.h> -#include <librsvg/rsvg-cairo.h> +#include <gconf/gconf-client.h> +#include <gdk-pixbuf/gdk-pixbuf.h> #include "akamaru.h" -typedef struct Closure Closure; -struct Closure { +#define KIBA_GCONF_PATH "/apps/kiba" +#define PIXMAP_PATH "/usr/share/pixmaps" + +typedef struct Dock Dock; +typedef struct Launcher Launcher; + +struct Dock { + GtkWidget *window; Model model; - int num_icons; - GdkWindow **windows; - int drag_offset_x, drag_offset_y; + int width, height; int spacing; - int height; + int num_launchers; + GList *launchers; + int drag_offset_x, drag_offset_y; + + GConfClient *gconf_client; +}; + +struct Launcher { + GdkWindow *window; + GdkPixbuf *pixbuf; + Dock *dock; + Object *object; + char *name; + char *exec; + char *comment; + char *icon; }; +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 () + static gint timeout_callback (gpointer data) { - Closure *closure = data; + Dock *dock = data; + GList *l; int i; - for (i = 0; i < closure->num_icons; i++) { - gdk_window_move (closure->windows[i], - closure->model.objects[i + 1].position.x + 0.5, - closure->model.objects[i + 1].position.y + 0.5); + for (l = dock->launchers; l != NULL; l = l->next) { + Launcher *launcher = l->data; + gdk_window_move (launcher->window, + launcher->object->position.x + 0.5, + launcher->object->position.y + 0.5); } for (i = 0; i < 6; i++) - model_step (&closure->model, 0.01); + model_step (&dock->model, 0.01); return TRUE; } -static GdkWindow * -create_window (GdkScreen *screen, int x, int y, int width, int height) -{ - GdkWindowAttr attributes; - gint attributes_mask; - - 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 = x; - attributes.y = y; - attributes.width = width; - attributes.height = height; - 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 | GDK_WA_VISUAL | GDK_WA_COLORMAP; - - return gdk_window_new (gdk_screen_get_root_window (screen), - &attributes, attributes_mask); -} - static void model_init_dock (Model *model, int num_items, int width, int height, int spacing) @@ -102,10 +107,16 @@ model_init_dock (Model *model, int num_items, model->k = 0.8; model->constrain_iterations = 1; - model->polygons = g_new (Polygon, 1); - model->num_polygons = 1; + model->polygons = g_new (Polygon, 3); + model->num_polygons = 3; + left_edge = (width - (num_items - 1) * spacing) / 2; polygon_init_enclosing_rectangle (&model->polygons[0], 0, 0, width - 50, height - 50); + polygon_init_rectangle (&model->polygons[1], + 0, height - 70, left_edge, height); + polygon_init_rectangle (&model->polygons[2], + width - left_edge + 2, height - 70, + width, height); model->anchors[0].x = width / 2; model->anchors[0].y = height - 50; @@ -121,8 +132,8 @@ model_init_dock (Model *model, int num_items, for (i = 1; i < num_objects; i++, object++) { object_init (&model->objects[i], - left_edge + (i - 1) * spread, height - 100, 10); - spring_init (spring++, &model->objects[0], object, spacing); + left_edge + (i - 1) * spread, height - 100, 12); + spring_init (spring++, &model->objects[0], object, 0); for (j = 1; j < num_objects - i; j++) { spacer_init (spacer++, object, object + j, spacing); } @@ -132,36 +143,34 @@ model_init_dock (Model *model, int num_items, static GdkFilterReturn window_event (GdkXEvent *xevent, GdkEvent *event, gpointer data) { - Closure *closure = data; + Dock *dock = data; + GList *l; GdkModifierType state; XEvent *ev = (XEvent *) xevent; - int x, y, i; - Object *object; + int x, y; switch (ev->type) { case ButtonPress: - closure->drag_offset_x = ev->xbutton.x; - closure->drag_offset_y = ev->xbutton.y; - for (i = 0; i < closure->num_icons; i++) { - if (closure->windows[i] == event->any.window) { - object = &closure->model.objects[i + 1]; - closure->model.mouse_anchor.x = object->position.x; - closure->model.mouse_anchor.y = object->position.y; - closure->model.mouse_anchor.object = object; + dock->drag_offset_x = ev->xbutton.x; + dock->drag_offset_y = ev->xbutton.y; + for (l = dock->launchers; l != NULL; l = l->next) { + Launcher *launcher = l->data; + if (launcher->window == event->any.window) { + dock->model.mouse_anchor.x = launcher->object->position.x; + dock->model.mouse_anchor.y = launcher->object->position.y; + dock->model.mouse_anchor.object = launcher->object; } } break; case ButtonRelease: - closure->model.mouse_anchor.object = NULL; + dock->model.mouse_anchor.object = NULL; break; case MotionNotify: gdk_window_get_pointer (gdk_get_default_root_window(), &x, &y, &state); - closure->model.mouse_anchor.x = x - closure->drag_offset_x; - closure->model.mouse_anchor.y = y - closure->drag_offset_y; - if (closure->model.mouse_anchor.y > closure->height) - closure->model.mouse_anchor.y = closure->height; + dock->model.mouse_anchor.x = x - dock->drag_offset_x; + dock->model.mouse_anchor.y = y - dock->drag_offset_y; break; default: @@ -171,126 +180,359 @@ window_event (GdkXEvent *xevent, GdkEvent *event, gpointer data) return GDK_FILTER_CONTINUE; } -static const char *icons[] = { - "svg/applications-office.svg", - "svg/camera-video.svg", - "svg/email.svg", - "svg/firefox-logo.svg", - "svg/gnome-dev-disc-dvdrom.svg", - "svg/gnome-terminal.svg", - "svg/help-browser.svg", - "svg/internet-group-chat.svg" -}; +static gboolean +dock_paint (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +{ + Dock *dock = user_data; + cairo_t *cr; + cairo_pattern_t *gradient; + const int hmargin = 5, vmargin = 40, radius = 5; + + cr = gdk_cairo_create (widget->window); + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_move_to (cr, hmargin, dock->height); + cairo_line_to (cr, hmargin, vmargin + radius); + cairo_arc (cr, hmargin + radius, vmargin + radius, + radius, M_PI, 3 * M_PI / 2); + cairo_line_to (cr, dock->width - hmargin - radius, vmargin); + cairo_arc (cr, dock->width - hmargin - radius, vmargin + radius, + radius, 3 * M_PI / 2, 2 * M_PI); + cairo_line_to (cr, dock->width - hmargin, dock->height); + + gradient = cairo_pattern_create_linear (dock->width / 2 - 1, vmargin, + dock->width / 2 + 1, dock->height); + 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); + + return TRUE; +} static void -create_dock (GdkScreen *screen, int num_icons, int spacing) +parse_desktop_file (Launcher *launcher, const char *uri, GError **error) { - const int padding = 20; - GdkWindow *dock; - int dock_width, dock_height; - int screen_width, screen_height; - cairo_t *cr; - cairo_pattern_t *gradient; - const int hmargin = 5, vmargin = 40, radius = 5; - - screen_width = gdk_screen_get_width (screen); - screen_height = gdk_screen_get_height (screen); - - dock_width = num_icons * spacing + 2 * padding; - dock_height = spacing + padding; - dock = create_window (screen, - (screen_width - dock_width + spacing) / 2, - screen_height - dock_height, - dock_width, dock_height); - - gdk_window_show (dock); - - cr = gdk_cairo_create (dock); - cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); - cairo_paint (cr); - - cairo_set_operator (cr, CAIRO_OPERATOR_OVER); - cairo_move_to (cr, hmargin, dock_height); - cairo_line_to (cr, hmargin, vmargin + radius); - cairo_arc (cr, hmargin + radius, vmargin + radius, - radius, M_PI, 3 * M_PI / 2); - cairo_line_to (cr, dock_width - hmargin - radius, vmargin); - cairo_arc (cr, dock_width - hmargin - radius, vmargin + radius, - radius, 3 * M_PI / 2, 2 * M_PI); - cairo_line_to (cr, dock_width - hmargin, dock_height); - - gradient = cairo_pattern_create_linear (dock_width / 2 - 1, vmargin, - dock_width / 2 + 1, dock_height); - 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 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 (Launcher, icon) }, + { "Exec", G_STRUCT_OFFSET (Launcher, exec) }, + { "Name", G_STRUCT_OFFSET (Launcher, name) }, + { "Comment", G_STRUCT_OFFSET (Launcher, 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; + } } -int main (int argc, char *argv[]) +static gboolean +load_icon (Launcher *launcher, GError **error) { - Closure closure; - GdkScreen *screen; - const int num_icons = G_N_ELEMENTS (icons); - int x, y, width, height, i; - RsvgHandle *handle; - RsvgDimensionData dimension; - cairo_t *cr; - const int spacing = 50; + char *path; + char *suffix; - gtk_init (&argc, &argv); + path = g_strdup_printf (PIXMAP_PATH "/%s", launcher->icon); + launcher->pixbuf = gdk_pixbuf_new_from_file (path, 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 +launcher_create_window (Launcher *launcher, GError **error) +{ + GdkWindowAttr attributes; + gint attributes_mask; + GdkScreen *screen; + screen = gdk_screen_get_default (); + cairo_t *cr; - rsvg_init (); + if (!load_icon (launcher, error)) + return; - screen = gdk_screen_get_default (); - width = gdk_screen_get_width (screen); - height = gdk_screen_get_height (screen); + 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; - closure.spacing = spacing; - closure.height = height - 50; - closure.num_icons = num_icons; - closure.windows = g_new (GdkWindow *, num_icons); + 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; - model_init_dock (&closure.model, num_icons, width, height, spacing); + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; - create_dock (screen, num_icons, spacing); + launcher->window = gdk_window_new (gdk_screen_get_root_window (screen), + &attributes, attributes_mask); + gdk_window_show (launcher->window); - for (i = 0; i < num_icons; i++) { + cr = gdk_cairo_create (launcher->window); + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + cairo_destroy (cr); - handle = rsvg_handle_new_from_file (icons[i], NULL); - rsvg_handle_get_dimensions (handle, &dimension); + gdk_pixbuf_render_to_drawable_alpha + (launcher->pixbuf, + launcher->window, + 0, 0, 0, 0, + gdk_pixbuf_get_width (launcher->pixbuf), + gdk_pixbuf_get_height (launcher->pixbuf), + GDK_PIXBUF_ALPHA_FULL, 0, + GDK_RGB_DITHER_NONE, 0, 0); - x = closure.model.objects[i + 1].position.x; - y = closure.model.objects[i + 1].position.y; - closure.windows[i] = - create_window (screen, x, y, dimension.width, dimension.height); + gdk_window_add_filter (launcher->window, window_event, launcher->dock); +} + +static Launcher * +launcher_new (Dock *dock, Object *object, const char *gconf_path) +{ + Launcher *launcher; + GError *error = NULL; + char *desktop_file; + char *key; + + launcher = g_new (Launcher, 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; + } - gdk_window_show (closure.windows[i]); + 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; + } + + launcher->object = object; + launcher->dock = dock; + + launcher_create_window (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; +} - cr = gdk_cairo_create (closure.windows[i]); - cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); - cairo_paint (cr); - cairo_set_operator (cr, CAIRO_OPERATOR_OVER); - rsvg_handle_render_cairo (handle, cr); - rsvg_handle_free (handle); - cairo_destroy (cr); +static void +dock_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context, + gint x, gint y, GtkSelectionData *selection_data, + guint info, guint time, gpointer data) +{ + Dock *dock = data; + 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); + } - gdk_window_add_filter (closure.windows[i], window_event, &closure); + 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_timeout_add (20, timeout_callback, &closure); + g_free (key); +} - gtk_main (); +static GtkWidget * +create_dock_window (Dock *dock) +{ + const int padding = 20; + GtkWidget *window; + GdkScreen *screen; + GdkColormap *colormap; + int screen_width, screen_height; + static const GtkTargetEntry targets[] = { + { "text/plain", 0, 0 } + }; + + screen = gdk_screen_get_default (); + + screen_width = gdk_screen_get_width (screen); + screen_height = gdk_screen_get_height (screen); + + dock->width = dock->num_launchers * dock->spacing + 2 * padding; + dock->height = dock->spacing + padding; + + window = gtk_window_new (GTK_WINDOW_POPUP); + colormap = gdk_screen_get_rgba_colormap (screen); + gtk_widget_set_colormap(GTK_WIDGET (window), colormap); + + gtk_drag_dest_set(window, + GTK_DEST_DEFAULT_ALL, + targets, G_N_ELEMENTS (targets), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + gtk_drag_dest_add_uri_targets (window); + + gtk_window_set_default_size (GTK_WINDOW (window), + dock->width, dock->height); + gtk_window_move (GTK_WINDOW (window), + (screen_width - dock->width + dock->spacing) / 2, + screen_height - dock->height); + + g_signal_connect (window, "expose-event", + G_CALLBACK (dock_paint), dock); + g_signal_connect (window, "drag-data-received", + G_CALLBACK (dock_drag_data_received), dock); + gtk_widget_set_app_paintable (window, TRUE); + gtk_widget_realize (window); + gdk_window_set_back_pixmap (window->window, NULL, FALSE); + gtk_widget_show (window); + + return window; +} + +static Dock * +dock_new (void) +{ + Dock *dock; + GSList *launchers, *l; + int num_launchers; + GError *error = NULL; + GdkScreen *screen; + int width, height, i; + Launcher *launcher; + + dock = g_new0 (Dock, 1); + + dock->gconf_client = gconf_client_get_default (); + + gconf_client_add_dir (dock->gconf_client, KIBA_GCONF_PATH, + GCONF_CLIENT_PRELOAD_NONE, NULL); + + launchers = gconf_client_all_dirs (dock->gconf_client, + KIBA_GCONF_PATH "/launchers", + &error); + if (error != NULL) { + printf ("error getting launchers: %s\n", error->message); + g_free (error->message); + } + + num_launchers = g_slist_length (launchers); + + screen = gdk_screen_get_default (); + width = gdk_screen_get_width (screen); + height = gdk_screen_get_height (screen); + + dock->spacing = 50; + dock->num_launchers = num_launchers; + dock->window = create_dock_window (dock); - rsvg_term (); + model_init_dock (&dock->model, num_launchers, width, height, dock->spacing); + + for (l = launchers, i = 0; l != NULL; l = l->next, i++) { + char *path = l->data; + launcher = launcher_new (dock, &dock->model.objects[i + 1], path); + if (launcher == NULL) + continue; + dock->launchers = g_list_prepend (dock->launchers, launcher); + } + + g_timeout_add (20, timeout_callback, dock); + + return dock; +} + +int main (int argc, char *argv[]) +{ + Dock *dock; + + gtk_init (&argc, &argv); + + dock = dock_new (); + + gtk_main (); return 0; } |