/* * Copyright (C) 2008-2010 Nick Schermer * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "windowmenu.h" #include "windowmenu-dialog_ui.h" #define ARROW_BUTTON_SIZE (12) #define DEFAULT_ICON_LUCENCY (50) #define DEFAULT_MAX_WIDTH_CHARS (24) #define DEFAULT_ELLIPSIZE_MODE (PANGO_ELLIPSIZE_MIDDLE) #define URGENT_FLAGS (WNCK_WINDOW_STATE_DEMANDS_ATTENTION | \ WNCK_WINDOW_STATE_URGENT) struct _WindowMenuPluginClass { XfcePanelPluginClass __parent__; }; struct _WindowMenuPlugin { XfcePanelPlugin __parent__; /* the screen we're showing */ WnckScreen *screen; /* panel widgets */ GtkWidget *button; GtkWidget *icon; /* settings */ guint button_style : 1; guint workspace_actions : 1; guint workspace_names : 1; guint urgentcy_notification : 1; guint all_workspaces : 1; /* urgent window counter */ gint urgent_windows; /* gtk style properties */ gint minimized_icon_lucency; PangoEllipsizeMode ellipsize_mode; gint max_width_chars; }; enum { PROP_0, PROP_STYLE, PROP_WORKSPACE_ACTIONS, PROP_WORKSPACE_NAMES, PROP_URGENTCY_NOTIFICATION, PROP_ALL_WORKSPACES }; enum { BUTTON_STYLE_ICON = 0, BUTTON_STYLE_ARROW }; static void window_menu_plugin_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void window_menu_plugin_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void window_menu_plugin_style_set (GtkWidget *widget, GtkStyle *previous_style); static void window_menu_plugin_screen_changed (GtkWidget *widget, GdkScreen *previous_screen); static void window_menu_plugin_construct (XfcePanelPlugin *panel_plugin); static void window_menu_plugin_free_data (XfcePanelPlugin *panel_plugin); static void window_menu_plugin_screen_position_changed (XfcePanelPlugin *panel_plugin, XfceScreenPosition screen_position); static gboolean window_menu_plugin_size_changed (XfcePanelPlugin *panel_plugin, gint size); static void window_menu_plugin_configure_plugin (XfcePanelPlugin *panel_plugin); static gboolean window_menu_plugin_remote_event (XfcePanelPlugin *panel_plugin, const gchar *name, const GValue *value); static void window_menu_plugin_active_window_changed (WnckScreen *screen, WnckWindow *previous_window, WindowMenuPlugin *plugin); static void window_menu_plugin_window_state_changed (WnckWindow *window, WnckWindowState changed_mask, WnckWindowState new_state, WindowMenuPlugin *plugin); static void window_menu_plugin_window_opened (WnckScreen *screen, WnckWindow *window, WindowMenuPlugin *plugin); static void window_menu_plugin_window_closed (WnckScreen *screen, WnckWindow *window, WindowMenuPlugin *plugin); static void window_menu_plugin_windows_disconnect (WindowMenuPlugin *plugin); static void window_menu_plugin_windows_connect (WindowMenuPlugin *plugin, gboolean traverse_windows); static void window_menu_plugin_menu (GtkWidget *button, WindowMenuPlugin *plugin); /* define the plugin */ XFCE_PANEL_DEFINE_PLUGIN_RESIDENT (WindowMenuPlugin, window_menu_plugin) static GQuark window_quark = 0; static GtkIconSize menu_icon_size = GTK_ICON_SIZE_INVALID; static void window_menu_plugin_class_init (WindowMenuPluginClass *klass) { XfcePanelPluginClass *plugin_class; GObjectClass *gobject_class; GtkWidgetClass *gtkwidget_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = window_menu_plugin_get_property; gobject_class->set_property = window_menu_plugin_set_property; gtkwidget_class = GTK_WIDGET_CLASS (klass); gtkwidget_class->style_set = window_menu_plugin_style_set; plugin_class = XFCE_PANEL_PLUGIN_CLASS (klass); plugin_class->construct = window_menu_plugin_construct; plugin_class->free_data = window_menu_plugin_free_data; plugin_class->screen_position_changed = window_menu_plugin_screen_position_changed; plugin_class->size_changed = window_menu_plugin_size_changed; plugin_class->configure_plugin = window_menu_plugin_configure_plugin; plugin_class->remote_event = window_menu_plugin_remote_event; g_object_class_install_property (gobject_class, PROP_STYLE, g_param_spec_uint ("style", NULL, NULL, BUTTON_STYLE_ICON, BUTTON_STYLE_ARROW, BUTTON_STYLE_ICON, EXO_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_WORKSPACE_ACTIONS, g_param_spec_boolean ("workspace-actions", NULL, NULL, FALSE, EXO_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_WORKSPACE_NAMES, g_param_spec_boolean ("workspace-names", NULL, NULL, TRUE, EXO_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_URGENTCY_NOTIFICATION, g_param_spec_boolean ("urgentcy-notification", NULL, NULL, TRUE, EXO_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ALL_WORKSPACES, g_param_spec_boolean ("all-workspaces", NULL, NULL, TRUE, EXO_PARAM_READWRITE)); gtk_widget_class_install_style_property (gtkwidget_class, g_param_spec_int ("minimized-icon-lucency", NULL, "Lucent percentage of minimized icons", 0, 100, DEFAULT_ICON_LUCENCY, EXO_PARAM_READABLE)); gtk_widget_class_install_style_property (gtkwidget_class, g_param_spec_enum ("ellipsize-mode", NULL, "The ellipsize mode used for the menu label", PANGO_TYPE_ELLIPSIZE_MODE, DEFAULT_ELLIPSIZE_MODE, EXO_PARAM_READABLE)); gtk_widget_class_install_style_property (gtkwidget_class, g_param_spec_int ("max-width-chars", NULL, "Maximum length of window/workspace name", 1, G_MAXINT, DEFAULT_MAX_WIDTH_CHARS, EXO_PARAM_READABLE)); window_quark = g_quark_from_static_string ("window-list-window-quark"); menu_icon_size = gtk_icon_size_from_name ("panel-window-menu"); if (menu_icon_size == GTK_ICON_SIZE_INVALID) menu_icon_size = gtk_icon_size_register ("panel-window-menu", 16, 16); } static void window_menu_plugin_init (WindowMenuPlugin *plugin) { plugin->button_style = BUTTON_STYLE_ICON; plugin->workspace_actions = FALSE; plugin->workspace_names = TRUE; plugin->urgentcy_notification = TRUE; plugin->all_workspaces = TRUE; plugin->urgent_windows = 0; plugin->minimized_icon_lucency = DEFAULT_ICON_LUCENCY; plugin->ellipsize_mode = DEFAULT_ELLIPSIZE_MODE; plugin->max_width_chars = DEFAULT_MAX_WIDTH_CHARS; /* create the widgets */ plugin->button = xfce_arrow_button_new (GTK_ARROW_NONE); xfce_panel_plugin_add_action_widget (XFCE_PANEL_PLUGIN (plugin), plugin->button); gtk_container_add (GTK_CONTAINER (plugin), plugin->button); gtk_button_set_relief (GTK_BUTTON (plugin->button), GTK_RELIEF_NONE); gtk_widget_set_name (plugin->button, "windowmenu-button"); g_signal_connect (G_OBJECT (plugin->button), "toggled", G_CALLBACK (window_menu_plugin_menu), plugin); plugin->icon = xfce_panel_image_new_from_source ("user-desktop"); gtk_container_add (GTK_CONTAINER (plugin->button), plugin->icon); gtk_widget_show (plugin->icon); } static void window_menu_plugin_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (object); switch (prop_id) { case PROP_STYLE: g_value_set_uint (value, plugin->button_style); break; case PROP_WORKSPACE_ACTIONS: g_value_set_boolean (value, plugin->workspace_actions); break; case PROP_WORKSPACE_NAMES: g_value_set_boolean (value, plugin->workspace_names); break; case PROP_URGENTCY_NOTIFICATION: g_value_set_boolean (value, plugin->urgentcy_notification); break; case PROP_ALL_WORKSPACES: g_value_set_boolean (value, plugin->all_workspaces); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void window_menu_plugin_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (object); XfcePanelPlugin *panel_plugin = XFCE_PANEL_PLUGIN (object); guint button_style; gboolean urgentcy_notification; panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); switch (prop_id) { case PROP_STYLE: button_style = g_value_get_uint (value); if (plugin->button_style != button_style) { plugin->button_style = button_style; /* show or hide the icon */ if (button_style == BUTTON_STYLE_ICON) gtk_widget_show (plugin->icon); else gtk_widget_hide (plugin->icon); /* update the plugin */ window_menu_plugin_size_changed (panel_plugin, xfce_panel_plugin_get_size (panel_plugin)); window_menu_plugin_screen_position_changed (panel_plugin, xfce_panel_plugin_get_screen_position (panel_plugin)); window_menu_plugin_active_window_changed (plugin->screen, NULL, plugin); } break; case PROP_WORKSPACE_ACTIONS: plugin->workspace_actions = g_value_get_boolean (value); break; case PROP_WORKSPACE_NAMES: plugin->workspace_names = g_value_get_boolean (value); break; case PROP_URGENTCY_NOTIFICATION: urgentcy_notification = g_value_get_boolean (value); if (plugin->urgentcy_notification != urgentcy_notification) { plugin->urgentcy_notification = urgentcy_notification; if (plugin->screen != NULL) { /* (dis)connect window signals */ if (plugin->urgentcy_notification) window_menu_plugin_windows_connect (plugin, TRUE); else window_menu_plugin_windows_disconnect (plugin); } } break; case PROP_ALL_WORKSPACES: plugin->all_workspaces = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void window_menu_plugin_style_set (GtkWidget *widget, GtkStyle *previous_style) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (widget); /* let gtk update the widget style */ (*GTK_WIDGET_CLASS (window_menu_plugin_parent_class)->style_set) (widget, previous_style); /* read the style properties */ gtk_widget_style_get (GTK_WIDGET (plugin), "minimized-icon-lucency", &plugin->minimized_icon_lucency, "ellipsize-mode", &plugin->ellipsize_mode, "max-width-chars", &plugin->max_width_chars, NULL); } static void window_menu_plugin_screen_changed (GtkWidget *widget, GdkScreen *previous_screen) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (widget); GdkScreen *screen; WnckScreen *wnck_screen; /* get the wnck screen */ screen = gtk_widget_get_screen (widget); panel_return_if_fail (GDK_IS_SCREEN (screen)); wnck_screen = wnck_screen_get (gdk_screen_get_number (screen)); panel_return_if_fail (WNCK_IS_SCREEN (wnck_screen)); /* leave when we same wnck screen was picked */ if (plugin->screen == wnck_screen) return; if (G_UNLIKELY (plugin->screen != NULL)) { /* disconnect from all windows on the old screen */ window_menu_plugin_windows_disconnect (plugin); /* disconnect from the previous screen */ g_signal_handlers_disconnect_by_func (G_OBJECT (plugin->screen), window_menu_plugin_active_window_changed, plugin); } /* set the new screen */ plugin->screen = wnck_screen; /* connect signal to monitor this screen */ g_signal_connect (G_OBJECT (plugin->screen), "active-window-changed", G_CALLBACK (window_menu_plugin_active_window_changed), plugin); if (plugin->urgentcy_notification) window_menu_plugin_windows_connect (plugin, FALSE); } static void window_menu_plugin_construct (XfcePanelPlugin *panel_plugin) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (panel_plugin); const PanelProperty properties[] = { { "style", G_TYPE_UINT }, { "workspace-actions", G_TYPE_BOOLEAN }, { "workspace-names", G_TYPE_BOOLEAN }, { "urgentcy-notification", G_TYPE_BOOLEAN }, { "all-workspaces", G_TYPE_BOOLEAN }, { NULL } }; /* show configure */ xfce_panel_plugin_menu_show_configure (XFCE_PANEL_PLUGIN (plugin)); /* bind all properties */ panel_properties_bind (NULL, G_OBJECT (plugin), xfce_panel_plugin_get_property_base (panel_plugin), properties, FALSE); /* monitor screen changes */ g_signal_connect (G_OBJECT (plugin), "screen-changed", G_CALLBACK (window_menu_plugin_screen_changed), NULL); /* initialize the screen */ window_menu_plugin_screen_changed (GTK_WIDGET (plugin), NULL); gtk_widget_show (plugin->button); } static void window_menu_plugin_free_data (XfcePanelPlugin *panel_plugin) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (panel_plugin); /* disconnect screen changed signal */ g_signal_handlers_disconnect_by_func (G_OBJECT (plugin), window_menu_plugin_screen_changed, NULL); /* disconnect from the screen */ if (G_LIKELY (plugin->screen != NULL)) { /* disconnect from all windows */ window_menu_plugin_windows_disconnect (plugin); /* disconnect from the screen */ g_signal_handlers_disconnect_by_func (G_OBJECT (plugin->screen), window_menu_plugin_active_window_changed, plugin); plugin->screen = NULL; } } static void window_menu_plugin_screen_position_changed (XfcePanelPlugin *panel_plugin, XfceScreenPosition screen_position) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (panel_plugin); GtkArrowType arrow_type = GTK_ARROW_NONE; /* set the arrow direction if the arrow is visible */ if (plugin->button_style == BUTTON_STYLE_ARROW) arrow_type = xfce_panel_plugin_arrow_type (panel_plugin); xfce_arrow_button_set_arrow_type (XFCE_ARROW_BUTTON (plugin->button), arrow_type); } static gboolean window_menu_plugin_size_changed (XfcePanelPlugin *panel_plugin, gint size) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (panel_plugin); if (plugin->button_style == BUTTON_STYLE_ICON) { /* square the plugin */ gtk_widget_set_size_request (GTK_WIDGET (plugin), size, size); } else { /* set the size of the arrow button */ if (xfce_panel_plugin_get_orientation (panel_plugin) == GTK_ORIENTATION_HORIZONTAL) gtk_widget_set_size_request (GTK_WIDGET (plugin), ARROW_BUTTON_SIZE, -1); else gtk_widget_set_size_request (GTK_WIDGET (plugin), -1, ARROW_BUTTON_SIZE); } return TRUE; } static void window_menu_plugin_configure_plugin (XfcePanelPlugin *panel_plugin) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (panel_plugin); GtkBuilder *builder; GObject *dialog, *object; guint i; const gchar *names[] = { "workspace-actions", "workspace-names", "urgentcy-notification", "all-workspaces", "style" }; /* setup the dialog */ PANEL_UTILS_LINK_4UI builder = panel_utils_builder_new (panel_plugin, windowmenu_dialog_ui, windowmenu_dialog_ui_length, &dialog); if (G_UNLIKELY (builder == NULL)) return; /* connect bindings */ for (i = 0; i < G_N_ELEMENTS (names); i++) { object = gtk_builder_get_object (builder, names[i]); panel_return_if_fail (GTK_IS_WIDGET (object)); exo_mutual_binding_new (G_OBJECT (plugin), names[i], G_OBJECT (object), "active"); } gtk_widget_show (GTK_WIDGET (dialog)); } static gboolean window_menu_plugin_remote_event (XfcePanelPlugin *panel_plugin, const gchar *name, const GValue *value) { WindowMenuPlugin *plugin = XFCE_WINDOW_MENU_PLUGIN (panel_plugin); panel_return_val_if_fail (value == NULL || G_IS_VALUE (value), FALSE); if (strcmp (name, "popup") == 0 && GTK_WIDGET_VISIBLE (panel_plugin) && !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (plugin->button)) && panel_utils_grab_available ()) { if (value != NULL && G_VALUE_HOLDS_BOOLEAN (value) && g_value_get_boolean (value)) { /* popup the menu under the pointer */ window_menu_plugin_menu (NULL, plugin); } else { /* show the menu */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (plugin->button), TRUE); } /* don't popup another menu */ return TRUE; } return FALSE; } static void window_menu_plugin_active_window_changed (WnckScreen *screen, WnckWindow *previous_window, WindowMenuPlugin *plugin) { WnckWindow *window; GdkPixbuf *pixbuf; XfcePanelImage *icon = XFCE_PANEL_IMAGE (plugin->icon); WnckWindowType type; panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (XFCE_IS_PANEL_IMAGE (icon)); panel_return_if_fail (WNCK_IS_SCREEN (screen)); panel_return_if_fail (plugin->screen == screen); /* only do this when the icon is visible */ if (plugin->button_style == BUTTON_STYLE_ICON) { window = wnck_screen_get_active_window (screen); if (G_LIKELY (window != NULL)) { /* skip 'fake' windows */ type = wnck_window_get_window_type (window); if (type == WNCK_WINDOW_DESKTOP || type == WNCK_WINDOW_DOCK) goto show_desktop_icon; /* get the window icon and set the tooltip */ gtk_widget_set_tooltip_text (GTK_WIDGET (icon), wnck_window_get_name (window)); pixbuf = wnck_window_get_icon (window); if (G_LIKELY (pixbuf != NULL)) xfce_panel_image_set_from_pixbuf (icon, pixbuf); else xfce_panel_image_set_from_source (icon, GTK_STOCK_MISSING_IMAGE); } else { show_desktop_icon: /* desktop is shown right now */ xfce_panel_image_set_from_source (icon, "user-desktop"); gtk_widget_set_tooltip_text (GTK_WIDGET (icon), _("Desktop")); } } } static void window_menu_plugin_window_state_changed (WnckWindow *window, WnckWindowState changed_mask, WnckWindowState new_state, WindowMenuPlugin *plugin) { panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (WNCK_IS_WINDOW (window)); panel_return_if_fail (plugin->urgentcy_notification); panel_return_if_fail (plugin->urgentcy_notification); /* only response to urgency changes and urgency notify is enabled */ if (!PANEL_HAS_FLAG (changed_mask, URGENT_FLAGS)) return; /* update the blinking state */ if (PANEL_HAS_FLAG (new_state, URGENT_FLAGS)) plugin->urgent_windows++; else plugin->urgent_windows--; /* check if we need to change the button */ if (plugin->urgent_windows == 1) xfce_arrow_button_set_blinking (XFCE_ARROW_BUTTON (plugin->button), TRUE); else if (plugin->urgent_windows == 0) xfce_arrow_button_set_blinking (XFCE_ARROW_BUTTON (plugin->button), FALSE); } static void window_menu_plugin_window_opened (WnckScreen *screen, WnckWindow *window, WindowMenuPlugin *plugin) { panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (WNCK_IS_WINDOW (window)); panel_return_if_fail (WNCK_IS_SCREEN (screen)); panel_return_if_fail (plugin->screen == screen); panel_return_if_fail (plugin->urgentcy_notification); /* monitor the window's state */ g_signal_connect (G_OBJECT (window), "state-changed", G_CALLBACK (window_menu_plugin_window_state_changed), plugin); /* check if the window needs attention */ if (wnck_window_needs_attention (window)) window_menu_plugin_window_state_changed (window, URGENT_FLAGS, URGENT_FLAGS, plugin); } static void window_menu_plugin_window_closed (WnckScreen *screen, WnckWindow *window, WindowMenuPlugin *plugin) { panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (WNCK_IS_WINDOW (window)); panel_return_if_fail (WNCK_IS_SCREEN (screen)); panel_return_if_fail (plugin->screen == screen); panel_return_if_fail (plugin->urgentcy_notification); /* check if we need to update the urgency counter */ if (wnck_window_needs_attention (window)) window_menu_plugin_window_state_changed (window, URGENT_FLAGS, 0, plugin); } static void window_menu_plugin_windows_disconnect (WindowMenuPlugin *plugin) { GList *windows, *li; panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (WNCK_IS_SCREEN (plugin->screen)); /* disconnect screen signals */ g_signal_handlers_disconnect_by_func (G_OBJECT (plugin->screen), window_menu_plugin_window_closed, plugin); g_signal_handlers_disconnect_by_func (G_OBJECT (plugin->screen), window_menu_plugin_window_opened, plugin); /* disconnect the state changed signal from all windows */ windows = wnck_screen_get_windows (plugin->screen); for (li = windows; li != NULL; li = li->next) { panel_return_if_fail (WNCK_IS_WINDOW (li->data)); g_signal_handlers_disconnect_by_func (G_OBJECT (li->data), window_menu_plugin_window_state_changed, plugin); } /* stop blinking */ plugin->urgent_windows = 0; xfce_arrow_button_set_blinking (XFCE_ARROW_BUTTON (plugin->button), FALSE); } static void window_menu_plugin_windows_connect (WindowMenuPlugin *plugin, gboolean traverse_windows) { GList *windows, *li; panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (WNCK_IS_SCREEN (plugin->screen)); panel_return_if_fail (plugin->urgentcy_notification); g_signal_connect (G_OBJECT (plugin->screen), "window-opened", G_CALLBACK (window_menu_plugin_window_opened), plugin); g_signal_connect (G_OBJECT (plugin->screen), "window-closed", G_CALLBACK (window_menu_plugin_window_closed), plugin); if (!traverse_windows) return; /* connect the state changed signal to all windows */ windows = wnck_screen_get_windows (plugin->screen); for (li = windows; li != NULL; li = li->next) { panel_return_if_fail (WNCK_IS_WINDOW (li->data)); window_menu_plugin_window_opened (plugin->screen, WNCK_WINDOW (li->data), plugin); } } static void window_menu_plugin_workspace_add (GtkWidget *mi, WindowMenuPlugin *plugin) { panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (WNCK_IS_SCREEN (plugin->screen)); /* increase the number of workspaces */ wnck_screen_change_workspace_count (plugin->screen, wnck_screen_get_workspace_count (plugin->screen) + 1); } static void window_menu_plugin_workspace_remove (GtkWidget *mi, WindowMenuPlugin *plugin) { gint n_workspaces; panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (WNCK_IS_SCREEN (plugin->screen)); /* decrease the number of workspaces */ n_workspaces = wnck_screen_get_workspace_count (plugin->screen); if (G_LIKELY (n_workspaces > 1)) wnck_screen_change_workspace_count (plugin->screen, n_workspaces - 1); } static void window_menu_plugin_menu_workspace_item_active (GtkWidget *mi, WnckWorkspace *workspace) { panel_return_if_fail (WNCK_IS_WORKSPACE (workspace)); /* activate the workspace */ wnck_workspace_activate (workspace, gtk_get_current_event_time ()); } static GtkWidget * window_menu_plugin_menu_workspace_item_new (WnckWorkspace *workspace, WindowMenuPlugin *plugin, PangoFontDescription *bold) { const gchar *name; gchar *utf8 = NULL, *name_num = NULL; GtkWidget *mi, *label; panel_return_val_if_fail (WNCK_IS_WORKSPACE (workspace), NULL); panel_return_val_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin), NULL); /* try to get an utf-8 valid name */ name = wnck_workspace_get_name (workspace); if (!exo_str_is_empty (name) && !g_utf8_validate (name, -1, NULL)) name = utf8 = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); if (exo_str_is_empty (name)) name = name_num = g_strdup_printf (_("Workspace %d"), wnck_workspace_get_number (workspace) + 1); mi = gtk_menu_item_new_with_label (name); g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (window_menu_plugin_menu_workspace_item_active), workspace); /* make the label pretty on long workspace names */ label = gtk_bin_get_child (GTK_BIN (mi)); panel_return_val_if_fail (GTK_IS_LABEL (label), NULL); gtk_label_set_ellipsize (GTK_LABEL (label), plugin->ellipsize_mode); gtk_label_set_max_width_chars (GTK_LABEL (label), plugin->max_width_chars); /* modify the label font if needed */ if (bold != NULL) gtk_widget_modify_font (label, bold); g_free (utf8); g_free (name_num); return mi; } static void window_menu_plugin_menu_actions_selection_done (GtkWidget *action_menu, GtkMenuShell *menu) { panel_return_if_fail (GTK_IS_MENU_SHELL (menu)); panel_return_if_fail (WNCK_IS_ACTION_MENU (action_menu)); gtk_widget_destroy (action_menu); /* deactive the window list menu */ gtk_menu_shell_cancel (menu); } static gboolean window_menu_plugin_menu_window_item_activate (GtkWidget *mi, GdkEventButton *event, WnckWindow *window) { WnckWorkspace *workspace; GtkWidget *menu; panel_return_val_if_fail (WNCK_IS_WINDOW (window), FALSE); panel_return_val_if_fail (GTK_IS_MENU_ITEM (mi), FALSE); panel_return_val_if_fail (GTK_IS_MENU_SHELL (mi->parent), FALSE); /* only respond to a button releases */ if (event->type != GDK_BUTTON_RELEASE) return FALSE; if (event->button == 1) { /* go to workspace and activate window */ workspace = wnck_window_get_workspace (window); if (workspace != NULL) wnck_workspace_activate (workspace, event->time - 1); wnck_window_activate (window, event->time); } else if (event->button == 2) { /* active the window (bring it to this workspace) */ wnck_window_activate (window, event->time); } else if (event->button == 3) { /* popup the window action menu */ menu = wnck_action_menu_new (window); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (window_menu_plugin_menu_actions_selection_done), gtk_widget_get_parent (mi)); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } return FALSE; } static GtkWidget * window_menu_plugin_menu_window_item_new (WnckWindow *window, WindowMenuPlugin *plugin, PangoFontDescription *italic, PangoFontDescription *bold, gint icon_w, gint icon_h) { const gchar *name, *tooltip; gchar *utf8 = NULL; gchar *decorated = NULL; GtkWidget *mi, *label, *image; GdkPixbuf *pixbuf, *lucent = NULL, *scaled = NULL; panel_return_val_if_fail (WNCK_IS_WINDOW (window), NULL); /* try to get an utf-8 valid name */ name = wnck_window_get_name (window); if (!exo_str_is_empty (name) && !g_utf8_validate (name, -1, NULL)) name = utf8 = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); if (exo_str_is_empty (name)) name = "?"; /* store the tooltip text */ tooltip = name; /* create a decorated name for the label */ if (wnck_window_is_shaded (window)) name = decorated = g_strdup_printf ("=%s=", name); else if (wnck_window_is_minimized (window)) name = decorated = g_strdup_printf ("[%s]", name); /* create the menu item */ mi = gtk_image_menu_item_new_with_label (name); gtk_widget_set_tooltip_text (mi, tooltip); g_object_set_qdata (G_OBJECT (mi), window_quark, window); g_signal_connect (G_OBJECT (mi), "button-release-event", G_CALLBACK (window_menu_plugin_menu_window_item_activate), window); g_free (utf8); g_free (decorated); /* make the label pretty on long window names */ label = gtk_bin_get_child (GTK_BIN (mi)); panel_return_val_if_fail (GTK_IS_LABEL (label), NULL); gtk_label_set_ellipsize (GTK_LABEL (label), plugin->ellipsize_mode); gtk_label_set_max_width_chars (GTK_LABEL (label), plugin->max_width_chars); /* modify the label font if needed */ if (wnck_window_is_active (window)) gtk_widget_modify_font (label, italic); else if (wnck_window_or_transient_needs_attention (window)) gtk_widget_modify_font (label, bold); if (plugin->minimized_icon_lucency > 0) { /* get the window icon */ pixbuf = wnck_window_get_mini_icon (window); if (pixbuf != NULL && (gdk_pixbuf_get_width (pixbuf) < icon_w || gdk_pixbuf_get_height (pixbuf) < icon_h)) pixbuf = wnck_window_get_icon (window); if (pixbuf != NULL) { /* scle the icon if needed */ if (gdk_pixbuf_get_width (pixbuf) > icon_w || gdk_pixbuf_get_height (pixbuf) > icon_h) { scaled = gdk_pixbuf_scale_simple (pixbuf, icon_w, icon_h, GDK_INTERP_BILINEAR); if (G_LIKELY (scaled != NULL)) pixbuf = scaled; } /* dimm the icon if the window is minimized */ if (wnck_window_is_minimized (window) && plugin->minimized_icon_lucency < 100) { lucent = exo_gdk_pixbuf_lucent (pixbuf, plugin->minimized_icon_lucency); if (G_LIKELY (lucent != NULL)) pixbuf = lucent; } /* set the menu item label */ image = gtk_image_new_from_pixbuf (pixbuf); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image); gtk_widget_show (image); if (lucent != NULL) g_object_unref (G_OBJECT (lucent)); if (scaled != NULL) g_object_unref (G_OBJECT (scaled)); } } return mi; } static void window_menu_plugin_menu_selection_done (GtkWidget *menu, GtkWidget *button) { panel_return_if_fail (button == NULL || GTK_IS_TOGGLE_BUTTON (button)); panel_return_if_fail (GTK_IS_MENU (menu)); if (button != NULL) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE); /* delay destruction so we can handle the activate event first */ exo_gtk_object_destroy_later (GTK_OBJECT (menu)); } static gboolean window_menu_plugin_menu_key_press_event (GtkWidget *menu, GdkEventKey *event) { GtkWidget *mi = NULL; GdkEventButton fake_event = { 0, }; guint modifiers; WnckWindow *window; panel_return_val_if_fail (GTK_IS_MENU (menu), FALSE); /* construct an event */ switch (event->keyval) { case GDK_space: case GDK_Return: case GDK_KP_Space: case GDK_KP_Enter: /* active the menu item */ fake_event.button = 1; break; case GDK_Menu: /* popup the window actions menu */ fake_event.button = 3; break; default: return FALSE; } /* popdown the menu, this will also update the active item */ gtk_menu_popdown (GTK_MENU (menu)); /* get the active menu item leave when no item if found */ mi = gtk_menu_get_active (GTK_MENU (menu)); panel_return_val_if_fail (mi == NULL || GTK_IS_MENU_ITEM (mi), FALSE); if (mi == NULL) return FALSE; if (fake_event.button == 1) { /* get the modifiers */ modifiers = event->state & gtk_accelerator_get_default_mod_mask (); if (modifiers == GDK_SHIFT_MASK) fake_event.button = 2; else if (modifiers == GDK_CONTROL_MASK) fake_event.button = 3; } /* complete the event */ fake_event.type = GDK_BUTTON_RELEASE; fake_event.time = event->time; /* try the get the window and active an item */ window = g_object_get_qdata (G_OBJECT (mi), window_quark); if (window != NULL) window_menu_plugin_menu_window_item_activate (mi, &fake_event, window); else gtk_menu_item_activate (GTK_MENU_ITEM (mi)); return FALSE; } static GtkWidget * window_menu_plugin_menu_new (WindowMenuPlugin *plugin) { GtkWidget *menu, *mi = NULL, *image; GList *workspaces, *lp, fake; GList *windows, *li; WnckWorkspace *workspace = NULL; WnckWorkspace *active_workspace, *window_workspace; WnckWindow *window; PangoFontDescription *italic, *bold; gint urgent_windows = 0; gboolean has_windows; gboolean is_empty = TRUE; guint n_workspaces = 0; const gchar *name = NULL; gchar *utf8 = NULL, *label; gint w, h; panel_return_val_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin), NULL); panel_return_val_if_fail (WNCK_IS_SCREEN (plugin->screen), NULL); italic = pango_font_description_from_string ("italic"); bold = pango_font_description_from_string ("bold"); if (!gtk_icon_size_lookup (menu_icon_size, &w, &h)) w = h = 16; menu = gtk_menu_new (); g_signal_connect (G_OBJECT (menu), "key-press-event", G_CALLBACK (window_menu_plugin_menu_key_press_event), plugin); /* get all the windows and the active workspace */ windows = wnck_screen_get_windows_stacked (plugin->screen); active_workspace = wnck_screen_get_active_workspace (plugin->screen); if (plugin->all_workspaces) { /* get all the workspaces */ workspaces = wnck_screen_get_workspaces (plugin->screen); } else { /* create a fake list with only the active workspace */ fake.next = fake.prev = NULL; fake.data = active_workspace; workspaces = &fake; } for (lp = workspaces; lp != NULL; lp = lp->next, n_workspaces++) { workspace = WNCK_WORKSPACE (lp->data); if (plugin->workspace_names) { /* create the workspace menu item */ mi = window_menu_plugin_menu_workspace_item_new (workspace, plugin, workspace == active_workspace ? bold : italic); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); mi = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); /* not empty anymore */ is_empty = FALSE; } for (li = windows, has_windows = FALSE; li != NULL; li = li->next) { window = WNCK_WINDOW (li->data); /* windows we always want to skip */ if (wnck_window_is_skip_pager (window) || wnck_window_is_skip_tasklist (window)) continue; /* get the window's workspace */ window_workspace = wnck_window_get_workspace (window); /* show only windows from this workspace or pinned * windows on the active workspace */ if (window_workspace != workspace && !(window_workspace == NULL && workspace == active_workspace)) continue; /* create the menu item */ mi = window_menu_plugin_menu_window_item_new (window, plugin, italic, bold, w, h); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); /* this workspace is not empty */ has_windows = TRUE; /* menu is not empty anymore */ is_empty = FALSE; /* count the urgent windows */ if (wnck_window_needs_attention (window)) urgent_windows++; } if (has_windows) { mi = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); } } /* destroy the last menu item if it's a separator */ if (mi != NULL && GTK_IS_SEPARATOR_MENU_ITEM (mi)) gtk_widget_destroy (mi); /* add a menu item if there are not windows found */ if (is_empty) { mi = gtk_menu_item_new_with_label (_("No Windows")); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_set_sensitive (mi, FALSE); gtk_widget_show (mi); } /* check if we need to append the urgent windows on other workspaces */ if (!plugin->all_workspaces && plugin->urgent_windows > urgent_windows) { if (plugin->workspace_names) { mi = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); mi = gtk_menu_item_new_with_label (_("Urgent Windows")); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_set_sensitive (mi, FALSE); gtk_widget_show (mi); } mi = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); for (li = windows; li != NULL; li = li->next) { window = WNCK_WINDOW (li->data); /* always skip these windows */ if (wnck_window_is_skip_pager (window) || wnck_window_is_skip_tasklist (window)) continue; /* get the window's workspace */ window_workspace = wnck_window_get_workspace (window); /* only acept windows that are not on the active workspace, * not sticky and urgent */ if (window_workspace == active_workspace || window_workspace == NULL || !wnck_window_needs_attention (window)) continue; /* create the menu item */ mi = window_menu_plugin_menu_window_item_new (window, plugin, italic, bold, w, h); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); } } if (plugin->workspace_actions) { mi = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_show (mi); mi = gtk_image_menu_item_new_with_label (_("Add Workspace")); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (window_menu_plugin_workspace_add), plugin); gtk_widget_show (mi); image = gtk_image_new_from_stock (GTK_STOCK_ADD, menu_icon_size); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image); gtk_widget_show (mi); if (G_LIKELY (workspace != NULL)) { /* try to get an utf-8 valid name */ name = wnck_workspace_get_name (workspace); if (!exo_str_is_empty (name) && !g_utf8_validate (name, -1, NULL)) name = utf8 = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); } /* create label */ if (!exo_str_is_empty (name)) label = g_strdup_printf (_("Remove Workspace \"%s\""), name); else label = g_strdup_printf (_("Remove Workspace %d"), n_workspaces); mi = gtk_image_menu_item_new_with_label (label); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); gtk_widget_set_sensitive (mi, !!(n_workspaces > 1)); g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (window_menu_plugin_workspace_remove), plugin); gtk_widget_show (mi); g_free (label); g_free (utf8); image = gtk_image_new_from_stock (GTK_STOCK_REMOVE, menu_icon_size); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image); gtk_widget_show (mi); } pango_font_description_free (italic); pango_font_description_free (bold); return menu; } static void window_menu_plugin_menu (GtkWidget *button, WindowMenuPlugin *plugin) { GtkWidget *menu; panel_return_if_fail (XFCE_IS_WINDOW_MENU_PLUGIN (plugin)); panel_return_if_fail (button == NULL || plugin->button == button); if (button != NULL && !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) return; /* popup the menu */ menu = window_menu_plugin_menu_new (plugin); g_signal_connect (G_OBJECT (menu), "deactivate", G_CALLBACK (window_menu_plugin_menu_selection_done), button); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, button != NULL ? xfce_panel_plugin_position_menu : NULL, plugin, 1, gtk_get_current_event_time ()); }