Skip to content

Commit 4096ea2

Browse files
authored
Convert the force quit dialog to a cinnamon dialog (#12408)
* layout.js: Allow adding already parented actors for chrome tracking We need this for the force-close dialog. It will be included in the MetaWindowGroup, but needs to tracked as chrome to recieve pointer events * Make the force quit dialog a Cinnamon dialog With the new dialogs and theming in place we can start looking at converting some of these away from Gtk dialogs * closeDialog: Switch the buttons around Gtk dialogs have the action on the right so move the Force Quit button to the right side to match the layout users are accustomed to
1 parent dac9613 commit 4096ea2

File tree

5 files changed

+228
-170
lines changed

5 files changed

+228
-170
lines changed

files/usr/bin/cinnamon-close-dialog

Lines changed: 0 additions & 104 deletions
This file was deleted.

js/ui/closeDialog.js

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
2+
3+
const Clutter = imports.gi.Clutter;
4+
const Gio = imports.gi.Gio;
5+
const GLib = imports.gi.GLib;
6+
const GObject = imports.gi.GObject;
7+
const Meta = imports.gi.Meta;
8+
const St = imports.gi.St;
9+
const Cinnamon = imports.gi.Cinnamon;
10+
11+
const Dialog = imports.ui.dialog;
12+
const Main = imports.ui.main;
13+
14+
const FROZEN_WINDOW_BRIGHTNESS = -0.3;
15+
const DIALOG_TRANSITION_TIME = 150;
16+
const ALIVE_TIMEOUT = 5000;
17+
18+
var CloseDialog = GObject.registerClass({
19+
Implements: [Meta.CloseDialog],
20+
Properties: {
21+
'window': GObject.ParamSpec.override('window', Meta.CloseDialog),
22+
},
23+
}, class CloseDialog extends GObject.Object {
24+
_init(window) {
25+
super._init();
26+
this._window = window;
27+
this._dialog = null;
28+
this._tracked = undefined;
29+
this._timeoutId = 0;
30+
this._windowFocusChangedId = 0;
31+
this._keyFocusChangedId = 0;
32+
}
33+
34+
get window() {
35+
return this._window;
36+
}
37+
38+
set window(window) {
39+
this._window = window;
40+
}
41+
42+
_createDialogContent() {
43+
let tracker = Cinnamon.WindowTracker.get_default();
44+
let windowApp = tracker.get_window_app(this._window);
45+
46+
/* Translators: %s is an application name */
47+
let title = _('“%s” Is Not Responding').format(windowApp.get_name());
48+
let description = _('You may choose to wait a short while for it to ' +
49+
'continue or force the app to quit entirely');
50+
return new Dialog.MessageDialogContent({title, description});
51+
}
52+
53+
_updateScale() {
54+
// Since this is a child of MetaWindowActor (which, for Wayland clients,
55+
// applies the geometry scale factor to its children itself, see
56+
// meta_window_actor_set_geometry_scale()), make sure we don't apply
57+
// the factor twice in the end.
58+
if (this._window.get_client_type() !== Meta.WindowClientType.WAYLAND)
59+
return;
60+
61+
let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
62+
this._dialog.set_scale(1 / scaleFactor, 1 / scaleFactor);
63+
}
64+
65+
_initDialog() {
66+
if (this._dialog)
67+
return;
68+
69+
let windowActor = this._window.get_compositor_private();
70+
this._dialog = new Dialog.Dialog(windowActor, 'close-dialog');
71+
this._dialog.width = windowActor.width;
72+
this._dialog.height = windowActor.height;
73+
74+
this._dialog.contentLayout.add_child(this._createDialogContent());
75+
this._dialog.addButton({
76+
label: _('Wait'),
77+
action: this._onWait.bind(this),
78+
key: Clutter.KEY_Escape,
79+
});
80+
this._dialog.addButton({
81+
label: _('Force Quit'),
82+
action: this._onClose.bind(this),
83+
destructive_action: true,
84+
});
85+
86+
global.focus_manager.add_group(this._dialog);
87+
88+
let themeContext = St.ThemeContext.get_for_stage(global.stage);
89+
themeContext.connect('notify::scale-factor', this._updateScale.bind(this));
90+
91+
this._updateScale();
92+
}
93+
94+
_addWindowEffect() {
95+
// We set the effect on the surface actor, so the dialog itself
96+
// (which is a child of the MetaWindowActor) does not get the
97+
// effect applied itself.
98+
let windowActor = this._window.get_compositor_private();
99+
let surfaceActor = windowActor.get_first_child();
100+
let effect = new Clutter.BrightnessContrastEffect();
101+
effect.set_brightness(FROZEN_WINDOW_BRIGHTNESS);
102+
surfaceActor.add_effect_with_name("cinnamon-frozen-window", effect);
103+
}
104+
105+
_removeWindowEffect() {
106+
let windowActor = this._window.get_compositor_private();
107+
let surfaceActor = windowActor.get_first_child();
108+
surfaceActor.remove_effect_by_name("cinnamon-frozen-window");
109+
}
110+
111+
_onWait() {
112+
this.response(Meta.CloseDialogResponse.WAIT);
113+
}
114+
115+
_onClose() {
116+
this.response(Meta.CloseDialogResponse.FORCE_CLOSE);
117+
}
118+
119+
_onFocusChanged() {
120+
if (Meta.is_wayland_compositor())
121+
return;
122+
123+
let focusWindow = global.display.focus_window;
124+
let keyFocus = global.stage.key_focus;
125+
126+
let shouldTrack;
127+
if (focusWindow != null)
128+
shouldTrack = focusWindow == this._window;
129+
else
130+
shouldTrack = keyFocus && this._dialog.contains(keyFocus);
131+
132+
if (this._tracked === shouldTrack)
133+
return;
134+
135+
if (shouldTrack)
136+
Main.layoutManager.trackChrome(this._dialog,
137+
{ affectsInputRegion: true });
138+
else
139+
Main.layoutManager.untrackChrome(this._dialog);
140+
141+
// The buttons are broken when they aren't added to the input region,
142+
// so disable them properly in that case
143+
this._dialog.buttonLayout.get_children().forEach(b => {
144+
b.reactive = shouldTrack;
145+
});
146+
147+
this._tracked = shouldTrack;
148+
}
149+
150+
vfunc_show() {
151+
if (this._dialog != null)
152+
return;
153+
154+
Meta.disable_unredirect_for_display(global.display);
155+
156+
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ALIVE_TIMEOUT,
157+
() => {
158+
this._window.check_alive(global.display.get_current_time_roundtrip());
159+
return GLib.SOURCE_CONTINUE;
160+
});
161+
162+
this._windowFocusChangedId =
163+
global.display.connect('notify::focus-window',
164+
this._onFocusChanged.bind(this));
165+
166+
this._keyFocusChangedId =
167+
global.stage.connect('notify::key-focus',
168+
this._onFocusChanged.bind(this));
169+
170+
this._addWindowEffect();
171+
this._initDialog();
172+
173+
this._dialog._dialog.scale_y = 0;
174+
this._dialog._dialog.set_pivot_point(0.5, 0.5);
175+
176+
this._dialog._dialog.ease({
177+
scale_y: 1,
178+
mode: Clutter.AnimationMode.LINEAR,
179+
duration: DIALOG_TRANSITION_TIME,
180+
onComplete: this._onFocusChanged.bind(this)
181+
});
182+
}
183+
184+
vfunc_hide() {
185+
if (this._dialog == null)
186+
return;
187+
188+
Meta.enable_unredirect_for_display(global.display);
189+
190+
GLib.source_remove(this._timeoutId);
191+
this._timeoutId = 0;
192+
193+
global.display.disconnect(this._windowFocusChangedId);
194+
this._windowFocusChangedId = 0;
195+
196+
global.stage.disconnect(this._keyFocusChangedId);
197+
this._keyFocusChangedId = 0;
198+
199+
this._dialog._dialog.remove_all_transitions();
200+
201+
let dialog = this._dialog;
202+
this._dialog = null;
203+
this._removeWindowEffect();
204+
205+
dialog.makeInactive();
206+
dialog._dialog.ease({
207+
scale_y: 0,
208+
mode: Clutter.AnimationMode.LINEAR,
209+
duration: DIALOG_TRANSITION_TIME,
210+
onComplete: () => dialog.destroy(),
211+
});
212+
}
213+
214+
vfunc_focus() {
215+
if (!this._dialog)
216+
return;
217+
218+
const keyFocus = global.stage.key_focus;
219+
if (!keyFocus || !this._dialog.contains(keyFocus))
220+
this._dialog.initialKeyFocus.grab_key_focus();
221+
}
222+
});

js/ui/layout.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,7 @@ LayoutManager.prototype = {
492492
* - addToWindowgroup (boolean): The actor should be added as a top-level window.
493493
* - doNotAdd (boolean): The actor should not be added to the uiGroup. This has no effect if %addToWindowgroup is %true.
494494
*
495-
* Tells the chrome to track @actor, which must be a descendant
496-
* of an actor added via addChrome(). This can be used to extend the
495+
* Tells the chrome to track @actor. This can be used to extend the
497496
* struts or input region to cover specific children.
498497
*
499498
* @params can have any of the same values as in addChrome(),
@@ -637,10 +636,9 @@ Chrome.prototype = {
637636
ancestor = ancestor.get_parent();
638637
index = this._findActor(ancestor);
639638
}
640-
if (!ancestor)
641-
throw new Error('actor is not a descendent of a chrome actor');
642639

643-
let ancestorData = this._trackedActors[index];
640+
let ancestorData = ancestor ? this._trackedActors[index]
641+
: defaultParams;
644642
if (!params)
645643
params = {};
646644
// We can't use Params.parse here because we want to drop

js/ui/windowManager.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const GObject = imports.gi.GObject;
1313
const AppSwitcher = imports.ui.appSwitcher.appSwitcher;
1414
const ModalDialog = imports.ui.modalDialog;
1515
const WmGtkDialogs = imports.ui.wmGtkDialogs;
16+
const CloseDialog = imports.ui.closeDialog;
1617
const WorkspaceOsd = imports.ui.workspaceOsd;
1718

1819
const {CoverflowSwitcher} = imports.ui.appSwitcher.coverflowSwitcher;
@@ -1392,8 +1393,8 @@ var WindowManager = class WindowManager {
13921393
}
13931394
}
13941395

1395-
_createCloseDialog(shellwm, window) {
1396-
return new WmGtkDialogs.CloseDialog(window);
1396+
_createCloseDialog(cinnamonwm, window) {
1397+
return new CloseDialog.CloseDialog(window);
13971398
}
13981399

13991400
_confirmDisplayChange() {

0 commit comments

Comments
 (0)