Skip to content

Commit

Permalink
Convert the force quit dialog to a cinnamon dialog (#12408)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JosephMcc authored Oct 2, 2024
1 parent dac9613 commit 4096ea2
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 170 deletions.
104 changes: 0 additions & 104 deletions files/usr/bin/cinnamon-close-dialog

This file was deleted.

222 changes: 222 additions & 0 deletions js/ui/closeDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Meta = imports.gi.Meta;
const St = imports.gi.St;
const Cinnamon = imports.gi.Cinnamon;

const Dialog = imports.ui.dialog;
const Main = imports.ui.main;

const FROZEN_WINDOW_BRIGHTNESS = -0.3;
const DIALOG_TRANSITION_TIME = 150;
const ALIVE_TIMEOUT = 5000;

var CloseDialog = GObject.registerClass({
Implements: [Meta.CloseDialog],
Properties: {
'window': GObject.ParamSpec.override('window', Meta.CloseDialog),
},
}, class CloseDialog extends GObject.Object {
_init(window) {
super._init();
this._window = window;
this._dialog = null;
this._tracked = undefined;
this._timeoutId = 0;
this._windowFocusChangedId = 0;
this._keyFocusChangedId = 0;
}

get window() {
return this._window;
}

set window(window) {
this._window = window;
}

_createDialogContent() {
let tracker = Cinnamon.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 description = _('You may choose to wait a short while for it to ' +
'continue or force the app to quit entirely');
return new Dialog.MessageDialogContent({title, description});
}

_updateScale() {
// Since this is a child of MetaWindowActor (which, for Wayland clients,
// applies the geometry scale factor to its children itself, see
// meta_window_actor_set_geometry_scale()), make sure we don't apply
// the factor twice in the end.
if (this._window.get_client_type() !== Meta.WindowClientType.WAYLAND)
return;

let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
this._dialog.set_scale(1 / scaleFactor, 1 / scaleFactor);
}

_initDialog() {
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.contentLayout.add_child(this._createDialogContent());
this._dialog.addButton({
label: _('Wait'),
action: this._onWait.bind(this),
key: Clutter.KEY_Escape,
});
this._dialog.addButton({
label: _('Force Quit'),
action: this._onClose.bind(this),
destructive_action: true,
});

global.focus_manager.add_group(this._dialog);

let themeContext = St.ThemeContext.get_for_stage(global.stage);
themeContext.connect('notify::scale-factor', this._updateScale.bind(this));

this._updateScale();
}

_addWindowEffect() {
// 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("cinnamon-frozen-window", effect);
}

_removeWindowEffect() {
let windowActor = this._window.get_compositor_private();
let surfaceActor = windowActor.get_first_child();
surfaceActor.remove_effect_by_name("cinnamon-frozen-window");
}

_onWait() {
this.response(Meta.CloseDialogResponse.WAIT);
}

_onClose() {
this.response(Meta.CloseDialogResponse.FORCE_CLOSE);
}

_onFocusChanged() {
if (Meta.is_wayland_compositor())
return;

let focusWindow = global.display.focus_window;
let keyFocus = global.stage.key_focus;

let shouldTrack;
if (focusWindow != null)
shouldTrack = focusWindow == this._window;
else
shouldTrack = keyFocus && this._dialog.contains(keyFocus);

if (this._tracked === shouldTrack)
return;

if (shouldTrack)
Main.layoutManager.trackChrome(this._dialog,
{ affectsInputRegion: true });
else
Main.layoutManager.untrackChrome(this._dialog);

// The buttons are broken when they aren't added to the input region,
// so disable them properly in that case
this._dialog.buttonLayout.get_children().forEach(b => {
b.reactive = shouldTrack;
});

this._tracked = shouldTrack;
}

vfunc_show() {
if (this._dialog != null)
return;

Meta.disable_unredirect_for_display(global.display);

this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ALIVE_TIMEOUT,
() => {
this._window.check_alive(global.display.get_current_time_roundtrip());
return GLib.SOURCE_CONTINUE;
});

this._windowFocusChangedId =
global.display.connect('notify::focus-window',
this._onFocusChanged.bind(this));

this._keyFocusChangedId =
global.stage.connect('notify::key-focus',
this._onFocusChanged.bind(this));

this._addWindowEffect();
this._initDialog();

this._dialog._dialog.scale_y = 0;
this._dialog._dialog.set_pivot_point(0.5, 0.5);

this._dialog._dialog.ease({
scale_y: 1,
mode: Clutter.AnimationMode.LINEAR,
duration: DIALOG_TRANSITION_TIME,
onComplete: this._onFocusChanged.bind(this)
});
}

vfunc_hide() {
if (this._dialog == null)
return;

Meta.enable_unredirect_for_display(global.display);

GLib.source_remove(this._timeoutId);
this._timeoutId = 0;

global.display.disconnect(this._windowFocusChangedId);
this._windowFocusChangedId = 0;

global.stage.disconnect(this._keyFocusChangedId);
this._keyFocusChangedId = 0;

this._dialog._dialog.remove_all_transitions();

let dialog = this._dialog;
this._dialog = null;
this._removeWindowEffect();

dialog.makeInactive();
dialog._dialog.ease({
scale_y: 0,
mode: Clutter.AnimationMode.LINEAR,
duration: DIALOG_TRANSITION_TIME,
onComplete: () => dialog.destroy(),
});
}

vfunc_focus() {
if (!this._dialog)
return;

const keyFocus = global.stage.key_focus;
if (!keyFocus || !this._dialog.contains(keyFocus))
this._dialog.initialKeyFocus.grab_key_focus();
}
});
8 changes: 3 additions & 5 deletions js/ui/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,7 @@ LayoutManager.prototype = {
* - addToWindowgroup (boolean): The actor should be added as a top-level window.
* - doNotAdd (boolean): The actor should not be added to the uiGroup. This has no effect if %addToWindowgroup is %true.
*
* Tells the chrome to track @actor, which must be a descendant
* of an actor added via addChrome(). This can be used to extend the
* Tells the chrome to track @actor. This can be used to extend the
* struts or input region to cover specific children.
*
* @params can have any of the same values as in addChrome(),
Expand Down Expand Up @@ -637,10 +636,9 @@ Chrome.prototype = {
ancestor = ancestor.get_parent();
index = this._findActor(ancestor);
}
if (!ancestor)
throw new Error('actor is not a descendent of a chrome actor');

let ancestorData = this._trackedActors[index];
let ancestorData = ancestor ? this._trackedActors[index]
: defaultParams;
if (!params)
params = {};
// We can't use Params.parse here because we want to drop
Expand Down
5 changes: 3 additions & 2 deletions js/ui/windowManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const GObject = imports.gi.GObject;
const AppSwitcher = imports.ui.appSwitcher.appSwitcher;
const ModalDialog = imports.ui.modalDialog;
const WmGtkDialogs = imports.ui.wmGtkDialogs;
const CloseDialog = imports.ui.closeDialog;
const WorkspaceOsd = imports.ui.workspaceOsd;

const {CoverflowSwitcher} = imports.ui.appSwitcher.coverflowSwitcher;
Expand Down Expand Up @@ -1392,8 +1393,8 @@ var WindowManager = class WindowManager {
}
}

_createCloseDialog(shellwm, window) {
return new WmGtkDialogs.CloseDialog(window);
_createCloseDialog(cinnamonwm, window) {
return new CloseDialog.CloseDialog(window);
}

_confirmDisplayChange() {
Expand Down
Loading

0 comments on commit 4096ea2

Please sign in to comment.