aboutsummaryrefslogtreecommitdiffstats
path: root/applets/launcher/launcher.c
diff options
context:
space:
mode:
Diffstat (limited to 'applets/launcher/launcher.c')
-rw-r--r--applets/launcher/launcher.c521
1 files changed, 521 insertions, 0 deletions
diff --git a/applets/launcher/launcher.c b/applets/launcher/launcher.c
new file mode 100644
index 0000000..7be0f8b
--- /dev/null
+++ b/applets/launcher/launcher.c
@@ -0,0 +1,521 @@
+/*
+ * (C) 2006 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn@openedhand.com>
+ *
+ * Licensed under the GPL v2 or greater.
+ */
+
+#include <config.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkeventbox.h>
+#include <gtk/gtkicontheme.h>
+#include <gdk/gdkx.h>
+#include <string.h>
+#include <unistd.h>
+#include <matchbox-panel/mb-panel.h>
+
+#ifdef USE_LIBSN
+ #define SN_API_NOT_YET_FROZEN 1
+ #include <libsn/sn.h>
+#endif
+
+typedef struct {
+ GtkImage *image;
+
+ char *icon;
+ int icon_size;
+
+ GtkIconTheme *icon_theme;
+ guint icon_theme_changed_id;
+
+ gboolean button_down;
+
+ gboolean use_sn;
+
+ char *name;
+ char **argv;
+} LauncherData;
+
+static void
+launcher_data_free (LauncherData *data)
+{
+ g_free (data->icon);
+
+ g_signal_handler_disconnect (data->icon_theme,
+ data->icon_theme_changed_id);
+
+ g_free (data->name);
+ g_strfreev (data->argv);
+
+ g_slice_free (LauncherData, data);
+}
+
+/* Strips extension off filename */
+static char *
+strip_extension (const char *file)
+{
+ char *stripped, *p;
+
+ stripped = g_strdup (file);
+
+ p = strrchr (stripped, '.');
+ if (p &&
+ (!strcmp (p, ".png") ||
+ !strcmp (p, ".svg") ||
+ !strcmp (p, ".xpm")))
+ *p = 0;
+
+ return stripped;
+}
+
+/* Find icon filename */
+/* This follows the same logic as gnome-panel. This should hopefully
+ * ensure correct behaviour. */
+static char *
+find_icon (LauncherData *data)
+{
+ GtkIconInfo *info;
+ char *icon, *stripped;
+
+ if (g_path_is_absolute (data->icon)) {
+ if (g_file_test (data->icon, G_FILE_TEST_EXISTS))
+ return g_strdup (data->icon);
+ else
+ icon = g_path_get_basename (data->icon);
+ } else
+ icon = data->icon;
+
+ stripped = strip_extension (icon);
+
+ if (icon != data->icon)
+ g_free (icon);
+
+ info = gtk_icon_theme_lookup_icon (data->icon_theme,
+ stripped,
+ data->icon_size,
+ 0);
+
+ g_free (stripped);
+
+ if (info) {
+ char *file;
+
+ file = g_strdup (gtk_icon_info_get_filename (info));
+
+ gtk_icon_info_free (info);
+
+ return file;
+ } else
+ return NULL;
+}
+
+/* Icon theme changed */
+static void
+icon_theme_changed_cb (GtkIconTheme *icon_theme,
+ LauncherData *data)
+{
+ /* Reload icon */
+ char *file;
+ GdkPixbuf *pixbuf;
+ GError *error;
+
+ file = find_icon (data);
+ if (!file) {
+ g_warning ("Icon \"%s\" not found", data->icon);
+
+ return;
+ }
+
+ error = NULL;
+ pixbuf = gdk_pixbuf_new_from_file_at_scale (file,
+ data->icon_size,
+ data->icon_size,
+ TRUE,
+ &error);
+
+ g_free (file);
+
+ if (pixbuf) {
+ gtk_image_set_from_pixbuf (data->image, pixbuf);
+
+ g_object_unref (pixbuf);
+ } else {
+ g_warning (error->message);
+
+ g_error_free (error);
+ }
+}
+
+/* Screen set or changed */
+static void
+screen_changed_cb (GtkWidget *widget,
+ GdkScreen *screen,
+ LauncherData *data)
+{
+ GtkIconTheme *new_icon_theme;
+
+ /* Get associated icon theme */
+ if (!screen)
+ screen = gdk_screen_get_default ();
+
+ new_icon_theme = gtk_icon_theme_get_for_screen (screen);
+ if (data->icon_theme == new_icon_theme)
+ return;
+
+ if (data->icon_theme_changed_id) {
+ g_signal_handler_disconnect (data->icon_theme,
+ data->icon_theme_changed_id);
+ }
+
+ data->icon_theme = new_icon_theme;
+
+ data->icon_theme_changed_id =
+ g_signal_connect (data->icon_theme,
+ "changed",
+ G_CALLBACK (icon_theme_changed_cb),
+ data);
+
+ icon_theme_changed_cb (data->icon_theme, data);
+}
+
+/* Convert command line to argv array, stripping % conversions on the way */
+#define MAX_ARGS 255
+
+static char **
+exec_to_argv (const char *exec)
+{
+ const char *p;
+ char *buf, *bufp, **argv;
+ int nargs;
+ gboolean escape, single_quote, double_quote;
+
+ argv = g_new (char *, MAX_ARGS + 1);
+ buf = g_alloca (strlen (exec) + 1);
+ bufp = buf;
+ nargs = 0;
+ escape = single_quote = double_quote = FALSE;
+
+ for (p = exec; *p; p++) {
+ if (escape) {
+ *bufp++ = *p;
+
+ escape = FALSE;
+ } else {
+ switch (*p) {
+ case '\\':
+ escape = TRUE;
+
+ break;
+ case '%':
+ /* Strip '%' conversions */
+ if (p[1] && p[1] == '%')
+ *bufp++ = *p;
+
+ p++;
+
+ break;
+ case '\'':
+ if (double_quote)
+ *bufp++ = *p;
+ else
+ single_quote = !single_quote;
+
+ break;
+ case '\"':
+ if (single_quote)
+ *bufp++ = *p;
+ else
+ double_quote = !double_quote;
+
+ break;
+ case ' ':
+ if (single_quote || double_quote)
+ *bufp++ = *p;
+ else {
+ *bufp = 0;
+
+ if (nargs < MAX_ARGS)
+ argv[nargs++] = g_strdup (buf);
+
+ bufp = buf;
+ }
+
+ break;
+ default:
+ *bufp++ = *p;
+ break;
+ }
+ }
+ }
+
+ if (bufp != buf) {
+ *bufp = 0;
+
+ if (nargs < MAX_ARGS)
+ argv[nargs++] = g_strdup (buf);
+ }
+
+ argv[nargs] = NULL;
+
+ return argv;
+}
+
+/* Button pressed on event box */
+static gboolean
+button_press_event_cb (GtkWidget *event_box,
+ GdkEventButton *event,
+ LauncherData *data)
+{
+ if (event->button != 1)
+ return TRUE;
+
+ data->button_down = TRUE;
+
+ return TRUE;
+}
+
+/* Button released on event box */
+static gboolean
+button_release_event_cb (GtkWidget *event_box,
+ GdkEventButton *event,
+ LauncherData *data)
+{
+ int x, y;
+ pid_t child_pid = 0;
+#ifdef USE_LIBSN
+ SnLauncherContext *context;
+#endif
+
+ if (event->button != 1 || !data->button_down)
+ return TRUE;
+
+ data->button_down = FALSE;
+
+ /* Only process if the button was released inside the button */
+ gtk_widget_translate_coordinates (event_box,
+ event_box->parent,
+ event->x,
+ event->y,
+ &x,
+ &y);
+
+ if (x < event_box->allocation.x ||
+ x > event_box->allocation.x + event_box->allocation.width ||
+ y < event_box->allocation.y ||
+ y > event_box->allocation.y + event_box->allocation.height)
+ return TRUE;
+
+#ifdef USE_LIBSN
+ context = NULL;
+
+ if (data->use_sn) {
+ SnDisplay *sn_dpy;
+ Display *display;
+ int screen;
+
+ display = gdk_x11_display_get_xdisplay
+ (gtk_widget_get_display (GTK_WIDGET (event_box)));
+ sn_dpy = sn_display_new (display, NULL, NULL);
+
+ screen = gdk_screen_get_number
+ (gtk_widget_get_screen (GTK_WIDGET (event_box)));
+ context = sn_launcher_context_new (sn_dpy, screen);
+ sn_display_unref (sn_dpy);
+
+ sn_launcher_context_set_name (context, data->name);
+ sn_launcher_context_set_binary_name (context,
+ data->argv[0]);
+
+ sn_launcher_context_initiate (context,
+ "matchbox-panel",
+ data->argv[0],
+ CurrentTime);
+ }
+#endif
+
+ switch ((child_pid = fork ())) {
+ case -1:
+ g_warning ("Fork failed");
+ break;
+ case 0:
+#ifdef USE_LIBSN
+ if (data->use_sn)
+ sn_launcher_context_setup_child_process (context);
+#endif
+ execvp (data->argv[0], data->argv);
+
+ g_warning ("Failed to execvp() %s", data->argv[0]);
+ _exit (1);
+
+ break;
+ }
+
+#ifdef USE_LIBSN
+ if (data->use_sn)
+ sn_launcher_context_unref (context);
+#endif
+
+ return TRUE;
+}
+
+/* Someone took or released the GTK+ grab */
+static void
+grab_notify_cb (GtkWidget *widget,
+ gboolean was_grabbed,
+ LauncherData *data)
+{
+ if (!was_grabbed) {
+ /* It wasn't us. Reset press state */
+ data->button_down = FALSE;
+ }
+}
+
+G_MODULE_EXPORT GtkWidget *
+mb_panel_applet_create (const char *id,
+ int panel_width,
+ int panel_height)
+{
+ char *filename;
+ GKeyFile *key_file;
+ GtkWidget *event_box, *image;
+ GError *error;
+ char *icon, *exec, *name;
+ gboolean use_sn;
+ LauncherData *data;
+
+ /* Try to find a .desktop file for @id */
+ key_file = g_key_file_new ();
+
+ filename = g_strdup_printf ("applications/%s.desktop", id);
+ error = NULL;
+ if (!g_key_file_load_from_data_dirs (key_file,
+ filename,
+ NULL,
+ G_KEY_FILE_NONE,
+ &error)) {
+ /* An error occured */
+ g_warning ("%s", error->message);
+
+ g_error_free (error);
+
+ g_free (filename);
+ g_key_file_free (key_file);
+
+ return NULL;
+ }
+
+ g_free (filename);
+
+ /* Found and opened keyfile. Read the values we want. */
+
+ /* Icon */
+ error = NULL;
+ icon = g_key_file_get_string (key_file,
+ "Desktop Entry",
+ "Icon",
+ &error);
+ icon = icon ? g_strstrip (icon) : NULL;
+ if (!icon || icon[0] == 0) {
+ if (error) {
+ g_warning ("%s", error->message);
+
+ g_error_free (error);
+ } else
+ g_warning ("No icon specified");
+
+ g_key_file_free (key_file);
+
+ return NULL;
+ }
+
+ /* Exec */
+ error = NULL;
+ exec = g_key_file_get_string (key_file,
+ "Desktop Entry",
+ "Exec",
+ &error);
+ exec = exec ? g_strstrip (exec) : NULL;
+ if (!exec || exec[0] == 0) {
+ if (error) {
+ g_warning ("%s", error->message);
+
+ g_error_free (error);
+ } else
+ g_warning ("No exec specified");
+
+ g_free (icon);
+ g_key_file_free (key_file);
+
+ return NULL;
+ }
+
+ /* Name */
+ name = g_key_file_get_string (key_file,
+ "Desktop Entry",
+ "Name",
+ NULL);
+
+ /* StartupNotify */
+ use_sn = g_key_file_get_boolean (key_file,
+ "Desktop Entry",
+ "StartupNotify",
+ NULL);
+
+ /* Close key file */
+ g_key_file_free (key_file);
+
+ /* Create widgets */
+ event_box = gtk_event_box_new ();
+
+ gtk_widget_set_name (event_box, "MatchboxPanelLauncher");
+
+ image = gtk_image_new ();
+
+ gtk_container_add (GTK_CONTAINER (event_box), image);
+
+ /* Set up data structure */
+ data = g_slice_new (LauncherData);
+
+ data->image = GTK_IMAGE (image);
+
+ data->icon = icon;
+ data->icon_size = MIN (panel_width, panel_height);
+
+ data->button_down = FALSE;
+
+ data->use_sn = use_sn;
+
+ data->name = name;
+
+ data->argv = exec_to_argv (exec);
+ g_free (exec);
+
+ g_object_weak_ref (G_OBJECT (event_box),
+ (GWeakNotify) launcher_data_free,
+ data);
+
+ /* Listen to events */
+ g_signal_connect (event_box,
+ "button-press-event",
+ G_CALLBACK (button_press_event_cb),
+ data);
+ g_signal_connect (event_box,
+ "button-release-event",
+ G_CALLBACK (button_release_event_cb),
+ data);
+ g_signal_connect (event_box,
+ "grab-notify",
+ G_CALLBACK (grab_notify_cb),
+ data);
+
+ g_signal_connect (image,
+ "screen-changed",
+ G_CALLBACK (screen_changed_cb),
+ data);
+
+ /* Show! */
+ gtk_widget_show_all (event_box);
+
+ return event_box;
+};