Skip to content

Commit 1e98ba0

Browse files
authored
Merge pull request #78 from nlpsuge/feature-close-windows-whitelist
Feature: close windows whitelist
2 parents 4642487 + a609813 commit 1e98ba0

16 files changed

Lines changed: 854 additions & 367 deletions

README.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,16 @@ sudo dnf install ydotool
9999

100100
#Check the permission of `/dev/uinput`, if it's `crw-rw----+`, you can skip step 2
101101
# 2. Get permission to access to `/dev/uinput` as the normal user
102-
sudo touch /etc/udev/rules.d/60-awsm-ydotool-uinput.rules
103-
sudo echo '# See:
104-
# https://github.com/ValveSoftware/steam-devices/blob/master/60-steam-input.rules
105-
# https://github.com/ReimuNotMoe/ydotool/issues/25
106-
107-
# ydotool udev write access
108-
KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", OPTIONS+="static_node=uinput"' > /etc/udev/rules.d/60-awsm-ydotool-uinput.rules
102+
sudo touch /etc/udev/rules.d/60-awsm-ydotool-uinput.
103+
# Here we use `tee`, not redirect(>), to avoid `warning: An error occurred while redirecting file '/etc/udev/rules.d/60-awsm-ydotool-uinput.rules' open: Permission denied`
104+
# See: https://www.shellhacks.com/sudo-echo-to-file-permission-denied/
105+
echo '# See:
106+
# https://github.com/ValveSoftware/steam-devices/blob/master/60-steam-input.rules
107+
# https://github.com/ReimuNotMoe/ydotool/issues/25
108+
109+
# ydotool udev write access
110+
KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", OPTIONS+="static_node=uinput"' | sudo tee --append /etc/udev/rules.d/60-awsm-ydotool-uinput.rules
111+
cat /etc/udev/rules.d/60-awsm-ydotool-uinput.rules
109112
#Remove executable permission (a.k.a. x)
110113
sudo chmod 644 /etc/udev/rules.d/60-awsm-ydotool-uinput.rules
111114

@@ -201,6 +204,14 @@ To install it:
201204
* Arch and derivatives:
202205
`pacman -S libgtop`
203206

207+
# Developing guidance
208+
209+
To compile the `data/org.gnome.shell.extensions.another-window-session-manager.gresource.xml`, use the bellow command:
210+
```shell
211+
cd root_of_this_project
212+
glib-compile-resources --sourcedir="data" --generate data/org.gnome.shell.extensions.another-window-session-manager.gresource.xml
213+
```
214+
204215
# Known issues
205216

206217
1. On both X11 and Wayland, if click restore button (<img src=icons/restore-symbolic.svg width="14" height="14">) continually during the process of restoring, the window size and position may can't be restored, and it may restore many instances of an application. **As a workaround, click the restore button (<img src=icons/restore-symbolic.svg width="14" height="14">) only once until all apps are restored.**

closeSession.js

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ const Constants = Me.imports.constants;
1818

1919
const UiHelper = Me.imports.ui.uiHelper;
2020

21+
var flags = {
22+
closeWindows: 1 << 0,
23+
logoff: 1 << 1,
24+
};
25+
26+
var allFlags = flags.closeWindows | flags.logoff;
2127

2228
var CloseSession = class {
23-
constructor() {
29+
constructor(flags) {
2430
this._log = new Log.Log();
2531
this._prefsUtils = new PrefsUtils.PrefsUtils();
2632
this._settings = this._prefsUtils.getSettings();
@@ -32,9 +38,7 @@ var CloseSession = class {
3238
flags: (Gio.SubprocessFlags.STDOUT_PIPE |
3339
Gio.SubprocessFlags.STDERR_PIPE)});
3440

35-
// TODO Put into Settings
36-
// All apps in the whitelist should be closed safely, no worrying about lost data
37-
this.whitelist = ['org.gnome.Terminal.desktop', 'org.gnome.Nautilus.desktop', 'smplayer.desktop'];
41+
this.flags = flags ? flags : allFlags;
3842
}
3943

4044
async closeWindows(workspacePersistent) {
@@ -129,18 +133,24 @@ var CloseSession = class {
129133
let reason;
130134
try {
131135
if (app.get_n_windows() > 1) {
132-
const appInWhitelist = this.whitelist.includes(app.get_id());
136+
const whitelistString = this._settings.get_string('close-windows-whitelist');
137+
const whitelist = JSON.parse(whitelistString);
138+
133139
let windows = this._sortWindows(app);
134140
for (let i = windows.length - 1; i >= 0; i--) {
135141
let window = windows[i];
136-
if (!window.can_close()) {
142+
const isInWhitelist = this._isInWhitelist(whitelist, window);
143+
if (isInWhitelist) {
144+
Log.Log.getDefault().info(`${app.get_name()} is in the whitelist`);
145+
}
146+
if (!window.can_close() && !isInWhitelist) {
137147
closed = false;
138148
reason = 'it has unclosable window(s)';
139149
continue;
140150
}
141151

142152
if (UiHelper.isDialog(window)
143-
|| appInWhitelist
153+
|| isInWhitelist
144154
|| !this._skip_app_with_multiple_windows)
145155
{
146156
closed = await this._deleteWindow(app, window);
@@ -172,6 +182,27 @@ var CloseSession = class {
172182
return [closed, reason];
173183
}
174184

185+
_isInWhitelist(whitelist, window) {
186+
for (const id in whitelist) {
187+
const row = whitelist[id];
188+
const {
189+
id_, enabled, name, compareWith, method,
190+
enableWhenCloseWindows, enableWhenLogout
191+
} = row;
192+
if (!enabled || !name) continue;
193+
194+
let compareWithValue = Function.callFunc(window, Meta.Window.prototype[`get_${compareWith}`]);
195+
const matched = this._ruleMatched(compareWithValue, method, name);
196+
if (matched) {
197+
let _flags = 0;
198+
if (enableWhenCloseWindows) _flags = _flags | flags.closeWindows;
199+
if (enableWhenLogout) _flags = _flags | flags.logoff;
200+
return true && (this.flags & _flags);
201+
}
202+
}
203+
return false;
204+
}
205+
175206
_deleteWindow(app, metaWindow) {
176207
return new Promise((resolve, reject) => {
177208
// We use 'windows-changed' here because a confirm window could be popped up
@@ -360,7 +391,9 @@ var CloseSession = class {
360391
Log.Log.getDefault().debug(`Finished to start ydotool.service. Started: ${started}`);
361392
resolve(started);
362393
} catch (error) {
363-
Log.Log.getDefault().error(error, 'Failed to start ydotool.service');
394+
const msg = 'Failed to start ydotool.service';
395+
Log.Log.getDefault().error(error, msg);
396+
global.notify_error(`${msg}`, `${error ? error.message : ''}`);
364397
reject(false);
365398
}
366399
});
@@ -531,18 +564,6 @@ var CloseSession = class {
531564
}
532565
}
533566

534-
_skip_multiple_windows(shellApp) {
535-
if (shellApp.get_n_windows() > 1 && this._skip_app_with_multiple_windows) {
536-
const app_id = shellApp.get_id();
537-
if (this.whitelist.includes(app_id)) {
538-
this._log.debug(`${shellApp.get_name()} (${app_id}) in the whitelist. Closing it anyway.`);
539-
return false;
540-
}
541-
return true;
542-
}
543-
return false;
544-
}
545-
546567
destroy() {
547568
if (this._defaultAppSystem) {
548569
this._defaultAppSystem = null;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<interface>
3+
<requires lib="gtk" version="4.0"/>
4+
<template class="AwsmColumnViewSimple" parent="GtkBox">
5+
<property name="orientation">vertical</property>
6+
<child>
7+
<object class="GtkBox">
8+
<property name="orientation">vertical</property>
9+
<style>
10+
<class name="view"/>
11+
</style>
12+
<child>
13+
<object class="GtkColumnView" id="list">
14+
<property name="show-column-separators">true</property>
15+
<style>
16+
<class name="data-table"/>
17+
<class name="list"/>
18+
</style>
19+
<child>
20+
<object class="GtkColumnViewColumn" id="enabled">
21+
<property name="title" translatable="yes">Enabled</property>
22+
<property name="factory">
23+
<object class="GtkSignalListItemFactory">
24+
<signal name="setup" handler="setup_enabled_cb"/>
25+
<signal name="bind" handler="bind_enabled_cb"/>
26+
</object>
27+
</property>
28+
</object>
29+
</child>
30+
<child>
31+
<object class="GtkColumnViewColumn" id="operation">
32+
<property name="title" translatable="yes">Operation</property>
33+
<property name="expand">0</property>
34+
<property name="factory">
35+
<object class="GtkSignalListItemFactory">
36+
<signal name="setup" handler="setup_operation_cb"/>
37+
<signal name="bind" handler="bind_operation_cb"/>
38+
</object>
39+
</property>
40+
</object>
41+
</child>
42+
</object>
43+
</child>
44+
</object>
45+
</child>
46+
</template>
47+
</interface>

model/closeWindowsRule.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
const { GObject } = imports.gi;
44

5+
6+
var CloseWindowsWhitelist = GObject.registerClass({
7+
}, class CloseWindowsWhitelist extends GObject.Object {
8+
id; // int. just like the id in MySQL. Used to update or delete rows.
9+
name; // string. Can be any string
10+
compareWith; // string. title, wm_class, wm_class_instance, app_name...
11+
method; // string. equals
12+
enabled; // boolean
13+
enableWhenCloseWindows; // boolean
14+
enableWhenLogout; // boolean
15+
16+
static new(param) {
17+
return Object.assign(new CloseWindowsWhitelist(), param);
18+
}
19+
});
20+
521
var CloseWindowsRuleBase = class {
622
category; // string. Applications, Keywords
723
type; // string, rule type, such as 'shortcut'

prefs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ const Prefs = GObject.registerClass(
266266
} else {
267267
this._log.error(new Error(`Failed to create folder: ${autostart_restore_desktop_file_path_parent}`));
268268
}
269-
}
269+
}
270270
}
271271
);
272272

0 commit comments

Comments
 (0)