[gnome-shell/T29763: 9/249] Add a dialog to force quit applications



commit 606b36cd4468d548e7d1c2cae20268b257e4684a
Author: Mario Sanchez Prada <mario endlessm com>
Date:   Mon Sep 11 17:30:53 2017 +0100

    Add a dialog to force quit applications
    
    This is an escape hatch for users who are used to Ctrl+Alt+Del allowing
    them to quit applications on their computer.

 data/org.gnome.shell.gschema.xml.in       |   8 ++
 data/theme/gnome-shell-sass/_endless.scss |  42 +++++++++
 js/js-resources.gresource.xml             |   4 +
 js/ui/forceAppExitDialog.js               | 144 ++++++++++++++++++++++++++++++
 js/ui/windowManager.js                    |  16 ++++
 po/POTFILES.in                            |   2 +
 6 files changed, 216 insertions(+)
---
diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in
index 49d38d7662..3d2d0cb200 100644
--- a/data/org.gnome.shell.gschema.xml.in
+++ b/data/org.gnome.shell.gschema.xml.in
@@ -186,6 +186,14 @@
       <default>["&lt;Super&gt;9"]</default>
       <summary>Switch to application 9</summary>
     </key>
+
+    <!-- Endless-specific keys beyond this point -->
+
+    <key name="show-force-app-exit-dialog" type="as">
+      <default>["&lt;Ctrl&gt;&lt;Alt&gt;Delete"]</default>
+      <summary>Keybinding that shows the force app exit dialog</summary>
+      <description></description>
+    </key>
   </schema>
 
   <schema id="org.gnome.shell.app-switcher"
diff --git a/data/theme/gnome-shell-sass/_endless.scss b/data/theme/gnome-shell-sass/_endless.scss
index 88cda2c0e1..b5daac4a97 100644
--- a/data/theme/gnome-shell-sass/_endless.scss
+++ b/data/theme/gnome-shell-sass/_endless.scss
@@ -16,3 +16,45 @@
 #lockDialogGroup {
   background: #12282e;
 }
+
+// Force Apps dialog
+
+.force-app-exit-dialog {
+    max-height: 500px;
+    min-height: 450px;
+    min-width: 470px;
+    border: 1px solid rgba(238, 238, 236, 0.2);
+
+    .modal-dialog-content-box {
+        spacing: 20px;
+        padding: 20px 17px;
+
+        .force-app-exit-dialog-header {
+            font-weight: bold;
+        }
+
+        .force-app-exit-dialog-subtitle {
+            color: #ccc;
+            font-size: 12pt;
+            max-width: 370px;
+        }
+
+        .force-app-exit-dialog-scroll-view {
+            border: 2px solid #666;
+            border-radius: 6px;
+            height: 288px;
+
+            .force-app-exit-dialog-item {
+                border: solid #242424;
+                padding: 16px;
+                spacing: 16px;
+                border-bottom-width: 1px;
+                &:hover { background-color: rgba(255,255,255,0.1); }
+                &:selected {
+                    background-color: $selected_bg_color;
+                    color: $selected_fg_color;
+                }
+            }
+        }
+    }
+}
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 302fdfb42e..b353ec8e3b 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -138,5 +138,9 @@
     <file>ui/status/screencast.js</file>
     <file>ui/status/system.js</file>
     <file>ui/status/thunderbolt.js</file>
+
+    <!-- Endless-specific files beyond this point -->
+
+    <file>ui/forceAppExitDialog.js</file>
   </gresource>
 </gresources>
diff --git a/js/ui/forceAppExitDialog.js b/js/ui/forceAppExitDialog.js
new file mode 100644
index 0000000000..9997c6bc1a
--- /dev/null
+++ b/js/ui/forceAppExitDialog.js
@@ -0,0 +1,144 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+/* exported ForceAppExitDialog */
+
+const { Clutter, GObject, Gtk, Shell, St } = imports.gi;
+
+const Main = imports.ui.main;
+const ModalDialog = imports.ui.modalDialog;
+
+const GNOME_SYSTEM_MONITOR_DESKTOP_ID = 'gnome-system-monitor.desktop';
+const ICON_SIZE = 32;
+
+const ForceAppExitDialogItem = GObject.registerClass({
+    Signals: { 'selected': {} },
+}, class ForceAppExitDialogItem extends St.BoxLayout {
+    _init(app) {
+        super._init({
+            style_class: 'force-app-exit-dialog-item',
+            can_focus: true,
+            reactive: true,
+            track_hover: true,
+        });
+        this.app = app;
+
+        this.connect('key-focus-in', () => this.emit('selected'));
+        let action = new Clutter.ClickAction();
+        action.connect('clicked', this.grab_key_focus.bind(this));
+        this.add_action(action);
+
+        this._icon = this.app.create_icon_texture(ICON_SIZE);
+        this.add(this._icon);
+
+        this._label = new St.Label({
+            text: this.app.get_name(),
+            y_expand: true,
+            y_align: Clutter.ActorAlign.CENTER,
+        });
+        this.label_actor = this._label;
+        this.add(this._label);
+    }
+});
+
+var ForceAppExitDialog = GObject.registerClass(
+class ForceAppExitDialog extends ModalDialog.ModalDialog {
+    _init() {
+        super._init({ styleClass: 'force-app-exit-dialog' });
+
+        let title = new St.Label({
+            style_class: 'force-app-exit-dialog-header',
+            text: _('Quit applications'),
+        });
+        this.contentLayout.add(title);
+
+        let subtitle = new St.Label({
+            style_class: 'force-app-exit-dialog-subtitle',
+            text: _("If an application doesn't respond for a while, select its name and click Quit 
Application."),
+        });
+        subtitle.clutter_text.line_wrap = true;
+        this.contentLayout.add(subtitle, {
+            x_fill: false,
+            x_align: St.Align.START,
+        });
+
+        this._itemBox = new St.BoxLayout({ vertical: true });
+        this._scrollView = new St.ScrollView({
+            style_class: 'force-app-exit-dialog-scroll-view',
+            hscrollbar_policy: Gtk.PolicyType.NEVER,
+            vscrollbar_policy: Gtk.PolicyType.AUTOMATIC,
+            overlay_scrollbars: true,
+            x_expand: true,
+            y_expand: true,
+        });
+        this._scrollView.add_actor(this._itemBox);
+
+        this.contentLayout.add(this._scrollView, { expand: true });
+
+        this._cancelButton = this.addButton({
+            action: this.close.bind(this),
+            label: _('Cancel'),
+            key: Clutter.Escape,
+        });
+
+        let appSystem = Shell.AppSystem.get_default();
+        if (appSystem.lookup_app(GNOME_SYSTEM_MONITOR_DESKTOP_ID)) {
+            this.addButton({
+                action: this._launchSystemMonitor.bind(this),
+                label: _('System Monitor'),
+            }, {
+                x_align: St.Align.END,
+            });
+        }
+
+        this._quitButton = this.addButton({
+            action: this._quitApp.bind(this),
+            label: _('Quit Application'),
+            key: Clutter.Return,
+        }, {
+            expand: true,
+            x_fill: false,
+            x_align: St.Align.END,
+        });
+
+        appSystem.get_running().forEach(app => {
+            let item = new ForceAppExitDialogItem(app);
+            item.connect('selected', this._selectApp.bind(this));
+            this._itemBox.add_child(item);
+        });
+
+        this._selectedAppItem = null;
+        this._updateSensitivity();
+    }
+
+    _updateSensitivity() {
+        let quitSensitive = this._selectedAppItem !== null;
+        this._quitButton.reactive = quitSensitive;
+        this._quitButton.can_focus = quitSensitive;
+    }
+
+    _launchSystemMonitor() {
+        let appSystem = Shell.AppSystem.get_default();
+        let systemMonitor = appSystem.lookup_app(GNOME_SYSTEM_MONITOR_DESKTOP_ID);
+        systemMonitor.activate();
+
+        this.close();
+        Main.overview.hide();
+    }
+
+    _quitApp() {
+        let app = this._selectedAppItem.app;
+        app.request_quit();
+        this.close();
+    }
+
+    _selectApp(appItem) {
+        if (this._selectedAppItem)
+            this._selectedAppItem.remove_style_pseudo_class('selected');
+
+        this._selectedAppItem = appItem;
+        this._updateSensitivity();
+
+        if (this._selectedAppItem)
+            this._selectedAppItem.add_style_pseudo_class('selected');
+    }
+});
diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js
index 21c63b5c32..13ea29dbde 100644
--- a/js/ui/windowManager.js
+++ b/js/ui/windowManager.js
@@ -6,6 +6,7 @@ const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
 const AltTab = imports.ui.altTab;
 const AppFavorites = imports.ui.appFavorites;
 const Dialog = imports.ui.dialog;
+const ForceAppExitDialog = imports.ui.forceAppExitDialog;
 const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
 const InhibitShortcutsDialog = imports.ui.inhibitShortcutsDialog;
 const Main = imports.ui.main;
@@ -789,6 +790,13 @@ var WindowManager = class {
                                         Shell.ActionMode.OVERVIEW,
                                         this._startSwitcher.bind(this));
 
+        this.addKeybinding('show-force-app-exit-dialog',
+                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
+                           Meta.KeyBindingFlags.NONE,
+                           Shell.ActionMode.NORMAL |
+                           Shell.ActionMode.OVERVIEW,
+                           this._showForceAppExitDialog.bind(this));
+
         this.addKeybinding('open-application-menu',
                            new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
@@ -2044,6 +2052,14 @@ var WindowManager = class {
             app.activate();
     }
 
+    _showForceAppExitDialog() {
+        if (!Main.sessionMode.hasOverview)
+            return;
+
+        let dialog = new ForceAppExitDialog.ForceAppExitDialog();
+        dialog.open();
+    }
+
     _toggleAppMenu() {
         Main.panel.toggleAppMenu();
     }
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 92cdaf5ce4..d4ab53a0ed 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -96,3 +96,5 @@ subprojects/extensions-tool/src/command-uninstall.c
 subprojects/extensions-tool/src/main.c
 # Please do not remove this file from POTFILES.in. Run "git submodule init && git submodule update" to get 
it.
 subprojects/gvc/gvc-mixer-control.c
+# Endless-specific files beyond this point
+js/ui/forceAppExitDialog.js


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]