diff options
-rw-r--r-- | ChangeLog | 15 | ||||
-rw-r--r-- | applets/Makefile.am | 2 | ||||
-rw-r--r-- | applets/showdesktop/showdesktop.c | 76 | ||||
-rw-r--r-- | applets/windowselector/Makefile.am | 10 | ||||
-rw-r--r-- | applets/windowselector/windowselector.c | 456 | ||||
-rw-r--r-- | configure.ac | 1 |
6 files changed, 528 insertions, 32 deletions
@@ -1,5 +1,20 @@ 2006-08-23 Jorn Baayen <jorn@openedhand.com> + * applets/Makefile.am: + * applets/windowselector/Makefile.am: + * applets/windowselector/windowselector.c: + * configure.ac: + + Added window selector applet. Still needs work to make icons + show up. + + * applets/showdesktop/showdesktop.c: (show_desktop_applet_free), + (sync), (filter_func), (screen_changed_cb): + + Perform initial sync. + +2006-08-23 Jorn Baayen <jorn@openedhand.com> + * applets/showdesktop/showdesktop.c: (show_desktop_applet_free), (filter_func), (screen_changed_cb): diff --git a/applets/Makefile.am b/applets/Makefile.am index b3a8059..35c2edc 100644 --- a/applets/Makefile.am +++ b/applets/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = clock launcher systray showdesktop +SUBDIRS = clock launcher systray showdesktop windowselector if HAVE_LIBAPM SUBDIRS += battery diff --git a/applets/showdesktop/showdesktop.c b/applets/showdesktop/showdesktop.c index 237d9eb..d1645be 100644 --- a/applets/showdesktop/showdesktop.c +++ b/applets/showdesktop/showdesktop.c @@ -39,6 +39,46 @@ show_desktop_applet_free (ShowDesktopApplet *applet) g_slice_free (ShowDesktopApplet, applet); } +/* Sync @applet with the _NET_SHOWING_DESKTOP root window property */ +static void +sync (ShowDesktopApplet *applet) +{ + GdkDisplay *display; + Atom type; + int format, result; + gulong nitems, bytes_after, *num; + + display = gtk_widget_get_display (GTK_WIDGET (applet->button)); + + type = 0; + + gdk_error_trap_push (); + result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_WINDOW_XWINDOW + (applet->root_window), + applet->atom, + 0, + G_MAXLONG, + False, + XA_CARDINAL, + &type, + &format, + &nitems, + &bytes_after, + (gpointer) &num); + if (!gdk_error_trap_pop () && result == Success) { + if (type == XA_CARDINAL) { + applet->block_toggle = TRUE; + + gtk_toggle_button_set_active (applet->button, *num); + + applet->block_toggle = FALSE; + } + + XFree (num); + } +} + /* Something happened on the root window */ static GdkFilterReturn filter_func (GdkXEvent *xevent, @@ -51,37 +91,8 @@ filter_func (GdkXEvent *xevent, if (xev->type == PropertyNotify) { if (xev->xproperty.atom == applet->atom) { - Atom type; - int format, result; - gulong nitems, bytes_after, *num; - - type = 0; - - gdk_error_trap_push (); - result = XGetWindowProperty (xev->xproperty.display, - xev->xproperty.window, - xev->xproperty.atom, - 0, - G_MAXLONG, - False, - XA_CARDINAL, - &type, - &format, - &nitems, - &bytes_after, - (gpointer) &num); - if (!gdk_error_trap_pop () && result == Success) { - if (type == XA_CARDINAL) { - applet->block_toggle = TRUE; - - gtk_toggle_button_set_active - (applet->button, *num); - - applet->block_toggle = FALSE; - } - - XFree (num); - } + /* _NET_SHOWING_DESKTOP changed */ + sync (applet); } } @@ -121,6 +132,9 @@ screen_changed_cb (GtkWidget *button, gdk_window_add_filter (applet->root_window, (GdkFilterFunc) filter_func, applet); + + /* Sync */ + sync (applet); } /* Button toggled */ diff --git a/applets/windowselector/Makefile.am b/applets/windowselector/Makefile.am new file mode 100644 index 0000000..ff9506f --- /dev/null +++ b/applets/windowselector/Makefile.am @@ -0,0 +1,10 @@ +AM_CPPFLAGS=-DPKGDATADIR=\"$(pkgdatadir)\" -DGETTEXT_PACKAGE=\"matchbox-panel\" +AM_CFLAGS = -Wall -g $(MATCHBOX_PANEL_CFLAGS) \ + -I$(top_srcdir) -I$(top_builddir) -Werror + +appletdir = $(pkglibdir) +applet_LTLIBRARIES = libwindowselector.la + +libwindowselector_la_SOURCES = windowselector.c + +MAINTAINERCLEANFILES = Makefile.in diff --git a/applets/windowselector/windowselector.c b/applets/windowselector/windowselector.c new file mode 100644 index 0000000..906d8c9 --- /dev/null +++ b/applets/windowselector/windowselector.c @@ -0,0 +1,456 @@ +/* + * (C) 2006 OpenedHand Ltd. + * + * Author: Jorn Baayen <jorn@openedhand.com> + * + * Licensed under the GPL v2 or greater. + */ + +#include <config.h> +#include <gtk/gtkmenubar.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkimagemenuitem.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkimage.h> +#include <gtk/gtkmain.h> +#include <gdk/gdkx.h> +#include <X11/Xatom.h> +#include <glib/gi18n.h> +#include <matchbox-panel/mb-panel.h> + +typedef struct { + GtkMenuItem *menu_item; + GtkImage *image; + GtkMenu *menu; + + Atom stacking_atom; + Atom utf8_string_atom; + Atom visible_name_atom; + Atom name_atom; + Atom active_window_atom; + + GdkWindow *root_window; +} WindowSelectorApplet; + +static GdkFilterReturn +filter_func (GdkXEvent *xevent, + GdkEvent *event, + WindowSelectorApplet *applet); + +static void +window_selector_applet_free (WindowSelectorApplet *applet) +{ + if (applet->root_window) { + gdk_window_remove_filter (applet->root_window, + (GdkFilterFunc) filter_func, + applet); + } + + g_slice_free (WindowSelectorApplet, applet); +} + +/* Retrieves the UTF-8 property @atom from @window */ +static char * +get_utf8_property (WindowSelectorApplet *applet, + Window window, + Atom atom) +{ + GdkDisplay *display; + Atom type; + int format, result; + gulong nitems, bytes_after; + guchar *val; + char *ret; + + display = gtk_widget_get_display (GTK_WIDGET (applet->menu_item)); + + type = None; + val = NULL; + + gdk_error_trap_push (); + result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), + window, + atom, + 0, + G_MAXLONG, + False, + applet->utf8_string_atom, + &type, + &format, + &nitems, + &bytes_after, + (gpointer) &val); + if (gdk_error_trap_pop () || result != Success) + return NULL; + + if (type != applet->utf8_string_atom || format != 8 || nitems == 0) { + if (val) + XFree (val); + + return NULL; + } + + if (!g_utf8_validate ((char *) val, nitems, NULL)) { + g_warning ("Invalid UTF-8 in window title"); + + XFree (val); + + return NULL; + } + + ret = g_strndup ((char *) val, nitems); + + XFree (val); + + return ret; +} + +/* Retrieves the text property @atom from @window */ +static char * +get_text_property (WindowSelectorApplet *applet, + Window window, + Atom atom) +{ + GdkDisplay *display; + XTextProperty text; + char *ret, **list; + int result, count; + + display = gtk_widget_get_display (GTK_WIDGET (applet->menu_item)); + + gdk_error_trap_push (); + result = XGetTextProperty (GDK_DISPLAY_XDISPLAY (display), + window, + &text, + atom); + if (gdk_error_trap_pop () || result == 0) + return NULL; + + count = gdk_text_property_to_utf8_list + (gdk_x11_xatom_to_atom (text.encoding), + text.format, + text.value, + text.nitems, + &list); + if (count > 0) { + int i; + + ret = list[0]; + + for (i = 1; i < count; i++) + g_free (list[i]); + g_free (list); + } else + ret = NULL; + + if (text.value) + XFree (text.value); + + return ret; +} + +/* Retrieves the title for @window */ +static char * +window_get_title (WindowSelectorApplet *applet, + Window window) +{ + char *name; + + name = get_utf8_property (applet, window, applet->visible_name_atom); + if (name == NULL) + name = get_utf8_property (applet, window, applet->name_atom); + if (name == NULL) + name = get_text_property (applet, window, XA_WM_NAME); + if (name == NULL) + name = g_strdup (_("(untitled)")); + + return name; +} + +/* Window menu item activated. Activate the associated window. */ +static void +window_menu_item_activate_cb (GtkWidget *widget, + WindowSelectorApplet *applet) +{ + Window window; + Screen *screen; + GtkWidget *toplevel; + XEvent xev; + + window = GPOINTER_TO_UINT + (g_object_get_data (G_OBJECT (widget), "window")); + screen = GDK_SCREEN_XSCREEN (gtk_widget_get_screen (widget)); + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (applet->menu_item)); + + /* Send _NET_ACTIVE_WINDOW message */ + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.display = DisplayOfScreen (screen); + xev.xclient.window = window; + xev.xclient.message_type = applet->active_window_atom; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 2; + xev.xclient.data.l[1] = gtk_get_current_event_time (); + xev.xclient.data.l[2] = GDK_WINDOW_XWINDOW (toplevel->window); + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + XSendEvent (DisplayOfScreen (screen), + RootWindowOfScreen (screen), + False, + SubstructureRedirectMask, + &xev); +} + +/* Rebuild the selector menu */ +static void +rebuild_menu (WindowSelectorApplet *applet) +{ + GList *kids; + GdkDisplay *display; + Atom type; + int format, result, i; + gulong nitems, bytes_after; + Window *windows; + + /* Empty menu */ + kids = gtk_container_get_children (GTK_CONTAINER (applet->menu)); + while (kids) { + gtk_widget_destroy (kids->data); + kids = g_list_delete_link (kids, kids); + } + + /* Retrieve list of app windows from root window */ + display = gtk_widget_get_display (GTK_WIDGET (applet->menu_item)); + + type = None; + + gdk_error_trap_push (); + result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_WINDOW_XWINDOW (applet->root_window), + applet->stacking_atom, + 0, + G_MAXLONG, + False, + XA_WINDOW, + &type, + &format, + &nitems, + &bytes_after, + (gpointer) &windows); + if (gdk_error_trap_pop () || result != Success) + return; + + if (type != XA_WINDOW) { + XFree (windows); + + return; + } + + /* Load into menu */ + for (i = 0; i < nitems; i++) { + char *title; + GtkWidget *menu_item; + + title = window_get_title (applet, windows[i]); + menu_item = gtk_image_menu_item_new_with_label (title); + g_free (title); + + g_object_set_data (G_OBJECT (menu_item), + "window", + GUINT_TO_POINTER (windows[i])); + + g_signal_connect (menu_item, + "activate", + G_CALLBACK (window_menu_item_activate_cb), + applet); + + gtk_menu_shell_prepend (GTK_MENU_SHELL (applet->menu), + menu_item); + gtk_widget_show (menu_item); + } + + /* If no windows were found, insert an insensitive "No tasks" item */ + if (nitems == 0) { + GtkWidget *menu_item; + + menu_item = gtk_menu_item_new_with_label (_("No tasks")); + + gtk_widget_set_sensitive (menu_item, FALSE); + + gtk_menu_shell_prepend (GTK_MENU_SHELL (applet->menu), + menu_item); + gtk_widget_show (menu_item); + } + + /* Cleanup */ + XFree (windows); +} + +/* Menu was hidden */ +static void +menu_hide_cb (GtkMenuShell *menu_shell, + WindowSelectorApplet *applet) +{ + /* Detach menu. This will cause it be destroyed. */ + gtk_menu_item_remove_submenu (applet->menu_item); +} + +/* Button press event received on the main menu item */ +static gboolean +button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + WindowSelectorApplet *applet) +{ + GtkWidget *menu; + + /* Set up menu */ + menu = gtk_menu_new (); + applet->menu = GTK_MENU (menu); + + g_object_add_weak_pointer (G_OBJECT (menu), (gpointer) &applet->menu); + + gtk_menu_item_set_submenu (applet->menu_item, menu); + + g_signal_connect (menu, + "hide", + G_CALLBACK (menu_hide_cb), + applet); + + rebuild_menu (applet); + + /* Continue processing. The builtin default handler will eventually + * cause the menu to pop up. */ + return FALSE; +} + +/* Something happened on the root window */ +static GdkFilterReturn +filter_func (GdkXEvent *xevent, + GdkEvent *event, + WindowSelectorApplet *applet) +{ + XEvent *xev; + + xev = (XEvent *) xevent; + + if (xev->type == PropertyNotify) { + if (xev->xproperty.atom == applet->stacking_atom) { + /* _MB_APP_WINDOW_LIST_STACKING changed. + * Rebuild menu if around. */ + if (applet->menu && GTK_WIDGET_VISIBLE (applet->menu)) + rebuild_menu (applet); + } else if (xev->xproperty.atom == applet->active_window_atom) { + /* _NET_ACTIVE_WINDOW changed. + * Update "active task" icon. + * TODO */ + } + } + + return GDK_FILTER_CONTINUE; +} + +/* Screen changed */ +static void +screen_changed_cb (GtkWidget *button, + GdkScreen *old_screen, + WindowSelectorApplet *applet) +{ + GdkScreen *screen; + GdkDisplay *display; + GdkEventMask events; + + if (applet->root_window) { + gdk_window_remove_filter (applet->root_window, + (GdkFilterFunc) filter_func, + applet); + } + + screen = gtk_widget_get_screen (button); + display = gdk_screen_get_display (screen); + + /* Get atoms */ + applet->stacking_atom = + gdk_x11_get_xatom_by_name_for_display + (display, "_MB_APP_WINDOW_LIST_STACKING"); + applet->utf8_string_atom = + gdk_x11_get_xatom_by_name_for_display + (display, "UTF8_STRING"); + applet->name_atom = + gdk_x11_get_xatom_by_name_for_display + (display, "_NET_WM_NAME"); + applet->visible_name_atom = + gdk_x11_get_xatom_by_name_for_display + (display, "_NET_WM_VISIBLE_NAME"); + applet->active_window_atom = + gdk_x11_get_xatom_by_name_for_display + (display, "_NET_ACTIVE_WINDOW"); + + /* Get root window */ + applet->root_window = gdk_screen_get_root_window (screen); + + /* Watch _MB_APP_WINDOW_LIST_STACKING */ + events = gdk_window_get_events (applet->root_window); + if ((events & GDK_PROPERTY_CHANGE_MASK) == 0) { + gdk_window_set_events (applet->root_window, + events & GDK_PROPERTY_CHANGE_MASK); + } + + gdk_window_add_filter (applet->root_window, + (GdkFilterFunc) filter_func, + applet); + + /* Rebuild menu if around */ + if (applet->menu && GTK_WIDGET_VISIBLE (applet->menu)) + rebuild_menu (applet); +} + +G_MODULE_EXPORT GtkWidget * +mb_panel_applet_create (const char *id, + GtkOrientation orientation) +{ + WindowSelectorApplet *applet; + GtkWidget *menu_bar, *menu_item, *image; + + /* Create applet data structure */ + applet = g_slice_new (WindowSelectorApplet); + + applet->root_window = NULL; + applet->menu = NULL; + + /* Create menu bar */ + menu_bar = gtk_menu_bar_new (); + + gtk_widget_set_name (menu_bar, "MatchboxPanelWindowSelector"); + + g_signal_connect (menu_bar, + "screen-changed", + G_CALLBACK (screen_changed_cb), + applet); + + g_object_weak_ref (G_OBJECT (menu_bar), + (GWeakNotify) window_selector_applet_free, + applet); + + /* Create menu item */ + menu_item = gtk_menu_item_new (); + applet->menu_item = GTK_MENU_ITEM (menu_item); + + g_signal_connect (menu_item, + "button-press-event", + G_CALLBACK (button_press_event_cb), + applet); + + image = gtk_image_new (); + applet->image = GTK_IMAGE (image); + + gtk_container_add (GTK_CONTAINER (menu_item), image); + + gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item); + + /* Show! */ + gtk_widget_show_all (menu_bar); + + return menu_bar; +}; diff --git a/configure.ac b/configure.ac index 839ed8a..4d88306 100644 --- a/configure.ac +++ b/configure.ac @@ -67,6 +67,7 @@ applets/clock/Makefile applets/launcher/Makefile applets/systray/Makefile applets/showdesktop/Makefile +applets/windowselector/Makefile applets/battery/Makefile applets/battery/data/Makefile po/Makefile.in |