From d220e353e058f1d080d7be5e6a8ecd9d19657baf Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Thu, 19 Jan 2017 19:52:18 +0100 Subject: [PATCH] ui: Implement "window doesn't respond" dialog on gnome-shell This does allow us to use ClutterActors instead of lousy legacy tools meant for shell scripting. https://bugzilla.gnome.org/show_bug.cgi?id=762083 --- js/js-resources.gresource.xml | 1 + js/ui/closeDialog.js | 138 ++++++++++++++++++++++++++++++++++ js/ui/windowManager.js | 6 ++ po/POTFILES.in | 1 + src/gnome-shell-plugin.c | 11 +++ src/shell-wm-private.h | 3 + src/shell-wm.c | 28 +++++++ 7 files changed, 188 insertions(+) create mode 100644 js/ui/closeDialog.js diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 71e713d2b..67295a4cd 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -44,6 +44,7 @@ ui/boxpointer.js ui/calendar.js ui/checkBox.js + ui/closeDialog.js ui/ctrlAltTab.js ui/dash.js ui/dateMenu.js diff --git a/js/ui/closeDialog.js b/js/ui/closeDialog.js new file mode 100644 index 000000000..942852184 --- /dev/null +++ b/js/ui/closeDialog.js @@ -0,0 +1,138 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const GObject = imports.gi.GObject; +const Lang = imports.lang; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; + +const Dialog = imports.ui.dialog; +const Main = imports.ui.main; +const Tweener = imports.ui.tweener; + +const FROZEN_WINDOW_BRIGHTNESS = -0.3 +const DIALOG_TRANSITION_TIME = 0.15 + +const CloseDialog = new Lang.Class({ + Name: 'CloseDialog', + Extends: GObject.Object, + Implements: [ Meta.CloseDialog ], + Properties: { + 'window': GObject.ParamSpec.override('window', Meta.CloseDialog) + }, + + _init: function (window) { + this.parent(); + this._window = window; + this._dialog = null; + }, + + get window() { + return this._window; + }, + + set window(window) { + this._window = window; + }, + + _createDialogContent: function () { + let tracker = Shell.WindowTracker.get_default(); + let windowApp = tracker.get_window_app(this._window); + + /* Translators: %s is an application name */ + let title = _("ā€œ%sā€ is not responding.").format(windowApp.get_name()); + let subtitle = _("You may choose to wait a short while for it to " + + "continue or force the application to quit entirely."); + let icon = new Gio.ThemedIcon({ name: 'dialog-warning-symbolic' }); + return new Dialog.MessageDialogContent({ icon, title, subtitle }); + }, + + _initDialog: function () { + if (this._dialog) + return; + + let windowActor = this._window.get_compositor_private(); + this._dialog = new Dialog.Dialog(windowActor, 'close-dialog'); + this._dialog.width = windowActor.width; + this._dialog.height = windowActor.height; + + this._dialog.addContent(this._createDialogContent()); + this._dialog.addButton({ label: _('Force Quit'), + action: Lang.bind(this, this._onClose), + default: true }); + this._dialog.addButton({ label: _('Wait'), + action: Lang.bind(this, this._onWait), + key: Clutter.Escape }); + + global.focus_manager.add_group(this._dialog); + }, + + _addWindowEffect: function () { + // We set the effect on the surface actor, so the dialog itself + // (which is a child of the MetaWindowActor) does not get the + // effect applied itself. + let windowActor = this._window.get_compositor_private(); + let surfaceActor = windowActor.get_first_child(); + let effect = new Clutter.BrightnessContrastEffect(); + effect.set_brightness(FROZEN_WINDOW_BRIGHTNESS); + surfaceActor.add_effect_with_name("gnome-shell-frozen-window", effect); + }, + + _removeWindowEffect: function () { + let windowActor = this._window.get_compositor_private(); + let surfaceActor = windowActor.get_first_child(); + surfaceActor.remove_effect_by_name("gnome-shell-frozen-window"); + }, + + _onWait: function () { + this.response(Meta.CloseDialogResponse.WAIT); + }, + + _onClose: function () { + this.response(Meta.CloseDialogResponse.FORCE_CLOSE); + }, + + vfunc_show: function () { + if (this._dialog != null) + return; + + this._addWindowEffect(); + this._initDialog(); + + this._dialog.scale_y = 0; + this._dialog.set_pivot_point(0.5, 0.5); + + Tweener.addTween(this._dialog, + { scale_y: 1, + transition: 'linear', + time: DIALOG_TRANSITION_TIME, + onComplete: Lang.bind(this, function () { + Main.layoutManager.trackChrome(this._dialog, { affectsInputRegion: true }); + }) + }); + }, + + vfunc_hide: function () { + if (this._dialog == null) + return; + + let dialog = this._dialog; + this._dialog = null; + this._removeWindowEffect(); + + Tweener.addTween(dialog, + { scale_y: 0, + transition: 'linear', + time: DIALOG_TRANSITION_TIME, + onComplete: Lang.bind(this, function () { + dialog.destroy(); + }) + }); + }, + + vfunc_focus: function () { + if (this._dialog) + this._dialog.grab_key_focus(); + } +}); diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js index 459812094..0b8ba53e8 100644 --- a/js/ui/windowManager.js +++ b/js/ui/windowManager.js @@ -20,6 +20,7 @@ const Tweener = imports.ui.tweener; const WindowMenu = imports.ui.windowMenu; const PadOsd = imports.ui.padOsd; const EdgeDragAction = imports.ui.edgeDragAction; +const CloseDialog = imports.ui.closeDialog; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; const MINIMIZE_WINDOW_ANIMATION_TIME = 0.2; @@ -710,6 +711,7 @@ const WindowManager = new Lang.Class({ this._shellwm.connect('destroy', Lang.bind(this, this._destroyWindow)); this._shellwm.connect('filter-keybinding', Lang.bind(this, this._filterKeybinding)); this._shellwm.connect('confirm-display-change', Lang.bind(this, this._confirmDisplayChange)); + this._shellwm.connect('create-close-dialog', Lang.bind(this, this._createCloseDialog)); global.screen.connect('restacked', Lang.bind(this, this._syncStacking)); this._workspaceSwitcherPopup = null; @@ -1971,6 +1973,10 @@ const WindowManager = new Lang.Class({ dialog.open(); }, + _createCloseDialog: function (shellwm, window) { + return new CloseDialog.CloseDialog(window); + }, + _showResizePopup: function(display, show, rect, displayW, displayH) { if (show) { if (!this._resizePopup) diff --git a/po/POTFILES.in b/po/POTFILES.in index 121c5129f..bc9e6ac55 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,6 +17,7 @@ js/ui/appFavorites.js js/ui/audioDeviceSelection.js js/ui/backgroundMenu.js js/ui/calendar.js +js/ui/closeDialog.js js/ui/components/automountManager.js js/ui/components/autorunManager.js js/ui/components/keyring.js diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index c9a8917e2..c047d0dde 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -88,6 +88,8 @@ static void gnome_shell_plugin_confirm_display_change (MetaPlugin *plugin); static const MetaPluginInfo *gnome_shell_plugin_plugin_info (MetaPlugin *plugin); +static MetaCloseDialog * gnome_shell_plugin_create_close_dialog (MetaPlugin *plugin, + MetaWindow *window); #define GNOME_TYPE_SHELL_PLUGIN (gnome_shell_plugin_get_type ()) #define GNOME_SHELL_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_SHELL_PLUGIN, GnomeShellPlugin)) @@ -149,6 +151,8 @@ gnome_shell_plugin_class_init (GnomeShellPluginClass *klass) plugin_class->confirm_display_change = gnome_shell_plugin_confirm_display_change; plugin_class->plugin_info = gnome_shell_plugin_plugin_info; + + plugin_class->create_close_dialog = gnome_shell_plugin_create_close_dialog; } static void @@ -425,3 +429,10 @@ MetaPluginInfo *gnome_shell_plugin_plugin_info (MetaPlugin *plugin) return &info; } + +static MetaCloseDialog * +gnome_shell_plugin_create_close_dialog (MetaPlugin *plugin, + MetaWindow *window) +{ + return _shell_wm_create_close_dialog (get_shell_wm (), window); +} diff --git a/src/shell-wm-private.h b/src/shell-wm-private.h index 7e0399c59..46faae9d7 100644 --- a/src/shell-wm-private.h +++ b/src/shell-wm-private.h @@ -52,6 +52,9 @@ gboolean _shell_wm_filter_keybinding (ShellWM *wm, void _shell_wm_confirm_display_change (ShellWM *wm); +MetaCloseDialog * _shell_wm_create_close_dialog (ShellWM *wm, + MetaWindow *window); + G_END_DECLS #endif /* __SHELL_WM_PRIVATE_H__ */ diff --git a/src/shell-wm.c b/src/shell-wm.c index 0b3f9258c..41ad08a09 100644 --- a/src/shell-wm.c +++ b/src/shell-wm.c @@ -33,6 +33,7 @@ enum SHOW_WINDOW_MENU, FILTER_KEYBINDING, CONFIRM_DISPLAY_CHANGE, + CREATE_CLOSE_DIALOG, LAST_SIGNAL }; @@ -168,6 +169,22 @@ shell_wm_class_init (ShellWMClass *klass) 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + /** + * ShellWM::create-close-dialog: + * @wm: The WM + * @window: The window to create the dialog for + * + * Creates a close dialog for the given window. + * + * Returns: (transfer full): The close dialog instance. + */ + shell_wm_signals[CREATE_CLOSE_DIALOG] = + g_signal_new ("create-close-dialog", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + META_TYPE_CLOSE_DIALOG, 1, META_TYPE_WINDOW); } void @@ -386,6 +403,17 @@ _shell_wm_confirm_display_change (ShellWM *wm) g_signal_emit (wm, shell_wm_signals[CONFIRM_DISPLAY_CHANGE], 0); } +MetaCloseDialog * +_shell_wm_create_close_dialog (ShellWM *wm, + MetaWindow *window) +{ + MetaCloseDialog *dialog; + + g_signal_emit (wm, shell_wm_signals[CREATE_CLOSE_DIALOG], 0, window, &dialog); + + return dialog; +} + /** * shell_wm_new: * @plugin: the #MetaPlugin