Files
fairy/sources/renderer.js
2023-05-13 15:58:13 +09:00

269 lines
7.8 KiB
JavaScript

"use strict";
const Meta = imports.gi.Meta;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
var Renderer = GObject.registerClass(
class Renderer extends GObject.Object {
_init(state) {
super._init();
this._state = state;
log("fairy init!");
}
disable() {
this._removeSignals();
}
enable() {
this._bindSignals();
for (const window of global.display.list_all_windows())
this.trackWindow(window);
const workspace = global.display
.get_workspace_manager()
.get_active_workspace_index();
const tags = 0b1 << workspace;
if (Meta.prefs_get_workspaces_only_on_primary()) {
this._state.monitors[global.display.get_primary_monitor()].tags = tags;
} else {
for (let i = 0; i < this._state.monitors.length; i++)
this._state.monitors[i].tags = tags;
}
this.renderAll();
}
/**
* @params {Meta.Window} handle
* @returns {boolean}
*/
_isValidWindow(handle) {
// Check if we marked the window as invalid before (unmanaging call).
if (handle._isInvalid) return false;
let windowType = handle.get_window_type();
return (
windowType === Meta.WindowType.NORMAL ||
windowType === Meta.WindowType.MODAL_DIALOG ||
windowType === Meta.WindowType.DIALOG
);
}
// Stolen from https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/183
// Trying to move a newly-created window without using this (or waiting an arbitrary delay) crashes the gnome-shell.
// @window: the metaWindow to wait for
// @cb: the callback function called when the window is ready
//
// Waits until the actor of a metaWindow is available, it has
// an allocation and a valid gtk-application-id.
_waitForWindow(window, cb) {
let windowActor;
// Wait until window actor is available
let waitForWindowId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
// Stop if the window doesn't exist anymore or the parent is gone
if (!window.get_workspace())
return GLib.SOURCE_REMOVE;
// Continue if there's no actor available yet
if (!windowActor) {
if (!window.get_compositor_private()) return GLib.SOURCE_CONTINUE;
windowActor = window.get_compositor_private();
}
// Continue if the window is not allocated yet
if (windowActor.visible && !windowActor.has_allocation())
return GLib.SOURCE_CONTINUE;
// HACK: Still runing the callback on a timeout because the window is now setup but not placed yet.
// FIXME: Find a proper way to wait for this, I know forge is using a 220ms queue.
Mainloop.timeout_add(100, cb);
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(
waitForWindowId,
"[gnome-shell] waitForWindow"
);
}
_removeSignals() {
for (const signal of this._displaySignals) {
global.display.disconnect(signal);
}
this._displaySignals = undefined;
for (const signal of this._workspaceSignals) {
global.workspace_manager.disconnect(signal);
}
this._wmSignals = undefined;
for (const window of this._state.windows) {
if (window._signals) {
for (const signal of window._signals) window.disconnect(signal);
}
let actor = window.handle.get_compositor_private();
if (actor && actor._signals) {
for (const signal of actor._signals) actor.disconnect(signal);
actor._signals = [];
}
}
this._state.windows = [];
}
_bindSignals() {
this._displaySignals = [
global.display.connect("window-created", (_display, window) =>
this._waitForWindow(window, () => {
this.trackWindow(window);
this.renderForWindow(window);
})
),
// global.display.connect("window-entered-monitor", (_display, monitor, window) => {
//
// }),
];
this._workspaceSignals = [
global.workspace_manager.connect("active-workspace-changed", () => {
// Convert gnome workspaces to fairy's tags
const workspace = global.display
.get_workspace_manager()
.get_active_workspace_index();
const tags = 0b1 << workspace;
log("Switch to tags", tags);
if (Meta.prefs_get_workspaces_only_on_primary()) {
const primaryMon = global.display.get_primary_monitor();
this._state.monitors[primaryMon].tags = tags;
this.render(primaryMon, tags);
} else {
for (let i = 0; i < this._state.monitors.length; i++) {
this._state.monitors[i].tags = tags;
this.render(i, tags);
}
}
}),
];
}
/**
* @param {Meta.Window} window
*/
trackWindow(window) {
if (!this._isValidWindow(window)) return;
// Add window signals
window._signals = [
window.connect("unmanaging", (window) => {
window._isInvalid = true;
const faWindow = this._state.popByActor(actor);
if (faWindow) this.render(faWindow.monitor, faWindow.tags);
}),
window.connect("workspace-changed", (window) => {
if (!this._isValidWindow(window)) return;
const [oldW, newW] = this._state.updateByHandle(window);
if (oldW) this.render(oldW.monitor, oldW.tags);
if (newW) this.render(newW.monitor, newW.tags);
}),
window.connect("focus", (window) => {
if (!this._isValidWindow(window)) return;
this._state.monitors[window.get_monitor()].focused = window;
}),
];
const actor = window.get_compositor_private();
// actor._signals = [
// actor.connect("destroy", (actor) => {
// const faWindow = this._state.popByActor(actor);
// if (faWindow) this.render(faWindow.monitor, faWindow.tags);
// }),
// ];
this._state.newWindow(window);
}
renderAll() {
const monN = global.display.get_n_monitors();
// TODO: Support different tags on different monitors.
const tags =
global.display.get_workspace_manager().get_active_workspace_index() + 1;
for (let mon = 0; mon < monN; mon++) {
this.render(mon, tags);
}
}
renderForWindow(window) {
const mon = window.get_monitor();
this.render(mon);
}
/**
* @param {number} mon
* @param {number?} tags
*/
render(mon, tags) {
if (!tags) tags = this._state.monitors[mon].tags;
// We don't care which workspace it is, we just want the geometry
// for the current monitor without the panel.
const monGeo = global.display
.get_workspace_manager()
.get_active_workspace()
.get_work_area_for_monitor(mon);
for (const window of this._state.render(mon, tags)) {
if (window.handle.get_monitor() !== mon)
window.handle.move_to_monitor(mon);
if (window.floating) continue;
if (window.handle.minimized !== window.minimized) {
if (window.minimized) window.handle.minimize();
else window.handle.unminimize();
}
if (
window.handle["maximized-vertically"] !=
window.handle["maximized-horizontally"] ||
window.handle["maximized-vertically"] != window.maximized
) {
if (window.maximized) {
window.handle.maximize(Meta.MaximizeFlags.BOTH);
// Do not resize if maximizing to keep the overview tiled.
continue;
}
else window.handle.unmaximize(Meta.MaximizeFlags.BOTH);
}
// TODO: Add gaps
log(
"Rezing percent: ",
window.x,
window.y,
window.width,
window.height,
"Real values",
monGeo.x + (window.x * monGeo.width) / 100,
monGeo.y + (window.y * monGeo.height) / 100,
(window.width * monGeo.width) / 100,
(window.height * monGeo.height) / 100
);
// let cmpWindow = window.handle.get_compositor_private();
// cmpWindow.remove_all_transitions();
window.handle.move_resize_frame(
true,
monGeo.x + (window.x * monGeo.width) / 100,
monGeo.y + (window.y * monGeo.height) / 100,
(window.width * monGeo.width) / 100,
(window.height * monGeo.height) / 100
);
}
}
}
);