%PDF- %PDF-
| Direktori : /usr/share/gnome-shell/extensions/ding@rastersoft.com/ |
| Current File : //usr/share/gnome-shell/extensions/ding@rastersoft.com/emulateX11WindowType.js |
/* Emulate X11WindowType
*
* Copyright (C) 2020 Sergio Costas (rastersoft@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* exported EmulateX11WindowType */
'use strict';
import GLib from 'gi://GLib'
import Meta from 'gi://Meta'
import * as Main from 'resource:///org/gnome/shell/ui/main.js'
class ManageWindow {
/* This class is added to each managed window, and it's used to
make it behave like an X11 Desktop window.
Trusted windows will set in the title the characters @!, followed
by the coordinates where to put the window separated by a colon, and
ended in semicolon. After that, it can have one or more of these letters
* B : put this window at the bottom of the screen
* T : put this window at the top of the screen
* D : show this window in all desktops
* H : hide this window from window list
Using the title is generally not a problem because the desktop windows
doesn't have a tittle. But some other windows may have and still need to
take advantage of this, so adding a single blank space at the end of the
title is equivalent to @!H, and having two blank spaces at the end of the
title is equivalent to @!HTD. This allows to take advantage of these flags
even to decorated windows.
*/
constructor(window, waylandClient, X11Emulator, changedStatusCB) {
this._waylandClient = waylandClient;
this._X11Emulator = X11Emulator;
this._window = window;
this._signalIDs = [];
this._changedStatusCB = changedStatusCB;
this._signalIDs.push(window.connect_after('raised', () => {
if (this._keepAtBottom && !this._keepAtTop) {
this._window.lower();
}
}));
this._signalIDs.push(window.connect('position-changed', () => {
if (this._fixed && (this._x !== null) && (this._y !== null)) {
this._window.move_frame(true, this._x, this._y);
}
}));
this._signalIDs.push(window.connect('notify::title', () => {
this._parseTitle();
}));
this._signalIDs.push(window.connect('notify::above', () => {
if (this._keepAtBottom && this._window.above) {
this._window.unmake_above();
}
}));
this._signalIDs.push(window.connect('notify::minimized', () => {
this._window.unminimize();
}));
this._signalIDs.push(window.connect('notify::maximized-vertically', () => {
if (!window.maximized_vertically) {
window.maximize(Meta.MaximizeFlags.VERTICAL);
}
this._moveIntoPlace();
}));
this._signalIDs.push(window.connect('notify::maximized-horizontally', () => {
if (!window.maximized_horizontally) {
window.maximize(Meta.MaximizeFlags.HORIZONTAL);
}
this._moveIntoPlace();
}));
this._parseTitle();
}
_moveIntoPlace() {
if (this._moveIntoPlaceID) {
GLib.source_remove(this._moveIntoPlaceID);
}
this._moveIntoPlaceID = GLib.timeout_add(GLib.PRIORITY_LOW, 250, () => {
if (this._fixed && (this._x !== null) && (this._y !== null)) {
this._window.move_frame(true, this._x, this._y);
}
this._moveIntoPlaceID = 0;
return GLib.SOURCE_REMOVE;
});
}
disconnect() {
for (let signalID of this._signalIDs) {
this._window.disconnect(signalID);
}
if (this._moveIntoPlaceID) {
GLib.source_remove(this._moveIntoPlaceID);
}
if (this._keepAtTop) {
this._window.unmake_above();
}
this._window = null;
this._waylandClient = null;
}
setWaylandClient(client) {
this._waylandClient = client;
}
_parseTitle() {
this._x = null;
this._y = null;
this._keepAtBottom = false;
let keepAtTop = this._keepAtTop;
this._keepAtTop = false;
this._showInAllDesktops = false;
this._hideFromWindowList = false;
this._fixed = false;
let title = this._window.get_title();
if (title != null) {
if ((title.length > 0) && (title[title.length - 1] == ' ')) {
if ((title.length > 1) && (title[title.length - 2] == ' ')) {
title = '@!HTD';
} else {
title = '@!H';
}
}
let pos = title.search('@!');
if (pos != -1) {
let pos2 = title.search(';', pos);
let coords;
if (pos2 != -1) {
coords = title.substring(pos + 2, pos2).trim().split(',');
} else {
coords = title.substring(pos + 2).trim().split(',');
}
try {
this._x = parseInt(coords[0]);
this._y = parseInt(coords[1]);
} catch (e) {
console.log(`Exception ${e.message}.\n${e.stack}`);
}
try {
let extraChars = title.substring(pos + 2).trim().toUpperCase();
for (let char of extraChars) {
switch (char) {
case 'B':
this._keepAtBottom = true;
this._keepAtTop = false;
break;
case 'T':
this._keepAtTop = true;
this._keepAtBottom = false;
break;
case 'D':
this._showInAllDesktops = true;
break;
case 'H':
this._hideFromWindowList = true;
break;
case 'F':
this._fixed = true;
break;
}
}
} catch (e) {
console.log(`Exception ${e.message}.\n${e.stack}`);
}
}
// This string must match the one at desktopManager.js
if (title.startsWith("Desktop Icons ")) {
this._keepAtBottom = true;
this._keepAtTop = false;
this._showInAllDesktops = true;
this._hideFromWindowList = true;
this._fixed = true;
try {
const desktopIndex = parseInt(title.substring(14).trim());
const desktopData = this._X11Emulator.getMonitorData(desktopIndex - 1);
this._x = desktopData.x;
this._y = desktopData.y;
} catch (e) {
console.log(`Exception ${e.message}.\n${e.stack}`);
}
}
if (this._waylandClient) {
if (this._hideFromWindowList) {
this._waylandClient.hide_from_window_list(this._window);
} else {
this._waylandClient.show_in_window_list(this._window);
}
}
if (this._keepAtTop != keepAtTop) {
if (this._keepAtTop) {
this._window.make_above();
} else {
this._window.unmake_above();
}
}
if (this._keepAtBottom) {
this._window.lower();
}
if (this._fixed && (this._x !== null) && (this._y !== null)) {
this._window.move_frame(true, this._x, this._y);
}
this._changedStatusCB(this);
}
}
refreshState(checkWorkspace) {
if (checkWorkspace && this._showInAllDesktops) {
let currentWorkspace = global.workspace_manager.get_active_workspace();
if (!this._window.located_on_workspace(currentWorkspace)) {
this._window.change_workspace(currentWorkspace);
}
}
if (this._keepAtBottom) {
this._window.lower();
}
}
get hideFromWindowList() {
return this._hideFromWindowList;
}
get keepAtBottom() {
return this._keepAtBottom;
}
}
export class EmulateX11WindowType {
/*
This class makes all the heavy lifting for emulating WindowType.
Just make one instance of it, call enable(), and whenever a window
that you want to give "superpowers" is mapped, add it with the
"addWindow" method. That's all.
*/
constructor() {
this._isX11 = !Meta.is_wayland_compositor();
this._windowList = [];
this._enableRefresh = true;
this._waylandClient = null;
this._monitorData = [];
}
setMonitorData(data) {
this._monitorData = data;
}
getMonitorData(idx) {
if (idx >= this._monitorData.length) {
return null;
}
return this._monitorData[idx];
}
setWaylandClient(client) {
this._waylandClient = client;
for (let window of this._windowList) {
if (window.customJS_ding) {
window.customJS_ding.setWaylandClient(this._waylandClient);
}
}
}
enable() {
if (this._isX11) {
return;
}
this._idMap = global.window_manager.connect_after('map', (obj, windowActor) => {
let window = windowActor.get_meta_window();
if (this._waylandClient && this._waylandClient.query_window_belongs_to(window)) {
this.addWindow(window);
}
this._refreshWindows(false);
});
this._idDestroy = global.window_manager.connect_after('destroy', (wm, windowActor) => {
// if a window is closed, ensure that the desktop doesn't receive the focus
let window = windowActor.get_meta_window();
if (window && (window.get_window_type() >= Meta.WindowType.DROPDOWN_MENU)) {
return;
}
this._refreshWindows(true);
});
/* Something odd happens with "stick" when using popup submenus, so
this implements the same functionality
*/
this._switchWorkspaceId = global.window_manager.connect('switch-workspace', () => {
this._refreshWindows(true);
});
/* But in Overview mode it is paramount to not change the workspace to emulate
"stick", or the windows will appear
*/
this._showingId = Main.overview.connect('showing', () => {
this._enableRefresh = false;
});
this._hidingId = Main.overview.connect('hiding', () => {
this._enableRefresh = true;
this._refreshWindows(true);
});
}
disable() {
if (this._isX11) {
return;
}
if (this._activate_window_ID) {
GLib.source_remove(this._activate_window_ID);
this._activate_window_ID = null;
}
for (let window of this._windowList) {
this._clearWindow(window);
}
this._windowList = [];
// disconnect signals
if (this._idMap) {
global.window_manager.disconnect(this._idMap);
this._idMap = null;
}
if (this._idDestroy) {
global.window_manager.disconnect(this._idDestroy);
this._idDestroy = null;
}
if (this._switchWorkspaceId) {
global.window_manager.disconnect(this._switchWorkspaceId);
this._switchWorkspaceId = null;
}
if (this._showingId) {
Main.overview.disconnect(this._showingId);
this._showingId = null;
}
if (this._hidingId) {
Main.overview.disconnect(this._hidingId);
this._hidingId = null;
}
}
addWindow(window) {
if (this._isX11) {
return;
}
if (window.get_meta_window) { // it is a MetaWindowActor
window = window.get_meta_window();
}
window.customJS_ding = new ManageWindow(window, this._waylandClient, this, () => {
this._refreshWindows(true);
});
this._windowList.push(window);
window.customJS_ding.unmanagedID = window.connect('unmanaged', window => {
this._clearWindow(window);
this._windowList = this._windowList.filter(item => item !== window);
});
}
_clearWindow(window) {
window.disconnect(window.customJS_ding.unmanagedID);
window.customJS_ding.disconnect();
window.customJS_ding = null;
}
_refreshWindows(checkWorkspace) {
if (!this._activate_window_ID) {
this._activate_window_ID = GLib.idle_add(GLib.PRIORITY_LOW, () => {
if (this._enableRefresh) {
for (let window of this._windowList) {
window.customJS_ding.refreshState(checkWorkspace);
}
if (checkWorkspace) {
// activate the top-most window
let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, global.workspace_manager.get_active_workspace());
let anyActive = false;
for (let window of windows) {
if ((!window.customJS_ding || !window.customJS_ding._keepAtBottom) && !window.minimized) {
Main.activateWindow(window);
anyActive = true;
break;
}
}
if (!anyActive) {
for (let window of this._windowList) {
if (window.customJS_ding && window.customJS_ding._keepAtBottom && !window.minimized) {
Main.activateWindow(window);
break;
}
}
}
}
}
this._activate_window_ID = null;
return GLib.SOURCE_REMOVE;
});
}
}
};