2017-01-19 18:52:18 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import Clutter from 'gi://Clutter';
|
|
|
|
import GLib from 'gi://GLib';
|
|
|
|
import GObject from 'gi://GObject';
|
|
|
|
import Meta from 'gi://Meta';
|
|
|
|
import Shell from 'gi://Shell';
|
|
|
|
import St from 'gi://St';
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import * as Dialog from './dialog.js';
|
|
|
|
import * as Main from './main.js';
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const FROZEN_WINDOW_BRIGHTNESS = -0.3;
|
|
|
|
const DIALOG_TRANSITION_TIME = 150;
|
|
|
|
const ALIVE_TIMEOUT = 5000;
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const CloseDialog = GObject.registerClass({
|
2019-01-29 01:27:05 +00:00
|
|
|
Implements: [Meta.CloseDialog],
|
2017-01-19 18:52:18 +00:00
|
|
|
Properties: {
|
2019-08-20 21:43:54 +00:00
|
|
|
'window': GObject.ParamSpec.override('window', Meta.CloseDialog),
|
2017-01-19 18:52:18 +00:00
|
|
|
},
|
2017-10-31 01:23:39 +00:00
|
|
|
}, class CloseDialog extends GObject.Object {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init(window) {
|
2017-10-31 01:23:39 +00:00
|
|
|
super._init();
|
2017-01-19 18:52:18 +00:00
|
|
|
this._window = window;
|
|
|
|
this._dialog = null;
|
2018-09-04 11:53:24 +00:00
|
|
|
this._tracked = undefined;
|
2018-05-21 21:12:35 +00:00
|
|
|
this._timeoutId = 0;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
|
|
|
get window() {
|
|
|
|
return this._window;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
|
|
|
set window(window) {
|
|
|
|
this._window = window;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_createDialogContent() {
|
2017-01-19 18:52:18 +00:00
|
|
|
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());
|
2020-01-13 13:07:28 +00:00
|
|
|
let description = _('You may choose to wait a short while for it to ' +
|
2022-12-16 21:33:10 +00:00
|
|
|
'continue or force the app to quit entirely.');
|
2020-01-13 13:07:28 +00:00
|
|
|
return new Dialog.MessageDialogContent({ title, description });
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2019-10-15 14:20:09 +00:00
|
|
|
_updateScale() {
|
2019-12-04 20:23:20 +00:00
|
|
|
// Since this is a child of MetaWindowActor (which, for Wayland clients,
|
2019-10-15 14:20:09 +00:00
|
|
|
// 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.
|
2019-12-04 20:23:20 +00:00
|
|
|
if (this._window.get_client_type() !== Meta.WindowClientType.WAYLAND)
|
2019-10-15 14:20:09 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
|
|
|
this._dialog.set_scale(1 / scaleFactor, 1 / scaleFactor);
|
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_initDialog() {
|
2017-01-19 18:52:18 +00:00
|
|
|
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;
|
|
|
|
|
2019-12-05 07:55:16 +00:00
|
|
|
this._dialog.contentLayout.add_child(this._createDialogContent());
|
2020-03-29 21:51:13 +00:00
|
|
|
this._dialog.addButton({
|
|
|
|
label: _('Force Quit'),
|
|
|
|
action: this._onClose.bind(this),
|
|
|
|
default: true,
|
|
|
|
});
|
|
|
|
this._dialog.addButton({
|
|
|
|
label: _('Wait'),
|
|
|
|
action: this._onWait.bind(this),
|
|
|
|
key: Clutter.KEY_Escape,
|
|
|
|
});
|
2017-01-19 18:52:18 +00:00
|
|
|
|
|
|
|
global.focus_manager.add_group(this._dialog);
|
2019-10-15 14:20:09 +00:00
|
|
|
|
|
|
|
let themeContext = St.ThemeContext.get_for_stage(global.stage);
|
|
|
|
themeContext.connect('notify::scale-factor', this._updateScale.bind(this));
|
|
|
|
|
|
|
|
this._updateScale();
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_addWindowEffect() {
|
2017-01-19 18:52:18 +00:00
|
|
|
// 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);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_removeWindowEffect() {
|
2017-01-19 18:52:18 +00:00
|
|
|
let windowActor = this._window.get_compositor_private();
|
|
|
|
let surfaceActor = windowActor.get_first_child();
|
|
|
|
surfaceActor.remove_effect_by_name("gnome-shell-frozen-window");
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onWait() {
|
2017-01-19 18:52:18 +00:00
|
|
|
this.response(Meta.CloseDialogResponse.WAIT);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onClose() {
|
2017-01-19 18:52:18 +00:00
|
|
|
this.response(Meta.CloseDialogResponse.FORCE_CLOSE);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2018-09-04 11:53:24 +00:00
|
|
|
_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;
|
|
|
|
|
2019-08-20 00:51:42 +00:00
|
|
|
if (shouldTrack) {
|
2018-09-04 11:53:24 +00:00
|
|
|
Main.layoutManager.trackChrome(this._dialog,
|
|
|
|
{ affectsInputRegion: true });
|
2019-08-20 00:51:42 +00:00
|
|
|
} else {
|
2018-09-04 11:53:24 +00:00
|
|
|
Main.layoutManager.untrackChrome(this._dialog);
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2018-09-04 11:53:24 +00:00
|
|
|
|
|
|
|
// 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;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2018-09-04 11:53:24 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
vfunc_show() {
|
2017-01-19 18:52:18 +00:00
|
|
|
if (this._dialog != null)
|
|
|
|
return;
|
|
|
|
|
2018-07-08 15:37:53 +00:00
|
|
|
Meta.disable_unredirect_for_display(global.display);
|
2018-05-21 19:21:05 +00:00
|
|
|
|
2018-05-21 21:12:35 +00:00
|
|
|
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ALIVE_TIMEOUT,
|
|
|
|
() => {
|
|
|
|
this._window.check_alive(global.display.get_current_time_roundtrip());
|
|
|
|
return GLib.SOURCE_CONTINUE;
|
|
|
|
});
|
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
global.display.connectObject(
|
|
|
|
'notify::focus-window', this._onFocusChanged.bind(this), this);
|
2018-09-04 11:53:24 +00:00
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
global.stage.connectObject(
|
|
|
|
'notify::key-focus', this._onFocusChanged.bind(this), this);
|
2018-09-04 11:53:24 +00:00
|
|
|
|
2017-01-19 18:52:18 +00:00
|
|
|
this._addWindowEffect();
|
|
|
|
this._initDialog();
|
|
|
|
|
2019-10-15 14:20:09 +00:00
|
|
|
this._dialog._dialog.scale_y = 0;
|
|
|
|
this._dialog._dialog.set_pivot_point(0.5, 0.5);
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2019-10-15 14:20:09 +00:00
|
|
|
this._dialog._dialog.ease({
|
2018-07-20 19:46:19 +00:00
|
|
|
scale_y: 1,
|
|
|
|
mode: Clutter.AnimationMode.LINEAR,
|
|
|
|
duration: DIALOG_TRANSITION_TIME,
|
2019-08-20 21:43:54 +00:00
|
|
|
onComplete: this._onFocusChanged.bind(this),
|
2018-07-20 19:46:19 +00:00
|
|
|
});
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
vfunc_hide() {
|
2017-01-19 18:52:18 +00:00
|
|
|
if (this._dialog == null)
|
|
|
|
return;
|
|
|
|
|
2018-07-08 15:37:53 +00:00
|
|
|
Meta.enable_unredirect_for_display(global.display);
|
2018-05-21 19:21:05 +00:00
|
|
|
|
2018-05-21 21:12:35 +00:00
|
|
|
GLib.source_remove(this._timeoutId);
|
|
|
|
this._timeoutId = 0;
|
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
global.display.disconnectObject(this);
|
|
|
|
global.stage.disconnectObject(this);
|
2018-09-04 11:53:24 +00:00
|
|
|
|
2020-03-21 23:18:13 +00:00
|
|
|
this._dialog._dialog.remove_all_transitions();
|
|
|
|
|
2017-01-19 18:52:18 +00:00
|
|
|
let dialog = this._dialog;
|
|
|
|
this._dialog = null;
|
|
|
|
this._removeWindowEffect();
|
|
|
|
|
2020-02-19 21:54:42 +00:00
|
|
|
dialog.makeInactive();
|
2019-10-15 14:20:09 +00:00
|
|
|
dialog._dialog.ease({
|
2018-07-20 19:46:19 +00:00
|
|
|
scale_y: 0,
|
|
|
|
mode: Clutter.AnimationMode.LINEAR,
|
|
|
|
duration: DIALOG_TRANSITION_TIME,
|
2019-08-20 21:43:54 +00:00
|
|
|
onComplete: () => dialog.destroy(),
|
2018-07-20 19:46:19 +00:00
|
|
|
});
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-01-19 18:52:18 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
vfunc_focus() {
|
2017-01-19 18:52:18 +00:00
|
|
|
if (this._dialog)
|
|
|
|
this._dialog.grab_key_focus();
|
|
|
|
}
|
|
|
|
});
|