From e7af257814b1b56a4cbd863c6f69699813a580dd Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Thu, 13 Mar 2014 18:51:10 -0400 Subject: [PATCH] Implement window menus in gnome-shell https://bugzilla.gnome.org/show_bug.cgi?id=726352 --- js/js-resources.gresource.xml | 1 + js/ui/windowManager.js | 8 ++ js/ui/windowMenu.js | 145 ++++++++++++++++++++++++++++++++++ src/gnome-shell-plugin.c | 11 ++- src/shell-wm-private.h | 2 + src/shell-wm.c | 14 ++++ 6 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 js/ui/windowMenu.js diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 6d9d58529..47bdd0048 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -84,6 +84,7 @@ ui/userWidget.js ui/viewSelector.js ui/windowAttentionHandler.js + ui/windowMenu.js ui/windowManager.js ui/workspace.js ui/workspaceSwitcherPopup.js diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js index ae6a20717..0acf4f609 100644 --- a/js/ui/windowManager.js +++ b/js/ui/windowManager.js @@ -15,6 +15,7 @@ const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; const Main = imports.ui.main; const ModalDialog = imports.ui.modalDialog; const Tweener = imports.ui.tweener; +const WindowMenu = imports.ui.windowMenu; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; const WINDOW_ANIMATION_TIME = 0.25; @@ -480,6 +481,7 @@ const WindowManager = new Lang.Class({ this._shellwm.connect('switch-workspace', Lang.bind(this, this._switchWorkspace)); this._shellwm.connect('show-tile-preview', Lang.bind(this, this._showTilePreview)); this._shellwm.connect('hide-tile-preview', Lang.bind(this, this._hideTilePreview)); + this._shellwm.connect('show-window-menu', Lang.bind(this, this._showWindowMenu)); this._shellwm.connect('minimize', Lang.bind(this, this._minimizeWindow)); this._shellwm.connect('maximize', Lang.bind(this, this._maximizeWindow)); this._shellwm.connect('unmaximize', Lang.bind(this, this._unmaximizeWindow)); @@ -669,6 +671,8 @@ const WindowManager = new Lang.Class({ this._dimWindow(this._dimmedWindows[i]); })); + this._windowMenuManager = new WindowMenu.WindowMenuManager(); + if (Main.sessionMode.hasWorkspaces) this._workspaceTracker = new WorkspaceTracker(this); @@ -1159,6 +1163,10 @@ const WindowManager = new Lang.Class({ this._tilePreview.hide(); }, + _showWindowMenu: function(shellwm, window) { + this._windowMenuManager.showForWindow(window); + }, + _startAppSwitcher : function(display, screen, window, binding) { /* prevent a corner case where both popups show up at once */ if (this._workspaceSwitcherPopup != null) diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js new file mode 100644 index 000000000..673827701 --- /dev/null +++ b/js/ui/windowMenu.js @@ -0,0 +1,145 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -* + +const Gtk = imports.gi.Gtk; +const Lang = imports.lang; +const Meta = imports.gi.Meta; +const St = imports.gi.St; +const Shell = imports.gi.Shell; + +const BoxPointer = imports.ui.boxpointer; +const Main = imports.ui.main; +const PopupMenu = imports.ui.popupMenu; + +const WindowMenu = new Lang.Class({ + Name: 'WindowMenu', + Extends: PopupMenu.PopupMenu, + + _init: function(window) { + this.parent(Main.layoutManager.dummyCursor, 0, St.Side.TOP); + + this.actor.add_style_class_name('window-menu'); + + Main.layoutManager.uiGroup.add_actor(this.actor); + this.actor.hide(); + + this._buildMenu(window); + }, + + _buildMenu: function(window) { + let type = window.get_window_type(); + + let item; + + item = this.addAction(_("Minimize"), Lang.bind(this, function(event) { + window.minimize(); + })); + if (!window.can_minimize()) + item.setSensitive(false); + + if (window.get_maximized()) { + item = this.addAction(_("Unmaximize"), Lang.bind(this, function() { + window.unmaximize(Meta.MaximizeFlags.BOTH); + })); + } else { + item = this.addAction(_("Maximize"), Lang.bind(this, function() { + window.maximize(Meta.MaximizeFlags.BOTH); + })); + } + if (!window.can_maximize()) + item.setSensitive(false); + + item = this.addAction(_("Move"), Lang.bind(this, function(event) { + window.begin_grab_op(Meta.GrabOp.KEYBOARD_MOVING, true, event.get_time()); + })); + if (!window.allows_move()) + item.setSensitive(false); + + item = this.addAction(_("Resize"), Lang.bind(this, function(event) { + window.begin_grab_op(Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN, true, event.get_time()); + })); + if (!window.allows_resize()) + item.setSensitive(false); + + if (!window.titlebar_is_onscreen() && type != Meta.WindowType.DOCK && type != Meta.WindowType.DESKTOP) { + this.addAction(_("Move Titlebar Onscreen"), Lang.bind(this, function(event) { + window.shove_titlebar_onscreen(); + })); + } + + item = this.addAction("Always on Top", Lang.bind(this, function() { + if (window.is_above()) + window.unmake_above(); + else + window.make_above(); + })); + if (window.is_above()) + item.setOrnament(PopupMenu.Ornament.DOT); + if (window.get_maximized() || + type == Meta.WindowType.DOCK || + type == Meta.WindowType.DESKTOP || + type == Meta.WindowType.SPLASHSCREEN) + item.setSensitive(false); + + if (!Meta.prefs_get_workspaces_only_on_primary() || window.is_on_primary_monitor()) { + let isSticky = window.is_on_all_workspaces(); + + item = this.addAction(_("Always on Visible Workspace"), Lang.bind(this, function() { + if (isSticky) + window.unstick(); + else + window.stick(); + })); + if (isSticky) + item.setOrnament(PopupMenu.Ornament.DOT); + if (window.is_always_on_all_workspaces()) + item.setSensitive(false); + + let nWorkspaces = global.screen.n_workspaces; + + if (!isSticky) { + let workspace = window.get_workspace(); + let idx = workspace.index(); + if (idx > 0) { + this.addAction(_("Move to Workspace Up"), Lang.bind(this, function(event) { + window.change_workspace(workspace.get_neighbor(Meta.MotionDirection.UP)); + })); + } + if (idx < nWorkspaces) { + this.addAction(_("Move to Workspace Down"), Lang.bind(this, function(event) { + window.change_workspace(workspace.get_neighbor(Meta.MotionDirection.DOWN)); + })); + } + } + } + + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + + item = this.addAction(_("Close"), Lang.bind(this, function(event) { + window.close(event.get_time()); + })); + if (!window.can_close()) + item.setSensitive(false); + } +}); + +const WindowMenuManager = new Lang.Class({ + Name: 'WindowMenuManager', + + _init: function() { + this._manager = new PopupMenu.PopupMenuManager({ actor: Main.layoutManager.dummyCursor }); + }, + + showForWindow: function(window) { + let menu = new WindowMenu(window); + this._manager.addMenu(menu); + + let [x, y] = global.get_pointer(); + Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0); + menu.open(BoxPointer.PopupAnimation.NONE); + menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); + menu.connect('open-state-changed', Lang.bind(this, function(menu_, isOpen) { + if (!isOpen) + menu.destroy(); + })); + }, +}); diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index 71503bca0..75a3bddf0 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -73,7 +73,8 @@ static void gnome_shell_plugin_show_tile_preview (MetaPlugin *plugin, MetaRectangle *tile_rect, int tile_monitor); static void gnome_shell_plugin_hide_tile_preview (MetaPlugin *plugin); - +static void gnome_shell_plugin_show_window_menu (MetaPlugin *plugin, + MetaWindow *window); static gboolean gnome_shell_plugin_xevent_filter (MetaPlugin *plugin, XEvent *event); @@ -140,6 +141,7 @@ gnome_shell_plugin_class_init (GnomeShellPluginClass *klass) plugin_class->show_tile_preview = gnome_shell_plugin_show_tile_preview; plugin_class->hide_tile_preview = gnome_shell_plugin_hide_tile_preview; + plugin_class->show_window_menu = gnome_shell_plugin_show_window_menu; plugin_class->xevent_filter = gnome_shell_plugin_xevent_filter; plugin_class->keybinding_filter = gnome_shell_plugin_keybinding_filter; @@ -303,6 +305,13 @@ gnome_shell_plugin_hide_tile_preview (MetaPlugin *plugin) _shell_wm_hide_tile_preview (get_shell_wm ()); } +static void +gnome_shell_plugin_show_window_menu (MetaPlugin *plugin, + MetaWindow *window) +{ + _shell_wm_show_window_menu (get_shell_wm (), window); +} + static gboolean gnome_shell_plugin_xevent_filter (MetaPlugin *plugin, XEvent *xev) diff --git a/src/shell-wm-private.h b/src/shell-wm-private.h index 9df4adab3..35cbcb0f7 100644 --- a/src/shell-wm-private.h +++ b/src/shell-wm-private.h @@ -40,6 +40,8 @@ void _shell_wm_show_tile_preview (ShellWM *wm, MetaRectangle *tile_rect, int tile_monitor); void _shell_wm_hide_tile_preview (ShellWM *wm); +void _shell_wm_show_window_menu (ShellWM *wm, + MetaWindow *window); gboolean _shell_wm_filter_keybinding (ShellWM *wm, MetaKeyBinding *binding); diff --git a/src/shell-wm.c b/src/shell-wm.c index f2c484d6d..0d51606d7 100644 --- a/src/shell-wm.c +++ b/src/shell-wm.c @@ -28,6 +28,7 @@ enum KILL_WINDOW_EFFECTS, SHOW_TILE_PREVIEW, HIDE_TILE_PREVIEW, + SHOW_WINDOW_MENU, FILTER_KEYBINDING, CONFIRM_DISPLAY_CHANGE, @@ -135,6 +136,13 @@ shell_wm_class_init (ShellWMClass *klass) 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + shell_wm_signals[SHOW_WINDOW_MENU] = + g_signal_new ("show-window-menu", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, + META_TYPE_WINDOW); shell_wm_signals[FILTER_KEYBINDING] = g_signal_new ("filter-keybinding", G_TYPE_FROM_CLASS (klass), @@ -288,6 +296,12 @@ _shell_wm_hide_tile_preview (ShellWM *wm) g_signal_emit (wm, shell_wm_signals[HIDE_TILE_PREVIEW], 0); } +void +_shell_wm_show_window_menu (ShellWM *wm, + MetaWindow *window) +{ + g_signal_emit (wm, shell_wm_signals[SHOW_WINDOW_MENU], 0, window); +} void _shell_wm_minimize (ShellWM *wm,