mirror of
https://github.com/zoriya/fairy.git
synced 2025-12-18 19:05:11 +00:00
269 lines
7.8 KiB
JavaScript
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
|
|
);
|
|
}
|
|
}
|
|
}
|
|
);
|