7972 lines
252 KiB
JavaScript
Executable File
7972 lines
252 KiB
JavaScript
Executable File
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
if (decorator = decorators[i])
|
|
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
if (kind && result)
|
|
__defProp(target, key, result);
|
|
return result;
|
|
};
|
|
var __publicField = (obj, key, value) => {
|
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
return value;
|
|
};
|
|
|
|
// src/gi.shared.ts
|
|
import Gio from "gi://Gio";
|
|
import GLib from "gi://GLib";
|
|
import GObject from "gi://GObject";
|
|
|
|
// src/gi.ext.ts
|
|
import Clutter from "gi://Clutter";
|
|
import Meta from "gi://Meta";
|
|
import Mtk from "gi://Mtk";
|
|
import Shell from "gi://Shell";
|
|
import St from "gi://St";
|
|
import Graphene from "gi://Graphene";
|
|
import Atk from "gi://Atk";
|
|
import Pango from "gi://Pango";
|
|
|
|
// src/utils/logger.ts
|
|
function rect_to_string(rect) {
|
|
return `{x: ${rect.x}, y: ${rect.y}, width: ${rect.width}, height: ${rect.height}}`;
|
|
}
|
|
var logger = (prefix) => (...content) => console.log("[tilingshell]", `[${prefix}]`, ...content);
|
|
|
|
// src/utils/ui.ts
|
|
import * as Main from "resource:///org/gnome/shell/ui/main.js";
|
|
var getMonitors = () => Main.layoutManager.monitors;
|
|
var isPointInsideRect = (point, rect) => {
|
|
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
};
|
|
var clampPointInsideRect = (point, rect) => {
|
|
const clamp = (n, min, max) => Math.min(Math.max(n, min), max);
|
|
return {
|
|
x: clamp(point.x, rect.x, rect.x + rect.width),
|
|
y: clamp(point.y, rect.y, rect.y + rect.height)
|
|
};
|
|
};
|
|
var isTileOnContainerBorder = (tilePos, container) => {
|
|
const almostEqual = (first, second) => Math.abs(first - second) <= 1;
|
|
const isLeft = almostEqual(tilePos.x, container.x);
|
|
const isTop = almostEqual(tilePos.y, container.y);
|
|
const isRight = almostEqual(
|
|
tilePos.x + tilePos.width,
|
|
container.x + container.width
|
|
);
|
|
const isBottom = almostEqual(
|
|
tilePos.y + tilePos.height,
|
|
container.y + container.height
|
|
);
|
|
return {
|
|
isTop,
|
|
isRight,
|
|
isBottom,
|
|
isLeft
|
|
};
|
|
};
|
|
var buildTileGaps = (tilePos, innerGaps, outerGaps, container, scalingFactor = 1) => {
|
|
const { isTop, isRight, isBottom, isLeft } = isTileOnContainerBorder(
|
|
tilePos,
|
|
container
|
|
);
|
|
const margin = new Clutter.Margin();
|
|
margin.top = (isTop ? outerGaps.top : innerGaps.top / 2) * scalingFactor;
|
|
margin.bottom = (isBottom ? outerGaps.bottom : innerGaps.bottom / 2) * scalingFactor;
|
|
margin.left = (isLeft ? outerGaps.left : innerGaps.left / 2) * scalingFactor;
|
|
margin.right = (isRight ? outerGaps.right : innerGaps.right / 2) * scalingFactor;
|
|
return {
|
|
gaps: margin,
|
|
isTop,
|
|
isRight,
|
|
isBottom,
|
|
isLeft
|
|
};
|
|
};
|
|
var getMonitorScalingFactor = (monitorIndex) => {
|
|
const scalingFactor = St.ThemeContext.get_for_stage(
|
|
global.get_stage()
|
|
).get_scale_factor();
|
|
if (scalingFactor === 1)
|
|
return global.display.get_monitor_scale(monitorIndex);
|
|
return scalingFactor;
|
|
};
|
|
var getScalingFactorOf = (widget) => {
|
|
const [hasReference, scalingReference] = widget.get_theme_node().lookup_length("scaling-reference", true);
|
|
if (!hasReference)
|
|
return [true, 1];
|
|
const [hasValue, monitorScalingFactor] = widget.get_theme_node().lookup_length("monitor-scaling-factor", true);
|
|
if (!hasValue)
|
|
return [true, 1];
|
|
return [scalingReference !== 1, monitorScalingFactor / scalingReference];
|
|
};
|
|
var enableScalingFactorSupport = (widget, monitorScalingFactor) => {
|
|
if (!monitorScalingFactor)
|
|
return;
|
|
widget.set_style(`${getScalingFactorSupportString(monitorScalingFactor)};`);
|
|
};
|
|
var getScalingFactorSupportString = (monitorScalingFactor) => {
|
|
return `scaling-reference: 1px; monitor-scaling-factor: ${monitorScalingFactor}px`;
|
|
};
|
|
function getWindowsOfMonitor(monitor) {
|
|
return global.workspaceManager.get_active_workspace().list_windows().filter(
|
|
(win) => win.get_window_type() === Meta.WindowType.NORMAL && Main.layoutManager.monitors[win.get_monitor()] === monitor
|
|
);
|
|
}
|
|
function buildMarginOf(value) {
|
|
const margin = new Clutter.Margin();
|
|
margin.top = value;
|
|
margin.bottom = value;
|
|
margin.left = value;
|
|
margin.right = value;
|
|
return margin;
|
|
}
|
|
function buildMargin(params) {
|
|
const margin = new Clutter.Margin();
|
|
if (params.top)
|
|
margin.top = params.top;
|
|
if (params.bottom)
|
|
margin.bottom = params.bottom;
|
|
if (params.left)
|
|
margin.left = params.left;
|
|
if (params.right)
|
|
margin.right = params.right;
|
|
return margin;
|
|
}
|
|
function buildRectangle(params = {}) {
|
|
return new Mtk.Rectangle({
|
|
x: params.x || 0,
|
|
y: params.y || 0,
|
|
width: params.width || 0,
|
|
height: params.height || 0
|
|
});
|
|
}
|
|
function getEventCoords(event) {
|
|
return event.get_coords ? event.get_coords() : [event.x, event.y];
|
|
}
|
|
function buildBlurEffect(sigma) {
|
|
const effect = new Shell.BlurEffect();
|
|
effect.set_mode(Shell.BlurMode.BACKGROUND);
|
|
effect.set_brightness(1);
|
|
if (effect.set_radius) {
|
|
effect.set_radius(sigma * 2);
|
|
} else {
|
|
effect.set_sigma(sigma);
|
|
}
|
|
return effect;
|
|
}
|
|
function getTransientOrParent(window) {
|
|
const transient = window.get_transient_for();
|
|
return window.is_attached_dialog() && transient !== null ? transient : window;
|
|
}
|
|
function filterUnfocusableWindows(windows) {
|
|
return windows.map(getTransientOrParent).filter((win, idx, arr) => {
|
|
return win !== null && !win.skipTaskbar && arr.indexOf(win) === idx;
|
|
});
|
|
}
|
|
function getWindows(workspace) {
|
|
if (!workspace)
|
|
workspace = global.workspaceManager.get_active_workspace();
|
|
return filterUnfocusableWindows(
|
|
global.display.get_tab_list(Meta.TabList.NORMAL_ALL, workspace)
|
|
);
|
|
}
|
|
function squaredEuclideanDistance(pointA, pointB) {
|
|
return (pointA.x - pointB.x) * (pointA.x - pointB.x) + (pointA.y - pointB.y) * (pointA.y - pointB.y);
|
|
}
|
|
function setWidgetOrientation(widget, vertical) {
|
|
if (widget.orientation) {
|
|
widget.orientation = vertical ? Clutter.Orientation.VERTICAL : Clutter.Orientation.HORIZONTAL;
|
|
} else {
|
|
widget.vertical = vertical;
|
|
}
|
|
}
|
|
|
|
// src/utils/gjs.ts
|
|
function registerGObjectClass(target) {
|
|
if (Object.prototype.hasOwnProperty.call(target, "metaInfo")) {
|
|
return GObject.registerClass(
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
target.metaInfo,
|
|
target
|
|
);
|
|
} else {
|
|
return GObject.registerClass(target);
|
|
}
|
|
}
|
|
|
|
// src/components/layout/Layout.ts
|
|
var Layout = class {
|
|
id;
|
|
tiles;
|
|
constructor(tiles, id) {
|
|
this.tiles = tiles;
|
|
this.id = id;
|
|
}
|
|
};
|
|
|
|
// src/components/layout/Tile.ts
|
|
var Tile2 = class {
|
|
// @ts-expect-error "GObject has TYPE_JSOBJECT"
|
|
static $gtype = GObject.TYPE_JSOBJECT;
|
|
x;
|
|
y;
|
|
width;
|
|
height;
|
|
groups;
|
|
constructor({
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
groups
|
|
}) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.groups = groups;
|
|
}
|
|
};
|
|
|
|
// src/settings/settings.ts
|
|
var ActivationKey = /* @__PURE__ */ ((ActivationKey2) => {
|
|
ActivationKey2[ActivationKey2["NONE"] = -1] = "NONE";
|
|
ActivationKey2[ActivationKey2["CTRL"] = 0] = "CTRL";
|
|
ActivationKey2[ActivationKey2["ALT"] = 1] = "ALT";
|
|
ActivationKey2[ActivationKey2["SUPER"] = 2] = "SUPER";
|
|
return ActivationKey2;
|
|
})(ActivationKey || {});
|
|
function get_string(key) {
|
|
return Settings.gioSetting.get_string(key) ?? Settings.gioSetting.get_default_value(key)?.get_string()[0];
|
|
}
|
|
function set_string(key, val) {
|
|
return Settings.gioSetting.set_string(key, val);
|
|
}
|
|
function get_boolean(key) {
|
|
return Settings.gioSetting.get_boolean(key) ?? Settings.gioSetting.get_default_value(key)?.get_boolean();
|
|
}
|
|
function set_boolean(key, val) {
|
|
return Settings.gioSetting.set_boolean(key, val);
|
|
}
|
|
function get_number(key) {
|
|
return Settings.gioSetting.get_int(key) ?? Settings.gioSetting.get_default_value(key)?.get_int64();
|
|
}
|
|
function set_number(key, val) {
|
|
return Settings.gioSetting.set_int(key, val);
|
|
}
|
|
function get_unsigned_number(key) {
|
|
return Settings.gioSetting.get_uint(key) ?? Settings.gioSetting.get_default_value(key)?.get_uint64();
|
|
}
|
|
function set_unsigned_number(key, val) {
|
|
return Settings.gioSetting.set_uint(key, val);
|
|
}
|
|
function get_activationkey(key, defaultValue) {
|
|
let val = Settings.gioSetting.get_strv(key);
|
|
if (!val || val.length === 0) {
|
|
val = Settings.gioSetting.get_default_value(key)?.get_strv() ?? [
|
|
String(defaultValue)
|
|
];
|
|
if (val.length === 0)
|
|
val = [String(defaultValue)];
|
|
}
|
|
return Number(val[0]);
|
|
}
|
|
function set_activationkey(key, val) {
|
|
return Settings.gioSetting.set_strv(key, [String(val)]);
|
|
}
|
|
var Settings = class _Settings {
|
|
static _settings;
|
|
static _is_initialized = false;
|
|
static KEY_LAST_VERSION_NAME_INSTALLED = "last-version-name-installed";
|
|
static KEY_OVERRIDDEN_SETTINGS = "overridden-settings";
|
|
static KEY_WINDOW_BORDER_COLOR = "window-border-color";
|
|
static KEY_TILING_SYSTEM = "enable-tiling-system";
|
|
static KEY_SNAP_ASSIST = "enable-snap-assist";
|
|
static KEY_SHOW_INDICATOR = "show-indicator";
|
|
static KEY_TILING_SYSTEM_ACTIVATION_KEY = "tiling-system-activation-key";
|
|
static KEY_TILING_SYSTEM_DEACTIVATION_KEY = "tiling-system-deactivation-key";
|
|
static KEY_SPAN_MULTIPLE_TILES_ACTIVATION_KEY = "span-multiple-tiles-activation-key";
|
|
static KEY_SPAN_MULTIPLE_TILES = "enable-span-multiple-tiles";
|
|
static KEY_RESTORE_WINDOW_ORIGINAL_SIZE = "restore-window-original-size";
|
|
static KEY_WRAPAROUND_FOCUS = "enable-wraparound-focus";
|
|
static KEY_RESIZE_COMPLEMENTING_WINDOWS = "resize-complementing-windows";
|
|
static KEY_ENABLE_BLUR_SNAP_ASSISTANT = "enable-blur-snap-assistant";
|
|
static KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW = "enable-blur-selected-tilepreview";
|
|
static KEY_ENABLE_MOVE_KEYBINDINGS = "enable-move-keybindings";
|
|
static KEY_ENABLE_AUTO_TILING = "enable-autotiling";
|
|
static KEY_ACTIVE_SCREEN_EDGES = "active-screen-edges";
|
|
static KEY_TOP_EDGE_MAXIMIZE = "top-edge-maximize";
|
|
static KEY_OVERRIDE_WINDOW_MENU = "override-window-menu";
|
|
static KEY_SNAP_ASSISTANT_THRESHOLD = "snap-assistant-threshold";
|
|
static KEY_ENABLE_WINDOW_BORDER = "enable-window-border";
|
|
static KEY_INNER_GAPS = "inner-gaps";
|
|
static KEY_OUTER_GAPS = "outer-gaps";
|
|
static KEY_SNAP_ASSISTANT_ANIMATION_TIME = "snap-assistant-animation-time";
|
|
static KEY_TILE_PREVIEW_ANIMATION_TIME = "tile-preview-animation-time";
|
|
static KEY_SETTING_LAYOUTS_JSON = "layouts-json";
|
|
static KEY_SETTING_SELECTED_LAYOUTS = "selected-layouts";
|
|
static KEY_WINDOW_BORDER_WIDTH = "window-border-width";
|
|
static KEY_ENABLE_SMART_WINDOW_BORDER_RADIUS = "enable-smart-window-border-radius";
|
|
static KEY_QUARTER_TILING_THRESHOLD = "quarter-tiling-threshold";
|
|
static KEY_ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS = "enable-tiling-system-windows-suggestions";
|
|
static KEY_ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS = "enable-snap-assistant-windows-suggestions";
|
|
static KEY_ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS = "enable-screen-edges-windows-suggestions";
|
|
static SETTING_MOVE_WINDOW_RIGHT = "move-window-right";
|
|
static SETTING_MOVE_WINDOW_LEFT = "move-window-left";
|
|
static SETTING_MOVE_WINDOW_UP = "move-window-up";
|
|
static SETTING_MOVE_WINDOW_DOWN = "move-window-down";
|
|
static SETTING_SPAN_WINDOW_RIGHT = "span-window-right";
|
|
static SETTING_SPAN_WINDOW_LEFT = "span-window-left";
|
|
static SETTING_SPAN_WINDOW_UP = "span-window-up";
|
|
static SETTING_SPAN_WINDOW_DOWN = "span-window-down";
|
|
static SETTING_SPAN_WINDOW_ALL_TILES = "span-window-all-tiles";
|
|
static SETTING_UNTILE_WINDOW = "untile-window";
|
|
static SETTING_MOVE_WINDOW_CENTER = "move-window-center";
|
|
static SETTING_FOCUS_WINDOW_RIGHT = "focus-window-right";
|
|
static SETTING_FOCUS_WINDOW_LEFT = "focus-window-left";
|
|
static SETTING_FOCUS_WINDOW_UP = "focus-window-up";
|
|
static SETTING_FOCUS_WINDOW_DOWN = "focus-window-down";
|
|
static SETTING_FOCUS_WINDOW_NEXT = "focus-window-next";
|
|
static SETTING_FOCUS_WINDOW_PREV = "focus-window-prev";
|
|
static SETTING_HIGHLIGHT_CURRENT_WINDOW = "highlight-current-window";
|
|
static initialize(settings) {
|
|
if (this._is_initialized)
|
|
return;
|
|
this._is_initialized = true;
|
|
this._settings = settings;
|
|
}
|
|
static destroy() {
|
|
if (this._is_initialized) {
|
|
this._is_initialized = false;
|
|
this._settings = null;
|
|
}
|
|
}
|
|
static get gioSetting() {
|
|
return this._settings ?? new Gio.Settings();
|
|
}
|
|
static bind(key, object, property, flags = Gio.SettingsBindFlags.DEFAULT) {
|
|
this._settings?.bind(key, object, property, flags);
|
|
}
|
|
static get LAST_VERSION_NAME_INSTALLED() {
|
|
return get_string(_Settings.KEY_LAST_VERSION_NAME_INSTALLED);
|
|
}
|
|
static set LAST_VERSION_NAME_INSTALLED(val) {
|
|
set_string(_Settings.KEY_LAST_VERSION_NAME_INSTALLED, val);
|
|
}
|
|
static get OVERRIDDEN_SETTINGS() {
|
|
return get_string(_Settings.KEY_OVERRIDDEN_SETTINGS);
|
|
}
|
|
static set OVERRIDDEN_SETTINGS(val) {
|
|
set_string(_Settings.KEY_OVERRIDDEN_SETTINGS, val);
|
|
}
|
|
static get TILING_SYSTEM() {
|
|
return get_boolean(_Settings.KEY_TILING_SYSTEM);
|
|
}
|
|
static set TILING_SYSTEM(val) {
|
|
set_boolean(_Settings.KEY_TILING_SYSTEM, val);
|
|
}
|
|
static get SNAP_ASSIST() {
|
|
return get_boolean(_Settings.KEY_SNAP_ASSIST);
|
|
}
|
|
static set SNAP_ASSIST(val) {
|
|
set_boolean(_Settings.KEY_SNAP_ASSIST, val);
|
|
}
|
|
static get SHOW_INDICATOR() {
|
|
return get_boolean(_Settings.KEY_SHOW_INDICATOR);
|
|
}
|
|
static set SHOW_INDICATOR(val) {
|
|
set_boolean(_Settings.KEY_SHOW_INDICATOR, val);
|
|
}
|
|
static get TILING_SYSTEM_ACTIVATION_KEY() {
|
|
return get_activationkey(
|
|
_Settings.KEY_TILING_SYSTEM_ACTIVATION_KEY,
|
|
0 /* CTRL */
|
|
);
|
|
}
|
|
static set TILING_SYSTEM_ACTIVATION_KEY(val) {
|
|
set_activationkey(_Settings.KEY_TILING_SYSTEM_ACTIVATION_KEY, val);
|
|
}
|
|
static get TILING_SYSTEM_DEACTIVATION_KEY() {
|
|
return get_activationkey(
|
|
_Settings.KEY_TILING_SYSTEM_DEACTIVATION_KEY,
|
|
-1 /* NONE */
|
|
);
|
|
}
|
|
static set TILING_SYSTEM_DEACTIVATION_KEY(val) {
|
|
set_activationkey(_Settings.KEY_TILING_SYSTEM_DEACTIVATION_KEY, val);
|
|
}
|
|
static get INNER_GAPS() {
|
|
return get_unsigned_number(_Settings.KEY_INNER_GAPS);
|
|
}
|
|
static set INNER_GAPS(val) {
|
|
set_unsigned_number(_Settings.KEY_INNER_GAPS, val);
|
|
}
|
|
static get OUTER_GAPS() {
|
|
return get_unsigned_number(_Settings.KEY_OUTER_GAPS);
|
|
}
|
|
static set OUTER_GAPS(val) {
|
|
set_unsigned_number(_Settings.KEY_OUTER_GAPS, val);
|
|
}
|
|
static get SPAN_MULTIPLE_TILES() {
|
|
return get_boolean(_Settings.KEY_SPAN_MULTIPLE_TILES);
|
|
}
|
|
static set SPAN_MULTIPLE_TILES(val) {
|
|
set_boolean(_Settings.KEY_SPAN_MULTIPLE_TILES, val);
|
|
}
|
|
static get SPAN_MULTIPLE_TILES_ACTIVATION_KEY() {
|
|
return get_activationkey(
|
|
_Settings.KEY_SPAN_MULTIPLE_TILES_ACTIVATION_KEY,
|
|
1 /* ALT */
|
|
);
|
|
}
|
|
static set SPAN_MULTIPLE_TILES_ACTIVATION_KEY(val) {
|
|
set_activationkey(_Settings.KEY_SPAN_MULTIPLE_TILES_ACTIVATION_KEY, val);
|
|
}
|
|
static get RESTORE_WINDOW_ORIGINAL_SIZE() {
|
|
return get_boolean(_Settings.KEY_RESTORE_WINDOW_ORIGINAL_SIZE);
|
|
}
|
|
static set RESTORE_WINDOW_ORIGINAL_SIZE(val) {
|
|
set_boolean(_Settings.KEY_RESTORE_WINDOW_ORIGINAL_SIZE, val);
|
|
}
|
|
static get WRAPAROUND_FOCUS() {
|
|
return get_boolean(_Settings.KEY_WRAPAROUND_FOCUS);
|
|
}
|
|
static set WRAPAROUND_FOCUS(val) {
|
|
set_boolean(_Settings.KEY_WRAPAROUND_FOCUS, val);
|
|
}
|
|
static get RESIZE_COMPLEMENTING_WINDOWS() {
|
|
return get_boolean(_Settings.KEY_RESIZE_COMPLEMENTING_WINDOWS);
|
|
}
|
|
static set RESIZE_COMPLEMENTING_WINDOWS(val) {
|
|
set_boolean(_Settings.KEY_RESIZE_COMPLEMENTING_WINDOWS, val);
|
|
}
|
|
static get ENABLE_BLUR_SNAP_ASSISTANT() {
|
|
return get_boolean(_Settings.KEY_ENABLE_BLUR_SNAP_ASSISTANT);
|
|
}
|
|
static set ENABLE_BLUR_SNAP_ASSISTANT(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_BLUR_SNAP_ASSISTANT, val);
|
|
}
|
|
static get ENABLE_BLUR_SELECTED_TILEPREVIEW() {
|
|
return get_boolean(_Settings.KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW);
|
|
}
|
|
static set ENABLE_BLUR_SELECTED_TILEPREVIEW(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW, val);
|
|
}
|
|
static get ENABLE_MOVE_KEYBINDINGS() {
|
|
return get_boolean(_Settings.KEY_ENABLE_MOVE_KEYBINDINGS);
|
|
}
|
|
static set ENABLE_MOVE_KEYBINDINGS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_MOVE_KEYBINDINGS, val);
|
|
}
|
|
static get ENABLE_AUTO_TILING() {
|
|
return get_boolean(_Settings.KEY_ENABLE_AUTO_TILING);
|
|
}
|
|
static set ENABLE_AUTO_TILING(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_AUTO_TILING, val);
|
|
}
|
|
static get ACTIVE_SCREEN_EDGES() {
|
|
return get_boolean(_Settings.KEY_ACTIVE_SCREEN_EDGES);
|
|
}
|
|
static set ACTIVE_SCREEN_EDGES(val) {
|
|
set_boolean(_Settings.KEY_ACTIVE_SCREEN_EDGES, val);
|
|
}
|
|
static get TOP_EDGE_MAXIMIZE() {
|
|
return get_boolean(_Settings.KEY_TOP_EDGE_MAXIMIZE);
|
|
}
|
|
static set TOP_EDGE_MAXIMIZE(val) {
|
|
set_boolean(_Settings.KEY_TOP_EDGE_MAXIMIZE, val);
|
|
}
|
|
static get OVERRIDE_WINDOW_MENU() {
|
|
return get_boolean(_Settings.KEY_OVERRIDE_WINDOW_MENU);
|
|
}
|
|
static set OVERRIDE_WINDOW_MENU(val) {
|
|
set_boolean(_Settings.KEY_OVERRIDE_WINDOW_MENU, val);
|
|
}
|
|
static get SNAP_ASSISTANT_THRESHOLD() {
|
|
return get_number(_Settings.KEY_SNAP_ASSISTANT_THRESHOLD);
|
|
}
|
|
static set SNAP_ASSISTANT_THRESHOLD(val) {
|
|
set_number(_Settings.KEY_SNAP_ASSISTANT_THRESHOLD, val);
|
|
}
|
|
static get QUARTER_TILING_THRESHOLD() {
|
|
return get_unsigned_number(_Settings.KEY_QUARTER_TILING_THRESHOLD);
|
|
}
|
|
static set QUARTER_TILING_THRESHOLD(val) {
|
|
set_unsigned_number(_Settings.KEY_QUARTER_TILING_THRESHOLD, val);
|
|
}
|
|
static get WINDOW_BORDER_COLOR() {
|
|
return get_string(_Settings.KEY_WINDOW_BORDER_COLOR);
|
|
}
|
|
static set WINDOW_BORDER_COLOR(val) {
|
|
set_string(_Settings.KEY_WINDOW_BORDER_COLOR, val);
|
|
}
|
|
static get WINDOW_BORDER_WIDTH() {
|
|
return get_unsigned_number(_Settings.KEY_WINDOW_BORDER_WIDTH);
|
|
}
|
|
static set WINDOW_BORDER_WIDTH(val) {
|
|
set_unsigned_number(_Settings.KEY_WINDOW_BORDER_WIDTH, val);
|
|
}
|
|
static get ENABLE_SMART_WINDOW_BORDER_RADIUS() {
|
|
return get_boolean(_Settings.KEY_ENABLE_SMART_WINDOW_BORDER_RADIUS);
|
|
}
|
|
static set ENABLE_SMART_WINDOW_BORDER_RADIUS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_SMART_WINDOW_BORDER_RADIUS, val);
|
|
}
|
|
static get ENABLE_WINDOW_BORDER() {
|
|
return get_boolean(_Settings.KEY_ENABLE_WINDOW_BORDER);
|
|
}
|
|
static set ENABLE_WINDOW_BORDER(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_WINDOW_BORDER, val);
|
|
}
|
|
static get SNAP_ASSISTANT_ANIMATION_TIME() {
|
|
return get_unsigned_number(_Settings.KEY_SNAP_ASSISTANT_ANIMATION_TIME);
|
|
}
|
|
static set SNAP_ASSISTANT_ANIMATION_TIME(val) {
|
|
set_unsigned_number(_Settings.KEY_SNAP_ASSISTANT_ANIMATION_TIME, val);
|
|
}
|
|
static get TILE_PREVIEW_ANIMATION_TIME() {
|
|
return get_unsigned_number(_Settings.KEY_TILE_PREVIEW_ANIMATION_TIME);
|
|
}
|
|
static set TILE_PREVIEW_ANIMATION_TIME(val) {
|
|
set_unsigned_number(_Settings.KEY_TILE_PREVIEW_ANIMATION_TIME, val);
|
|
}
|
|
static get ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS() {
|
|
return get_boolean(
|
|
_Settings.KEY_ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS
|
|
);
|
|
}
|
|
static set ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS, val);
|
|
}
|
|
static get ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS() {
|
|
return get_boolean(
|
|
_Settings.KEY_ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS
|
|
);
|
|
}
|
|
static set ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS(val) {
|
|
set_boolean(
|
|
_Settings.KEY_ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS,
|
|
val
|
|
);
|
|
}
|
|
static get ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS() {
|
|
return get_boolean(
|
|
_Settings.KEY_ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS
|
|
);
|
|
}
|
|
static set ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS(val) {
|
|
set_boolean(_Settings.KEY_ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS, val);
|
|
}
|
|
static get_inner_gaps(scaleFactor = 1) {
|
|
const value = this.INNER_GAPS * scaleFactor;
|
|
return {
|
|
top: value,
|
|
bottom: value,
|
|
left: value,
|
|
right: value
|
|
};
|
|
}
|
|
static get_outer_gaps(scaleFactor = 1) {
|
|
const value = this.OUTER_GAPS * scaleFactor;
|
|
return {
|
|
top: value,
|
|
bottom: value,
|
|
left: value,
|
|
right: value
|
|
};
|
|
}
|
|
static get_layouts_json() {
|
|
try {
|
|
const layouts = JSON.parse(
|
|
this._settings?.get_string(this.KEY_SETTING_LAYOUTS_JSON) || "[]"
|
|
);
|
|
if (layouts.length === 0)
|
|
throw new Error("At least one layout is required");
|
|
return layouts.filter((layout) => layout.tiles.length > 0);
|
|
} catch (ex) {
|
|
this.reset_layouts_json();
|
|
return JSON.parse(
|
|
this._settings?.get_string(this.KEY_SETTING_LAYOUTS_JSON) || "[]"
|
|
);
|
|
}
|
|
}
|
|
static get_selected_layouts() {
|
|
const variant = this._settings?.get_value(
|
|
_Settings.KEY_SETTING_SELECTED_LAYOUTS
|
|
);
|
|
if (!variant)
|
|
return [];
|
|
const result = [];
|
|
for (let i = 0; i < variant.n_children(); i++) {
|
|
const monitor_variant = variant.get_child_value(i);
|
|
if (!monitor_variant)
|
|
continue;
|
|
const n_workspaces = monitor_variant.n_children();
|
|
const monitor_result = [];
|
|
for (let j = 0; j < n_workspaces; j++) {
|
|
const layout_variant = monitor_variant.get_child_value(j);
|
|
if (!layout_variant)
|
|
continue;
|
|
monitor_result.push(layout_variant.get_string()[0]);
|
|
}
|
|
result.push(monitor_result);
|
|
}
|
|
return result;
|
|
}
|
|
static reset_layouts_json() {
|
|
this.save_layouts_json([
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [1, 2]
|
|
}),
|
|
// top-left
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0.5,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [1, 2]
|
|
}),
|
|
// bottom-left
|
|
new Tile2({
|
|
x: 0.22,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.56,
|
|
groups: [2, 3]
|
|
}),
|
|
// center
|
|
new Tile2({
|
|
x: 0.78,
|
|
y: 0,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [3, 4]
|
|
}),
|
|
// top-right
|
|
new Tile2({
|
|
x: 0.78,
|
|
y: 0.5,
|
|
height: 0.5,
|
|
width: 0.22,
|
|
groups: [3, 4]
|
|
})
|
|
// bottom-right
|
|
],
|
|
"Layout 1"
|
|
),
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.22,
|
|
groups: [1]
|
|
}),
|
|
new Tile2({
|
|
x: 0.22,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.56,
|
|
groups: [1, 2]
|
|
}),
|
|
new Tile2({
|
|
x: 0.78,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.22,
|
|
groups: [2]
|
|
})
|
|
],
|
|
"Layout 2"
|
|
),
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.33,
|
|
groups: [1]
|
|
}),
|
|
new Tile2({
|
|
x: 0.33,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.67,
|
|
groups: [1]
|
|
})
|
|
],
|
|
"Layout 3"
|
|
),
|
|
new Layout(
|
|
[
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.67,
|
|
groups: [1]
|
|
}),
|
|
new Tile2({
|
|
x: 0.67,
|
|
y: 0,
|
|
height: 1,
|
|
width: 0.33,
|
|
groups: [1]
|
|
})
|
|
],
|
|
"Layout 4"
|
|
)
|
|
]);
|
|
}
|
|
static save_layouts_json(layouts) {
|
|
this._settings?.set_string(
|
|
this.KEY_SETTING_LAYOUTS_JSON,
|
|
JSON.stringify(layouts)
|
|
);
|
|
}
|
|
static save_selected_layouts(ids) {
|
|
if (ids.length === 0) {
|
|
this._settings?.reset(_Settings.KEY_SETTING_SELECTED_LAYOUTS);
|
|
return;
|
|
}
|
|
const variants = ids.map(
|
|
(monitor_ids) => GLib.Variant.new_strv(monitor_ids)
|
|
);
|
|
const result = GLib.Variant.new_array(null, variants);
|
|
this._settings?.set_value(
|
|
_Settings.KEY_SETTING_SELECTED_LAYOUTS,
|
|
// @ts-expect-error "'result' is of a correct variant type"
|
|
result
|
|
);
|
|
}
|
|
static connect(key, func) {
|
|
return this._settings?.connect(`changed::${key}`, func) || -1;
|
|
}
|
|
static disconnect(id) {
|
|
this._settings?.disconnect(id);
|
|
}
|
|
};
|
|
|
|
// src/utils/signalHandling.ts
|
|
var SignalHandling = class {
|
|
_signalsIds;
|
|
constructor() {
|
|
this._signalsIds = {};
|
|
}
|
|
connect(obj, key, fun) {
|
|
const signalId = obj.connect(key, fun);
|
|
this._signalsIds[key] = { id: signalId, obj };
|
|
}
|
|
disconnect(obj) {
|
|
if (!obj) {
|
|
const toDelete = [];
|
|
Object.keys(this._signalsIds).forEach((key) => {
|
|
this._signalsIds[key].obj.disconnect(this._signalsIds[key].id);
|
|
toDelete.push(key);
|
|
});
|
|
const result = toDelete.length > 0;
|
|
toDelete.forEach((key) => delete this._signalsIds[key]);
|
|
return result;
|
|
} else {
|
|
const keyFound = Object.keys(this._signalsIds).find(
|
|
(key) => this._signalsIds[key].obj === obj
|
|
);
|
|
if (keyFound) {
|
|
obj.disconnect(this._signalsIds[keyFound].id);
|
|
delete this._signalsIds[keyFound];
|
|
}
|
|
return keyFound;
|
|
}
|
|
}
|
|
};
|
|
|
|
// src/utils/globalState.ts
|
|
import * as Main2 from "resource:///org/gnome/shell/ui/main.js";
|
|
var debug = logger("GlobalState");
|
|
var GlobalState = class extends GObject.Object {
|
|
_signals;
|
|
_layouts;
|
|
_tilePreviewAnimationTime;
|
|
// if workspaces are reordered, we use this map to know which layouts where selected
|
|
// to each workspace and we save the new ordering in the settings
|
|
_selected_layouts;
|
|
// used to handle reordering of workspaces
|
|
static get() {
|
|
if (!this._instance)
|
|
this._instance = new GlobalState();
|
|
return this._instance;
|
|
}
|
|
static destroy() {
|
|
if (this._instance) {
|
|
this._instance._signals.disconnect();
|
|
this._instance._layouts = [];
|
|
this._instance = null;
|
|
}
|
|
}
|
|
constructor() {
|
|
super();
|
|
this._signals = new SignalHandling();
|
|
this._layouts = Settings.get_layouts_json();
|
|
this._tilePreviewAnimationTime = 100;
|
|
this._selected_layouts = /* @__PURE__ */ new Map();
|
|
this.validate_selected_layouts();
|
|
Settings.bind(
|
|
Settings.KEY_TILE_PREVIEW_ANIMATION_TIME,
|
|
this,
|
|
"tilePreviewAnimationTime",
|
|
Gio.SettingsBindFlags.GET
|
|
);
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_SETTING_LAYOUTS_JSON,
|
|
() => {
|
|
this._layouts = Settings.get_layouts_json();
|
|
this.emit(GlobalState.SIGNAL_LAYOUTS_CHANGED);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_SETTING_SELECTED_LAYOUTS,
|
|
() => {
|
|
const selected_layouts = Settings.get_selected_layouts();
|
|
if (selected_layouts.length === 0) {
|
|
this.validate_selected_layouts();
|
|
return;
|
|
}
|
|
const defaultLayout = this._layouts[0];
|
|
const n_monitors = Main2.layoutManager.monitors.length;
|
|
const n_workspaces = global.workspaceManager.get_n_workspaces();
|
|
for (let i = 0; i < n_workspaces; i++) {
|
|
const ws = global.workspaceManager.get_workspace_by_index(i);
|
|
if (!ws)
|
|
continue;
|
|
const monitors_layouts = i < selected_layouts.length ? selected_layouts[i] : [defaultLayout.id];
|
|
while (monitors_layouts.length < n_monitors)
|
|
monitors_layouts.push(defaultLayout.id);
|
|
while (monitors_layouts.length > n_monitors)
|
|
monitors_layouts.pop();
|
|
this._selected_layouts.set(ws, monitors_layouts);
|
|
}
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.workspaceManager,
|
|
"workspace-added",
|
|
(_2, index) => {
|
|
const n_workspaces = global.workspaceManager.get_n_workspaces();
|
|
const newWs = global.workspaceManager.get_workspace_by_index(index);
|
|
if (!newWs)
|
|
return;
|
|
debug(`added workspace ${index}`);
|
|
const secondLastWs = global.workspaceManager.get_workspace_by_index(
|
|
n_workspaces - 2
|
|
);
|
|
const secondLastWsLayoutsId = secondLastWs ? this._selected_layouts.get(secondLastWs) ?? [] : [];
|
|
if (secondLastWsLayoutsId.length === 0) {
|
|
secondLastWsLayoutsId.push(
|
|
...Main2.layoutManager.monitors.map(
|
|
() => this._layouts[0].id
|
|
)
|
|
);
|
|
}
|
|
this._selected_layouts.set(
|
|
newWs,
|
|
secondLastWsLayoutsId
|
|
// Main.layoutManager.monitors.map(() => layout.id),
|
|
);
|
|
const to_be_saved = [];
|
|
for (let i = 0; i < n_workspaces; i++) {
|
|
const ws = global.workspaceManager.get_workspace_by_index(i);
|
|
if (!ws)
|
|
continue;
|
|
const monitors_layouts = this._selected_layouts.get(ws);
|
|
if (!monitors_layouts)
|
|
continue;
|
|
to_be_saved.push(monitors_layouts);
|
|
}
|
|
Settings.save_selected_layouts(to_be_saved);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.workspaceManager,
|
|
"workspace-removed",
|
|
(_2) => {
|
|
const newMap = /* @__PURE__ */ new Map();
|
|
const n_workspaces = global.workspaceManager.get_n_workspaces();
|
|
const to_be_saved = [];
|
|
for (let i = 0; i < n_workspaces; i++) {
|
|
const ws = global.workspaceManager.get_workspace_by_index(i);
|
|
if (!ws)
|
|
continue;
|
|
const monitors_layouts = this._selected_layouts.get(ws);
|
|
if (!monitors_layouts)
|
|
continue;
|
|
this._selected_layouts.delete(ws);
|
|
newMap.set(ws, monitors_layouts);
|
|
to_be_saved.push(monitors_layouts);
|
|
}
|
|
Settings.save_selected_layouts(to_be_saved);
|
|
this._selected_layouts.clear();
|
|
this._selected_layouts = newMap;
|
|
debug("deleted workspace");
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.workspaceManager,
|
|
"workspaces-reordered",
|
|
(_2) => {
|
|
this._save_selected_layouts();
|
|
debug("reordered workspaces");
|
|
}
|
|
);
|
|
}
|
|
validate_selected_layouts() {
|
|
const n_monitors = Main2.layoutManager.monitors.length;
|
|
const old_selected_layouts = Settings.get_selected_layouts();
|
|
for (let i = 0; i < global.workspaceManager.get_n_workspaces(); i++) {
|
|
const ws = global.workspaceManager.get_workspace_by_index(i);
|
|
if (!ws)
|
|
continue;
|
|
const monitors_layouts = i < old_selected_layouts.length ? old_selected_layouts[i] : [];
|
|
while (monitors_layouts.length < n_monitors)
|
|
monitors_layouts.push(this._layouts[0].id);
|
|
while (monitors_layouts.length > n_monitors)
|
|
monitors_layouts.pop();
|
|
monitors_layouts.forEach((_2, ind) => {
|
|
if (this._layouts.findIndex(
|
|
(lay) => lay.id === monitors_layouts[ind]
|
|
) === -1)
|
|
monitors_layouts[ind] = monitors_layouts[0];
|
|
});
|
|
this._selected_layouts.set(ws, monitors_layouts);
|
|
}
|
|
this._save_selected_layouts();
|
|
}
|
|
_save_selected_layouts() {
|
|
const to_be_saved = [];
|
|
const n_workspaces = global.workspaceManager.get_n_workspaces();
|
|
for (let i = 0; i < n_workspaces; i++) {
|
|
const ws = global.workspaceManager.get_workspace_by_index(i);
|
|
if (!ws)
|
|
continue;
|
|
const monitors_layouts = this._selected_layouts.get(ws);
|
|
if (!monitors_layouts)
|
|
continue;
|
|
to_be_saved.push(monitors_layouts);
|
|
}
|
|
Settings.save_selected_layouts(to_be_saved);
|
|
}
|
|
get layouts() {
|
|
return this._layouts;
|
|
}
|
|
addLayout(newLay) {
|
|
this._layouts.push(newLay);
|
|
this.layouts = this._layouts;
|
|
}
|
|
deleteLayout(layoutToDelete) {
|
|
const layFoundIndex = this._layouts.findIndex(
|
|
(lay) => lay.id === layoutToDelete.id
|
|
);
|
|
if (layFoundIndex === -1)
|
|
return;
|
|
this._layouts.splice(layFoundIndex, 1);
|
|
this.layouts = this._layouts;
|
|
this._selected_layouts.forEach((monitors_selected) => {
|
|
if (layoutToDelete.id === monitors_selected[Main2.layoutManager.primaryIndex]) {
|
|
monitors_selected[Main2.layoutManager.primaryIndex] = this._layouts[0].id;
|
|
this._save_selected_layouts();
|
|
}
|
|
});
|
|
}
|
|
editLayout(newLay) {
|
|
const layFoundIndex = this._layouts.findIndex(
|
|
(lay) => lay.id === newLay.id
|
|
);
|
|
if (layFoundIndex === -1)
|
|
return;
|
|
this._layouts[layFoundIndex] = newLay;
|
|
this.layouts = this._layouts;
|
|
}
|
|
set layouts(layouts) {
|
|
this._layouts = layouts;
|
|
Settings.save_layouts_json(layouts);
|
|
this.emit(GlobalState.SIGNAL_LAYOUTS_CHANGED);
|
|
}
|
|
getSelectedLayoutOfMonitor(monitorIndex, workspaceIndex) {
|
|
const selectedLayouts = Settings.get_selected_layouts();
|
|
if (workspaceIndex < 0 || workspaceIndex >= selectedLayouts.length)
|
|
workspaceIndex = 0;
|
|
const monitors_selected = workspaceIndex < selectedLayouts.length ? selectedLayouts[workspaceIndex] : GlobalState.get().layouts[0].id;
|
|
if (monitorIndex < 0 || monitorIndex >= monitors_selected.length)
|
|
monitorIndex = 0;
|
|
return this._layouts.find(
|
|
(lay) => lay.id === monitors_selected[monitorIndex]
|
|
) || this._layouts[0];
|
|
}
|
|
get tilePreviewAnimationTime() {
|
|
return this._tilePreviewAnimationTime;
|
|
}
|
|
set tilePreviewAnimationTime(value) {
|
|
this._tilePreviewAnimationTime = value;
|
|
}
|
|
};
|
|
__publicField(GlobalState, "metaInfo", {
|
|
GTypeName: "GlobalState",
|
|
Signals: {
|
|
"layouts-changed": {
|
|
param_types: []
|
|
}
|
|
},
|
|
Properties: {
|
|
tilePreviewAnimationTime: GObject.ParamSpec.uint(
|
|
"tilePreviewAnimationTime",
|
|
"tilePreviewAnimationTime",
|
|
"Animation time of tile previews in milliseconds",
|
|
GObject.ParamFlags.READWRITE,
|
|
0,
|
|
2e3,
|
|
100
|
|
)
|
|
}
|
|
});
|
|
__publicField(GlobalState, "SIGNAL_LAYOUTS_CHANGED", "layouts-changed");
|
|
__publicField(GlobalState, "_instance");
|
|
GlobalState = __decorateClass([
|
|
registerGObjectClass
|
|
], GlobalState);
|
|
|
|
// src/components/tilepreview/tilePreview.ts
|
|
var debug2 = logger("TilePreview");
|
|
var TilePreview = class extends St.Widget {
|
|
_rect;
|
|
_showing;
|
|
_tile;
|
|
_gaps;
|
|
constructor(params) {
|
|
super(params);
|
|
if (params.parent)
|
|
params.parent.add_child(this);
|
|
this._showing = false;
|
|
this._rect = params.rect || buildRectangle({});
|
|
this._gaps = new Clutter.Margin();
|
|
this.gaps = params.gaps || new Clutter.Margin();
|
|
this._tile = params.tile || new Tile2({ x: 0, y: 0, width: 0, height: 0, groups: [] });
|
|
}
|
|
set gaps(gaps) {
|
|
const [, scalingFactor] = getScalingFactorOf(this);
|
|
this._gaps.top = gaps.top * scalingFactor;
|
|
this._gaps.right = gaps.right * scalingFactor;
|
|
this._gaps.bottom = gaps.bottom * scalingFactor;
|
|
this._gaps.left = gaps.left * scalingFactor;
|
|
}
|
|
updateBorderRadius(hasNeighborTop, hasNeighborRight, hasNeighborBottom, hasNeighborLeft) {
|
|
this.remove_style_class_name("top-left-border-radius");
|
|
this.remove_style_class_name("top-right-border-radius");
|
|
this.remove_style_class_name("bottom-right-border-radius");
|
|
this.remove_style_class_name("bottom-left-border-radius");
|
|
this.remove_style_class_name("custom-tile-preview");
|
|
const topLeft = hasNeighborTop && hasNeighborLeft;
|
|
const topRight = hasNeighborTop && hasNeighborRight;
|
|
const bottomRight = hasNeighborBottom && hasNeighborRight;
|
|
const bottomLeft = hasNeighborBottom && hasNeighborLeft;
|
|
if (topLeft)
|
|
this.add_style_class_name("top-left-border-radius");
|
|
if (topRight)
|
|
this.add_style_class_name("top-right-border-radius");
|
|
if (bottomRight)
|
|
this.add_style_class_name("bottom-right-border-radius");
|
|
if (bottomLeft)
|
|
this.add_style_class_name("bottom-left-border-radius");
|
|
}
|
|
get gaps() {
|
|
return this._gaps;
|
|
}
|
|
get tile() {
|
|
return this._tile;
|
|
}
|
|
_init() {
|
|
super._init();
|
|
this.set_style_class_name("tile-preview");
|
|
this.hide();
|
|
}
|
|
get innerX() {
|
|
return this._rect.x + this._gaps.left;
|
|
}
|
|
get innerY() {
|
|
return this._rect.y + this._gaps.top;
|
|
}
|
|
get innerWidth() {
|
|
return this._rect.width - this._gaps.right - this._gaps.left;
|
|
}
|
|
get innerHeight() {
|
|
return this._rect.height - this._gaps.top - this._gaps.bottom;
|
|
}
|
|
get rect() {
|
|
return this._rect;
|
|
}
|
|
get showing() {
|
|
return this._showing;
|
|
}
|
|
open(ease = false, position) {
|
|
if (position)
|
|
this._rect = position;
|
|
const fadeInMove = this._showing;
|
|
this._showing = true;
|
|
this.show();
|
|
if (fadeInMove) {
|
|
this.ease({
|
|
x: this.innerX,
|
|
y: this.innerY,
|
|
width: this.innerWidth,
|
|
height: this.innerHeight,
|
|
opacity: 255,
|
|
duration: ease ? GlobalState.get().tilePreviewAnimationTime : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|
});
|
|
} else {
|
|
this.set_position(this.innerX, this.innerY);
|
|
this.set_size(this.innerWidth, this.innerHeight);
|
|
this.ease({
|
|
opacity: 255,
|
|
duration: ease ? GlobalState.get().tilePreviewAnimationTime : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|
});
|
|
}
|
|
}
|
|
openBelow(window, ease = false, position) {
|
|
if (this.get_parent() === global.windowGroup) {
|
|
const windowActor = window.get_compositor_private();
|
|
if (!windowActor)
|
|
return;
|
|
global.windowGroup.set_child_below_sibling(this, windowActor);
|
|
}
|
|
this.open(ease, position);
|
|
}
|
|
openAbove(window, ease = false, position) {
|
|
if (this.get_parent() === global.windowGroup) {
|
|
global.windowGroup.set_child_above_sibling(this, null);
|
|
}
|
|
this.open(ease, position);
|
|
}
|
|
close(ease = false) {
|
|
if (!this._showing)
|
|
return;
|
|
this._showing = false;
|
|
this.ease({
|
|
opacity: 0,
|
|
duration: ease ? GlobalState.get().tilePreviewAnimationTime : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => this.hide()
|
|
});
|
|
}
|
|
};
|
|
TilePreview = __decorateClass([
|
|
registerGObjectClass
|
|
], TilePreview);
|
|
|
|
// src/components/layout/TileUtils.ts
|
|
var TileUtils = class {
|
|
static apply_props(tile, container) {
|
|
return buildRectangle({
|
|
x: Math.round(container.width * tile.x + container.x),
|
|
y: Math.round(container.height * tile.y + container.y),
|
|
width: Math.round(container.width * tile.width),
|
|
height: Math.round(container.height * tile.height)
|
|
});
|
|
}
|
|
static apply_props_relative_to(tile, container) {
|
|
return buildRectangle({
|
|
x: Math.round(container.width * tile.x),
|
|
y: Math.round(container.height * tile.y),
|
|
width: Math.round(container.width * tile.width),
|
|
height: Math.round(container.height * tile.height)
|
|
});
|
|
}
|
|
static build_tile(rect, container) {
|
|
return new Tile2({
|
|
x: (rect.x - container.x) / container.width,
|
|
y: (rect.y - container.y) / container.height,
|
|
width: rect.width / container.width,
|
|
height: rect.height / container.height,
|
|
groups: []
|
|
});
|
|
}
|
|
};
|
|
|
|
// src/components/layout/LayoutWidget.ts
|
|
var debug3 = logger("LayoutWidget");
|
|
var LayoutWidget = class extends St.Widget {
|
|
_previews;
|
|
_containerRect;
|
|
_layout;
|
|
_innerGaps;
|
|
_outerGaps;
|
|
_scalingFactor;
|
|
constructor(params) {
|
|
super({ styleClass: params.styleClass || "" });
|
|
if (params.parent)
|
|
params.parent.add_child(this);
|
|
this._scalingFactor = 1;
|
|
if (params.scalingFactor)
|
|
this.scalingFactor = params.scalingFactor;
|
|
this._previews = [];
|
|
this._containerRect = params.containerRect || buildRectangle();
|
|
this._layout = params.layout || new Layout([], "");
|
|
this._innerGaps = params.innerGaps || new Clutter.Margin();
|
|
this._outerGaps = params.outerGaps || new Clutter.Margin();
|
|
}
|
|
set scalingFactor(value) {
|
|
enableScalingFactorSupport(this, value);
|
|
this._scalingFactor = value;
|
|
}
|
|
get scalingFactor() {
|
|
return this._scalingFactor;
|
|
}
|
|
get innerGaps() {
|
|
return this._innerGaps.copy();
|
|
}
|
|
get outerGaps() {
|
|
return this._outerGaps.copy();
|
|
}
|
|
get layout() {
|
|
return this._layout;
|
|
}
|
|
draw_layout() {
|
|
const containerWithoutOuterGaps = buildRectangle({
|
|
x: this._outerGaps.left + this._containerRect.x,
|
|
y: this._outerGaps.top + this._containerRect.y,
|
|
width: this._containerRect.width - this._outerGaps.left - this._outerGaps.right,
|
|
height: this._containerRect.height - this._outerGaps.top - this._outerGaps.bottom
|
|
});
|
|
this._previews = this._layout.tiles.map((tile) => {
|
|
const tileRect = TileUtils.apply_props(
|
|
tile,
|
|
containerWithoutOuterGaps
|
|
);
|
|
const { gaps, isTop, isRight, isBottom, isLeft } = buildTileGaps(
|
|
tileRect,
|
|
this._innerGaps,
|
|
this._outerGaps,
|
|
containerWithoutOuterGaps
|
|
);
|
|
if (isTop) {
|
|
tileRect.height += this._outerGaps.top;
|
|
tileRect.y -= this._outerGaps.top;
|
|
}
|
|
if (isLeft) {
|
|
tileRect.width += this._outerGaps.left;
|
|
tileRect.x -= this._outerGaps.left;
|
|
}
|
|
if (isRight)
|
|
tileRect.width += this._outerGaps.right;
|
|
if (isBottom)
|
|
tileRect.height += this._outerGaps.bottom;
|
|
return this.buildTile(this, tileRect, gaps, tile);
|
|
});
|
|
}
|
|
buildTile(_parent, _rect, _margin, _tile) {
|
|
throw new Error(
|
|
"This class shouldn't be instantiated but it should be extended instead"
|
|
);
|
|
}
|
|
relayout(params) {
|
|
let trigger_relayout = this._previews.length === 0;
|
|
if (params?.layout && this._layout !== params.layout) {
|
|
this._layout = params.layout;
|
|
trigger_relayout = true;
|
|
}
|
|
if (params?.innerGaps) {
|
|
trigger_relayout || (trigger_relayout = !this._areGapsEqual(
|
|
this._innerGaps,
|
|
params.innerGaps
|
|
));
|
|
this._innerGaps = params.innerGaps.copy();
|
|
}
|
|
if (params?.outerGaps && this._outerGaps !== params.outerGaps) {
|
|
trigger_relayout || (trigger_relayout = !this._areGapsEqual(
|
|
this._outerGaps,
|
|
params.outerGaps
|
|
));
|
|
this._outerGaps = params.outerGaps.copy();
|
|
}
|
|
if (params?.containerRect && !this._containerRect.equal(params.containerRect)) {
|
|
this._containerRect = params.containerRect.copy();
|
|
trigger_relayout = true;
|
|
}
|
|
if (!trigger_relayout) {
|
|
debug3("relayout not needed");
|
|
return false;
|
|
}
|
|
this._previews?.forEach((preview) => {
|
|
if (preview.get_parent() === this)
|
|
this.remove_child(preview);
|
|
preview.destroy();
|
|
});
|
|
this._previews = [];
|
|
if (this._containerRect.width === 0 || this._containerRect.height === 0)
|
|
return true;
|
|
this.draw_layout();
|
|
this._previews.forEach((lay) => lay.open());
|
|
return true;
|
|
}
|
|
_areGapsEqual(first, second) {
|
|
return first.bottom === second.bottom && first.top === second.top && first.left === second.left && first.right === second.right;
|
|
}
|
|
};
|
|
LayoutWidget = __decorateClass([
|
|
registerGObjectClass
|
|
], LayoutWidget);
|
|
|
|
// src/settings/settingsOverride.ts
|
|
var SettingsOverride = class _SettingsOverride {
|
|
// map schema_id with map of keys and old values
|
|
_overriddenKeys;
|
|
static _instance;
|
|
constructor() {
|
|
this._overriddenKeys = this._jsonToOverriddenKeys(
|
|
Settings.OVERRIDDEN_SETTINGS
|
|
);
|
|
}
|
|
static get() {
|
|
if (!this._instance)
|
|
this._instance = new _SettingsOverride();
|
|
return this._instance;
|
|
}
|
|
static destroy() {
|
|
if (!this._instance)
|
|
return;
|
|
this._instance.restoreAll();
|
|
this._instance = null;
|
|
}
|
|
/*
|
|
json will have the following structure
|
|
{
|
|
"schema.id": {
|
|
"overridden.key.one": oldvalue,
|
|
"overridden.key.two": oldvalue
|
|
...
|
|
},
|
|
...
|
|
}
|
|
*/
|
|
_overriddenKeysToJSON() {
|
|
const obj = {};
|
|
this._overriddenKeys.forEach((override, schemaId) => {
|
|
obj[schemaId] = {};
|
|
override.forEach((oldValue, key) => {
|
|
obj[schemaId][key] = oldValue.print(true);
|
|
});
|
|
});
|
|
return JSON.stringify(obj);
|
|
}
|
|
_jsonToOverriddenKeys(json) {
|
|
const result = /* @__PURE__ */ new Map();
|
|
const obj = JSON.parse(json);
|
|
for (const schemaId in obj) {
|
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
result.set(schemaId, schemaMap);
|
|
const overrideObj = obj[schemaId];
|
|
for (const key in overrideObj) {
|
|
schemaMap.set(
|
|
key,
|
|
GLib.Variant.parse(null, overrideObj[key], null, null)
|
|
);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
override(giosettings, keyToOverride, newValue) {
|
|
const schemaId = giosettings.schemaId;
|
|
const schemaMap = this._overriddenKeys.get(schemaId) || /* @__PURE__ */ new Map();
|
|
if (!this._overriddenKeys.has(schemaId))
|
|
this._overriddenKeys.set(schemaId, schemaMap);
|
|
const oldValue = schemaMap.has(keyToOverride) ? schemaMap.get(keyToOverride) : giosettings.get_value(keyToOverride);
|
|
const res = giosettings.set_value(keyToOverride, newValue);
|
|
if (!res)
|
|
return null;
|
|
if (!schemaMap.has(keyToOverride)) {
|
|
schemaMap.set(keyToOverride, oldValue);
|
|
Settings.OVERRIDDEN_SETTINGS = this._overriddenKeysToJSON();
|
|
}
|
|
return oldValue;
|
|
}
|
|
restoreKey(giosettings, keyToOverride) {
|
|
const overridden = this._overriddenKeys.get(giosettings.schemaId);
|
|
if (!overridden)
|
|
return null;
|
|
const oldValue = overridden.get(keyToOverride);
|
|
if (!oldValue)
|
|
return null;
|
|
const res = giosettings.set_value(keyToOverride, oldValue);
|
|
if (res) {
|
|
overridden.delete(keyToOverride);
|
|
if (overridden.size === 0)
|
|
this._overriddenKeys.delete(giosettings.schemaId);
|
|
Settings.OVERRIDDEN_SETTINGS = this._overriddenKeysToJSON();
|
|
}
|
|
return oldValue;
|
|
}
|
|
restoreAll() {
|
|
const schemaToDelete = [];
|
|
this._overriddenKeys.forEach(
|
|
(map, schemaId) => {
|
|
const giosettings = new Gio.Settings({ schemaId });
|
|
const overridden = this._overriddenKeys.get(
|
|
giosettings.schemaId
|
|
);
|
|
if (!overridden)
|
|
return;
|
|
const toDelete = [];
|
|
overridden.forEach((oldValue, key) => {
|
|
const done = giosettings.set_value(key, oldValue);
|
|
if (done)
|
|
toDelete.push(key);
|
|
});
|
|
toDelete.forEach((key) => overridden.delete(key));
|
|
if (overridden.size === 0)
|
|
schemaToDelete.push(schemaId);
|
|
}
|
|
);
|
|
schemaToDelete.forEach((schemaId) => {
|
|
this._overriddenKeys.delete(schemaId);
|
|
});
|
|
if (this._overriddenKeys.size === 0)
|
|
this._overriddenKeys = /* @__PURE__ */ new Map();
|
|
Settings.OVERRIDDEN_SETTINGS = this._overriddenKeysToJSON();
|
|
}
|
|
};
|
|
|
|
// src/keybindings.ts
|
|
import * as Main3 from "resource:///org/gnome/shell/ui/main.js";
|
|
var debug4 = logger("KeyBindings");
|
|
var KeyBindingsDirection = /* @__PURE__ */ ((KeyBindingsDirection2) => {
|
|
KeyBindingsDirection2[KeyBindingsDirection2["NODIRECTION"] = 1] = "NODIRECTION";
|
|
KeyBindingsDirection2[KeyBindingsDirection2["UP"] = 2] = "UP";
|
|
KeyBindingsDirection2[KeyBindingsDirection2["DOWN"] = 3] = "DOWN";
|
|
KeyBindingsDirection2[KeyBindingsDirection2["LEFT"] = 4] = "LEFT";
|
|
KeyBindingsDirection2[KeyBindingsDirection2["RIGHT"] = 5] = "RIGHT";
|
|
return KeyBindingsDirection2;
|
|
})(KeyBindingsDirection || {});
|
|
var FocusSwitchDirection = /* @__PURE__ */ ((FocusSwitchDirection2) => {
|
|
FocusSwitchDirection2[FocusSwitchDirection2["NEXT"] = 1] = "NEXT";
|
|
FocusSwitchDirection2[FocusSwitchDirection2["PREV"] = 2] = "PREV";
|
|
return FocusSwitchDirection2;
|
|
})(FocusSwitchDirection || {});
|
|
var KeyBindings = class extends GObject.Object {
|
|
_signals;
|
|
constructor(extensionSettings) {
|
|
super();
|
|
this._signals = new SignalHandling();
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_ENABLE_MOVE_KEYBINDINGS,
|
|
() => {
|
|
this._setupKeyBindings(extensionSettings);
|
|
}
|
|
);
|
|
if (Settings.ENABLE_MOVE_KEYBINDINGS)
|
|
this._setupKeyBindings(extensionSettings);
|
|
}
|
|
_setupKeyBindings(extensionSettings) {
|
|
if (Settings.ENABLE_MOVE_KEYBINDINGS)
|
|
this._applyKeybindings(extensionSettings);
|
|
else
|
|
this._removeKeybindings();
|
|
}
|
|
_applyKeybindings(extensionSettings) {
|
|
this._overrideNatives(extensionSettings);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_SPAN_WINDOW_RIGHT,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("span-window", display, 5 /* RIGHT */);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_SPAN_WINDOW_LEFT,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("span-window", display, 4 /* LEFT */);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_SPAN_WINDOW_UP,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("span-window", display, 2 /* UP */);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_SPAN_WINDOW_DOWN,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("span-window", display, 3 /* DOWN */);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_SPAN_WINDOW_ALL_TILES,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("span-window-all-tiles", display);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_UNTILE_WINDOW,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(dp) => this.emit("untile-window", dp)
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_MOVE_WINDOW_CENTER,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(dp) => this.emit("move-window-center", dp)
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_FOCUS_WINDOW_RIGHT,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit(
|
|
"focus-window-direction",
|
|
display,
|
|
5 /* RIGHT */
|
|
);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_FOCUS_WINDOW_LEFT,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit(
|
|
"focus-window-direction",
|
|
display,
|
|
4 /* LEFT */
|
|
);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_FOCUS_WINDOW_UP,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit(
|
|
"focus-window-direction",
|
|
display,
|
|
2 /* UP */
|
|
);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_FOCUS_WINDOW_DOWN,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit(
|
|
"focus-window-direction",
|
|
display,
|
|
3 /* DOWN */
|
|
);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_FOCUS_WINDOW_NEXT,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("focus-window", display, 1 /* NEXT */);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_FOCUS_WINDOW_PREV,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("focus-window", display, 2 /* PREV */);
|
|
}
|
|
);
|
|
Main3.wm.addKeybinding(
|
|
Settings.SETTING_HIGHLIGHT_CURRENT_WINDOW,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
(display) => {
|
|
this.emit("highlight-current-window", display);
|
|
}
|
|
);
|
|
}
|
|
_overrideNatives(extensionSettings) {
|
|
const mutterKeybindings = new Gio.Settings({
|
|
schema_id: "org.gnome.mutter.keybindings"
|
|
});
|
|
this._overrideKeyBinding(
|
|
Settings.SETTING_MOVE_WINDOW_RIGHT,
|
|
(display) => {
|
|
this.emit("move-window", display, 5 /* RIGHT */);
|
|
},
|
|
extensionSettings,
|
|
mutterKeybindings,
|
|
"toggle-tiled-right"
|
|
);
|
|
this._overrideKeyBinding(
|
|
Settings.SETTING_MOVE_WINDOW_LEFT,
|
|
(display) => {
|
|
this.emit("move-window", display, 4 /* LEFT */);
|
|
},
|
|
extensionSettings,
|
|
mutterKeybindings,
|
|
"toggle-tiled-left"
|
|
);
|
|
const desktopWm = new Gio.Settings({
|
|
schema_id: "org.gnome.desktop.wm.keybindings"
|
|
});
|
|
this._overrideKeyBinding(
|
|
Settings.SETTING_MOVE_WINDOW_UP,
|
|
(display) => {
|
|
this.emit("move-window", display, 2 /* UP */);
|
|
},
|
|
extensionSettings,
|
|
desktopWm,
|
|
"maximize"
|
|
);
|
|
this._overrideKeyBinding(
|
|
Settings.SETTING_MOVE_WINDOW_DOWN,
|
|
(display) => {
|
|
this.emit("move-window", display, 3 /* DOWN */);
|
|
},
|
|
extensionSettings,
|
|
desktopWm,
|
|
"unmaximize"
|
|
);
|
|
}
|
|
_overrideKeyBinding(name, handler, extensionSettings, nativeSettings, nativeKeyName) {
|
|
const done = SettingsOverride.get().override(
|
|
nativeSettings,
|
|
nativeKeyName,
|
|
new GLib.Variant("as", [])
|
|
);
|
|
if (!done) {
|
|
debug4(`failed to override ${nativeKeyName}`);
|
|
return;
|
|
}
|
|
Main3.wm.addKeybinding(
|
|
name,
|
|
extensionSettings,
|
|
Meta.KeyBindingFlags.NONE,
|
|
Shell.ActionMode.NORMAL,
|
|
handler
|
|
);
|
|
}
|
|
_removeKeybindings() {
|
|
this._restoreNatives();
|
|
Main3.wm.removeKeybinding(Settings.SETTING_MOVE_WINDOW_RIGHT);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_MOVE_WINDOW_LEFT);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_MOVE_WINDOW_UP);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_MOVE_WINDOW_DOWN);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_SPAN_WINDOW_RIGHT);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_SPAN_WINDOW_LEFT);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_SPAN_WINDOW_UP);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_SPAN_WINDOW_DOWN);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_SPAN_WINDOW_ALL_TILES);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_UNTILE_WINDOW);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_MOVE_WINDOW_CENTER);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_UP);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_DOWN);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_LEFT);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_RIGHT);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_NEXT);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_PREV);
|
|
Main3.wm.removeKeybinding(Settings.SETTING_HIGHLIGHT_CURRENT_WINDOW);
|
|
}
|
|
_restoreNatives() {
|
|
const mutterKeybindings = new Gio.Settings({
|
|
schema_id: "org.gnome.mutter.keybindings"
|
|
});
|
|
SettingsOverride.get().restoreKey(
|
|
mutterKeybindings,
|
|
"toggle-tiled-right"
|
|
);
|
|
SettingsOverride.get().restoreKey(
|
|
mutterKeybindings,
|
|
"toggle-tiled-left"
|
|
);
|
|
const desktopWm = new Gio.Settings({
|
|
schema_id: "org.gnome.desktop.wm.keybindings"
|
|
});
|
|
SettingsOverride.get().restoreKey(desktopWm, "maximize");
|
|
SettingsOverride.get().restoreKey(desktopWm, "unmaximize");
|
|
}
|
|
destroy() {
|
|
this._removeKeybindings();
|
|
}
|
|
};
|
|
__publicField(KeyBindings, "metaInfo", {
|
|
GTypeName: "KeyBindings",
|
|
Signals: {
|
|
"move-window": {
|
|
param_types: [Meta.Display.$gtype, GObject.TYPE_INT]
|
|
// Meta.Display, KeyBindingsDirection
|
|
},
|
|
"span-window": {
|
|
param_types: [Meta.Display.$gtype, GObject.TYPE_INT]
|
|
// Meta.Display, KeyBindingsDirection
|
|
},
|
|
"span-window-all-tiles": {
|
|
param_types: [Meta.Display.$gtype]
|
|
// Meta.Display
|
|
},
|
|
"untile-window": {
|
|
param_types: [Meta.Display.$gtype]
|
|
// Meta.Display
|
|
},
|
|
"move-window-center": {
|
|
param_types: [Meta.Display.$gtype]
|
|
// Meta.Display
|
|
},
|
|
"focus-window-direction": {
|
|
param_types: [Meta.Display.$gtype, GObject.TYPE_INT]
|
|
// Meta.Display, KeyBindingsDirection
|
|
},
|
|
"focus-window": {
|
|
param_types: [Meta.Display.$gtype, GObject.TYPE_INT]
|
|
// Meta.Display, FocusSwitchDirection
|
|
},
|
|
"highlight-current-window": {
|
|
param_types: [Meta.Display.$gtype]
|
|
// Meta.Display,
|
|
}
|
|
}
|
|
});
|
|
KeyBindings = __decorateClass([
|
|
registerGObjectClass
|
|
], KeyBindings);
|
|
|
|
// src/components/tilingsystem/tilingLayout.ts
|
|
var debug5 = logger("TilingLayout");
|
|
var DynamicTilePreview = class extends TilePreview {
|
|
_originalRect;
|
|
_canRestore;
|
|
constructor(params, canRestore) {
|
|
super(params);
|
|
this._canRestore = canRestore || false;
|
|
this._originalRect = this.rect.copy();
|
|
}
|
|
get originalRect() {
|
|
return this._originalRect;
|
|
}
|
|
get canRestore() {
|
|
return this._canRestore;
|
|
}
|
|
restore(ease = false) {
|
|
if (!this._canRestore)
|
|
return false;
|
|
this._rect = this._originalRect.copy();
|
|
if (this.showing)
|
|
this.open(ease);
|
|
return true;
|
|
}
|
|
};
|
|
DynamicTilePreview = __decorateClass([
|
|
registerGObjectClass
|
|
], DynamicTilePreview);
|
|
var TilingLayout = class extends LayoutWidget {
|
|
_showing;
|
|
constructor(layout, innerGaps, outerGaps, workarea, scalingFactor) {
|
|
super({
|
|
containerRect: workarea,
|
|
parent: global.windowGroup,
|
|
layout,
|
|
innerGaps,
|
|
outerGaps,
|
|
scalingFactor
|
|
});
|
|
this._showing = false;
|
|
super.relayout();
|
|
}
|
|
_init() {
|
|
super._init();
|
|
this.hide();
|
|
}
|
|
buildTile(parent, rect, gaps, tile) {
|
|
const prev = new DynamicTilePreview({ parent, rect, gaps, tile }, true);
|
|
prev.updateBorderRadius(
|
|
prev.gaps.top > 0,
|
|
prev.gaps.right > 0,
|
|
prev.gaps.bottom > 0,
|
|
prev.gaps.left > 0
|
|
);
|
|
return prev;
|
|
}
|
|
get showing() {
|
|
return this._showing;
|
|
}
|
|
openBelow(window) {
|
|
if (this._showing)
|
|
return;
|
|
const windowActor = window.get_compositor_private();
|
|
if (!windowActor)
|
|
return;
|
|
global.windowGroup.set_child_below_sibling(this, windowActor);
|
|
this.open();
|
|
}
|
|
openAbove(window) {
|
|
if (this._showing)
|
|
return;
|
|
global.windowGroup.set_child_above_sibling(this, null);
|
|
this.open();
|
|
}
|
|
open(ease = false) {
|
|
if (this._showing)
|
|
return;
|
|
this.show();
|
|
this._showing = true;
|
|
this.ease({
|
|
x: this.x,
|
|
y: this.y,
|
|
opacity: 255,
|
|
duration: ease ? GlobalState.get().tilePreviewAnimationTime : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|
});
|
|
}
|
|
close(ease = false) {
|
|
if (!this._showing)
|
|
return;
|
|
this._showing = false;
|
|
this.ease({
|
|
opacity: 0,
|
|
duration: ease ? GlobalState.get().tilePreviewAnimationTime : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => {
|
|
this.unhoverAllTiles();
|
|
this.hide();
|
|
}
|
|
});
|
|
}
|
|
_isHovered(currPointerPos, preview) {
|
|
return currPointerPos.x >= preview.x && currPointerPos.x <= preview.x + preview.width && currPointerPos.y >= preview.y && currPointerPos.y <= preview.y + preview.height;
|
|
}
|
|
getTileBelow(currPointerPos, reset) {
|
|
let found = this._previews.find(
|
|
(preview) => this._isHovered(currPointerPos, preview.rect)
|
|
);
|
|
if (!found || !found.canRestore && reset) {
|
|
found = this._previews.find(
|
|
(preview) => preview.canRestore && this._isHovered(currPointerPos, preview.originalRect)
|
|
);
|
|
}
|
|
if (!found)
|
|
return void 0;
|
|
if (reset && found.originalRect)
|
|
return found.originalRect;
|
|
return found.rect;
|
|
}
|
|
unhoverAllTiles() {
|
|
const newPreviewsArray = [];
|
|
this._previews.forEach((preview) => {
|
|
if (preview.restore(true)) {
|
|
newPreviewsArray.push(preview);
|
|
preview.open(true);
|
|
} else {
|
|
this.remove_child(preview);
|
|
preview.destroy();
|
|
}
|
|
});
|
|
this._previews = newPreviewsArray;
|
|
}
|
|
hoverTilesInRect(rect, reset) {
|
|
const newPreviewsArray = [];
|
|
this._previews.forEach((preview) => {
|
|
const [hasIntersection, rectangles] = this._subtractRectangles(
|
|
preview.rect,
|
|
rect
|
|
);
|
|
if (hasIntersection) {
|
|
if (rectangles.length > 0) {
|
|
let maxIndex = 0;
|
|
for (let i = 0; i < rectangles.length; i++) {
|
|
if (rectangles[i].area() > rectangles[maxIndex].area())
|
|
maxIndex = i;
|
|
}
|
|
for (let i = 0; i < rectangles.length; i++) {
|
|
if (i === maxIndex)
|
|
continue;
|
|
const currRect = rectangles[i];
|
|
const gaps = buildTileGaps(
|
|
currRect,
|
|
this._innerGaps,
|
|
this._outerGaps,
|
|
this._containerRect
|
|
).gaps;
|
|
const innerPreview = new DynamicTilePreview(
|
|
{
|
|
parent: this,
|
|
rect: currRect,
|
|
gaps,
|
|
tile: TileUtils.build_tile(
|
|
currRect,
|
|
this._containerRect
|
|
)
|
|
},
|
|
false
|
|
);
|
|
innerPreview.open();
|
|
this.set_child_above_sibling(innerPreview, preview);
|
|
newPreviewsArray.push(innerPreview);
|
|
}
|
|
preview.open(
|
|
false,
|
|
rectangles[maxIndex].union(
|
|
preview.rect.intersect(rect)[1]
|
|
)
|
|
);
|
|
preview.open(true, rectangles[maxIndex]);
|
|
newPreviewsArray.push(preview);
|
|
} else {
|
|
preview.close();
|
|
newPreviewsArray.push(preview);
|
|
}
|
|
} else if (reset) {
|
|
if (preview.restore(true)) {
|
|
preview.open(true);
|
|
newPreviewsArray.push(preview);
|
|
} else {
|
|
this.remove_child(preview);
|
|
preview.destroy();
|
|
}
|
|
} else {
|
|
preview.open(true);
|
|
newPreviewsArray.push(preview);
|
|
}
|
|
});
|
|
this._previews = newPreviewsArray;
|
|
}
|
|
/*
|
|
Given the source rectangle (made by A, B, C, D and Hole), subtract the hole and obtain A, B, C and D.
|
|
Edge cases:
|
|
- The hole may not be inside the source rect (i.e there is no interstaction).
|
|
It returns false and an array with the source rectangle only
|
|
- The hole intersects the source rectangle, it returns true and an array with A, B, C and D rectangles.
|
|
Some of A, B, C and D may not be returned if they don't exist
|
|
- The hole is equal to the source rectangle, it returns true and an empty array since A, B, C and D
|
|
rectangles do not exist
|
|
|
|
Example:
|
|
-------------------------
|
|
| A |
|
|
|-----------------------|
|
|
| B | hole | C |
|
|
|-----------------------|
|
|
| D |
|
|
-------------------------
|
|
*/
|
|
_subtractRectangles(sourceRect, holeRect) {
|
|
const [hasIntersection, intersection] = sourceRect.intersect(holeRect);
|
|
if (!hasIntersection)
|
|
return [false, [sourceRect]];
|
|
if (intersection.area() >= sourceRect.area() * 0.98)
|
|
return [true, []];
|
|
const results = [];
|
|
const heightA = intersection.y - sourceRect.y;
|
|
if (heightA > 0) {
|
|
results.push(
|
|
buildRectangle({
|
|
x: sourceRect.x,
|
|
y: sourceRect.y,
|
|
width: sourceRect.width,
|
|
height: heightA
|
|
})
|
|
);
|
|
}
|
|
const widthB = intersection.x - sourceRect.x;
|
|
if (widthB > 0 && intersection.height > 0) {
|
|
results.push(
|
|
buildRectangle({
|
|
x: sourceRect.x,
|
|
y: intersection.y,
|
|
width: widthB,
|
|
height: intersection.height
|
|
})
|
|
);
|
|
}
|
|
const widthC = sourceRect.x + sourceRect.width - intersection.x - intersection.width;
|
|
if (widthC > 0 && intersection.height > 0) {
|
|
results.push(
|
|
buildRectangle({
|
|
x: intersection.x + intersection.width,
|
|
y: intersection.y,
|
|
width: widthC,
|
|
height: intersection.height
|
|
})
|
|
);
|
|
}
|
|
const heightD = sourceRect.y + sourceRect.height - intersection.y - intersection.height;
|
|
if (heightD > 0) {
|
|
results.push(
|
|
buildRectangle({
|
|
x: sourceRect.x,
|
|
y: intersection.y + intersection.height,
|
|
width: sourceRect.width,
|
|
height: heightD
|
|
})
|
|
);
|
|
}
|
|
return [true, results];
|
|
}
|
|
// enlarge the side of the direction and search a tile that contains that point
|
|
// clamp to ensure we do not go outside of the container area (e.g. the screen)
|
|
findNearestTileDirection(source, direction, clamp, enlarge) {
|
|
if (direction === 1 /* NODIRECTION */)
|
|
return void 0;
|
|
const sourceCoords = {
|
|
x: source.x + source.width / 2,
|
|
y: source.y + source.height / 2
|
|
};
|
|
switch (direction) {
|
|
case 5 /* RIGHT */:
|
|
sourceCoords.x = source.x + source.width + enlarge;
|
|
break;
|
|
case 4 /* LEFT */:
|
|
sourceCoords.x = source.x - enlarge;
|
|
break;
|
|
case 3 /* DOWN */:
|
|
sourceCoords.y = source.y + source.height + enlarge;
|
|
break;
|
|
case 2 /* UP */:
|
|
sourceCoords.y = source.y - enlarge;
|
|
break;
|
|
}
|
|
if (sourceCoords.x < this._containerRect.x || sourceCoords.x > this._containerRect.width + this._containerRect.x || sourceCoords.y < this._containerRect.y || sourceCoords.y > this._containerRect.height + this._containerRect.y) {
|
|
if (!clamp)
|
|
return void 0;
|
|
sourceCoords.x = Math.clamp(
|
|
sourceCoords.x,
|
|
this._containerRect.x,
|
|
this._containerRect.width + this._containerRect.x
|
|
);
|
|
sourceCoords.y = Math.clamp(
|
|
sourceCoords.y,
|
|
this._containerRect.y,
|
|
this._containerRect.height + this._containerRect.y
|
|
);
|
|
}
|
|
for (let i = 0; i < this._previews.length; i++) {
|
|
const previewFound = this._previews[i];
|
|
if (isPointInsideRect(sourceCoords, previewFound.rect)) {
|
|
return {
|
|
rect: buildRectangle({
|
|
x: previewFound.innerX,
|
|
y: previewFound.innerY,
|
|
width: previewFound.innerWidth,
|
|
height: previewFound.innerHeight
|
|
}),
|
|
tile: previewFound.tile
|
|
};
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
findNearestTile(source) {
|
|
let previewFound;
|
|
let bestDistance = -1;
|
|
const sourceCenter = {
|
|
x: source.x + source.width / 2,
|
|
y: source.x + source.height / 2
|
|
};
|
|
for (let i = 0; i < this._previews.length; i++) {
|
|
const preview = this._previews[i];
|
|
const previewCenter = {
|
|
x: preview.innerX + preview.innerWidth / 2,
|
|
y: preview.innerY + preview.innerHeight / 2
|
|
};
|
|
const euclideanDistance = squaredEuclideanDistance(
|
|
previewCenter,
|
|
sourceCenter
|
|
);
|
|
if (!previewFound || euclideanDistance < bestDistance) {
|
|
previewFound = preview;
|
|
bestDistance = euclideanDistance;
|
|
}
|
|
}
|
|
if (!previewFound)
|
|
return void 0;
|
|
return {
|
|
rect: buildRectangle({
|
|
x: previewFound.innerX,
|
|
y: previewFound.innerY,
|
|
width: previewFound.innerWidth,
|
|
height: previewFound.innerHeight
|
|
}),
|
|
tile: previewFound.tile
|
|
};
|
|
}
|
|
};
|
|
TilingLayout = __decorateClass([
|
|
registerGObjectClass
|
|
], TilingLayout);
|
|
|
|
// src/components/snapassist/snapAssistTile.ts
|
|
var debug6 = logger("SnapAssistTile");
|
|
var MIN_RADIUS = 2;
|
|
var SnapAssistTile = class extends TilePreview {
|
|
_styleChangedSignalID;
|
|
constructor(params) {
|
|
super({
|
|
parent: params.parent,
|
|
rect: params.rect,
|
|
gaps: params.gaps,
|
|
tile: params.tile
|
|
});
|
|
const isLeft = this._tile.x <= 1e-3;
|
|
const isTop = this._tile.y <= 1e-3;
|
|
const isRight = this._tile.x + this._tile.width >= 0.99;
|
|
const isBottom = this._tile.y + this._tile.height >= 0.99;
|
|
const [alreadyScaled, scalingFactor] = getScalingFactorOf(this);
|
|
const radiusValue = (alreadyScaled ? 1 : scalingFactor) * (this.get_theme_node().get_length("border-radius-value") / (alreadyScaled ? scalingFactor : 1));
|
|
const borderWidthValue = (alreadyScaled ? 1 : scalingFactor) * (this.get_theme_node().get_length("border-width-value") / (alreadyScaled ? scalingFactor : 1));
|
|
const radius = [
|
|
this._gaps.top === 0 && this._gaps.left === 0 ? 0 : MIN_RADIUS,
|
|
this._gaps.top === 0 && this._gaps.right === 0 ? 0 : MIN_RADIUS,
|
|
this._gaps.bottom === 0 && this._gaps.right === 0 ? 0 : MIN_RADIUS,
|
|
this._gaps.bottom === 0 && this._gaps.left === 0 ? 0 : MIN_RADIUS
|
|
];
|
|
if (isTop && isLeft)
|
|
radius[St.Corner.TOPLEFT] = radiusValue;
|
|
if (isTop && isRight)
|
|
radius[St.Corner.TOPRIGHT] = radiusValue;
|
|
if (isBottom && isRight)
|
|
radius[St.Corner.BOTTOMRIGHT] = radiusValue;
|
|
if (isBottom && isLeft)
|
|
radius[St.Corner.BOTTOMLEFT] = radiusValue;
|
|
const borderWidth = [
|
|
borderWidthValue,
|
|
borderWidthValue,
|
|
borderWidthValue,
|
|
borderWidthValue
|
|
];
|
|
if (isTop || this._gaps.top > 0)
|
|
borderWidth[St.Side.TOP] *= 2;
|
|
else
|
|
borderWidth[St.Side.TOP] = Math.floor(borderWidth[St.Side.TOP]);
|
|
if (isRight || this._gaps.right > 0)
|
|
borderWidth[St.Side.RIGHT] *= 2;
|
|
else
|
|
borderWidth[St.Side.RIGHT] = Math.floor(borderWidth[St.Side.RIGHT]);
|
|
if (isBottom || this._gaps.bottom > 0)
|
|
borderWidth[St.Side.BOTTOM] *= 2;
|
|
if (isLeft || this._gaps.left > 0)
|
|
borderWidth[St.Side.LEFT] *= 2;
|
|
this.set_style(`
|
|
border-radius: ${radius[St.Corner.TOPLEFT]}px ${radius[St.Corner.TOPRIGHT]}px ${radius[St.Corner.BOTTOMRIGHT]}px ${radius[St.Corner.BOTTOMLEFT]}px;
|
|
border-top-width: ${borderWidth[St.Side.TOP]}px;
|
|
border-right-width: ${borderWidth[St.Side.RIGHT]}px;
|
|
border-bottom-width: ${borderWidth[St.Side.BOTTOM]}px;
|
|
border-left-width: ${borderWidth[St.Side.LEFT]}px;`);
|
|
this._applyStyle();
|
|
this._styleChangedSignalID = St.ThemeContext.get_for_stage(
|
|
global.get_stage()
|
|
).connect("changed", () => {
|
|
this._applyStyle();
|
|
});
|
|
this.connect("destroy", () => this.onDestroy());
|
|
}
|
|
_init() {
|
|
super._init();
|
|
this.set_style_class_name("snap-assist-tile");
|
|
}
|
|
_applyStyle() {
|
|
const [hasColor, { red, green, blue }] = this.get_theme_node().lookup_color("color", true);
|
|
if (!hasColor)
|
|
return;
|
|
if (red * 0.299 + green * 0.587 + blue * 0.114 > 186) {
|
|
this.remove_style_class_name("dark");
|
|
} else {
|
|
this.add_style_class_name("dark");
|
|
}
|
|
}
|
|
onDestroy() {
|
|
if (this._styleChangedSignalID) {
|
|
St.ThemeContext.get_for_stage(global.get_stage()).disconnect(
|
|
this._styleChangedSignalID
|
|
);
|
|
this._styleChangedSignalID = void 0;
|
|
}
|
|
}
|
|
};
|
|
SnapAssistTile = __decorateClass([
|
|
registerGObjectClass
|
|
], SnapAssistTile);
|
|
|
|
// src/components/snapassist/snapAssistLayout.ts
|
|
var SnapAssistLayout = class extends LayoutWidget {
|
|
constructor(parent, layout, innerGaps, outerGaps, width, height) {
|
|
super({
|
|
containerRect: buildRectangle({ x: 0, y: 0, width, height }),
|
|
parent,
|
|
layout,
|
|
innerGaps,
|
|
outerGaps
|
|
});
|
|
this.set_size(width, height);
|
|
super.relayout();
|
|
}
|
|
buildTile(parent, rect, gaps, tile) {
|
|
return new SnapAssistTile({ parent, rect, gaps, tile });
|
|
}
|
|
getTileBelow(cursorPos) {
|
|
const [x, y] = this.get_transformed_position();
|
|
for (let i = 0; i < this._previews.length; i++) {
|
|
const preview = this._previews[i];
|
|
const pos = { x: x + preview.rect.x, y: y + preview.rect.y };
|
|
const isHovering = cursorPos.x >= pos.x && cursorPos.x <= pos.x + preview.rect.width && cursorPos.y >= pos.y && cursorPos.y <= pos.y + preview.rect.height;
|
|
if (isHovering)
|
|
return preview;
|
|
}
|
|
}
|
|
};
|
|
SnapAssistLayout = __decorateClass([
|
|
registerGObjectClass
|
|
], SnapAssistLayout);
|
|
|
|
// src/components/snapassist/snapAssist.ts
|
|
var SNAP_ASSIST_SIGNAL = "snap-assist";
|
|
var GAPS = 4;
|
|
var SNAP_ASSIST_LAYOUT_WIDTH = 120;
|
|
var SNAP_ASSIST_LAYOUT_HEIGHT = 68;
|
|
var debug7 = logger("SnapAssist");
|
|
var SnapAssistContent = class extends St.BoxLayout {
|
|
_container;
|
|
_showing;
|
|
_signals;
|
|
_snapAssistLayouts;
|
|
_isEnlarged = false;
|
|
_hoveredInfo;
|
|
_padding;
|
|
_blur;
|
|
_snapAssistantThreshold;
|
|
_snapAssistantAnimationTime;
|
|
_monitorIndex;
|
|
constructor(container, monitorIndex) {
|
|
super({
|
|
name: "snap_assist_content",
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
reactive: true,
|
|
styleClass: "popup-menu-content snap-assistant"
|
|
});
|
|
this._container = container;
|
|
this._container.add_child(this);
|
|
this._signals = new SignalHandling();
|
|
this._snapAssistLayouts = [];
|
|
this._isEnlarged = false;
|
|
this._showing = true;
|
|
this._padding = 0;
|
|
this._blur = false;
|
|
this._snapAssistantAnimationTime = 100;
|
|
this._monitorIndex = monitorIndex;
|
|
this._snapAssistantThreshold = 54 * getMonitorScalingFactor(this._monitorIndex);
|
|
Settings.bind(
|
|
Settings.KEY_ENABLE_BLUR_SNAP_ASSISTANT,
|
|
this,
|
|
"blur",
|
|
Gio.SettingsBindFlags.GET
|
|
);
|
|
Settings.bind(
|
|
Settings.KEY_SNAP_ASSISTANT_THRESHOLD,
|
|
this,
|
|
"snapAssistantThreshold",
|
|
Gio.SettingsBindFlags.GET
|
|
);
|
|
Settings.bind(
|
|
Settings.KEY_SNAP_ASSISTANT_ANIMATION_TIME,
|
|
this,
|
|
"snapAssistantAnimationTime",
|
|
Gio.SettingsBindFlags.GET
|
|
);
|
|
this._applyStyle();
|
|
this._signals.connect(
|
|
St.ThemeContext.get_for_stage(global.get_stage()),
|
|
"changed",
|
|
() => {
|
|
this._applyStyle();
|
|
}
|
|
);
|
|
this._setLayouts(GlobalState.get().layouts);
|
|
this._signals.connect(
|
|
GlobalState.get(),
|
|
GlobalState.SIGNAL_LAYOUTS_CHANGED,
|
|
() => {
|
|
this._setLayouts(GlobalState.get().layouts);
|
|
}
|
|
);
|
|
this.connect("destroy", () => this._signals.disconnect());
|
|
this.close();
|
|
}
|
|
set blur(value) {
|
|
if (this._blur === value)
|
|
return;
|
|
this._blur = value;
|
|
this.get_effect("blur")?.set_enabled(value);
|
|
this._applyStyle();
|
|
}
|
|
set snapAssistantThreshold(value) {
|
|
this._snapAssistantThreshold = value * getMonitorScalingFactor(this._monitorIndex);
|
|
}
|
|
set snapAssistantAnimationTime(value) {
|
|
this._snapAssistantAnimationTime = value;
|
|
}
|
|
get showing() {
|
|
return this._showing;
|
|
}
|
|
_init() {
|
|
super._init();
|
|
const effect = buildBlurEffect(36);
|
|
effect.set_name("blur");
|
|
effect.set_enabled(this._blur);
|
|
this.add_effect(effect);
|
|
this.add_style_class_name("popup-menu-content snap-assistant");
|
|
}
|
|
_applyStyle() {
|
|
this.set_style(null);
|
|
const [alreadyScaled, finalScalingFactor] = getScalingFactorOf(this);
|
|
this._padding = (alreadyScaled ? 1 : finalScalingFactor) * (this.get_theme_node().get_length("padding-value") / (alreadyScaled ? finalScalingFactor : 1));
|
|
const backgroundColor = this.get_theme_node().get_background_color().copy();
|
|
const alpha = this._blur ? 0.7 : backgroundColor.alpha;
|
|
this.set_style(`
|
|
padding: ${this._padding}px;
|
|
background-color: rgba(${backgroundColor.red}, ${backgroundColor.green}, ${backgroundColor.blue}, ${alpha}) !important;
|
|
`);
|
|
}
|
|
close(ease = false) {
|
|
if (!this._showing)
|
|
return;
|
|
this._showing = false;
|
|
this._isEnlarged = false;
|
|
this.set_x(this._container.width / 2 - this.width / 2);
|
|
this.ease({
|
|
y: this._desiredY,
|
|
opacity: 0,
|
|
duration: ease ? this._snapAssistantAnimationTime : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => {
|
|
this.hide();
|
|
}
|
|
});
|
|
}
|
|
get _desiredY() {
|
|
return this._isEnlarged ? Math.max(
|
|
0,
|
|
this._snapAssistantThreshold - this.height / 2 + this._padding
|
|
) : -this.height + this._padding;
|
|
}
|
|
open(ease = false) {
|
|
if (!this._showing)
|
|
this.get_parent()?.set_child_above_sibling(this, null);
|
|
this.set_x(this._container.width / 2 - this.width / 2);
|
|
this.show();
|
|
this._showing = true;
|
|
this.ease({
|
|
y: this._desiredY,
|
|
opacity: 255,
|
|
duration: ease ? this._snapAssistantAnimationTime : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|
});
|
|
}
|
|
_setLayouts(layouts) {
|
|
this._snapAssistLayouts.forEach((lay) => lay.destroy());
|
|
this.remove_all_children();
|
|
const [, scalingFactor] = getScalingFactorOf(this);
|
|
const layoutGaps = buildMarginOf(GAPS);
|
|
const width = SNAP_ASSIST_LAYOUT_WIDTH * scalingFactor;
|
|
const height = SNAP_ASSIST_LAYOUT_HEIGHT * scalingFactor;
|
|
this._snapAssistLayouts = layouts.map((lay, ind) => {
|
|
const saLay = new SnapAssistLayout(
|
|
this,
|
|
lay,
|
|
layoutGaps,
|
|
new Clutter.Margin(),
|
|
width,
|
|
height
|
|
);
|
|
if (ind < layouts.length - 1) {
|
|
this.add_child(
|
|
new St.Widget({ width: this._padding, height: 1 })
|
|
);
|
|
}
|
|
return saLay;
|
|
});
|
|
this.ensure_style();
|
|
this.set_x(this._container.width / 2 - this.width / 2);
|
|
}
|
|
onMovingWindow(window, ease = false, currPointerPos) {
|
|
const wasEnlarged = this._isEnlarged;
|
|
this.handleOpening(window, ease, currPointerPos);
|
|
if (!this._showing || !this._isEnlarged) {
|
|
if (this._hoveredInfo)
|
|
this._hoveredInfo[0].set_hover(false);
|
|
this._hoveredInfo = void 0;
|
|
if (wasEnlarged) {
|
|
this._container.emit(
|
|
SNAP_ASSIST_SIGNAL,
|
|
new Tile2({ x: 0, y: 0, width: 0, height: 0, groups: [] }),
|
|
""
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
const layoutHovered = this.handleTileHovering(currPointerPos);
|
|
if (layoutHovered) {
|
|
const snapTile = this._hoveredInfo ? this._hoveredInfo[0] : void 0;
|
|
const snapLay = this._hoveredInfo ? this._hoveredInfo[1] : void 0;
|
|
const tile = snapTile?.tile || new Tile2({ x: 0, y: 0, width: 0, height: 0, groups: [] });
|
|
const layoutId = snapLay?.layout.id ?? "";
|
|
this._container.emit(SNAP_ASSIST_SIGNAL, tile, layoutId);
|
|
}
|
|
}
|
|
handleOpening(window, ease = false, currPointerPos) {
|
|
if (!this._showing) {
|
|
if (this.get_parent() === global.windowGroup) {
|
|
const windowActor = window.get_compositor_private();
|
|
if (!windowActor)
|
|
return;
|
|
global.windowGroup.set_child_above_sibling(this, windowActor);
|
|
}
|
|
}
|
|
const height = this.height + (this._isEnlarged ? 0 : this._snapAssistantThreshold);
|
|
const minY = this._container.y;
|
|
const maxY = this._container.y + this._desiredY + height;
|
|
const minX = this._container.x + this.x - this._snapAssistantThreshold;
|
|
const maxX = this._container.x + this.x + this.width + this._snapAssistantThreshold;
|
|
const isNear = this.isBetween(minX, currPointerPos.x, maxX) && this.isBetween(minY, currPointerPos.y, maxY);
|
|
if (this._showing && this._isEnlarged === isNear)
|
|
return;
|
|
this._isEnlarged = isNear;
|
|
this.open(ease);
|
|
}
|
|
handleTileHovering(cursorPos) {
|
|
if (!this._isEnlarged) {
|
|
const changed = this._hoveredInfo !== void 0;
|
|
if (this._hoveredInfo)
|
|
this._hoveredInfo[0].set_hover(false);
|
|
this._hoveredInfo = void 0;
|
|
return changed;
|
|
}
|
|
let newTileHovered;
|
|
let layoutHovered;
|
|
const layoutsLen = this._snapAssistLayouts.length;
|
|
for (let index = 0; index < layoutsLen; index++) {
|
|
newTileHovered = this._snapAssistLayouts[index].getTileBelow(cursorPos);
|
|
if (newTileHovered) {
|
|
layoutHovered = this._snapAssistLayouts[index];
|
|
break;
|
|
}
|
|
}
|
|
const oldTile = this._hoveredInfo ? this._hoveredInfo[0] : void 0;
|
|
const tileChanged = newTileHovered !== oldTile;
|
|
if (tileChanged) {
|
|
oldTile?.set_hover(false);
|
|
if (newTileHovered === void 0 || layoutHovered === void 0)
|
|
this._hoveredInfo = void 0;
|
|
else
|
|
this._hoveredInfo = [newTileHovered, layoutHovered];
|
|
}
|
|
if (this._hoveredInfo)
|
|
this._hoveredInfo[0].set_hover(true);
|
|
return tileChanged;
|
|
}
|
|
isBetween(min, num, max) {
|
|
return min <= num && num <= max;
|
|
}
|
|
};
|
|
__publicField(SnapAssistContent, "metaInfo", {
|
|
GTypeName: "SnapAssistContent",
|
|
Properties: {
|
|
blur: GObject.ParamSpec.boolean(
|
|
"blur",
|
|
"blur",
|
|
"Enable or disable the blur effect",
|
|
GObject.ParamFlags.READWRITE,
|
|
false
|
|
),
|
|
snapAssistantThreshold: GObject.ParamSpec.uint(
|
|
"snapAssistantThreshold",
|
|
"snapAssistantThreshold",
|
|
"Distance from the snap assistant to trigger its opening/closing",
|
|
GObject.ParamFlags.READWRITE,
|
|
0,
|
|
2e3,
|
|
16
|
|
),
|
|
snapAssistantAnimationTime: GObject.ParamSpec.uint(
|
|
"snapAssistantAnimationTime",
|
|
"snapAssistantAnimationTime",
|
|
"Animation time in milliseconds",
|
|
GObject.ParamFlags.READWRITE,
|
|
0,
|
|
2e3,
|
|
180
|
|
)
|
|
}
|
|
});
|
|
SnapAssistContent = __decorateClass([
|
|
registerGObjectClass
|
|
], SnapAssistContent);
|
|
var SnapAssist = class extends St.Widget {
|
|
_content;
|
|
constructor(parent, workArea, monitorIndex, scalingFactor) {
|
|
super();
|
|
parent.add_child(this);
|
|
this.workArea = workArea;
|
|
this.set_clip(0, 0, workArea.width, workArea.height);
|
|
if (scalingFactor)
|
|
enableScalingFactorSupport(this, scalingFactor);
|
|
this._content = new SnapAssistContent(this, monitorIndex);
|
|
}
|
|
set workArea(newWorkArea) {
|
|
this.set_position(newWorkArea.x, newWorkArea.y);
|
|
this.set_width(newWorkArea.width);
|
|
this.set_clip(0, 0, newWorkArea.width, newWorkArea.height);
|
|
}
|
|
onMovingWindow(window, ease = false, currPointerPos) {
|
|
this._content.onMovingWindow(window, ease, currPointerPos);
|
|
}
|
|
close(ease = false) {
|
|
this._content.close(ease);
|
|
}
|
|
};
|
|
__publicField(SnapAssist, "metaInfo", {
|
|
GTypeName: "SnapAssist",
|
|
Signals: {
|
|
"snap-assist": {
|
|
param_types: [Tile2.$gtype, String.$gtype]
|
|
// tile, layout_id
|
|
}
|
|
}
|
|
});
|
|
SnapAssist = __decorateClass([
|
|
registerGObjectClass
|
|
], SnapAssist);
|
|
|
|
// src/components/tilepreview/selectionTilePreview.ts
|
|
var debug8 = logger("SelectionTilePreview");
|
|
var SelectionTilePreview = class extends TilePreview {
|
|
_blur;
|
|
constructor(params) {
|
|
super(params);
|
|
this._blur = false;
|
|
Settings.bind(
|
|
Settings.KEY_ENABLE_BLUR_SELECTED_TILEPREVIEW,
|
|
this,
|
|
"blur",
|
|
Gio.SettingsBindFlags.GET
|
|
);
|
|
this._recolor();
|
|
const styleChangedSignalID = St.ThemeContext.get_for_stage(
|
|
global.get_stage()
|
|
).connect("changed", () => {
|
|
this._recolor();
|
|
});
|
|
this.connect(
|
|
"destroy",
|
|
() => St.ThemeContext.get_for_stage(global.get_stage()).disconnect(
|
|
styleChangedSignalID
|
|
)
|
|
);
|
|
this._rect.width = this.gaps.left + this.gaps.right;
|
|
this._rect.height = this.gaps.top + this.gaps.bottom;
|
|
}
|
|
set blur(value) {
|
|
if (this._blur === value)
|
|
return;
|
|
this._blur = value;
|
|
this.get_effect("blur")?.set_enabled(value);
|
|
if (this._blur)
|
|
this.add_style_class_name("blur-tile-preview");
|
|
else
|
|
this.remove_style_class_name("blur-tile-preview");
|
|
this._recolor();
|
|
}
|
|
_init() {
|
|
super._init();
|
|
const effect = buildBlurEffect(48);
|
|
effect.set_name("blur");
|
|
effect.set_enabled(this._blur);
|
|
this.add_effect(effect);
|
|
this.add_style_class_name("selection-tile-preview");
|
|
}
|
|
_recolor() {
|
|
this.set_style(null);
|
|
const backgroundColor = this.get_theme_node().get_background_color().copy();
|
|
const newAlpha = Math.max(
|
|
Math.min(backgroundColor.alpha + 35, 255),
|
|
160
|
|
);
|
|
this.set_style(`
|
|
background-color: rgba(${backgroundColor.red}, ${backgroundColor.green}, ${backgroundColor.blue}, ${newAlpha / 255}) !important;
|
|
`);
|
|
}
|
|
close(ease = false) {
|
|
if (!this._showing)
|
|
return;
|
|
this._rect.width = this.gaps.left + this.gaps.right;
|
|
this._rect.height = this.gaps.top + this.gaps.bottom;
|
|
super.close(ease);
|
|
}
|
|
};
|
|
__publicField(SelectionTilePreview, "metaInfo", {
|
|
GTypeName: "SelectionTilePreview",
|
|
Properties: {
|
|
blur: GObject.ParamSpec.boolean(
|
|
"blur",
|
|
"blur",
|
|
"Enable or disable the blur effect",
|
|
GObject.ParamFlags.READWRITE,
|
|
false
|
|
)
|
|
}
|
|
});
|
|
SelectionTilePreview = __decorateClass([
|
|
registerGObjectClass
|
|
], SelectionTilePreview);
|
|
|
|
// src/components/tilingsystem/edgeTilingManager.ts
|
|
var EDGE_TILING_OFFSET = 16;
|
|
var TOP_EDGE_TILING_OFFSET = 8;
|
|
var QUARTER_PERCENTAGE = 0.5;
|
|
var EdgeTilingManager = class extends GObject.Object {
|
|
_workArea;
|
|
_quarterActivationPercentage;
|
|
// activation zones
|
|
_topLeft;
|
|
_topRight;
|
|
_bottomLeft;
|
|
_bottomRight;
|
|
_topCenter;
|
|
_leftCenter;
|
|
_rightCenter;
|
|
// current active zone
|
|
_activeEdgeTile;
|
|
constructor(initialWorkArea) {
|
|
super();
|
|
this._workArea = buildRectangle();
|
|
this._topLeft = buildRectangle();
|
|
this._topRight = buildRectangle();
|
|
this._bottomLeft = buildRectangle();
|
|
this._bottomRight = buildRectangle();
|
|
this._topCenter = buildRectangle();
|
|
this._leftCenter = buildRectangle();
|
|
this._rightCenter = buildRectangle();
|
|
this._activeEdgeTile = null;
|
|
this.workarea = initialWorkArea;
|
|
this._quarterActivationPercentage = Settings.QUARTER_TILING_THRESHOLD;
|
|
Settings.bind(
|
|
Settings.KEY_QUARTER_TILING_THRESHOLD,
|
|
this,
|
|
"quarterActivationPercentage"
|
|
);
|
|
}
|
|
set quarterActivationPercentage(value) {
|
|
this._quarterActivationPercentage = value / 100;
|
|
this._updateActivationZones();
|
|
}
|
|
set workarea(newWorkArea) {
|
|
this._workArea.x = newWorkArea.x;
|
|
this._workArea.y = newWorkArea.y;
|
|
this._workArea.width = newWorkArea.width;
|
|
this._workArea.height = newWorkArea.height;
|
|
this._updateActivationZones();
|
|
}
|
|
_updateActivationZones() {
|
|
const width = this._workArea.width * this._quarterActivationPercentage;
|
|
const height = this._workArea.height * this._quarterActivationPercentage;
|
|
this._topLeft.x = this._workArea.x;
|
|
this._topLeft.y = this._workArea.y;
|
|
this._topLeft.width = width;
|
|
this._topLeft.height = height;
|
|
this._topRight.x = this._workArea.x + this._workArea.width - this._topLeft.width;
|
|
this._topRight.y = this._topLeft.y;
|
|
this._topRight.width = width;
|
|
this._topRight.height = height;
|
|
this._bottomLeft.x = this._workArea.x;
|
|
this._bottomLeft.y = this._workArea.y + this._workArea.height - height;
|
|
this._bottomLeft.width = width;
|
|
this._bottomLeft.height = height;
|
|
this._bottomRight.x = this._topRight.x;
|
|
this._bottomRight.y = this._bottomLeft.y;
|
|
this._bottomRight.width = width;
|
|
this._bottomRight.height = height;
|
|
this._topCenter.x = this._topLeft.x + this._topLeft.width;
|
|
this._topCenter.y = this._topRight.y;
|
|
this._topCenter.height = this._topRight.height;
|
|
this._topCenter.width = this._topRight.x - this._topCenter.x;
|
|
this._leftCenter.x = this._topLeft.x;
|
|
this._leftCenter.y = this._topLeft.y + this._topLeft.height;
|
|
this._leftCenter.height = this._bottomLeft.y - this._leftCenter.y;
|
|
this._leftCenter.width = this._topLeft.width;
|
|
this._rightCenter.x = this._topRight.x;
|
|
this._rightCenter.y = this._topRight.y + this._topRight.height;
|
|
this._rightCenter.height = this._bottomRight.y - this._rightCenter.y;
|
|
this._rightCenter.width = this._topRight.width;
|
|
}
|
|
canActivateEdgeTiling(pointerPos) {
|
|
return pointerPos.x <= this._workArea.x + EDGE_TILING_OFFSET || pointerPos.y <= this._workArea.y + TOP_EDGE_TILING_OFFSET || pointerPos.x >= this._workArea.x + this._workArea.width - EDGE_TILING_OFFSET || pointerPos.y >= this._workArea.y + this._workArea.height - EDGE_TILING_OFFSET;
|
|
}
|
|
isPerformingEdgeTiling() {
|
|
return this._activeEdgeTile !== null;
|
|
}
|
|
startEdgeTiling(pointerPos) {
|
|
const { x, y } = clampPointInsideRect(pointerPos, this._workArea);
|
|
const previewRect = buildRectangle();
|
|
if (this._activeEdgeTile && isPointInsideRect({ x, y }, this._activeEdgeTile)) {
|
|
return {
|
|
changed: false,
|
|
rect: previewRect
|
|
};
|
|
}
|
|
if (!this._activeEdgeTile)
|
|
this._activeEdgeTile = buildRectangle();
|
|
previewRect.width = this._workArea.width * QUARTER_PERCENTAGE;
|
|
previewRect.height = this._workArea.height * QUARTER_PERCENTAGE;
|
|
previewRect.y = this._workArea.y;
|
|
previewRect.x = this._workArea.x;
|
|
if (isPointInsideRect({ x, y }, this._topCenter)) {
|
|
previewRect.width = this._workArea.width;
|
|
previewRect.height = this._workArea.height;
|
|
this._activeEdgeTile = this._topCenter;
|
|
} else if (isPointInsideRect({ x, y }, this._leftCenter)) {
|
|
previewRect.width = this._workArea.width * QUARTER_PERCENTAGE;
|
|
previewRect.height = this._workArea.height;
|
|
this._activeEdgeTile = this._leftCenter;
|
|
} else if (isPointInsideRect({ x, y }, this._rightCenter)) {
|
|
previewRect.x = this._workArea.x + this._workArea.width - previewRect.width;
|
|
previewRect.width = this._workArea.width * QUARTER_PERCENTAGE;
|
|
previewRect.height = this._workArea.height;
|
|
this._activeEdgeTile = this._rightCenter;
|
|
} else if (x <= this._workArea.x + this._workArea.width / 2) {
|
|
if (isPointInsideRect({ x, y }, this._topLeft)) {
|
|
this._activeEdgeTile = this._topLeft;
|
|
} else if (isPointInsideRect({ x, y }, this._bottomLeft)) {
|
|
previewRect.y = this._workArea.y + this._workArea.height - previewRect.height;
|
|
this._activeEdgeTile = this._bottomLeft;
|
|
} else {
|
|
return {
|
|
changed: false,
|
|
rect: previewRect
|
|
};
|
|
}
|
|
} else {
|
|
previewRect.x = this._workArea.x + this._workArea.width - previewRect.width;
|
|
if (isPointInsideRect({ x, y }, this._topRight)) {
|
|
this._activeEdgeTile = this._topRight;
|
|
} else if (isPointInsideRect({ x, y }, this._bottomRight)) {
|
|
previewRect.y = this._workArea.y + this._workArea.height - previewRect.height;
|
|
this._activeEdgeTile = this._bottomRight;
|
|
} else {
|
|
return {
|
|
changed: false,
|
|
rect: previewRect
|
|
};
|
|
}
|
|
}
|
|
return {
|
|
changed: true,
|
|
rect: previewRect
|
|
};
|
|
}
|
|
needMaximize() {
|
|
return this._activeEdgeTile !== null && Settings.TOP_EDGE_MAXIMIZE && this._activeEdgeTile === this._topCenter;
|
|
}
|
|
abortEdgeTiling() {
|
|
this._activeEdgeTile = null;
|
|
}
|
|
};
|
|
__publicField(EdgeTilingManager, "metaInfo", {
|
|
GTypeName: "EdgeTilingManager",
|
|
Properties: {
|
|
quarterActivationPercentage: GObject.ParamSpec.uint(
|
|
"quarterActivationPercentage",
|
|
"quarterActivationPercentage",
|
|
"Threshold to trigger quarter tiling",
|
|
GObject.ParamFlags.READWRITE,
|
|
1,
|
|
50,
|
|
40
|
|
)
|
|
}
|
|
});
|
|
EdgeTilingManager = __decorateClass([
|
|
registerGObjectClass
|
|
], EdgeTilingManager);
|
|
|
|
// src/components/tilingsystem/touchPointer.ts
|
|
var TouchPointer = class _TouchPointer {
|
|
static _instance = null;
|
|
_x;
|
|
_y;
|
|
_windowPos;
|
|
constructor() {
|
|
this._x = -1;
|
|
this._y = -1;
|
|
this._windowPos = buildRectangle();
|
|
}
|
|
static get() {
|
|
if (!this._instance)
|
|
this._instance = new _TouchPointer();
|
|
return this._instance;
|
|
}
|
|
isTouchDeviceActive() {
|
|
return this._x !== -1 && this._y !== -1 && this._windowPos.x !== -1 && this._windowPos.y !== -1;
|
|
}
|
|
onTouchEvent(x, y) {
|
|
this._x = x;
|
|
this._y = y;
|
|
}
|
|
updateWindowPosition(newSize) {
|
|
this._windowPos.x = newSize.x;
|
|
this._windowPos.y = newSize.y;
|
|
}
|
|
reset() {
|
|
this._x = -1;
|
|
this._y = -1;
|
|
this._windowPos.x = -1;
|
|
this._windowPos.y = -1;
|
|
}
|
|
get_pointer(window) {
|
|
const currPos = window.get_frame_rect();
|
|
this._x += currPos.x - this._windowPos.x;
|
|
this._y += currPos.y - this._windowPos.y;
|
|
this._windowPos.x = currPos.x;
|
|
this._windowPos.y = currPos.y;
|
|
return [this._x, this._y, global.get_pointer()[2]];
|
|
}
|
|
};
|
|
|
|
// src/components/windowManager/tilingShellWindowManager.ts
|
|
var debug9 = logger("TilingShellWindowManager");
|
|
var CachedWindowProperties = class {
|
|
_is_initialized = false;
|
|
maximized = false;
|
|
constructor(window, manager) {
|
|
this.update(window, manager);
|
|
this._is_initialized = true;
|
|
}
|
|
update(window, manager) {
|
|
const newMaximized = window.maximizedVertically && window.maximizedHorizontally;
|
|
if (this._is_initialized) {
|
|
if (this.maximized && !newMaximized)
|
|
manager.emit("unmaximized", window);
|
|
else if (!this.maximized && newMaximized)
|
|
manager.emit("maximized", window);
|
|
}
|
|
this.maximized = newMaximized;
|
|
}
|
|
};
|
|
var TilingShellWindowManager = class extends GObject.Object {
|
|
_signals;
|
|
static get() {
|
|
if (!this._instance)
|
|
this._instance = new TilingShellWindowManager();
|
|
return this._instance;
|
|
}
|
|
static destroy() {
|
|
if (this._instance) {
|
|
this._instance._signals.disconnect();
|
|
this._instance = null;
|
|
}
|
|
}
|
|
constructor() {
|
|
super();
|
|
this._signals = new SignalHandling();
|
|
global.get_window_actors().forEach((winActor) => {
|
|
winActor.metaWindow.__ts_cached = new CachedWindowProperties(winActor.metaWindow, this);
|
|
});
|
|
this._signals.connect(
|
|
global.display,
|
|
"window-created",
|
|
(_2, window) => {
|
|
window.__ts_cached = new CachedWindowProperties(window, this);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.windowManager,
|
|
"minimize",
|
|
(_2, actor) => {
|
|
actor.metaWindow.__ts_cached?.update(
|
|
actor.metaWindow,
|
|
this
|
|
);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.windowManager,
|
|
"unminimize",
|
|
(_2, actor) => {
|
|
actor.metaWindow.__ts_cached?.update(
|
|
actor.metaWindow,
|
|
this
|
|
);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.windowManager,
|
|
"size-changed",
|
|
(_2, actor) => {
|
|
actor.metaWindow.__ts_cached?.update(
|
|
actor.metaWindow,
|
|
this
|
|
);
|
|
}
|
|
);
|
|
}
|
|
static easeMoveWindow(params) {
|
|
const winActor = params.window.get_compositor_private();
|
|
if (!winActor)
|
|
return;
|
|
const winRect = params.window.get_frame_rect();
|
|
const xExcludingShadow = winRect.x - winActor.get_x();
|
|
const yExcludingShadow = winRect.y - winActor.get_y();
|
|
const staticClone = new Clutter.Clone({
|
|
source: winActor,
|
|
reactive: false,
|
|
scale_x: 1,
|
|
scale_y: 1,
|
|
x: params.from.x,
|
|
y: params.from.y,
|
|
width: params.from.width,
|
|
height: params.from.height,
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 })
|
|
});
|
|
global.windowGroup.add_child(staticClone);
|
|
winActor.opacity = 0;
|
|
staticClone.ease({
|
|
x: params.to.x - xExcludingShadow,
|
|
y: params.to.y - yExcludingShadow,
|
|
width: params.to.width + 2 * yExcludingShadow,
|
|
height: params.to.height + 2 * xExcludingShadow,
|
|
duration: params.duration,
|
|
onStopped: () => {
|
|
winActor.opacity = 255;
|
|
winActor.set_scale(1, 1);
|
|
staticClone.destroy();
|
|
}
|
|
});
|
|
winActor.set_pivot_point(0, 0);
|
|
winActor.set_position(params.to.x, params.to.y);
|
|
winActor.set_size(params.to.width, params.to.height);
|
|
const user_op = false;
|
|
if (params.monitorIndex)
|
|
params.window.move_to_monitor(params.monitorIndex);
|
|
params.window.move_frame(user_op, params.to.x, params.to.y);
|
|
params.window.move_resize_frame(
|
|
user_op,
|
|
params.to.x,
|
|
params.to.y,
|
|
params.to.width,
|
|
params.to.height
|
|
);
|
|
winActor.show();
|
|
}
|
|
};
|
|
__publicField(TilingShellWindowManager, "metaInfo", {
|
|
GTypeName: "TilingShellWindowManager",
|
|
Signals: {
|
|
unmaximized: {
|
|
param_types: [Meta.Window.$gtype]
|
|
},
|
|
maximized: {
|
|
param_types: [Meta.Window.$gtype]
|
|
}
|
|
}
|
|
});
|
|
__publicField(TilingShellWindowManager, "_instance");
|
|
TilingShellWindowManager = __decorateClass([
|
|
registerGObjectClass
|
|
], TilingShellWindowManager);
|
|
|
|
// src/components/windowsSuggestions/suggestedWindowPreview.ts
|
|
var WINDOW_OVERLAY_FADE_TIME = 200;
|
|
var WINDOW_SCALE_TIME = 200;
|
|
var WINDOW_ACTIVE_SIZE_INC = 5;
|
|
var ICON_SIZE = 36;
|
|
var ICON_OVERLAP = 0.7;
|
|
var debug10 = logger("SuggestedWindowPreview");
|
|
var SuggestedWindowPreview = class extends Shell.WindowPreview {
|
|
_overlayShown;
|
|
_icon;
|
|
_metaWindow;
|
|
_windowActor;
|
|
_title;
|
|
_previewContainer;
|
|
constructor(metaWindow) {
|
|
super({
|
|
reactive: true,
|
|
can_focus: true,
|
|
accessible_role: Atk.Role.PUSH_BUTTON,
|
|
offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY
|
|
});
|
|
this._metaWindow = metaWindow;
|
|
this._windowActor = metaWindow.get_compositor_private();
|
|
this._previewContainer = new St.Widget({
|
|
style_class: "popup-window-preview-container",
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
|
|
layoutManager: new Clutter.BinLayout(),
|
|
xAlign: Clutter.ActorAlign.CENTER
|
|
});
|
|
const windowContainer = new Clutter.Actor({
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 })
|
|
});
|
|
this.window_container = windowContainer;
|
|
windowContainer.layout_manager = new Shell.WindowPreviewLayout();
|
|
this.add_child(this._previewContainer);
|
|
this._previewContainer.add_child(windowContainer);
|
|
this._addWindow(metaWindow);
|
|
this._stackAbove = null;
|
|
this._windowActor.connectObject("destroy", () => this.destroy(), this);
|
|
this._updateAttachedDialogs();
|
|
this.connect("destroy", this._onDestroy.bind(this));
|
|
this._overlayShown = false;
|
|
const tracker = Shell.WindowTracker.get_default();
|
|
const app = tracker.get_window_app(this._metaWindow);
|
|
this._icon = app.create_icon_texture(ICON_SIZE);
|
|
this._icon.add_style_class_name("window-icon");
|
|
this._icon.add_style_class_name("icon-dropshadow");
|
|
this._icon.set({
|
|
reactive: false,
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 })
|
|
});
|
|
this._icon.add_constraint(
|
|
new Clutter.BindConstraint({
|
|
source: this._previewContainer,
|
|
coordinate: Clutter.BindCoordinate.POSITION
|
|
})
|
|
);
|
|
this._icon.add_constraint(
|
|
new Clutter.AlignConstraint({
|
|
source: this._previewContainer,
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
|
factor: 0.5
|
|
})
|
|
);
|
|
this._icon.add_constraint(
|
|
new Clutter.AlignConstraint({
|
|
source: this._previewContainer,
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
|
pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }),
|
|
factor: 1
|
|
})
|
|
);
|
|
this._title = new St.Label({
|
|
visible: false,
|
|
style_class: "window-caption",
|
|
text: this._getCaption(),
|
|
reactive: false
|
|
});
|
|
this._title.clutter_text.single_line_mode = true;
|
|
this._title.add_constraint(
|
|
new Clutter.AlignConstraint({
|
|
source: this._previewContainer,
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
|
factor: 0
|
|
// Center horizontally
|
|
})
|
|
);
|
|
this._title.add_constraint(
|
|
new Clutter.AlignConstraint({
|
|
source: this._previewContainer,
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
|
factor: 0,
|
|
// Center vertically
|
|
pivot_point: new Graphene.Point({ x: -1, y: 0 })
|
|
})
|
|
);
|
|
this._title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
|
this.label_actor = this._title;
|
|
this._metaWindow.connectObject(
|
|
"notify::title",
|
|
() => this._title.text = this._getCaption(),
|
|
this
|
|
);
|
|
this._previewContainer.add_child(this._title);
|
|
this._previewContainer.add_child(this._icon);
|
|
this.connect("notify::realized", () => {
|
|
if (!this.realized)
|
|
return;
|
|
this._title.ensure_style();
|
|
this._icon.ensure_style();
|
|
});
|
|
}
|
|
get_window_clone() {
|
|
return this.window_container;
|
|
}
|
|
_getCaption() {
|
|
if (this._metaWindow.title)
|
|
return this._metaWindow.title;
|
|
const tracker = Shell.WindowTracker.get_default();
|
|
const app = tracker.get_window_app(this._metaWindow);
|
|
return app.get_name();
|
|
}
|
|
showOverlay(animate) {
|
|
if (this._overlayShown)
|
|
return;
|
|
this._overlayShown = true;
|
|
const ongoingTransition = this._title.get_transition("opacity");
|
|
if (animate && ongoingTransition && ongoingTransition.get_interval().peek_final_value() === 255)
|
|
return;
|
|
[this._title].forEach((a) => {
|
|
a.opacity = 0;
|
|
a.show();
|
|
a.ease({
|
|
opacity: 255,
|
|
duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|
});
|
|
});
|
|
const [width, height] = this.windowContainer.get_size();
|
|
const { scaleFactor } = St.ThemeContext.get_for_stage(
|
|
global.stage
|
|
);
|
|
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * 2 * scaleFactor;
|
|
const origSize = Math.max(width, height);
|
|
const scale = (origSize + activeExtraSize) / origSize;
|
|
this._previewContainer.ease({
|
|
scaleX: scale,
|
|
scaleY: scale,
|
|
duration: animate ? WINDOW_SCALE_TIME : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|
});
|
|
}
|
|
hideOverlay(animate) {
|
|
if (!this._overlayShown)
|
|
return;
|
|
this._overlayShown = false;
|
|
const ongoingTransition = this._title.get_transition("opacity");
|
|
if (animate && ongoingTransition && ongoingTransition.get_interval().peek_final_value() === 0)
|
|
return;
|
|
[this._title].forEach((a) => {
|
|
a.opacity = 255;
|
|
a.ease({
|
|
opacity: 0,
|
|
duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => a.hide()
|
|
});
|
|
});
|
|
this._previewContainer.ease({
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
duration: animate ? WINDOW_SCALE_TIME : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|
});
|
|
}
|
|
_addWindow(metaWindow) {
|
|
this.clone = this.window_container.layout_manager.add_window(metaWindow);
|
|
}
|
|
vfunc_has_overlaps() {
|
|
return this._hasAttachedDialogs() || this._icon.visible;
|
|
}
|
|
addDialog(win) {
|
|
let parent = win.get_transient_for();
|
|
while (parent && parent.is_attached_dialog())
|
|
parent = parent.get_transient_for();
|
|
if (win.is_attached_dialog() && parent === this._metaWindow)
|
|
this._addWindow(win);
|
|
}
|
|
_hasAttachedDialogs() {
|
|
return this.window_container.layout_manager.get_windows().length > 1;
|
|
}
|
|
_updateAttachedDialogs() {
|
|
const iter = (win) => {
|
|
const actor = win.get_compositor_private();
|
|
if (!actor)
|
|
return false;
|
|
if (!win.is_attached_dialog())
|
|
return false;
|
|
this._addWindow(win);
|
|
win.foreach_transient(iter);
|
|
return true;
|
|
};
|
|
this._metaWindow.foreach_transient(iter);
|
|
}
|
|
// Find the actor just below us, respecting reparenting done by DND code
|
|
_getActualStackAbove() {
|
|
if (this._stackAbove == null)
|
|
return null;
|
|
return this._stackAbove;
|
|
}
|
|
setStackAbove(actor) {
|
|
this._stackAbove = actor;
|
|
const parent = this.get_parent();
|
|
const actualAbove = this._getActualStackAbove();
|
|
if (actualAbove == null)
|
|
parent.set_child_below_sibling(this, null);
|
|
else
|
|
parent.set_child_above_sibling(this, actualAbove);
|
|
}
|
|
_onDestroy() {
|
|
this._destroyed = true;
|
|
if (this._idleHideOverlayId > 0) {
|
|
GLib.source_remove(this._idleHideOverlayId);
|
|
this._idleHideOverlayId = 0;
|
|
}
|
|
}
|
|
vfunc_enter_event(event) {
|
|
this.showOverlay(true);
|
|
return super.vfunc_enter_event(event);
|
|
}
|
|
vfunc_leave_event(event) {
|
|
if (this._destroyed)
|
|
return super.vfunc_leave_event(event);
|
|
if (!this["has-pointer"])
|
|
this.hideOverlay(true);
|
|
return super.vfunc_leave_event(event);
|
|
}
|
|
vfunc_key_focus_in() {
|
|
super.vfunc_key_focus_in();
|
|
this.showOverlay(true);
|
|
}
|
|
vfunc_key_focus_out() {
|
|
super.vfunc_key_focus_out();
|
|
this.hideOverlay(true);
|
|
}
|
|
};
|
|
__publicField(SuggestedWindowPreview, "metaInfo", {
|
|
GTypeName: "PopupWindowPreview"
|
|
});
|
|
SuggestedWindowPreview = __decorateClass([
|
|
registerGObjectClass
|
|
], SuggestedWindowPreview);
|
|
|
|
// src/components/windowsSuggestions/masonryLayoutManager.ts
|
|
var MASONRY_ROW_MIN_HEIGHT_PERCENTAGE = 0.15;
|
|
var MasonryLayoutManager = class extends Clutter.LayoutManager {
|
|
_rowCount;
|
|
_spacing;
|
|
_maxRowHeight;
|
|
_rowHeight;
|
|
constructor(spacing, rowHeight, maxRowHeight) {
|
|
super();
|
|
this._rowCount = 0;
|
|
this._spacing = spacing;
|
|
this._maxRowHeight = maxRowHeight;
|
|
this._rowHeight = rowHeight;
|
|
}
|
|
static computePlacements(children, availableWidth, availableHeight, rowHeight) {
|
|
let rowCount = Math.max(1, Math.ceil(Math.sqrt(children.length)) - 1);
|
|
while (rowCount > 1 && rowHeight < availableHeight * MASONRY_ROW_MIN_HEIGHT_PERCENTAGE) {
|
|
rowCount--;
|
|
rowHeight = availableHeight / rowCount;
|
|
}
|
|
const rowWidths = Array(rowCount).fill(0);
|
|
const placements = [];
|
|
for (const child of children) {
|
|
const [minWidth, natWidth] = child.get_preferred_width(-1);
|
|
const [minHeight, natHeight] = child.get_preferred_height(-1);
|
|
const aspectRatio = natWidth / natHeight;
|
|
const width = rowHeight * aspectRatio;
|
|
let shortestRow = rowWidths.indexOf(Math.min(...rowWidths));
|
|
if (rowWidths[shortestRow] + width > availableWidth && rowWidths[shortestRow] !== 0) {
|
|
shortestRow = rowCount;
|
|
rowWidths.push(0);
|
|
rowCount++;
|
|
}
|
|
const childWidth = Math.clamp(width, width, availableWidth);
|
|
const childHeight = childWidth / aspectRatio;
|
|
placements.push({
|
|
child,
|
|
row: shortestRow,
|
|
width: childWidth,
|
|
height: childHeight,
|
|
x: rowWidths[shortestRow],
|
|
rowWidth: 0
|
|
});
|
|
if (rowWidths[shortestRow] === 0)
|
|
rowWidths[shortestRow] = width;
|
|
else
|
|
rowWidths[shortestRow] += width;
|
|
}
|
|
for (const placement of placements)
|
|
placement.rowWidth = rowWidths[placement.row];
|
|
const sortedRowWidths = [...rowWidths].map((v, i) => [
|
|
v,
|
|
i
|
|
]);
|
|
sortedRowWidths.sort((a, b) => b[0] - a[0]);
|
|
const rowsOrdering = /* @__PURE__ */ new Map();
|
|
sortedRowWidths.forEach((row, oldIndex) => {
|
|
const index = row[1];
|
|
const newIndex = sortedRowWidths.length <= 2 ? oldIndex : (oldIndex + Math.floor(rowCount / 2)) % rowCount;
|
|
rowsOrdering.set(index, newIndex);
|
|
});
|
|
for (const placement of placements)
|
|
placement.row = rowsOrdering.get(placement.row) ?? placement.row;
|
|
const result = Array(rowCount);
|
|
for (const placement of placements)
|
|
result[placement.row] = [];
|
|
for (const placement of placements) {
|
|
result[placement.row].push({
|
|
actor: placement.child,
|
|
width: placement.width,
|
|
height: placement.height
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
vfunc_allocate(container, box) {
|
|
const children = container.get_children();
|
|
if (children.length === 0)
|
|
return;
|
|
console.log(
|
|
box.get_width(),
|
|
container.width,
|
|
box.get_height(),
|
|
container.height
|
|
);
|
|
const availableWidth = container.width - 2 * this._spacing;
|
|
const availableHeight = container.height - 2 * this._spacing;
|
|
const allocationCache = container._allocationCache || /* @__PURE__ */ new Map();
|
|
container._allocationCache = allocationCache;
|
|
if (!children.find((ch) => !allocationCache.has(ch))) {
|
|
children.forEach((ch) => ch.allocate(allocationCache.get(ch)));
|
|
return;
|
|
}
|
|
allocationCache.clear();
|
|
this._rowCount = Math.ceil(Math.sqrt(children.length)) + 1;
|
|
let rowHeight = 0;
|
|
while (this._rowCount > 1 && rowHeight < availableHeight * MASONRY_ROW_MIN_HEIGHT_PERCENTAGE) {
|
|
this._rowCount--;
|
|
rowHeight = (availableHeight - this._spacing * (this._rowCount - 1)) / this._rowCount;
|
|
}
|
|
rowHeight = Math.min(rowHeight, this._maxRowHeight);
|
|
rowHeight = this._rowHeight;
|
|
const rowWidths = Array(this._rowCount).fill(0);
|
|
const placements = [];
|
|
for (const child of children) {
|
|
const [minHeight, naturalHeight] = child.get_preferred_height(-1);
|
|
const [minWidth, naturalWidth] = child.get_preferred_width(naturalHeight);
|
|
const aspectRatio = naturalWidth / naturalHeight;
|
|
const width = rowHeight * aspectRatio;
|
|
let shortestRow = rowWidths.indexOf(Math.min(...rowWidths));
|
|
if (rowWidths[shortestRow] + width > availableWidth && rowWidths[shortestRow] !== 0) {
|
|
shortestRow = this._rowCount;
|
|
rowWidths.push(0);
|
|
this._rowCount++;
|
|
}
|
|
const childWidth = Math.clamp(width, width, availableWidth);
|
|
const childHeight = childWidth / aspectRatio;
|
|
placements.push({
|
|
child,
|
|
row: shortestRow,
|
|
width: childWidth,
|
|
height: childHeight,
|
|
x: rowWidths[shortestRow],
|
|
rowWidth: 0
|
|
});
|
|
if (rowWidths[shortestRow] === 0)
|
|
rowWidths[shortestRow] = width;
|
|
else
|
|
rowWidths[shortestRow] += this._spacing + width;
|
|
}
|
|
for (const placement of placements)
|
|
placement.rowWidth = rowWidths[placement.row];
|
|
const sortedRowWidths = [...rowWidths].map((v, i) => [
|
|
v,
|
|
i
|
|
]);
|
|
sortedRowWidths.sort((a, b) => b[0] - a[0]);
|
|
const rowsOrdering = /* @__PURE__ */ new Map();
|
|
sortedRowWidths.forEach((row, newIndex) => {
|
|
const index = row[1];
|
|
rowsOrdering.set(
|
|
index,
|
|
(newIndex + Math.floor(this._rowCount / 2)) % this._rowCount
|
|
);
|
|
});
|
|
for (const placement of placements)
|
|
placement.row = rowsOrdering.get(placement.row) ?? placement.row;
|
|
const rowYPosition = Array(this._rowCount).fill({ y: 0, height: 0 });
|
|
for (const placement of placements) {
|
|
rowYPosition[placement.row] = {
|
|
y: 0,
|
|
height: placement.height
|
|
};
|
|
}
|
|
rowYPosition[0].y = this._spacing;
|
|
for (let r = 1; r < this._rowCount; r++) {
|
|
rowYPosition[r].y = this._spacing + rowYPosition[r - 1].y + rowYPosition[r - 1].height;
|
|
}
|
|
const contentHeight = rowYPosition[this._rowCount - 1].y + rowYPosition[this._rowCount - 1].height;
|
|
const verticalOffset = this._spacing / 2 + Math.max(0, (availableHeight - contentHeight) / 2);
|
|
for (const placement of placements) {
|
|
const { child, row, width, x, rowWidth, height } = placement;
|
|
const y = box.y1 + rowYPosition[row].y + verticalOffset;
|
|
const horizontalOffset = Math.max(0, (availableWidth - rowWidth) / 2) + this._spacing;
|
|
const xPosition = box.x1 + x + horizontalOffset;
|
|
const newBox = new Clutter.ActorBox({
|
|
x1: xPosition,
|
|
y1: y,
|
|
x2: xPosition + width,
|
|
y2: y + height
|
|
});
|
|
allocationCache.set(child, newBox);
|
|
child.allocate(newBox);
|
|
}
|
|
}
|
|
vfunc_get_preferred_width(container, forHeight) {
|
|
let maxX = 0;
|
|
container.get_children().forEach((ch) => {
|
|
maxX = Math.max(maxX, ch.x + ch.width);
|
|
});
|
|
return [maxX + this._spacing, maxX + this._spacing];
|
|
}
|
|
vfunc_get_preferred_height(container, forWidth) {
|
|
let maxY = 0;
|
|
container.get_children().forEach((ch) => {
|
|
maxY = Math.max(maxY, ch.y + ch.height);
|
|
});
|
|
return [maxY + this._spacing, maxY + this._spacing];
|
|
}
|
|
};
|
|
MasonryLayoutManager = __decorateClass([
|
|
registerGObjectClass
|
|
], MasonryLayoutManager);
|
|
|
|
// src/components/windowsSuggestions/suggestionsTilePreview.ts
|
|
var MASONRY_LAYOUT_SPACING = 32;
|
|
var SCROLLBARS_SHOW_ANIM_DURATION = 100;
|
|
var SuggestionsTilePreview = class extends TilePreview {
|
|
_blur;
|
|
_container;
|
|
_scrollView;
|
|
constructor(params) {
|
|
super(params);
|
|
this._blur = false;
|
|
this._recolor();
|
|
const styleChangedSignalID = St.ThemeContext.get_for_stage(
|
|
global.get_stage()
|
|
).connect("changed", () => {
|
|
this._recolor();
|
|
});
|
|
this.connect(
|
|
"destroy",
|
|
() => St.ThemeContext.get_for_stage(global.get_stage()).disconnect(
|
|
styleChangedSignalID
|
|
)
|
|
);
|
|
this.reactive = true;
|
|
this.layout_manager = new Clutter.BinLayout();
|
|
this._container = new St.BoxLayout({
|
|
x_expand: true,
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
style: `spacing: ${MASONRY_LAYOUT_SPACING}px;`
|
|
});
|
|
setWidgetOrientation(this._container, true);
|
|
this._scrollView = new St.ScrollView({
|
|
style_class: "vfade",
|
|
vscrollbar_policy: St.PolicyType.AUTOMATIC,
|
|
hscrollbar_policy: St.PolicyType.NEVER,
|
|
overlay_scrollbars: true,
|
|
clip_to_allocation: true,
|
|
// Ensure clipping
|
|
x_expand: true,
|
|
y_expand: true
|
|
});
|
|
if (this._scrollView.add_actor)
|
|
this._scrollView.add_actor(this._container);
|
|
else
|
|
this._scrollView.add_child(this._container);
|
|
this.add_child(this._scrollView);
|
|
this._scrollView.get_hscroll_bar().opacity = 0;
|
|
this._scrollView.get_vscroll_bar().opacity = 0;
|
|
}
|
|
set blur(value) {
|
|
if (this._blur === value)
|
|
return;
|
|
this._blur = value;
|
|
}
|
|
set gaps(newGaps) {
|
|
super.gaps = newGaps;
|
|
this.updateBorderRadius(
|
|
this._gaps.top > 0,
|
|
this._gaps.right > 0,
|
|
this._gaps.bottom > 0,
|
|
this._gaps.left > 0
|
|
);
|
|
}
|
|
_init() {
|
|
super._init();
|
|
const effect = buildBlurEffect(48);
|
|
effect.set_name("blur");
|
|
effect.set_enabled(this._blur);
|
|
this.add_effect(effect);
|
|
this.add_style_class_name("selection-tile-preview");
|
|
}
|
|
_recolor() {
|
|
this.set_style(null);
|
|
const backgroundColor = this.get_theme_node().get_background_color().copy();
|
|
const newAlpha = Math.max(
|
|
Math.min(backgroundColor.alpha + 35, 255),
|
|
160
|
|
);
|
|
this.set_style(`
|
|
background-color: rgba(${backgroundColor.red}, ${backgroundColor.green}, ${backgroundColor.blue}, ${newAlpha / 255}) !important;
|
|
`);
|
|
}
|
|
_showScrollBars() {
|
|
[
|
|
this._scrollView.get_hscroll_bar(),
|
|
this._scrollView.get_vscroll_bar()
|
|
].forEach(
|
|
(bar) => bar?.ease({
|
|
opacity: 255,
|
|
duration: SCROLLBARS_SHOW_ANIM_DURATION
|
|
})
|
|
);
|
|
}
|
|
_hideScrollBars() {
|
|
[
|
|
this._scrollView.get_hscroll_bar(),
|
|
this._scrollView.get_vscroll_bar()
|
|
].forEach(
|
|
(bar) => bar?.ease({
|
|
opacity: 0,
|
|
duration: SCROLLBARS_SHOW_ANIM_DURATION
|
|
})
|
|
);
|
|
}
|
|
vfunc_enter_event(event) {
|
|
this._showScrollBars();
|
|
return super.vfunc_enter_event(event);
|
|
}
|
|
vfunc_leave_event(event) {
|
|
this._hideScrollBars();
|
|
return super.vfunc_leave_event(event);
|
|
}
|
|
addWindows(windows, maxRowHeight) {
|
|
this._container.hide();
|
|
this._container.destroy_all_children();
|
|
windows.forEach((actor) => this._container.add_child(actor));
|
|
this._container.queue_relayout();
|
|
const placements = MasonryLayoutManager.computePlacements(
|
|
windows,
|
|
this.innerWidth - 2 * MASONRY_LAYOUT_SPACING,
|
|
this.innerHeight,
|
|
maxRowHeight
|
|
);
|
|
this._container.remove_all_children();
|
|
this._container.show();
|
|
this._container.add_child(
|
|
new St.Widget({ height: MASONRY_LAYOUT_SPACING })
|
|
);
|
|
placements.forEach((row) => {
|
|
const rowBox = new St.BoxLayout({
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
style: `spacing: ${MASONRY_LAYOUT_SPACING}px;`
|
|
});
|
|
this._container.add_child(rowBox);
|
|
row.forEach((pl) => {
|
|
rowBox.add_child(pl.actor);
|
|
pl.actor.set_height(pl.height);
|
|
pl.actor.set_width(pl.width);
|
|
});
|
|
});
|
|
this._container.add_child(
|
|
new St.Widget({ height: MASONRY_LAYOUT_SPACING })
|
|
);
|
|
}
|
|
removeAllWindows() {
|
|
this._container.destroy_all_children();
|
|
}
|
|
};
|
|
__publicField(SuggestionsTilePreview, "metaInfo", {
|
|
GTypeName: "PopupTilePreview",
|
|
Properties: {
|
|
blur: GObject.ParamSpec.boolean(
|
|
"blur",
|
|
"blur",
|
|
"Enable or disable the blur effect",
|
|
GObject.ParamFlags.READWRITE,
|
|
false
|
|
)
|
|
}
|
|
});
|
|
SuggestionsTilePreview = __decorateClass([
|
|
registerGObjectClass
|
|
], SuggestionsTilePreview);
|
|
|
|
// src/components/windowsSuggestions/tilingLayoutWithSuggestions.ts
|
|
import * as Main4 from "resource:///org/gnome/shell/ui/main.js";
|
|
var debug11 = logger("TilingLayoutWithSuggestions");
|
|
var ANIMATION_SPEED = 200;
|
|
var MASONRY_LAYOUT_ROW_HEIGHT = 0.31;
|
|
var TilingLayoutWithSuggestions = class extends LayoutWidget {
|
|
_signals;
|
|
_lastTiledWindow;
|
|
_showing;
|
|
_oldPreviews;
|
|
constructor(innerGaps, outerGaps, containerRect, scalingFactor) {
|
|
super({
|
|
containerRect,
|
|
parent: global.windowGroup,
|
|
layout: new Layout([], ""),
|
|
innerGaps,
|
|
outerGaps,
|
|
scalingFactor
|
|
});
|
|
this.canFocus = true;
|
|
this.reactive = true;
|
|
this._signals = new SignalHandling();
|
|
this._lastTiledWindow = null;
|
|
this._showing = false;
|
|
this._oldPreviews = [];
|
|
this.connect("destroy", () => this._signals.disconnect());
|
|
}
|
|
buildTile(parent, rect, gaps, tile) {
|
|
return new SuggestionsTilePreview({
|
|
parent,
|
|
rect,
|
|
gaps,
|
|
tile
|
|
});
|
|
}
|
|
open(tiledWindows, nontiledWindows, window, windowDesiredRect, monitorIndex) {
|
|
if (this._showing)
|
|
return;
|
|
this._showing = true;
|
|
this._lastTiledWindow = global.display.focusWindow;
|
|
this._showVacantPreviewsOnly(tiledWindows, windowDesiredRect, window);
|
|
this.show();
|
|
this._recursivelyShowPopup(nontiledWindows, monitorIndex);
|
|
this._signals.disconnect();
|
|
this._signals.connect(this, "key-focus-out", () => this.close());
|
|
this._signals.connect(this, "button-press-event", () => {
|
|
this.close();
|
|
});
|
|
this._signals.connect(
|
|
global.stage,
|
|
"key-press-event",
|
|
(_2, event) => {
|
|
const symbol = event.get_key_symbol();
|
|
if (symbol === Clutter.KEY_Escape)
|
|
this.close();
|
|
return Clutter.EVENT_PROPAGATE;
|
|
}
|
|
);
|
|
}
|
|
_showVacantPreviewsOnly(tiledWindows, windowDesiredRect, window) {
|
|
const vacantPreviews = this._previews.map((prev) => {
|
|
const previewRect = buildRectangle({
|
|
x: prev.innerX,
|
|
y: prev.innerY,
|
|
width: prev.innerWidth,
|
|
height: prev.innerHeight
|
|
});
|
|
return !tiledWindows.find(
|
|
(win) => previewRect.overlap(
|
|
win === window ? windowDesiredRect : win.get_frame_rect()
|
|
)
|
|
);
|
|
});
|
|
const newPreviews = [];
|
|
for (let index = 0; index < this._previews.length; index++) {
|
|
if (vacantPreviews[index]) {
|
|
this._previews[index].open();
|
|
newPreviews.push(this._previews[index]);
|
|
} else {
|
|
this._previews[index].close();
|
|
this._oldPreviews.push(this._previews[index]);
|
|
}
|
|
}
|
|
this._previews = newPreviews;
|
|
}
|
|
_recursivelyShowPopup(nontiledWindows, monitorIndex) {
|
|
if (this._previews.length === 0 || nontiledWindows.length === 0) {
|
|
this.close();
|
|
return;
|
|
}
|
|
let preview = this._previews[0];
|
|
this._previews.forEach((prev) => {
|
|
if (prev.x < preview.x)
|
|
preview = prev;
|
|
});
|
|
const clones = nontiledWindows.map((nonTiledWin) => {
|
|
const winClone = new SuggestedWindowPreview(nonTiledWin);
|
|
const winActor = nonTiledWin.get_compositor_private();
|
|
winActor.set_pivot_point(0.5, 0.5);
|
|
if (!nonTiledWin.minimized) {
|
|
winActor.ease({
|
|
opacity: 0,
|
|
duration: ANIMATION_SPEED,
|
|
scaleX: 0.9,
|
|
scaleY: 0.9,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => {
|
|
winActor.hide();
|
|
winActor.set_pivot_point(0, 0);
|
|
}
|
|
});
|
|
}
|
|
winClone.connect("destroy", () => {
|
|
if (nonTiledWin.minimized) {
|
|
winActor.set_pivot_point(0, 0);
|
|
return;
|
|
}
|
|
if (winActor.visible)
|
|
return;
|
|
winActor.set_pivot_point(0.5, 0.5);
|
|
winActor.show();
|
|
winActor.ease({
|
|
opacity: 255,
|
|
duration: ANIMATION_SPEED,
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onStopped: () => winActor.set_pivot_point(0, 0)
|
|
});
|
|
});
|
|
winClone.connect("button-press-event", () => {
|
|
this._lastTiledWindow = nonTiledWin;
|
|
if (nonTiledWin.maximizedHorizontally || nonTiledWin.maximizedVertically)
|
|
nonTiledWin.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
if (nonTiledWin.is_fullscreen())
|
|
nonTiledWin.unmake_fullscreen();
|
|
if (nonTiledWin.minimized)
|
|
nonTiledWin.unminimize();
|
|
const winRect = nonTiledWin.get_frame_rect();
|
|
nonTiledWin.originalSize = winRect.copy();
|
|
const cl = winClone.get_window_clone() ?? winClone;
|
|
const [x, y] = cl.get_transformed_position();
|
|
const allocation = cl.get_allocation_box();
|
|
TilingShellWindowManager.easeMoveWindow({
|
|
window: nonTiledWin,
|
|
from: buildRectangle({
|
|
x,
|
|
y,
|
|
width: allocation.x2 - allocation.x1,
|
|
height: allocation.y2 - allocation.y1
|
|
}),
|
|
to: buildRectangle({
|
|
x: preview.innerX,
|
|
y: preview.innerY,
|
|
width: preview.innerWidth,
|
|
height: preview.innerHeight
|
|
}),
|
|
duration: ANIMATION_SPEED * 1.8,
|
|
monitorIndex
|
|
});
|
|
nonTiledWin.assignedTile = new Tile2({
|
|
...preview.tile
|
|
});
|
|
winClone.opacity = 0;
|
|
const removed = this._previews.splice(
|
|
this._previews.indexOf(preview),
|
|
1
|
|
);
|
|
this._oldPreviews.push(...removed);
|
|
nontiledWindows.splice(nontiledWindows.indexOf(nonTiledWin), 1);
|
|
preview.close(true);
|
|
this._recursivelyShowPopup(nontiledWindows, monitorIndex);
|
|
return Clutter.EVENT_STOP;
|
|
});
|
|
return winClone;
|
|
});
|
|
preview.addWindows(
|
|
clones,
|
|
this._containerRect.height * MASONRY_LAYOUT_ROW_HEIGHT
|
|
);
|
|
clones.forEach((winClone) => {
|
|
winClone.set_opacity(0);
|
|
winClone.set_pivot_point(0.5, 0.5);
|
|
winClone.set_scale(0.6, 0.6);
|
|
winClone.ease({
|
|
opacity: 255,
|
|
duration: Math.floor(ANIMATION_SPEED * 1.8),
|
|
scaleX: 1.03,
|
|
scaleY: 1.03,
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT,
|
|
onComplete: () => {
|
|
winClone.ease({
|
|
delay: 60,
|
|
duration: Math.floor(ANIMATION_SPEED * 2.1),
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT
|
|
});
|
|
}
|
|
});
|
|
});
|
|
this.grab_key_focus();
|
|
}
|
|
close() {
|
|
if (!this._showing)
|
|
return;
|
|
this._showing = false;
|
|
this._signals.disconnect();
|
|
if (this._lastTiledWindow)
|
|
Main4.activateWindow(this._lastTiledWindow);
|
|
this._previews.push(...this._oldPreviews);
|
|
this._oldPreviews = [];
|
|
this._previews.forEach((prev) => prev.removeAllWindows());
|
|
this.ease({
|
|
opacity: 0,
|
|
duration: GlobalState.get().tilePreviewAnimationTime,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onStopped: () => {
|
|
this.hide();
|
|
this._previews.forEach((prev) => prev.open());
|
|
}
|
|
});
|
|
}
|
|
};
|
|
TilingLayoutWithSuggestions = __decorateClass([
|
|
registerGObjectClass
|
|
], TilingLayoutWithSuggestions);
|
|
|
|
// src/components/tilingsystem/tilingManager.ts
|
|
import * as Main5 from "resource:///org/gnome/shell/ui/main.js";
|
|
var MINIMUM_DISTANCE_TO_RESTORE_ORIGINAL_SIZE = 90;
|
|
var SnapAssistingInfo = class {
|
|
_snapAssistantLayoutId;
|
|
constructor() {
|
|
this._snapAssistantLayoutId = void 0;
|
|
}
|
|
get layoutId() {
|
|
return this._snapAssistantLayoutId ?? "";
|
|
}
|
|
get isSnapAssisting() {
|
|
return this._snapAssistantLayoutId !== void 0;
|
|
}
|
|
update(layoutId) {
|
|
this._snapAssistantLayoutId = !layoutId || layoutId.length === 0 ? void 0 : layoutId;
|
|
}
|
|
};
|
|
var TilingManager = class {
|
|
_monitor;
|
|
_selectedTilesPreview;
|
|
_snapAssist;
|
|
_workspaceTilingLayout;
|
|
_edgeTilingManager;
|
|
_tilingSuggestionsLayout;
|
|
_workArea;
|
|
_enableScaling;
|
|
_isGrabbingWindow;
|
|
_movingWindowTimerDuration = 15;
|
|
_lastCursorPos = null;
|
|
_grabStartPosition = null;
|
|
_wasSpanMultipleTilesActivated;
|
|
_wasTilingSystemActivated;
|
|
_snapAssistingInfo;
|
|
_movingWindowTimerId = null;
|
|
_signals;
|
|
_debug;
|
|
/**
|
|
* Constructs a new TilingManager instance.
|
|
* @param monitor The monitor to manage tiling for.
|
|
*/
|
|
constructor(monitor, enableScaling) {
|
|
this._isGrabbingWindow = false;
|
|
this._wasSpanMultipleTilesActivated = false;
|
|
this._wasTilingSystemActivated = false;
|
|
this._snapAssistingInfo = new SnapAssistingInfo();
|
|
this._enableScaling = enableScaling;
|
|
this._monitor = monitor;
|
|
this._signals = new SignalHandling();
|
|
this._debug = logger(`TilingManager ${monitor.index}`);
|
|
this._workArea = Main5.layoutManager.getWorkAreaForMonitor(
|
|
this._monitor.index
|
|
);
|
|
this._debug(
|
|
`Work area for monitor ${this._monitor.index}: ${this._workArea.x} ${this._workArea.y} ${this._workArea.width}x${this._workArea.height}`
|
|
);
|
|
this._edgeTilingManager = new EdgeTilingManager(this._workArea);
|
|
const monitorScalingFactor = this._enableScaling ? getMonitorScalingFactor(monitor.index) : void 0;
|
|
this._workspaceTilingLayout = /* @__PURE__ */ new Map();
|
|
for (let i = 0; i < global.workspaceManager.get_n_workspaces(); i++) {
|
|
const ws = global.workspaceManager.get_workspace_by_index(i);
|
|
if (!ws)
|
|
continue;
|
|
const innerGaps = buildMargin(Settings.get_inner_gaps());
|
|
const outerGaps = buildMargin(Settings.get_outer_gaps());
|
|
const layout = GlobalState.get().getSelectedLayoutOfMonitor(
|
|
monitor.index,
|
|
ws.index()
|
|
);
|
|
this._workspaceTilingLayout.set(
|
|
ws,
|
|
new TilingLayout(
|
|
layout,
|
|
innerGaps,
|
|
outerGaps,
|
|
this._workArea,
|
|
monitorScalingFactor
|
|
)
|
|
);
|
|
}
|
|
this._tilingSuggestionsLayout = new TilingLayoutWithSuggestions(
|
|
buildMargin(Settings.get_inner_gaps()),
|
|
buildMargin(Settings.get_outer_gaps()),
|
|
this._workArea,
|
|
monitorScalingFactor
|
|
);
|
|
this._selectedTilesPreview = new SelectionTilePreview({
|
|
parent: global.windowGroup
|
|
});
|
|
this._snapAssist = new SnapAssist(
|
|
Main5.uiGroup,
|
|
this._workArea,
|
|
this._monitor.index,
|
|
monitorScalingFactor
|
|
);
|
|
}
|
|
/**
|
|
* Enables tiling manager by setting up event listeners:
|
|
* - handle any window's grab begin.
|
|
* - handle any window's grab end.
|
|
* - handle grabbed window's movement.
|
|
*/
|
|
enable() {
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_SETTING_SELECTED_LAYOUTS,
|
|
() => {
|
|
const ws = global.workspaceManager.get_active_workspace();
|
|
if (!ws)
|
|
return;
|
|
const layout = GlobalState.get().getSelectedLayoutOfMonitor(
|
|
this._monitor.index,
|
|
ws.index()
|
|
);
|
|
this._workspaceTilingLayout.get(ws)?.relayout({ layout });
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
GlobalState.get(),
|
|
GlobalState.SIGNAL_LAYOUTS_CHANGED,
|
|
() => {
|
|
const ws = global.workspaceManager.get_active_workspace();
|
|
if (!ws)
|
|
return;
|
|
const layout = GlobalState.get().getSelectedLayoutOfMonitor(
|
|
this._monitor.index,
|
|
ws.index()
|
|
);
|
|
this._workspaceTilingLayout.get(ws)?.relayout({ layout });
|
|
}
|
|
);
|
|
this._signals.connect(Settings, Settings.KEY_INNER_GAPS, () => {
|
|
const innerGaps = buildMargin(Settings.get_inner_gaps());
|
|
this._workspaceTilingLayout.forEach(
|
|
(tilingLayout) => tilingLayout.relayout({ innerGaps })
|
|
);
|
|
});
|
|
this._signals.connect(Settings, Settings.KEY_OUTER_GAPS, () => {
|
|
const outerGaps = buildMargin(Settings.get_outer_gaps());
|
|
this._workspaceTilingLayout.forEach(
|
|
(tilingLayout) => tilingLayout.relayout({ outerGaps })
|
|
);
|
|
});
|
|
this._signals.connect(
|
|
global.display,
|
|
"grab-op-begin",
|
|
(_display, window, grabOp) => {
|
|
const moving = (grabOp & ~1024) === 1;
|
|
if (!moving)
|
|
return;
|
|
this._onWindowGrabBegin(window, grabOp);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.display,
|
|
"grab-op-end",
|
|
(_display, window) => {
|
|
if (!this._isGrabbingWindow)
|
|
return;
|
|
this._onWindowGrabEnd(window);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
this._snapAssist,
|
|
"snap-assist",
|
|
this._onSnapAssist.bind(this)
|
|
);
|
|
this._signals.connect(
|
|
global.workspaceManager,
|
|
"active-workspace-changed",
|
|
() => {
|
|
const ws = global.workspaceManager.get_active_workspace();
|
|
if (this._workspaceTilingLayout.has(ws))
|
|
return;
|
|
const monitorScalingFactor = this._enableScaling ? getMonitorScalingFactor(this._monitor.index) : void 0;
|
|
const layout = GlobalState.get().getSelectedLayoutOfMonitor(
|
|
this._monitor.index,
|
|
ws.index()
|
|
);
|
|
const innerGaps = buildMargin(Settings.get_inner_gaps());
|
|
const outerGaps = buildMargin(Settings.get_outer_gaps());
|
|
this._debug("created new tiling layout for active workspace");
|
|
this._workspaceTilingLayout.set(
|
|
ws,
|
|
new TilingLayout(
|
|
layout,
|
|
innerGaps,
|
|
outerGaps,
|
|
this._workArea,
|
|
monitorScalingFactor
|
|
)
|
|
);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.workspaceManager,
|
|
"workspace-removed",
|
|
(_2) => {
|
|
const newMap = /* @__PURE__ */ new Map();
|
|
const n_workspaces = global.workspaceManager.get_n_workspaces();
|
|
for (let i = 0; i < n_workspaces; i++) {
|
|
const ws = global.workspaceManager.get_workspace_by_index(i);
|
|
if (!ws)
|
|
continue;
|
|
const tl = this._workspaceTilingLayout.get(ws);
|
|
if (!tl)
|
|
continue;
|
|
this._workspaceTilingLayout.delete(ws);
|
|
newMap.set(ws, tl);
|
|
}
|
|
[...this._workspaceTilingLayout.values()].forEach(
|
|
(tl) => tl.destroy()
|
|
);
|
|
this._workspaceTilingLayout.clear();
|
|
this._workspaceTilingLayout = newMap;
|
|
this._debug("deleted workspace");
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.display,
|
|
"window-created",
|
|
(_display, window) => {
|
|
if (Settings.ENABLE_AUTO_TILING)
|
|
this._autoTile(window, true);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
TilingShellWindowManager.get(),
|
|
"unmaximized",
|
|
(_2, window) => {
|
|
if (Settings.ENABLE_AUTO_TILING)
|
|
this._autoTile(window, false);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
TilingShellWindowManager.get(),
|
|
"maximized",
|
|
(_2, window) => {
|
|
delete window.assignedTile;
|
|
}
|
|
);
|
|
}
|
|
onUntileWindow(window, force) {
|
|
const destination = window.originalSize;
|
|
if (!destination)
|
|
return;
|
|
this._easeWindowRect(window, destination, false, force);
|
|
window.assignedTile = void 0;
|
|
}
|
|
onKeyboardMoveWindow(window, direction, force, spanFlag, clamp) {
|
|
let destination;
|
|
if (spanFlag && window.get_maximized())
|
|
return false;
|
|
const currentWs = window.get_workspace();
|
|
const tilingLayout = this._workspaceTilingLayout.get(currentWs);
|
|
if (!tilingLayout)
|
|
return false;
|
|
const windowRectCopy = window.get_frame_rect().copy();
|
|
const extWin = window;
|
|
if (window.get_maximized()) {
|
|
switch (direction) {
|
|
case 1 /* NODIRECTION */:
|
|
case 4 /* LEFT */:
|
|
case 5 /* RIGHT */:
|
|
break;
|
|
case 3 /* DOWN */:
|
|
window.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
return true;
|
|
case 2 /* UP */:
|
|
return false;
|
|
}
|
|
}
|
|
if (direction === 2 /* UP */ && extWin.assignedTile && extWin.assignedTile?.y === 0) {
|
|
window.maximize(Meta.MaximizeFlags.BOTH);
|
|
return true;
|
|
}
|
|
if (direction === 1 /* NODIRECTION */) {
|
|
const rect = buildRectangle({
|
|
x: this._workArea.x + this._workArea.width / 2 - windowRectCopy.width / 2,
|
|
y: this._workArea.y + this._workArea.height / 2 - windowRectCopy.height / 2,
|
|
width: windowRectCopy.width,
|
|
height: windowRectCopy.height
|
|
});
|
|
destination = {
|
|
rect,
|
|
tile: TileUtils.build_tile(rect, this._workArea)
|
|
};
|
|
} else if (window.get_monitor() === this._monitor.index) {
|
|
const enlargeFactor = Math.max(
|
|
64,
|
|
// if the gaps are all 0 we choose 8 instead
|
|
tilingLayout.innerGaps.right,
|
|
tilingLayout.innerGaps.left,
|
|
tilingLayout.innerGaps.right,
|
|
tilingLayout.innerGaps.bottom
|
|
);
|
|
destination = tilingLayout.findNearestTileDirection(
|
|
windowRectCopy,
|
|
direction,
|
|
clamp,
|
|
enlargeFactor
|
|
);
|
|
} else {
|
|
destination = tilingLayout.findNearestTile(windowRectCopy);
|
|
}
|
|
if (window.get_monitor() === this._monitor.index && destination && !window.maximizedHorizontally && !window.maximizedVertically && window.assignedTile && window.assignedTile?.x === destination.tile.x && window.assignedTile?.y === destination.tile.y && window.assignedTile?.width === destination.tile.width && window.assignedTile?.height === destination.tile.height)
|
|
return true;
|
|
if (!destination) {
|
|
if (spanFlag)
|
|
return false;
|
|
if (direction === 2 /* UP */ && window.can_maximize()) {
|
|
window.maximize(Meta.MaximizeFlags.BOTH);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
const isMaximized = window.maximizedHorizontally || window.maximizedVertically;
|
|
if (!window.assignedTile && !isMaximized)
|
|
window.originalSize = windowRectCopy;
|
|
if (spanFlag) {
|
|
destination.rect = destination.rect.union(windowRectCopy);
|
|
destination.tile = TileUtils.build_tile(
|
|
destination.rect,
|
|
this._workArea
|
|
);
|
|
}
|
|
if (isMaximized)
|
|
window.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
this._easeWindowRect(window, destination.rect, false, force);
|
|
if (direction !== 1 /* NODIRECTION */) {
|
|
window.assignedTile = new Tile2({
|
|
...destination.tile
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Destroys the tiling manager and cleans up resources.
|
|
*/
|
|
destroy() {
|
|
if (this._movingWindowTimerId) {
|
|
GLib.Source.remove(this._movingWindowTimerId);
|
|
this._movingWindowTimerId = null;
|
|
}
|
|
this._signals.disconnect();
|
|
this._isGrabbingWindow = false;
|
|
this._snapAssistingInfo.update(void 0);
|
|
this._edgeTilingManager.abortEdgeTiling();
|
|
this._workspaceTilingLayout.forEach((tl) => tl.destroy());
|
|
this._workspaceTilingLayout.clear();
|
|
this._snapAssist.destroy();
|
|
this._selectedTilesPreview.destroy();
|
|
this._tilingSuggestionsLayout.destroy();
|
|
}
|
|
set workArea(newWorkArea) {
|
|
if (newWorkArea.equal(this._workArea))
|
|
return;
|
|
this._workArea = newWorkArea;
|
|
this._debug(
|
|
`new work area for monitor ${this._monitor.index}: ${newWorkArea.x} ${newWorkArea.y} ${newWorkArea.width}x${newWorkArea.height}`
|
|
);
|
|
this._workspaceTilingLayout.forEach(
|
|
(tl) => tl.relayout({ containerRect: this._workArea })
|
|
);
|
|
this._snapAssist.workArea = this._workArea;
|
|
this._edgeTilingManager.workarea = this._workArea;
|
|
}
|
|
_onWindowGrabBegin(window, grabOp) {
|
|
if (this._isGrabbingWindow)
|
|
return;
|
|
TouchPointer.get().updateWindowPosition(window.get_frame_rect());
|
|
this._signals.connect(
|
|
global.stage,
|
|
"touch-event",
|
|
(_source, event) => {
|
|
const [x, y] = event.get_coords();
|
|
TouchPointer.get().onTouchEvent(x, y);
|
|
}
|
|
);
|
|
if (Settings.ENABLE_BLUR_SNAP_ASSISTANT || Settings.ENABLE_BLUR_SELECTED_TILEPREVIEW) {
|
|
this._signals.connect(window, "position-changed", () => {
|
|
if (Settings.ENABLE_BLUR_SELECTED_TILEPREVIEW) {
|
|
this._selectedTilesPreview.get_effect("blur")?.queue_repaint();
|
|
}
|
|
if (Settings.ENABLE_BLUR_SNAP_ASSISTANT) {
|
|
this._snapAssist.get_first_child()?.get_effect("blur")?.queue_repaint();
|
|
}
|
|
});
|
|
}
|
|
this._isGrabbingWindow = true;
|
|
this._movingWindowTimerId = GLib.timeout_add(
|
|
GLib.PRIORITY_DEFAULT_IDLE,
|
|
this._movingWindowTimerDuration,
|
|
this._onMovingWindow.bind(this, window, grabOp)
|
|
);
|
|
this._onMovingWindow(window, grabOp);
|
|
}
|
|
_activationKeyStatus(modifier, key) {
|
|
if (key === -1 /* NONE */)
|
|
return true;
|
|
let val = 2;
|
|
switch (key) {
|
|
case 0 /* CTRL */:
|
|
val = 2;
|
|
break;
|
|
case 1 /* ALT */:
|
|
val = 3;
|
|
break;
|
|
case 2 /* SUPER */:
|
|
val = 6;
|
|
break;
|
|
}
|
|
return (modifier & 1 << val) !== 0;
|
|
}
|
|
_onMovingWindow(window, grabOp) {
|
|
if (!this._isGrabbingWindow) {
|
|
this._movingWindowTimerId = null;
|
|
return GLib.SOURCE_REMOVE;
|
|
}
|
|
const currentWs = window.get_workspace();
|
|
const tilingLayout = this._workspaceTilingLayout.get(currentWs);
|
|
if (!tilingLayout)
|
|
return GLib.SOURCE_REMOVE;
|
|
if (!window.allows_resize() || !window.allows_move() || !this._isPointerInsideThisMonitor(window)) {
|
|
tilingLayout.close();
|
|
this._selectedTilesPreview.close(true);
|
|
this._snapAssist.close(true);
|
|
this._snapAssistingInfo.update(void 0);
|
|
this._edgeTilingManager.abortEdgeTiling();
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
const [x, y, modifier] = TouchPointer.get().isTouchDeviceActive() ? TouchPointer.get().get_pointer(window) : global.get_pointer();
|
|
const extWin = window;
|
|
extWin.assignedTile = void 0;
|
|
const currPointerPos = { x, y };
|
|
if (this._grabStartPosition === null)
|
|
this._grabStartPosition = { x, y };
|
|
if (extWin.originalSize && squaredEuclideanDistance(currPointerPos, this._grabStartPosition) > MINIMUM_DISTANCE_TO_RESTORE_ORIGINAL_SIZE) {
|
|
if (Settings.RESTORE_WINDOW_ORIGINAL_SIZE) {
|
|
const windowRect = window.get_frame_rect();
|
|
const offsetX = (x - windowRect.x) / windowRect.width;
|
|
const offsetY = (y - windowRect.y) / windowRect.height;
|
|
const newSize = buildRectangle({
|
|
x: x - extWin.originalSize.width * offsetX,
|
|
y: y - extWin.originalSize.height * offsetY,
|
|
width: extWin.originalSize.width,
|
|
height: extWin.originalSize.height
|
|
});
|
|
const restartGrab = (
|
|
// @ts-expect-error "grab is available on GNOME 42"
|
|
global.display.end_grab_op && global.display.begin_grab_op
|
|
);
|
|
if (restartGrab) {
|
|
global.display.end_grab_op(global.get_current_time());
|
|
}
|
|
this._easeWindowRect(window, newSize, restartGrab, restartGrab);
|
|
TouchPointer.get().updateWindowPosition(newSize);
|
|
if (restartGrab) {
|
|
extWin.originalSize = void 0;
|
|
global.display.begin_grab_op(
|
|
window,
|
|
grabOp,
|
|
true,
|
|
// pointer already grabbed
|
|
true,
|
|
// frame action
|
|
-1,
|
|
// Button
|
|
modifier,
|
|
global.get_current_time(),
|
|
x,
|
|
y
|
|
);
|
|
}
|
|
}
|
|
extWin.originalSize = void 0;
|
|
this._grabStartPosition = null;
|
|
}
|
|
const isSpanMultiTilesActivated = this._activationKeyStatus(
|
|
modifier,
|
|
Settings.SPAN_MULTIPLE_TILES_ACTIVATION_KEY
|
|
);
|
|
const isTilingSystemActivated = this._activationKeyStatus(
|
|
modifier,
|
|
Settings.TILING_SYSTEM_ACTIVATION_KEY
|
|
);
|
|
const deactivationKey = Settings.TILING_SYSTEM_DEACTIVATION_KEY;
|
|
const isTilingSystemDeactivated = deactivationKey === -1 /* NONE */ ? false : this._activationKeyStatus(modifier, deactivationKey);
|
|
const allowSpanMultipleTiles = Settings.SPAN_MULTIPLE_TILES && isSpanMultiTilesActivated;
|
|
const showTilingSystem = Settings.TILING_SYSTEM && isTilingSystemActivated && !isTilingSystemDeactivated;
|
|
const changedSpanMultipleTiles = Settings.SPAN_MULTIPLE_TILES && isSpanMultiTilesActivated !== this._wasSpanMultipleTilesActivated;
|
|
const changedShowTilingSystem = Settings.TILING_SYSTEM && isTilingSystemActivated !== this._wasTilingSystemActivated;
|
|
if (!changedSpanMultipleTiles && !changedShowTilingSystem && currPointerPos.x === this._lastCursorPos?.x && currPointerPos.y === this._lastCursorPos?.y)
|
|
return GLib.SOURCE_CONTINUE;
|
|
this._lastCursorPos = currPointerPos;
|
|
this._wasTilingSystemActivated = isTilingSystemActivated;
|
|
this._wasSpanMultipleTilesActivated = isSpanMultiTilesActivated;
|
|
if (!showTilingSystem) {
|
|
if (tilingLayout.showing) {
|
|
tilingLayout.close();
|
|
this._selectedTilesPreview.close(true);
|
|
}
|
|
if (Settings.ACTIVE_SCREEN_EDGES && !this._snapAssistingInfo.isSnapAssisting && this._edgeTilingManager.canActivateEdgeTiling(currPointerPos)) {
|
|
const { changed, rect } = this._edgeTilingManager.startEdgeTiling(currPointerPos);
|
|
if (changed)
|
|
this._showEdgeTiling(window, rect, x, y, tilingLayout);
|
|
this._snapAssist.close(true);
|
|
} else {
|
|
if (this._edgeTilingManager.isPerformingEdgeTiling()) {
|
|
this._selectedTilesPreview.close(true);
|
|
this._edgeTilingManager.abortEdgeTiling();
|
|
}
|
|
if (Settings.SNAP_ASSIST) {
|
|
this._snapAssist.onMovingWindow(
|
|
window,
|
|
true,
|
|
currPointerPos
|
|
);
|
|
}
|
|
}
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
if (!tilingLayout.showing) {
|
|
tilingLayout.openAbove(window);
|
|
this._snapAssist.close(true);
|
|
if (this._edgeTilingManager.isPerformingEdgeTiling()) {
|
|
this._selectedTilesPreview.close(true);
|
|
this._edgeTilingManager.abortEdgeTiling();
|
|
}
|
|
}
|
|
if (this._snapAssistingInfo.isSnapAssisting) {
|
|
this._selectedTilesPreview.close(true);
|
|
this._snapAssistingInfo.update(void 0);
|
|
}
|
|
if (!changedSpanMultipleTiles && isPointInsideRect(currPointerPos, this._selectedTilesPreview.rect))
|
|
return GLib.SOURCE_CONTINUE;
|
|
let selectionRect = tilingLayout.getTileBelow(
|
|
currPointerPos,
|
|
changedSpanMultipleTiles && !allowSpanMultipleTiles
|
|
);
|
|
if (!selectionRect)
|
|
return GLib.SOURCE_CONTINUE;
|
|
selectionRect = selectionRect.copy();
|
|
if (allowSpanMultipleTiles && this._selectedTilesPreview.showing) {
|
|
selectionRect = selectionRect.union(
|
|
this._selectedTilesPreview.rect
|
|
);
|
|
}
|
|
tilingLayout.hoverTilesInRect(selectionRect, !allowSpanMultipleTiles);
|
|
this.openSelectionTilePreview(selectionRect, true, true, window);
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
_onWindowGrabEnd(window) {
|
|
this._isGrabbingWindow = false;
|
|
this._grabStartPosition = null;
|
|
this._signals.disconnect(window);
|
|
TouchPointer.get().reset();
|
|
const currentWs = window.get_workspace();
|
|
const tilingLayout = this._workspaceTilingLayout.get(currentWs);
|
|
if (tilingLayout)
|
|
tilingLayout.close();
|
|
const desiredWindowRect = buildRectangle({
|
|
x: this._selectedTilesPreview.innerX,
|
|
y: this._selectedTilesPreview.innerY,
|
|
width: this._selectedTilesPreview.innerWidth,
|
|
height: this._selectedTilesPreview.innerHeight
|
|
});
|
|
const selectedTilesRect = this._selectedTilesPreview.rect.copy();
|
|
this._selectedTilesPreview.close(true);
|
|
this._snapAssist.close(true);
|
|
this._lastCursorPos = null;
|
|
const isTilingSystemActivated = this._activationKeyStatus(
|
|
global.get_pointer()[2],
|
|
Settings.TILING_SYSTEM_ACTIVATION_KEY
|
|
);
|
|
if (!isTilingSystemActivated && !this._snapAssistingInfo.isSnapAssisting && !this._edgeTilingManager.isPerformingEdgeTiling())
|
|
return;
|
|
const wasSnapAssistingLayout = this._snapAssistingInfo.isSnapAssisting ? GlobalState.get().layouts.find(
|
|
(lay) => lay.id === this._snapAssistingInfo.layoutId
|
|
) : void 0;
|
|
this._snapAssistingInfo.update(void 0);
|
|
if (this._edgeTilingManager.isPerformingEdgeTiling() && this._edgeTilingManager.needMaximize() && window.can_maximize())
|
|
window.maximize(Meta.MaximizeFlags.BOTH);
|
|
const wasEdgeTiling = this._edgeTilingManager.isPerformingEdgeTiling();
|
|
this._edgeTilingManager.abortEdgeTiling();
|
|
const canShowTilingSuggestions = wasSnapAssistingLayout && Settings.ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS || wasEdgeTiling && Settings.ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS || isTilingSystemActivated && Settings.ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS;
|
|
if (!this._isPointerInsideThisMonitor(window))
|
|
return;
|
|
if (desiredWindowRect.width <= 0 || desiredWindowRect.height <= 0)
|
|
return;
|
|
if (window.get_maximized())
|
|
return;
|
|
window.originalSize = window.get_frame_rect().copy();
|
|
window.assignedTile = new Tile2({
|
|
...TileUtils.build_tile(selectedTilesRect, this._workArea)
|
|
});
|
|
this._easeWindowRect(window, desiredWindowRect);
|
|
if (!tilingLayout || !canShowTilingSuggestions)
|
|
return;
|
|
const layout = wasEdgeTiling ? new Layout(
|
|
[
|
|
// top-left
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
width: 0.5,
|
|
height: 0.5,
|
|
groups: []
|
|
}),
|
|
// top-right
|
|
new Tile2({
|
|
x: 0.5,
|
|
y: 0,
|
|
width: 0.5,
|
|
height: 0.5,
|
|
groups: []
|
|
}),
|
|
// bottom-left
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0.5,
|
|
width: 0.5,
|
|
height: 0.5,
|
|
groups: []
|
|
}),
|
|
// bottom-right
|
|
new Tile2({
|
|
x: 0.5,
|
|
y: 0.5,
|
|
width: 0.5,
|
|
height: 0.5,
|
|
groups: []
|
|
})
|
|
],
|
|
"edge-tiling-layout"
|
|
) : wasSnapAssistingLayout ? wasSnapAssistingLayout : GlobalState.get().getSelectedLayoutOfMonitor(
|
|
this._monitor.index,
|
|
window.get_workspace().index()
|
|
);
|
|
this._openWindowsSuggestions(
|
|
window,
|
|
desiredWindowRect,
|
|
window.get_monitor(),
|
|
layout,
|
|
tilingLayout.innerGaps,
|
|
tilingLayout.outerGaps,
|
|
tilingLayout.scalingFactor
|
|
);
|
|
}
|
|
_openWindowsSuggestions(window, windowDesiredRect, monitorIndex, layout, innerGaps, outerGaps, scalingFactor) {
|
|
const tiledWindows = [];
|
|
const nontiledWindows = [];
|
|
getWindows().forEach((extWin) => {
|
|
if (extWin && !extWin.minimized && extWin.assignedTile)
|
|
tiledWindows.push(extWin);
|
|
else
|
|
nontiledWindows.push(extWin);
|
|
});
|
|
if (nontiledWindows.length === 0)
|
|
return;
|
|
this._tilingSuggestionsLayout.destroy();
|
|
this._tilingSuggestionsLayout = new TilingLayoutWithSuggestions(
|
|
innerGaps,
|
|
outerGaps,
|
|
this._workArea,
|
|
scalingFactor
|
|
);
|
|
this._tilingSuggestionsLayout.relayout({ layout });
|
|
this._tilingSuggestionsLayout.open(
|
|
tiledWindows,
|
|
nontiledWindows,
|
|
window,
|
|
windowDesiredRect,
|
|
monitorIndex
|
|
);
|
|
}
|
|
_easeWindowRect(window, destRect, user_op = false, force = false) {
|
|
const windowActor = window.get_compositor_private();
|
|
const beforeRect = window.get_frame_rect();
|
|
if (destRect.x === beforeRect.x && destRect.y === beforeRect.y && destRect.width === beforeRect.width && destRect.height === beforeRect.height)
|
|
return;
|
|
windowActor.remove_all_transitions();
|
|
Main5.wm._prepareAnimationInfo(
|
|
global.windowManager,
|
|
windowActor,
|
|
beforeRect.copy(),
|
|
Meta.SizeChange.UNMAXIMIZE
|
|
);
|
|
window.move_to_monitor(this._monitor.index);
|
|
if (force)
|
|
window.move_frame(user_op, destRect.x, destRect.y);
|
|
window.move_resize_frame(
|
|
user_op,
|
|
destRect.x,
|
|
destRect.y,
|
|
destRect.width,
|
|
destRect.height
|
|
);
|
|
}
|
|
_onSnapAssist(_2, tile, layoutId) {
|
|
if (tile.width === 0 || tile.height === 0) {
|
|
this._selectedTilesPreview.close(true);
|
|
this._snapAssistingInfo.update(void 0);
|
|
return;
|
|
}
|
|
const scaledRect = TileUtils.apply_props(tile, this._workArea);
|
|
if (scaledRect.x + scaledRect.width > this._workArea.x + this._workArea.width) {
|
|
scaledRect.width -= scaledRect.x + scaledRect.width - this._workArea.x - this._workArea.width;
|
|
}
|
|
if (scaledRect.y + scaledRect.height > this._workArea.y + this._workArea.height) {
|
|
scaledRect.height -= scaledRect.y + scaledRect.height - this._workArea.y - this._workArea.height;
|
|
}
|
|
const currentWs = global.workspaceManager.get_active_workspace();
|
|
const tilingLayout = this._workspaceTilingLayout.get(currentWs);
|
|
if (!tilingLayout)
|
|
return;
|
|
this._selectedTilesPreview.get_parent()?.set_child_above_sibling(this._selectedTilesPreview, null);
|
|
this.openSelectionTilePreview(scaledRect, false, true, void 0);
|
|
this._snapAssistingInfo.update(layoutId);
|
|
}
|
|
openSelectionTilePreview(position, isAboveLayout, ease, window) {
|
|
const currentWs = global.workspaceManager.get_active_workspace();
|
|
const tilingLayout = this._workspaceTilingLayout.get(currentWs);
|
|
if (!tilingLayout)
|
|
return;
|
|
this._selectedTilesPreview.gaps = buildTileGaps(
|
|
position,
|
|
tilingLayout.innerGaps,
|
|
tilingLayout.outerGaps,
|
|
this._workArea,
|
|
this._enableScaling ? getScalingFactorOf(tilingLayout)[1] : void 0
|
|
).gaps;
|
|
this._selectedTilesPreview.get_parent()?.set_child_above_sibling(this._selectedTilesPreview, null);
|
|
const gaps = this._selectedTilesPreview.gaps;
|
|
if (isAboveLayout) {
|
|
this._selectedTilesPreview.updateBorderRadius(
|
|
gaps.top > 0,
|
|
gaps.right > 0,
|
|
gaps.bottom > 0,
|
|
gaps.left > 0
|
|
);
|
|
} else {
|
|
const { isTop, isRight, isBottom, isLeft } = isTileOnContainerBorder(
|
|
buildRectangle({
|
|
x: position.x + gaps.left,
|
|
y: position.y + gaps.top,
|
|
width: position.width - gaps.left - gaps.right,
|
|
height: position.height - gaps.top - gaps.bottom
|
|
}),
|
|
this._workArea
|
|
);
|
|
this._selectedTilesPreview.updateBorderRadius(
|
|
!isTop,
|
|
!isRight,
|
|
!isBottom,
|
|
!isLeft
|
|
);
|
|
}
|
|
if (window)
|
|
this._selectedTilesPreview.openAbove(window, ease, position);
|
|
else
|
|
this._selectedTilesPreview.open(ease, position);
|
|
}
|
|
/**
|
|
* Checks if pointer is inside the current monitor
|
|
* @returns true if the pointer is inside the current monitor, false otherwise
|
|
*/
|
|
_isPointerInsideThisMonitor(window) {
|
|
const [x, y] = TouchPointer.get().isTouchDeviceActive() ? TouchPointer.get().get_pointer(window) : global.get_pointer();
|
|
const pointerMonitorIndex = global.display.get_monitor_index_for_rect(
|
|
buildRectangle({
|
|
x,
|
|
y,
|
|
width: 1,
|
|
height: 1
|
|
})
|
|
);
|
|
return this._monitor.index === pointerMonitorIndex;
|
|
}
|
|
_showEdgeTiling(window, edgeTile, pointerX, pointerY, tilingLayout) {
|
|
this._selectedTilesPreview.gaps = buildTileGaps(
|
|
edgeTile,
|
|
tilingLayout.innerGaps,
|
|
tilingLayout.outerGaps,
|
|
this._workArea,
|
|
this._enableScaling ? getScalingFactorOf(tilingLayout)[1] : void 0
|
|
).gaps;
|
|
if (!this._selectedTilesPreview.showing) {
|
|
const { left, right, top, bottom } = this._selectedTilesPreview.gaps;
|
|
const initialRect = buildRectangle({
|
|
x: pointerX,
|
|
y: pointerY,
|
|
width: left + right + 8,
|
|
// width without gaps will be 8
|
|
height: top + bottom + 8
|
|
// height without gaps will be 8
|
|
});
|
|
initialRect.x -= initialRect.width / 2;
|
|
initialRect.y -= initialRect.height / 2;
|
|
this._selectedTilesPreview.open(false, initialRect);
|
|
}
|
|
this.openSelectionTilePreview(edgeTile, false, true, window);
|
|
}
|
|
_easeWindowRectFromTile(tile, window, skipAnimation = false) {
|
|
const currentWs = window.get_workspace();
|
|
const tilingLayout = this._workspaceTilingLayout.get(currentWs);
|
|
if (!tilingLayout)
|
|
return;
|
|
const scaledRect = TileUtils.apply_props(tile, this._workArea);
|
|
if (scaledRect.x + scaledRect.width > this._workArea.x + this._workArea.width) {
|
|
scaledRect.width -= scaledRect.x + scaledRect.width - this._workArea.x - this._workArea.width;
|
|
}
|
|
if (scaledRect.y + scaledRect.height > this._workArea.y + this._workArea.height) {
|
|
scaledRect.height -= scaledRect.y + scaledRect.height - this._workArea.y - this._workArea.height;
|
|
}
|
|
const gaps = buildTileGaps(
|
|
scaledRect,
|
|
tilingLayout.innerGaps,
|
|
tilingLayout.outerGaps,
|
|
this._workArea,
|
|
this._enableScaling ? getScalingFactorOf(tilingLayout)[1] : void 0
|
|
).gaps;
|
|
const destinationRect = buildRectangle({
|
|
x: scaledRect.x + gaps.left,
|
|
y: scaledRect.y + gaps.top,
|
|
width: scaledRect.width - gaps.left - gaps.right,
|
|
height: scaledRect.height - gaps.top - gaps.bottom
|
|
});
|
|
if (destinationRect.width <= 0 || destinationRect.height <= 0)
|
|
return;
|
|
const rememberOriginalSize = !window.get_maximized();
|
|
if (window.get_maximized())
|
|
window.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
if (rememberOriginalSize && !window.assignedTile) {
|
|
window.originalSize = window.get_frame_rect().copy();
|
|
}
|
|
window.assignedTile = TileUtils.build_tile(
|
|
buildRectangle({
|
|
x: scaledRect.x,
|
|
y: scaledRect.y,
|
|
width: scaledRect.width,
|
|
height: scaledRect.height
|
|
}),
|
|
this._workArea
|
|
);
|
|
if (skipAnimation) {
|
|
window.move_resize_frame(
|
|
false,
|
|
destinationRect.x,
|
|
destinationRect.y,
|
|
destinationRect.width,
|
|
destinationRect.height
|
|
);
|
|
} else {
|
|
this._easeWindowRect(window, destinationRect);
|
|
}
|
|
}
|
|
onTileFromWindowMenu(tile, window) {
|
|
this._easeWindowRectFromTile(tile, window);
|
|
}
|
|
onSpanAllTiles(window) {
|
|
this._easeWindowRectFromTile(
|
|
new Tile2({
|
|
x: 0,
|
|
y: 0,
|
|
width: 1,
|
|
height: 1,
|
|
groups: []
|
|
}),
|
|
window
|
|
);
|
|
}
|
|
_autoTile(window, windowCreated) {
|
|
if (window.get_monitor() !== this._monitor.index)
|
|
return;
|
|
if (window === null || window.windowType !== Meta.WindowType.NORMAL || window.get_transient_for() !== null || window.is_attached_dialog() || window.minimized || window.maximizedHorizontally || window.maximizedVertically)
|
|
return;
|
|
window.assignedTile = void 0;
|
|
const vacantTile = this._findEmptyTile(window);
|
|
if (!vacantTile)
|
|
return;
|
|
if (windowCreated) {
|
|
const windowActor = window.get_compositor_private();
|
|
const id = windowActor.connect("first-frame", () => {
|
|
if (!window.minimized && !window.maximizedHorizontally && !window.maximizedVertically && window.get_transient_for() === null && !window.is_attached_dialog())
|
|
this._easeWindowRectFromTile(vacantTile, window, true);
|
|
windowActor.disconnect(id);
|
|
});
|
|
} else {
|
|
this._easeWindowRectFromTile(vacantTile, window, true);
|
|
}
|
|
}
|
|
_findEmptyTile(window) {
|
|
const tiledWindows = getWindows().filter((otherWindow) => {
|
|
return otherWindow && otherWindow.assignedTile && !otherWindow.minimized && !otherWindow.maximizedVertically && !otherWindow.maximizedHorizontally;
|
|
}).map((w) => w);
|
|
const tiles = GlobalState.get().getSelectedLayoutOfMonitor(
|
|
window.get_monitor(),
|
|
global.workspaceManager.get_active_workspace_index()
|
|
).tiles;
|
|
const workArea = Main5.layoutManager.getWorkAreaForMonitor(
|
|
window.get_monitor()
|
|
);
|
|
const vacantTiles = tiles.filter((t) => {
|
|
const tileRect = TileUtils.apply_props(t, workArea);
|
|
return !tiledWindows.find(
|
|
(win) => tileRect.overlap(win.get_frame_rect())
|
|
);
|
|
});
|
|
if (vacantTiles.length === 0)
|
|
return void 0;
|
|
vacantTiles.sort((a, b) => a.x - b.x);
|
|
let bestTileIndex = 0;
|
|
let bestDistance = Math.abs(
|
|
0.5 - vacantTiles[bestTileIndex].x + vacantTiles[bestTileIndex].width / 2
|
|
);
|
|
for (let index = 1; index < vacantTiles.length; index++) {
|
|
const distance = Math.abs(
|
|
0.5 - (vacantTiles[index].x + vacantTiles[index].width / 2)
|
|
);
|
|
if (bestDistance > distance) {
|
|
bestTileIndex = index;
|
|
bestDistance = distance;
|
|
}
|
|
}
|
|
if (bestTileIndex < 0 || bestTileIndex >= vacantTiles.length)
|
|
return void 0;
|
|
return vacantTiles[bestTileIndex];
|
|
}
|
|
};
|
|
|
|
// src/components/editor/editableTilePreview.ts
|
|
var EditableTilePreview = class extends TilePreview {
|
|
_btn;
|
|
_containerRect;
|
|
_sliders;
|
|
_signals;
|
|
constructor(params) {
|
|
super(params);
|
|
this.add_style_class_name("editable-tile-preview");
|
|
this._tile = params.tile;
|
|
this._containerRect = params.containerRect;
|
|
this._sliders = [null, null, null, null];
|
|
this._signals = [null, null, null, null];
|
|
this._btn = new St.Button({
|
|
styleClass: "editable-tile-preview-button",
|
|
xExpand: true,
|
|
trackHover: true
|
|
});
|
|
this.add_child(this._btn);
|
|
this._btn.set_size(this.innerWidth, this.innerHeight);
|
|
this._btn.set_button_mask(St.ButtonMask.ONE | St.ButtonMask.THREE);
|
|
this._updateLabelText();
|
|
this.connect("destroy", this._onDestroy.bind(this));
|
|
}
|
|
set gaps(newGaps) {
|
|
super.gaps = newGaps;
|
|
this.updateBorderRadius(
|
|
this._gaps.top > 0,
|
|
this._gaps.right > 0,
|
|
this._gaps.bottom > 0,
|
|
this._gaps.left > 0
|
|
);
|
|
}
|
|
getSlider(side) {
|
|
return this._sliders[side];
|
|
}
|
|
getAllSliders() {
|
|
return [...this._sliders];
|
|
}
|
|
get hover() {
|
|
return this._btn.hover;
|
|
}
|
|
addSlider(slider, side) {
|
|
const sig = this._signals[side];
|
|
if (sig)
|
|
this._sliders[side]?.disconnect(sig);
|
|
this._sliders[side] = slider;
|
|
this._signals[side] = slider.connect(
|
|
"slide",
|
|
() => this._onSliderMove(side)
|
|
);
|
|
this._tile.groups = [];
|
|
this._sliders.forEach((sl) => sl && this._tile.groups.push(sl.groupId));
|
|
}
|
|
removeSlider(side) {
|
|
if (this._sliders[side] === null)
|
|
return;
|
|
const sig = this._signals[side];
|
|
if (sig)
|
|
this._sliders[side]?.disconnect(sig);
|
|
this._sliders[side] = null;
|
|
this._tile.groups = [];
|
|
this._sliders.forEach((sl) => sl && this._tile.groups.push(sl.groupId));
|
|
}
|
|
updateTile({
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
innerGaps,
|
|
outerGaps
|
|
}) {
|
|
const oldSize = this._rect.copy();
|
|
this._tile.x = x;
|
|
this._tile.y = y;
|
|
this._tile.width = width;
|
|
this._tile.height = height;
|
|
this._rect = TileUtils.apply_props(this._tile, this._containerRect);
|
|
if (innerGaps && outerGaps) {
|
|
this.gaps = buildTileGaps(
|
|
this._rect,
|
|
innerGaps,
|
|
outerGaps,
|
|
this._containerRect
|
|
).gaps;
|
|
}
|
|
this.set_size(this.innerWidth, this.innerHeight);
|
|
this.set_position(this.innerX, this.innerY);
|
|
this._btn.set_size(this.width, this.height);
|
|
this._updateLabelText();
|
|
const newSize = this._rect.copy();
|
|
this.emit("size-changed", oldSize, newSize);
|
|
}
|
|
connect(signal, callback) {
|
|
if (signal === "clicked" || signal === "notify::hover" || signal === "motion-event")
|
|
return this._btn.connect(signal, callback);
|
|
return super.connect(signal, callback);
|
|
}
|
|
_updateLabelText() {
|
|
this._btn.label = `${this.innerWidth}x${this.innerHeight}`;
|
|
}
|
|
_onSliderMove(side) {
|
|
const slider = this._sliders[side];
|
|
if (slider === null)
|
|
return;
|
|
const posHoriz = (slider.x + slider.width / 2 - this._containerRect.x) / this._containerRect.width;
|
|
const posVert = (slider.y + slider.height / 2 - this._containerRect.y) / this._containerRect.height;
|
|
switch (side) {
|
|
case St.Side.TOP:
|
|
this._tile.height += this._tile.y - posVert;
|
|
this._tile.y = posVert;
|
|
break;
|
|
case St.Side.RIGHT:
|
|
this._tile.width = posHoriz - this._tile.x;
|
|
break;
|
|
case St.Side.BOTTOM:
|
|
this._tile.height = posVert - this._tile.y;
|
|
break;
|
|
case St.Side.LEFT:
|
|
this._tile.width += this._tile.x - posHoriz;
|
|
this._tile.x = posHoriz;
|
|
break;
|
|
}
|
|
this.updateTile({ ...this._tile });
|
|
}
|
|
_onDestroy() {
|
|
this._signals.forEach(
|
|
(id, side) => id && this._sliders[side]?.disconnect(id)
|
|
);
|
|
}
|
|
};
|
|
__publicField(EditableTilePreview, "metaInfo", {
|
|
Signals: {
|
|
"size-changed": {
|
|
param_types: [Mtk.Rectangle.$gtype, Mtk.Rectangle.$gtype]
|
|
// oldSize, newSize
|
|
}
|
|
},
|
|
GTypeName: "EditableTilePreview"
|
|
});
|
|
__publicField(EditableTilePreview, "MIN_TILE_SIZE", 140);
|
|
EditableTilePreview = __decorateClass([
|
|
registerGObjectClass
|
|
], EditableTilePreview);
|
|
|
|
// src/components/editor/slider.ts
|
|
var Slider2 = class extends St.Button {
|
|
_sliderSize = 48;
|
|
_groupId;
|
|
_signals;
|
|
_dragging;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
_grab;
|
|
_horizontalDir;
|
|
_lastEventCoord;
|
|
_previousTiles;
|
|
_nextTiles;
|
|
_minTileCoord;
|
|
_maxTileCoord;
|
|
_scalingFactor;
|
|
constructor(parent, groupId, x, y, horizontal) {
|
|
super({
|
|
styleClass: "layout-editor-slider",
|
|
canFocus: true,
|
|
xExpand: false,
|
|
trackHover: true
|
|
});
|
|
parent.add_child(this);
|
|
this._signals = /* @__PURE__ */ new Map();
|
|
this._groupId = groupId;
|
|
this._horizontalDir = horizontal;
|
|
const [, scalingFactor] = getScalingFactorOf(this);
|
|
this._scalingFactor = scalingFactor;
|
|
this.set_width(this.desiredWidth);
|
|
this.set_height(this.desiredHeight);
|
|
this._previousTiles = [];
|
|
this._nextTiles = [];
|
|
this._minTileCoord = Number.MAX_VALUE;
|
|
this._maxTileCoord = Number.MIN_VALUE;
|
|
this._dragging = false;
|
|
this._lastEventCoord = null;
|
|
this.set_position(
|
|
Math.round(Math.round(x - this.width / 2)),
|
|
Math.round(y - this.height / 2)
|
|
);
|
|
this.connect(
|
|
"notify::hover",
|
|
() => global.display.set_cursor(this.preferredCursor)
|
|
);
|
|
this.connect("destroy", this._onDestroy.bind(this));
|
|
}
|
|
get groupId() {
|
|
return this._groupId;
|
|
}
|
|
get desiredWidth() {
|
|
return (this._horizontalDir ? 12 : this._sliderSize) * this._scalingFactor;
|
|
}
|
|
get desiredHeight() {
|
|
return (this._horizontalDir ? this._sliderSize : 12) * this._scalingFactor;
|
|
}
|
|
get preferredCursor() {
|
|
const horizCursor = Meta.Cursor.WEST_RESIZE ?? Meta.Cursor.W_RESIZE;
|
|
const vertCursor = Meta.Cursor.NORTH_RESIZE ?? Meta.Cursor.N_RESIZE;
|
|
return this.hover || this._dragging ? this._horizontalDir ? horizCursor : vertCursor : Meta.Cursor.DEFAULT;
|
|
}
|
|
addTile(tile) {
|
|
const isNext = this._horizontalDir ? this.x <= tile.rect.x : this.y <= tile.rect.y;
|
|
if (isNext)
|
|
this._nextTiles.push(tile);
|
|
else
|
|
this._previousTiles.push(tile);
|
|
const side = this._horizontalDir ? isNext ? St.Side.LEFT : St.Side.RIGHT : isNext ? St.Side.TOP : St.Side.BOTTOM;
|
|
tile.addSlider(this, side);
|
|
this._minTileCoord = Math.min(
|
|
this._minTileCoord,
|
|
this._horizontalDir ? tile.rect.y : tile.rect.x
|
|
);
|
|
this._maxTileCoord = Math.max(
|
|
this._maxTileCoord,
|
|
this._horizontalDir ? tile.rect.y + tile.rect.height : tile.rect.x + tile.rect.width
|
|
);
|
|
this._updatePosition();
|
|
this._createTileSignals(tile);
|
|
}
|
|
_onTileSizeChanged(tile, oldSize, newSize) {
|
|
if (this._horizontalDir) {
|
|
if (this._minTileCoord !== oldSize.y && this._maxTileCoord !== oldSize.y + oldSize.height)
|
|
return;
|
|
if (this._minTileCoord === oldSize.y)
|
|
this._minTileCoord = newSize.y;
|
|
if (this._maxTileCoord === oldSize.y + oldSize.height)
|
|
this._maxTileCoord = newSize.y + newSize.height;
|
|
} else {
|
|
if (this._minTileCoord !== oldSize.x && this._maxTileCoord !== oldSize.x + oldSize.width)
|
|
return;
|
|
if (this._minTileCoord === oldSize.x)
|
|
this._minTileCoord = newSize.x;
|
|
if (this._maxTileCoord === oldSize.x + oldSize.width)
|
|
this._maxTileCoord = newSize.x + newSize.width;
|
|
}
|
|
this._updatePosition();
|
|
}
|
|
_updatePosition() {
|
|
this.set_width(this.desiredWidth);
|
|
this.set_height(this.desiredHeight);
|
|
const newCoord = (this._minTileCoord + this._maxTileCoord) / 2;
|
|
if (this._horizontalDir)
|
|
this.set_y(Math.round(newCoord - this.height / 2));
|
|
else
|
|
this.set_x(Math.round(newCoord - this.width / 2));
|
|
}
|
|
_onTileDeleted(tile) {
|
|
const isNext = this._horizontalDir ? this.x <= tile.rect.x : this.y <= tile.rect.y;
|
|
const array = isNext ? this._nextTiles : this._previousTiles;
|
|
const index = array.indexOf(tile, 0);
|
|
if (index >= 0)
|
|
array.splice(index, 1);
|
|
const sig = this._signals.get(tile);
|
|
if (sig) {
|
|
sig.forEach((id) => tile.disconnect(id));
|
|
this._signals.delete(tile);
|
|
}
|
|
}
|
|
onTileSplit(tileToRemove, newTiles) {
|
|
if (newTiles.length === 0)
|
|
return;
|
|
const isNext = this._horizontalDir ? this.x <= tileToRemove.rect.x : this.y <= tileToRemove.rect.y;
|
|
const array = isNext ? this._nextTiles : this._previousTiles;
|
|
const index = array.indexOf(tileToRemove);
|
|
if (index < 0)
|
|
return;
|
|
const side = this._horizontalDir ? isNext ? St.Side.LEFT : St.Side.RIGHT : isNext ? St.Side.TOP : St.Side.BOTTOM;
|
|
const sig = this._signals.get(tileToRemove);
|
|
if (sig) {
|
|
sig.forEach((id) => tileToRemove.disconnect(id));
|
|
this._signals.delete(tileToRemove);
|
|
}
|
|
array[index] = newTiles[0];
|
|
newTiles[0].addSlider(this, side);
|
|
this._createTileSignals(newTiles[0]);
|
|
for (let i = 1; i < newTiles.length; i++) {
|
|
const tile = newTiles[i];
|
|
array.push(tile);
|
|
tile.addSlider(this, side);
|
|
this._createTileSignals(tile);
|
|
}
|
|
}
|
|
_createTileSignals(tile) {
|
|
if (this._signals.has(tile))
|
|
return;
|
|
this._signals.set(tile, []);
|
|
this._signals.get(tile)?.push(
|
|
tile.connect(
|
|
"size-changed",
|
|
this._onTileSizeChanged.bind(this)
|
|
)
|
|
);
|
|
this._signals.get(tile)?.push(tile.connect("destroy", this._onTileDeleted.bind(this)));
|
|
}
|
|
deleteSlider(tileToDelete, innerGaps, outerGaps) {
|
|
const isNext = this._horizontalDir ? this.x <= tileToDelete.rect.x : this.y <= tileToDelete.rect.y;
|
|
const array = isNext ? this._nextTiles : this._previousTiles;
|
|
if (array.length > 1 || array[0] !== tileToDelete)
|
|
return false;
|
|
array.pop();
|
|
const oppositeSide = this._horizontalDir ? isNext ? St.Side.RIGHT : St.Side.LEFT : isNext ? St.Side.BOTTOM : St.Side.TOP;
|
|
const extendTilesArray = isNext ? this._previousTiles : this._nextTiles;
|
|
extendTilesArray.forEach((tileToExtend) => {
|
|
tileToExtend.updateTile({
|
|
x: !isNext && this._horizontalDir ? tileToDelete.tile.x : tileToExtend.tile.x,
|
|
y: !isNext && !this._horizontalDir ? tileToDelete.tile.y : tileToExtend.tile.y,
|
|
width: this._horizontalDir ? tileToExtend.tile.width + tileToDelete.tile.width : tileToExtend.tile.width,
|
|
height: this._horizontalDir ? tileToExtend.tile.height : tileToExtend.tile.height + tileToDelete.tile.height,
|
|
innerGaps,
|
|
outerGaps
|
|
});
|
|
tileToExtend.removeSlider(oppositeSide);
|
|
tileToDelete.getSlider(oppositeSide)?.addTile(tileToExtend);
|
|
});
|
|
return true;
|
|
}
|
|
vfunc_button_press_event(event) {
|
|
return this._startDragging(event);
|
|
}
|
|
vfunc_button_release_event() {
|
|
if (this._dragging)
|
|
return this._endDragging();
|
|
return Clutter.EVENT_PROPAGATE;
|
|
}
|
|
vfunc_motion_event(event) {
|
|
if (this._dragging) {
|
|
const [stageX, stageY] = getEventCoords(event);
|
|
this._move(stageX, stageY);
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
}
|
|
_startDragging(event) {
|
|
if (this._dragging)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
this._dragging = true;
|
|
global.display.set_cursor(this.preferredCursor);
|
|
this._grab = global.stage.grab(this);
|
|
const [stageX, stageY] = getEventCoords(event);
|
|
this._move(stageX, stageY);
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
_endDragging() {
|
|
if (this._dragging) {
|
|
if (this._grab) {
|
|
this._grab.dismiss();
|
|
this._grab = null;
|
|
}
|
|
this._dragging = false;
|
|
this._lastEventCoord = null;
|
|
}
|
|
global.display.set_cursor(this.preferredCursor);
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
_move(eventX, eventY) {
|
|
eventX = Math.round(eventX);
|
|
eventY = Math.round(eventY);
|
|
if (this._lastEventCoord !== null) {
|
|
const movement = {
|
|
x: this._horizontalDir ? eventX - this._lastEventCoord.x : 0,
|
|
y: this._horizontalDir ? 0 : eventY - this._lastEventCoord.y
|
|
};
|
|
for (const prevTile of this._previousTiles) {
|
|
if (prevTile.rect.width + movement.x < EditableTilePreview.MIN_TILE_SIZE || prevTile.rect.height + movement.y < EditableTilePreview.MIN_TILE_SIZE)
|
|
return;
|
|
}
|
|
for (const nextTile of this._nextTiles) {
|
|
if (nextTile.rect.width - movement.x < EditableTilePreview.MIN_TILE_SIZE || nextTile.rect.height - movement.y < EditableTilePreview.MIN_TILE_SIZE)
|
|
return;
|
|
}
|
|
this.set_position(this.x + movement.x, this.y + movement.y);
|
|
this.emit("slide", this._horizontalDir ? movement.x : movement.y);
|
|
}
|
|
this._lastEventCoord = { x: eventX, y: eventY };
|
|
}
|
|
_onDestroy() {
|
|
this._signals.forEach(
|
|
(ids, tile) => ids.forEach((id) => tile.disconnect(id))
|
|
);
|
|
this._minTileCoord = Number.MAX_VALUE;
|
|
this._maxTileCoord = Number.MIN_VALUE;
|
|
this._previousTiles = [];
|
|
this._nextTiles = [];
|
|
this._lastEventCoord = null;
|
|
this._endDragging();
|
|
}
|
|
};
|
|
__publicField(Slider2, "metaInfo", {
|
|
Signals: {
|
|
slide: {
|
|
param_types: [GObject.TYPE_INT]
|
|
// movement
|
|
}
|
|
},
|
|
GTypeName: "Slider"
|
|
});
|
|
Slider2 = __decorateClass([
|
|
registerGObjectClass
|
|
], Slider2);
|
|
|
|
// src/components/editor/hoverLine.ts
|
|
var HoverLine = class extends St.Widget {
|
|
_hoverTimer;
|
|
_size;
|
|
_hoveredTile;
|
|
constructor(parent) {
|
|
super({ styleClass: "hover-line" });
|
|
parent.add_child(this);
|
|
this._hoveredTile = null;
|
|
const [, scalingFactor] = getScalingFactorOf(this);
|
|
this._size = 16 * scalingFactor;
|
|
this.hide();
|
|
this._hoverTimer = GLib.timeout_add(
|
|
GLib.PRIORITY_DEFAULT_IDLE,
|
|
100,
|
|
this._handleModifierChange.bind(this)
|
|
);
|
|
this.connect("destroy", this._onDestroy.bind(this));
|
|
}
|
|
handleTileDestroy(tile) {
|
|
if (this._hoveredTile === tile) {
|
|
this._hoveredTile = null;
|
|
this.hide();
|
|
}
|
|
}
|
|
handleMouseMove(tile, x, y) {
|
|
this._hoveredTile = tile;
|
|
const modifier = Shell.Global.get().get_pointer()[2];
|
|
const splitHorizontally = (modifier & Clutter.ModifierType.CONTROL_MASK) === 0;
|
|
this._drawLine(splitHorizontally, x, y);
|
|
}
|
|
_handleModifierChange() {
|
|
if (!this._hoveredTile)
|
|
return GLib.SOURCE_CONTINUE;
|
|
if (!this._hoveredTile.hover) {
|
|
this.hide();
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
const [x, y, modifier] = global.get_pointer();
|
|
const splitHorizontally = (modifier & Clutter.ModifierType.CONTROL_MASK) === 0;
|
|
this._drawLine(
|
|
splitHorizontally,
|
|
x - (this.get_parent()?.x || 0),
|
|
y - (this.get_parent()?.y || 0)
|
|
);
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
_drawLine(splitHorizontally, x, y) {
|
|
if (!this._hoveredTile)
|
|
return;
|
|
if (splitHorizontally) {
|
|
const newX = x - this._size / 2;
|
|
if (newX < this._hoveredTile.x || newX + this._size > this._hoveredTile.x + this._hoveredTile.width)
|
|
return;
|
|
this.set_size(this._size, this._hoveredTile.height);
|
|
this.set_position(newX, this._hoveredTile.y);
|
|
} else {
|
|
const newY = y - this._size / 2;
|
|
if (newY < this._hoveredTile.y || newY + this._size > this._hoveredTile.y + this._hoveredTile.height)
|
|
return;
|
|
this.set_size(this._hoveredTile.width, this._size);
|
|
this.set_position(this._hoveredTile.x, newY);
|
|
}
|
|
this.show();
|
|
}
|
|
_onDestroy() {
|
|
GLib.Source.remove(this._hoverTimer);
|
|
this._hoveredTile = null;
|
|
}
|
|
};
|
|
HoverLine = __decorateClass([
|
|
registerGObjectClass
|
|
], HoverLine);
|
|
|
|
// src/components/editor/layoutEditor.ts
|
|
import * as Main6 from "resource:///org/gnome/shell/ui/main.js";
|
|
var LayoutEditor = class extends St.Widget {
|
|
_layout;
|
|
_containerRect;
|
|
_innerGaps;
|
|
_outerGaps;
|
|
_hoverWidget;
|
|
_sliders;
|
|
_minimizedWindows;
|
|
constructor(layout, monitor, enableScaling) {
|
|
super({ styleClass: "layout-editor" });
|
|
Main6.layoutManager.addChrome(this);
|
|
global.windowGroup.bind_property(
|
|
"visible",
|
|
this,
|
|
"visible",
|
|
GObject.BindingFlags.DEFAULT
|
|
);
|
|
if (enableScaling) {
|
|
const scalingFactor = getMonitorScalingFactor(monitor.index);
|
|
enableScalingFactorSupport(this, scalingFactor);
|
|
}
|
|
const workArea = Main6.layoutManager.getWorkAreaForMonitor(
|
|
monitor.index
|
|
);
|
|
this.set_position(workArea.x, workArea.y);
|
|
this.set_size(workArea.width, workArea.height);
|
|
this._innerGaps = buildMargin(Settings.get_inner_gaps());
|
|
this._outerGaps = buildMargin(Settings.get_outer_gaps());
|
|
this._sliders = [];
|
|
this._containerRect = buildRectangle({
|
|
x: 0,
|
|
y: 0,
|
|
width: workArea.width,
|
|
height: workArea.height
|
|
});
|
|
this._minimizedWindows = getWindowsOfMonitor(monitor).filter(
|
|
(win) => !win.is_hidden()
|
|
);
|
|
this._minimizedWindows.forEach(
|
|
(win) => win.can_minimize() && win.minimize()
|
|
);
|
|
this._hoverWidget = new HoverLine(this);
|
|
this._layout = layout;
|
|
this._drawEditor();
|
|
this.grab_key_focus();
|
|
this.connect("destroy", this._onDestroy.bind(this));
|
|
}
|
|
get layout() {
|
|
return this._layout;
|
|
}
|
|
set layout(newLayout) {
|
|
this.destroy_all_children();
|
|
this._sliders = [];
|
|
this._hoverWidget = new HoverLine(this);
|
|
this._layout = newLayout;
|
|
this._drawEditor();
|
|
}
|
|
_drawEditor() {
|
|
const groups = /* @__PURE__ */ new Map();
|
|
this._layout.tiles.forEach((tile) => {
|
|
const rect = TileUtils.apply_props(tile, this._containerRect);
|
|
const prev = this._buildEditableTile(tile, rect);
|
|
tile.groups.forEach((id) => {
|
|
if (!groups.has(id))
|
|
groups.set(id, []);
|
|
groups.get(id)?.push(prev);
|
|
});
|
|
});
|
|
groups.forEach((tiles, groupdId) => {
|
|
let lines = tiles.flatMap((t) => [
|
|
{
|
|
c: Math.round(t.tile.x * 1e3) / 1e3,
|
|
end: false,
|
|
r: t.rect.x
|
|
},
|
|
{
|
|
c: Math.round((t.tile.x + t.tile.width) * 1e3) / 1e3,
|
|
end: true,
|
|
r: t.rect.x + t.rect.width
|
|
}
|
|
]).sort((a, b) => a.c - b.c !== 0 ? a.c - b.c : a.end ? -1 : 1);
|
|
let count = 0;
|
|
let coord = -1;
|
|
let horizontal = false;
|
|
for (const line of lines) {
|
|
count += line.end ? -1 : 1;
|
|
if (count === 0 && line !== lines[lines.length - 1]) {
|
|
coord = line.r;
|
|
horizontal = true;
|
|
break;
|
|
}
|
|
}
|
|
if (coord === -1) {
|
|
lines = tiles.flatMap((t) => [
|
|
{
|
|
c: Math.round(t.tile.y * 1e3) / 1e3,
|
|
end: false,
|
|
r: t.rect.y
|
|
},
|
|
{
|
|
c: Math.round((t.tile.y + t.tile.height) * 1e3) / 1e3,
|
|
end: true,
|
|
r: t.rect.y + t.rect.height
|
|
}
|
|
]).sort(
|
|
(a, b) => a.c - b.c !== 0 ? a.c - b.c : a.end ? -1 : 1
|
|
);
|
|
count = 0;
|
|
for (const line of lines) {
|
|
count += line.end ? -1 : 1;
|
|
if (count === 0 && line !== lines[lines.length - 1]) {
|
|
coord = line.r;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const slider = this._buildSlider(horizontal, coord, groupdId);
|
|
this._sliders.push(slider);
|
|
tiles.forEach((editable) => slider.addTile(editable));
|
|
});
|
|
}
|
|
_buildEditableTile(tile, rect) {
|
|
const gaps = buildTileGaps(
|
|
rect,
|
|
this._innerGaps,
|
|
this._outerGaps,
|
|
this._containerRect
|
|
).gaps;
|
|
const editableTile = new EditableTilePreview({
|
|
parent: this,
|
|
tile,
|
|
containerRect: this._containerRect,
|
|
rect,
|
|
gaps
|
|
});
|
|
editableTile.open();
|
|
editableTile.connect("clicked", (_2, clicked_button) => {
|
|
if (clicked_button === St.ButtonMask.ONE)
|
|
this.splitTile(editableTile);
|
|
else if (clicked_button === 3)
|
|
this.deleteTile(editableTile);
|
|
});
|
|
editableTile.connect("motion-event", (_2, event) => {
|
|
const [stageX, stageY] = getEventCoords(event);
|
|
this._hoverWidget.handleMouseMove(
|
|
editableTile,
|
|
stageX - this.x,
|
|
stageY - this.y
|
|
);
|
|
return Clutter.EVENT_PROPAGATE;
|
|
});
|
|
editableTile.connect("notify::hover", () => {
|
|
const [stageX, stageY] = Shell.Global.get().get_pointer();
|
|
this._hoverWidget.handleMouseMove(
|
|
editableTile,
|
|
stageX - this.x,
|
|
stageY - this.y
|
|
);
|
|
});
|
|
if (this._sliders.length > 0)
|
|
this.set_child_below_sibling(editableTile, this._sliders[0]);
|
|
return editableTile;
|
|
}
|
|
splitTile(editableTile) {
|
|
const oldTile = editableTile.tile;
|
|
const index = this._layout.tiles.indexOf(oldTile);
|
|
if (index < 0)
|
|
return;
|
|
const [x, y, modifier] = global.get_pointer();
|
|
const splitX = (x - this.x) / this._containerRect.width;
|
|
const splitY = (y - this.y) / this._containerRect.height;
|
|
const splitHorizontally = (modifier & Clutter.ModifierType.CONTROL_MASK) === 0;
|
|
const prevTile = new Tile2({
|
|
x: oldTile.x,
|
|
y: oldTile.y,
|
|
width: splitHorizontally ? splitX - oldTile.x : oldTile.width,
|
|
height: splitHorizontally ? oldTile.height : splitY - oldTile.y,
|
|
groups: []
|
|
});
|
|
const nextTile = new Tile2({
|
|
x: splitHorizontally ? splitX : oldTile.x,
|
|
y: splitHorizontally ? oldTile.y : splitY,
|
|
width: splitHorizontally ? oldTile.width - prevTile.width : oldTile.width,
|
|
height: splitHorizontally ? oldTile.height : oldTile.height - prevTile.height,
|
|
groups: []
|
|
});
|
|
const prevRect = TileUtils.apply_props(prevTile, this._containerRect);
|
|
const nextRect = TileUtils.apply_props(nextTile, this._containerRect);
|
|
if (prevRect.height < EditableTilePreview.MIN_TILE_SIZE || prevRect.width < EditableTilePreview.MIN_TILE_SIZE || nextRect.height < EditableTilePreview.MIN_TILE_SIZE || nextRect.width < EditableTilePreview.MIN_TILE_SIZE)
|
|
return;
|
|
this._layout.tiles[index] = prevTile;
|
|
this._layout.tiles.push(nextTile);
|
|
const prevEditableTile = this._buildEditableTile(prevTile, prevRect);
|
|
const nextEditableTile = this._buildEditableTile(nextTile, nextRect);
|
|
const slider = this._buildSlider(
|
|
splitHorizontally,
|
|
splitHorizontally ? nextEditableTile.rect.x : nextEditableTile.rect.y
|
|
);
|
|
this._sliders.push(slider);
|
|
slider.addTile(prevEditableTile);
|
|
slider.addTile(nextEditableTile);
|
|
if (splitHorizontally) {
|
|
editableTile.getSlider(St.Side.TOP)?.onTileSplit(editableTile, [
|
|
prevEditableTile,
|
|
nextEditableTile
|
|
]);
|
|
editableTile.getSlider(St.Side.BOTTOM)?.onTileSplit(editableTile, [
|
|
prevEditableTile,
|
|
nextEditableTile
|
|
]);
|
|
editableTile.getSlider(St.Side.LEFT)?.onTileSplit(editableTile, [prevEditableTile]);
|
|
editableTile.getSlider(St.Side.RIGHT)?.onTileSplit(editableTile, [nextEditableTile]);
|
|
} else {
|
|
editableTile.getSlider(St.Side.LEFT)?.onTileSplit(editableTile, [
|
|
prevEditableTile,
|
|
nextEditableTile
|
|
]);
|
|
editableTile.getSlider(St.Side.RIGHT)?.onTileSplit(editableTile, [
|
|
prevEditableTile,
|
|
nextEditableTile
|
|
]);
|
|
editableTile.getSlider(St.Side.TOP)?.onTileSplit(editableTile, [prevEditableTile]);
|
|
editableTile.getSlider(St.Side.BOTTOM)?.onTileSplit(editableTile, [nextEditableTile]);
|
|
}
|
|
this._hoverWidget.handleTileDestroy(editableTile);
|
|
editableTile.destroy();
|
|
}
|
|
deleteTile(editableTile) {
|
|
for (const slider of editableTile.getAllSliders()) {
|
|
if (slider === null)
|
|
continue;
|
|
const success = slider.deleteSlider(
|
|
editableTile,
|
|
this._innerGaps,
|
|
this._outerGaps
|
|
);
|
|
if (success) {
|
|
this._layout.tiles = this._layout.tiles.filter(
|
|
(tile) => tile !== editableTile.tile
|
|
);
|
|
this._sliders = this._sliders.filter((sl) => sl !== slider);
|
|
this._hoverWidget.handleTileDestroy(editableTile);
|
|
editableTile.destroy();
|
|
slider.destroy();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
_buildSlider(isHorizontal, coord, groupId) {
|
|
if (!groupId) {
|
|
const groups = this._sliders.map((slider) => slider.groupId).sort();
|
|
groupId = groups.length === 0 ? 1 : groups[groups.length - 1] + 1;
|
|
for (let i = 1; i < groups.length; i++) {
|
|
if (groups[i - 1] + 1 < groups[i]) {
|
|
groupId = groups[i - 1] + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return new Slider2(this, groupId, coord, coord, isHorizontal);
|
|
}
|
|
_onDestroy() {
|
|
this._minimizedWindows.forEach((win) => win.unminimize());
|
|
this.destroy_all_children();
|
|
this._sliders = [];
|
|
super.destroy();
|
|
}
|
|
};
|
|
LayoutEditor = __decorateClass([
|
|
registerGObjectClass
|
|
], LayoutEditor);
|
|
|
|
// src/indicator/utils.ts
|
|
var createButton = (iconName, text, path) => {
|
|
const btn = createIconButton(iconName, path, 8);
|
|
btn.set_style("padding-left: 5px !important;");
|
|
btn.child.add_child(
|
|
new St.Label({
|
|
marginBottom: 4,
|
|
marginTop: 4,
|
|
text,
|
|
yAlign: Clutter.ActorAlign.CENTER
|
|
})
|
|
);
|
|
return btn;
|
|
};
|
|
var createIconButton = (iconName, path, spacing = 0) => {
|
|
const btn = new St.Button({
|
|
styleClass: "message-list-clear-button button",
|
|
canFocus: true,
|
|
xExpand: true,
|
|
style: "padding-left: 5px !important; padding-right: 5px !important;",
|
|
child: new St.BoxLayout({
|
|
clipToAllocation: true,
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
reactive: true,
|
|
xExpand: true,
|
|
style: spacing > 0 ? `spacing: ${spacing}px` : ""
|
|
})
|
|
});
|
|
const icon = new St.Icon({
|
|
iconSize: 16,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
style: "padding: 6px"
|
|
});
|
|
if (path)
|
|
icon.gicon = Gio.icon_new_for_string(`${path}/icons/${iconName}.svg`);
|
|
else
|
|
icon.iconName = iconName;
|
|
btn.child.add_child(icon);
|
|
return btn;
|
|
};
|
|
|
|
// src/indicator/layoutButton.ts
|
|
var LayoutButtonWidget = class extends LayoutWidget {
|
|
constructor(parent, layout, gapSize, height, width) {
|
|
super({
|
|
parent,
|
|
layout,
|
|
containerRect: buildRectangle({ x: 0, y: 0, width, height }),
|
|
innerGaps: buildMarginOf(gapSize),
|
|
outerGaps: new Clutter.Margin()
|
|
});
|
|
this.relayout();
|
|
}
|
|
buildTile(parent, rect, gaps, tile) {
|
|
return new SnapAssistTile({ parent, rect, gaps, tile });
|
|
}
|
|
};
|
|
LayoutButtonWidget = __decorateClass([
|
|
registerGObjectClass
|
|
], LayoutButtonWidget);
|
|
var LayoutButton = class extends St.Button {
|
|
constructor(parent, layout, gapSize, height, width) {
|
|
super({
|
|
styleClass: "layout-button button",
|
|
xExpand: false,
|
|
yExpand: false
|
|
});
|
|
parent.add_child(this);
|
|
const scalingFactor = getScalingFactorOf(this)[1];
|
|
this.child = new St.Widget();
|
|
new LayoutButtonWidget(
|
|
this.child,
|
|
layout,
|
|
gapSize,
|
|
height * scalingFactor,
|
|
width * scalingFactor
|
|
);
|
|
}
|
|
};
|
|
LayoutButton = __decorateClass([
|
|
registerGObjectClass
|
|
], LayoutButton);
|
|
|
|
// src/translations.ts
|
|
import { gettext as _, ngettext, pgettext } from "resource:///org/gnome/shell/extensions/extension.js";
|
|
|
|
// src/polyfill.ts
|
|
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
|
|
function openPrefs() {
|
|
if (Extension.openPrefs) {
|
|
Extension.openPrefs();
|
|
} else {
|
|
Extension.lookupByUUID(
|
|
"tilingshell@ferrarodomenico.com"
|
|
)?.openPreferences();
|
|
}
|
|
}
|
|
|
|
// src/indicator/defaultMenu.ts
|
|
import * as Main7 from "resource:///org/gnome/shell/ui/main.js";
|
|
import * as PopupMenu from "resource:///org/gnome/shell/ui/popupMenu.js";
|
|
var debug12 = logger("DefaultMenu");
|
|
var LayoutsRow = class extends St.BoxLayout {
|
|
_layoutsBox;
|
|
_layoutsButtons;
|
|
_label;
|
|
_monitor;
|
|
constructor(parent, layouts, selectedId, showMonitorName, monitor) {
|
|
super({
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
yExpand: true,
|
|
style: "spacing: 8px"
|
|
});
|
|
setWidgetOrientation(this, true);
|
|
this._layoutsBox = new St.BoxLayout({
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
yExpand: true,
|
|
styleClass: "layouts-box-layout"
|
|
});
|
|
this._monitor = monitor;
|
|
this._label = new St.Label({
|
|
text: `Monitor ${this._monitor.index + 1}`,
|
|
styleClass: "monitor-layouts-title"
|
|
});
|
|
this.add_child(this._label);
|
|
if (!showMonitorName)
|
|
this._label.hide();
|
|
this.add_child(this._layoutsBox);
|
|
parent.add_child(this);
|
|
const selectedIndex = layouts.findIndex((lay) => lay.id === selectedId);
|
|
const hasGaps = Settings.get_inner_gaps(1).top > 0;
|
|
const layoutHeight = 36;
|
|
const layoutWidth = 64;
|
|
this._layoutsButtons = layouts.map((lay, ind) => {
|
|
const btn = new LayoutButton(
|
|
this._layoutsBox,
|
|
lay,
|
|
hasGaps ? 2 : 0,
|
|
layoutHeight,
|
|
layoutWidth
|
|
);
|
|
btn.connect(
|
|
"clicked",
|
|
() => !btn.checked && this.emit("selected-layout", lay.id)
|
|
);
|
|
if (ind === selectedIndex)
|
|
btn.set_checked(true);
|
|
return btn;
|
|
});
|
|
}
|
|
selectLayout(selectedId) {
|
|
const selectedIndex = GlobalState.get().layouts.findIndex(
|
|
(lay) => lay.id === selectedId
|
|
);
|
|
this._layoutsButtons.forEach(
|
|
(btn, ind) => btn.set_checked(ind === selectedIndex)
|
|
);
|
|
}
|
|
updateMonitorName(showMonitorName, monitorsDetails) {
|
|
if (!showMonitorName)
|
|
this._label.hide();
|
|
else
|
|
this._label.show();
|
|
const details = monitorsDetails.find(
|
|
(m) => m.x === this._monitor.x && m.y === this._monitor.y
|
|
);
|
|
if (!details)
|
|
return;
|
|
this._label.set_text(details.name);
|
|
}
|
|
};
|
|
__publicField(LayoutsRow, "metaInfo", {
|
|
GTypeName: "LayoutsRow",
|
|
Signals: {
|
|
"selected-layout": {
|
|
param_types: [GObject.TYPE_STRING]
|
|
}
|
|
}
|
|
});
|
|
LayoutsRow = __decorateClass([
|
|
registerGObjectClass
|
|
], LayoutsRow);
|
|
var DefaultMenu = class {
|
|
_signals;
|
|
_indicator;
|
|
_layoutsRows;
|
|
_container;
|
|
_scalingFactor;
|
|
_children;
|
|
constructor(indicator, enableScalingFactor) {
|
|
this._indicator = indicator;
|
|
this._signals = new SignalHandling();
|
|
this._children = [];
|
|
const layoutsPopupMenu = new PopupMenu.PopupBaseMenuItem({
|
|
style_class: "indicator-menu-item"
|
|
});
|
|
this._children.push(layoutsPopupMenu);
|
|
this._container = new St.BoxLayout({
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
yExpand: true,
|
|
styleClass: "default-menu-container"
|
|
});
|
|
setWidgetOrientation(this._container, true);
|
|
layoutsPopupMenu.add_child(this._container);
|
|
this._indicator.menu.addMenuItem(
|
|
layoutsPopupMenu
|
|
);
|
|
if (enableScalingFactor) {
|
|
const monitor = Main7.layoutManager.findMonitorForActor(
|
|
this._container
|
|
);
|
|
const scalingFactor = getMonitorScalingFactor(
|
|
monitor?.index || Main7.layoutManager.primaryIndex
|
|
);
|
|
enableScalingFactorSupport(this._container, scalingFactor);
|
|
}
|
|
this._scalingFactor = getScalingFactorOf(this._container)[1];
|
|
this._layoutsRows = [];
|
|
this._drawLayouts();
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_SETTING_LAYOUTS_JSON,
|
|
() => {
|
|
this._drawLayouts();
|
|
}
|
|
);
|
|
this._signals.connect(Settings, Settings.KEY_INNER_GAPS, () => {
|
|
this._drawLayouts();
|
|
});
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_SETTING_SELECTED_LAYOUTS,
|
|
() => {
|
|
this._updateScaling();
|
|
if (this._layoutsRows.length !== getMonitors().length)
|
|
this._drawLayouts();
|
|
const selected_layouts = Settings.get_selected_layouts();
|
|
const wsIndex = global.workspaceManager.get_active_workspace_index();
|
|
getMonitors().forEach((m, index) => {
|
|
const selectedId = wsIndex < selected_layouts.length ? selected_layouts[wsIndex][index] : GlobalState.get().layouts[0].id;
|
|
this._layoutsRows[index].selectLayout(selectedId);
|
|
});
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.workspaceManager,
|
|
"active-workspace-changed",
|
|
() => {
|
|
const selected_layouts = Settings.get_selected_layouts();
|
|
const wsIndex = global.workspaceManager.get_active_workspace_index();
|
|
getMonitors().forEach((m, index) => {
|
|
const selectedId = wsIndex < selected_layouts.length ? selected_layouts[wsIndex][index] : GlobalState.get().layouts[0].id;
|
|
this._layoutsRows[index].selectLayout(selectedId);
|
|
});
|
|
}
|
|
);
|
|
this._signals.connect(Main7.layoutManager, "monitors-changed", () => {
|
|
if (!enableScalingFactor)
|
|
return;
|
|
const monitor = Main7.layoutManager.findMonitorForActor(
|
|
this._container
|
|
);
|
|
const scalingFactor = getMonitorScalingFactor(
|
|
monitor?.index || Main7.layoutManager.primaryIndex
|
|
);
|
|
enableScalingFactorSupport(this._container, scalingFactor);
|
|
this._updateScaling();
|
|
if (this._layoutsRows.length !== getMonitors().length)
|
|
this._drawLayouts();
|
|
this._computeMonitorsDetails();
|
|
});
|
|
this._computeMonitorsDetails();
|
|
const buttonsPopupMenu = this._buildEditingButtonsRow();
|
|
this._indicator.menu.addMenuItem(
|
|
buttonsPopupMenu
|
|
);
|
|
this._children.push(buttonsPopupMenu);
|
|
}
|
|
// compute monitors details and update labels asynchronously (if we have successful results...)
|
|
_computeMonitorsDetails() {
|
|
if (getMonitors().length === 1) {
|
|
this._layoutsRows.forEach((lr) => lr.updateMonitorName(false, []));
|
|
return;
|
|
}
|
|
try {
|
|
const proc = Gio.Subprocess.new(
|
|
["gjs", "-m", `${this._indicator.path}/monitorDescription.js`],
|
|
Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
|
|
);
|
|
proc.communicate_utf8_async(
|
|
null,
|
|
null,
|
|
(pr, res) => {
|
|
if (!pr)
|
|
return;
|
|
const [, stdout, stderr] = pr.communicate_utf8_finish(res);
|
|
if (pr.get_successful()) {
|
|
debug12(stdout);
|
|
const monitorsDetails = JSON.parse(stdout);
|
|
this._layoutsRows.forEach(
|
|
(lr) => lr.updateMonitorName(true, monitorsDetails)
|
|
);
|
|
} else {
|
|
debug12("error:", stderr);
|
|
}
|
|
}
|
|
);
|
|
} catch (e) {
|
|
debug12(e);
|
|
}
|
|
}
|
|
_updateScaling() {
|
|
const newScalingFactor = getScalingFactorOf(this._container)[1];
|
|
if (this._scalingFactor === newScalingFactor)
|
|
return;
|
|
this._scalingFactor = newScalingFactor;
|
|
this._drawLayouts();
|
|
}
|
|
_buildEditingButtonsRow() {
|
|
const buttonsBoxLayout = new St.BoxLayout({
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
yExpand: true,
|
|
styleClass: "buttons-box-layout"
|
|
});
|
|
const editLayoutsBtn = createButton(
|
|
"edit-symbolic",
|
|
`${_("Edit Layouts")}...`,
|
|
this._indicator.path
|
|
);
|
|
editLayoutsBtn.connect(
|
|
"clicked",
|
|
() => this._indicator.openLayoutEditor()
|
|
);
|
|
buttonsBoxLayout.add_child(editLayoutsBtn);
|
|
const newLayoutBtn = createButton(
|
|
"add-symbolic",
|
|
`${_("New Layout")}...`,
|
|
this._indicator.path
|
|
);
|
|
newLayoutBtn.connect(
|
|
"clicked",
|
|
() => this._indicator.newLayoutOnClick(true)
|
|
);
|
|
buttonsBoxLayout.add_child(newLayoutBtn);
|
|
const prefsBtn = createIconButton(
|
|
"prefs-symbolic",
|
|
this._indicator.path
|
|
);
|
|
prefsBtn.connect("clicked", () => {
|
|
openPrefs();
|
|
this._indicator.menu.toggle();
|
|
});
|
|
buttonsBoxLayout.add_child(prefsBtn);
|
|
const buttonsPopupMenu = new PopupMenu.PopupBaseMenuItem({
|
|
style_class: "indicator-menu-item"
|
|
});
|
|
buttonsPopupMenu.add_child(buttonsBoxLayout);
|
|
return buttonsPopupMenu;
|
|
}
|
|
_drawLayouts() {
|
|
const layouts = GlobalState.get().layouts;
|
|
this._container.destroy_all_children();
|
|
this._layoutsRows = [];
|
|
const selected_layouts = Settings.get_selected_layouts();
|
|
const ws_index = global.workspaceManager.get_active_workspace_index();
|
|
const monitors = getMonitors();
|
|
this._layoutsRows = monitors.map((monitor) => {
|
|
const ws_selected_layouts = ws_index < selected_layouts.length ? selected_layouts[ws_index] : [];
|
|
const selectedId = monitor.index < ws_selected_layouts.length ? ws_selected_layouts[monitor.index] : GlobalState.get().layouts[0].id;
|
|
const row = new LayoutsRow(
|
|
this._container,
|
|
layouts,
|
|
selectedId,
|
|
monitors.length > 1,
|
|
monitor
|
|
);
|
|
row.connect(
|
|
"selected-layout",
|
|
(r, layoutId) => {
|
|
this._indicator.selectLayoutOnClick(
|
|
monitor.index,
|
|
layoutId
|
|
);
|
|
}
|
|
);
|
|
return row;
|
|
});
|
|
}
|
|
destroy() {
|
|
this._signals.disconnect();
|
|
this._layoutsRows.forEach((lr) => lr.destroy());
|
|
this._layoutsRows = [];
|
|
this._children.forEach((c) => c.destroy());
|
|
this._children = [];
|
|
}
|
|
};
|
|
|
|
// src/indicator/editingMenu.ts
|
|
import * as PopupMenu2 from "resource:///org/gnome/shell/ui/popupMenu.js";
|
|
var EditingMenu = class {
|
|
_indicator;
|
|
constructor(indicator) {
|
|
this._indicator = indicator;
|
|
const boxLayout = new St.BoxLayout({
|
|
styleClass: "buttons-box-layout",
|
|
xExpand: true,
|
|
style: "spacing: 8px"
|
|
});
|
|
setWidgetOrientation(boxLayout, true);
|
|
const openMenuBtn = createButton(
|
|
"menu-symbolic",
|
|
_("Menu"),
|
|
this._indicator.path
|
|
);
|
|
openMenuBtn.connect("clicked", () => this._indicator.openMenu(false));
|
|
boxLayout.add_child(openMenuBtn);
|
|
const infoMenuBtn = createButton(
|
|
"info-symbolic",
|
|
_("Info"),
|
|
this._indicator.path
|
|
);
|
|
infoMenuBtn.connect("clicked", () => this._indicator.openMenu(true));
|
|
boxLayout.add_child(infoMenuBtn);
|
|
const saveBtn = createButton(
|
|
"save-symbolic",
|
|
_("Save"),
|
|
this._indicator.path
|
|
);
|
|
saveBtn.connect("clicked", () => {
|
|
this._indicator.menu.toggle();
|
|
this._indicator.saveLayoutOnClick();
|
|
});
|
|
boxLayout.add_child(saveBtn);
|
|
const cancelBtn = createButton(
|
|
"cancel-symbolic",
|
|
_("Cancel"),
|
|
this._indicator.path
|
|
);
|
|
cancelBtn.connect("clicked", () => {
|
|
this._indicator.menu.toggle();
|
|
this._indicator.cancelLayoutOnClick();
|
|
});
|
|
boxLayout.add_child(cancelBtn);
|
|
const menuItem = new PopupMenu2.PopupBaseMenuItem({
|
|
style_class: "indicator-menu-item"
|
|
});
|
|
menuItem.add_child(boxLayout);
|
|
this._indicator.menu.addMenuItem(menuItem);
|
|
}
|
|
destroy() {
|
|
this._indicator.menu.removeAll();
|
|
}
|
|
};
|
|
|
|
// src/components/editor/editorDialog.ts
|
|
import * as ModalDialog from "resource:///org/gnome/shell/ui/modalDialog.js";
|
|
import * as Main8 from "resource:///org/gnome/shell/ui/main.js";
|
|
var EditorDialog = class extends ModalDialog.ModalDialog {
|
|
_layoutHeight = 72;
|
|
_layoutWidth = 128;
|
|
// 16:9 ratio. -> (16*layoutHeight) / 9 and then rounded to int
|
|
_gapsSize = 3;
|
|
_layoutsBoxLayout;
|
|
constructor(params) {
|
|
super({
|
|
destroyOnClose: true,
|
|
styleClass: "editor-dialog"
|
|
});
|
|
if (params.enableScaling) {
|
|
const monitor = Main8.layoutManager.findMonitorForActor(this);
|
|
const scalingFactor = getMonitorScalingFactor(
|
|
monitor?.index || Main8.layoutManager.primaryIndex
|
|
);
|
|
enableScalingFactorSupport(this, scalingFactor);
|
|
}
|
|
this.contentLayout.add_child(
|
|
new St.Label({
|
|
text: _("Select the layout to edit"),
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
styleClass: "editor-dialog-title"
|
|
})
|
|
);
|
|
this._layoutsBoxLayout = new St.BoxLayout({
|
|
styleClass: "layouts-box-layout",
|
|
xAlign: Clutter.ActorAlign.CENTER
|
|
});
|
|
this.contentLayout.add_child(this._layoutsBoxLayout);
|
|
if (!params.legend) {
|
|
this._drawLayouts({
|
|
layouts: GlobalState.get().layouts,
|
|
...params
|
|
});
|
|
}
|
|
this.addButton({
|
|
label: _("Close"),
|
|
default: true,
|
|
key: Clutter.KEY_Escape,
|
|
action: () => params.onClose()
|
|
});
|
|
if (params.legend) {
|
|
this._makeLegendDialog({
|
|
onClose: params.onClose,
|
|
path: params.path
|
|
});
|
|
}
|
|
}
|
|
_makeLegendDialog(params) {
|
|
const suggestion1 = new St.BoxLayout();
|
|
suggestion1.add_child(
|
|
new St.Label({
|
|
text: "LEFT CLICK",
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "button kbd",
|
|
xExpand: false,
|
|
pseudoClass: "active"
|
|
})
|
|
);
|
|
suggestion1.add_child(
|
|
new St.Label({
|
|
text: ` ${_("to split a tile")}.`,
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "",
|
|
xExpand: false
|
|
})
|
|
);
|
|
const suggestion2 = new St.BoxLayout();
|
|
suggestion2.add_child(
|
|
new St.Label({
|
|
text: "LEFT CLICK",
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "button kbd",
|
|
xExpand: false,
|
|
pseudoClass: "active"
|
|
})
|
|
);
|
|
suggestion2.add_child(
|
|
new St.Label({
|
|
text: " + ",
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "",
|
|
xExpand: false
|
|
})
|
|
);
|
|
suggestion2.add_child(
|
|
new St.Label({
|
|
text: "CTRL",
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "button kbd",
|
|
xExpand: false,
|
|
pseudoClass: "active"
|
|
})
|
|
);
|
|
suggestion2.add_child(
|
|
new St.Label({
|
|
text: ` ${_("to split a tile vertically")}.`,
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "",
|
|
xExpand: false
|
|
})
|
|
);
|
|
const suggestion3 = new St.BoxLayout();
|
|
suggestion3.add_child(
|
|
new St.Label({
|
|
text: "RIGHT CLICK",
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "button kbd",
|
|
xExpand: false,
|
|
pseudoClass: "active"
|
|
})
|
|
);
|
|
suggestion3.add_child(
|
|
new St.Label({
|
|
text: ` ${_("to delete a tile")}.`,
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "",
|
|
xExpand: false
|
|
})
|
|
);
|
|
const suggestion4 = new St.BoxLayout({
|
|
xExpand: true,
|
|
margin_top: 16
|
|
});
|
|
suggestion4.add_child(
|
|
new St.Icon({
|
|
iconSize: 16,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
gicon: Gio.icon_new_for_string(
|
|
`${params.path}/icons/indicator-symbolic.svg`
|
|
),
|
|
styleClass: "button kbd",
|
|
pseudoClass: "active"
|
|
})
|
|
);
|
|
suggestion4.add_child(
|
|
new St.Label({
|
|
text: ` ${_("use the indicator button to save or cancel")}.`,
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "",
|
|
xExpand: false
|
|
})
|
|
);
|
|
const legend = new St.BoxLayout({
|
|
styleClass: "legend"
|
|
});
|
|
setWidgetOrientation(legend, true);
|
|
legend.add_child(suggestion1);
|
|
legend.add_child(suggestion2);
|
|
legend.add_child(suggestion3);
|
|
legend.add_child(suggestion4);
|
|
this.contentLayout.destroy_all_children();
|
|
this.contentLayout.add_child(
|
|
new St.Label({
|
|
text: _("How to use the editor"),
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
styleClass: "editor-dialog-title"
|
|
})
|
|
);
|
|
this.contentLayout.add_child(legend);
|
|
this.clearButtons();
|
|
this.addButton({
|
|
label: _("Start editing"),
|
|
default: true,
|
|
key: Clutter.KEY_Escape,
|
|
action: params.onClose
|
|
});
|
|
}
|
|
_drawLayouts(params) {
|
|
const gaps = Settings.get_inner_gaps(1).top > 0 ? this._gapsSize : 0;
|
|
this._layoutsBoxLayout.destroy_all_children();
|
|
params.layouts.forEach((lay, btnInd) => {
|
|
const box2 = new St.BoxLayout({
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "layout-button-container"
|
|
});
|
|
setWidgetOrientation(box2, true);
|
|
this._layoutsBoxLayout.add_child(box2);
|
|
const btn = new LayoutButton(
|
|
box2,
|
|
lay,
|
|
gaps,
|
|
this._layoutHeight,
|
|
this._layoutWidth
|
|
);
|
|
if (params.layouts.length > 1) {
|
|
const deleteBtn = new St.Button({
|
|
xExpand: false,
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "message-list-clear-button icon-button button delete-layout-button"
|
|
});
|
|
deleteBtn.child = new St.Icon({
|
|
gicon: Gio.icon_new_for_string(
|
|
`${params.path}/icons/delete-symbolic.svg`
|
|
),
|
|
iconSize: 16
|
|
});
|
|
deleteBtn.connect("clicked", () => {
|
|
params.onDeleteLayout(btnInd, lay);
|
|
this._drawLayouts({
|
|
...params,
|
|
layouts: GlobalState.get().layouts
|
|
});
|
|
});
|
|
box2.add_child(deleteBtn);
|
|
}
|
|
btn.connect("clicked", () => {
|
|
params.onSelectLayout(btnInd, lay);
|
|
this._makeLegendDialog({
|
|
onClose: params.onClose,
|
|
path: params.path
|
|
});
|
|
});
|
|
return btn;
|
|
});
|
|
const box = new St.BoxLayout({
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
styleClass: "layout-button-container"
|
|
});
|
|
setWidgetOrientation(box, true);
|
|
this._layoutsBoxLayout.add_child(box);
|
|
const newLayoutBtn = new LayoutButton(
|
|
box,
|
|
new Layout(
|
|
[new Tile2({ x: 0, y: 0, width: 1, height: 1, groups: [] })],
|
|
"New Layout"
|
|
),
|
|
gaps,
|
|
this._layoutHeight,
|
|
this._layoutWidth
|
|
);
|
|
const icon = new St.Icon({
|
|
gicon: Gio.icon_new_for_string(
|
|
`${params.path}/icons/add-symbolic.svg`
|
|
),
|
|
iconSize: 32
|
|
});
|
|
icon.set_size(newLayoutBtn.child.width, newLayoutBtn.child.height);
|
|
newLayoutBtn.child.add_child(icon);
|
|
newLayoutBtn.connect("clicked", () => {
|
|
params.onNewLayout();
|
|
this._makeLegendDialog({
|
|
onClose: params.onClose,
|
|
path: params.path
|
|
});
|
|
});
|
|
}
|
|
};
|
|
EditorDialog = __decorateClass([
|
|
registerGObjectClass
|
|
], EditorDialog);
|
|
|
|
// src/indicator/indicator.ts
|
|
import * as Main9 from "resource:///org/gnome/shell/ui/main.js";
|
|
import * as PanelMenu from "resource:///org/gnome/shell/ui/panelMenu.js";
|
|
var IndicatorState = /* @__PURE__ */ ((IndicatorState2) => {
|
|
IndicatorState2[IndicatorState2["DEFAULT"] = 1] = "DEFAULT";
|
|
IndicatorState2[IndicatorState2["CREATE_NEW"] = 2] = "CREATE_NEW";
|
|
IndicatorState2[IndicatorState2["EDITING_LAYOUT"] = 3] = "EDITING_LAYOUT";
|
|
return IndicatorState2;
|
|
})(IndicatorState || {});
|
|
var Indicator3 = class extends PanelMenu.Button {
|
|
_layoutEditor;
|
|
_editorDialog;
|
|
_currentMenu;
|
|
_state;
|
|
_enableScaling;
|
|
_path;
|
|
_keyPressEvent;
|
|
constructor(path, uuid) {
|
|
super(0.5, "Tiling Shell Indicator", false);
|
|
Main9.panel.addToStatusArea(uuid, this, 1, "right");
|
|
Settings.bind(
|
|
Settings.KEY_SHOW_INDICATOR,
|
|
this,
|
|
"visible",
|
|
Gio.SettingsBindFlags.GET
|
|
);
|
|
const icon = new St.Icon({
|
|
gicon: Gio.icon_new_for_string(
|
|
`${path}/icons/indicator-symbolic.svg`
|
|
),
|
|
styleClass: "system-status-icon indicator-icon"
|
|
});
|
|
this.add_child(icon);
|
|
this._layoutEditor = null;
|
|
this._editorDialog = null;
|
|
this._currentMenu = null;
|
|
this._state = 1 /* DEFAULT */;
|
|
this._keyPressEvent = null;
|
|
this._enableScaling = false;
|
|
this._path = path;
|
|
this.connect("destroy", this._onDestroy.bind(this));
|
|
}
|
|
get path() {
|
|
return this._path;
|
|
}
|
|
set enableScaling(value) {
|
|
if (this._enableScaling === value)
|
|
return;
|
|
this._enableScaling = value;
|
|
if (this._currentMenu && this._state === 1 /* DEFAULT */) {
|
|
this._currentMenu.destroy();
|
|
this._currentMenu = new DefaultMenu(this, this._enableScaling);
|
|
}
|
|
}
|
|
enable() {
|
|
this.menu.removeAll();
|
|
this._currentMenu = new DefaultMenu(this, this._enableScaling);
|
|
}
|
|
selectLayoutOnClick(monitorIndex, layoutToSelectId) {
|
|
const selected = Settings.get_selected_layouts();
|
|
selected[global.workspaceManager.get_active_workspace_index()][monitorIndex] = layoutToSelectId;
|
|
const n_workspaces = global.workspaceManager.get_n_workspaces();
|
|
if (global.workspaceManager.get_active_workspace_index() === n_workspaces - 2) {
|
|
const lastWs = global.workspaceManager.get_workspace_by_index(
|
|
n_workspaces - 1
|
|
);
|
|
if (!lastWs)
|
|
return;
|
|
const tiledWindows = getWindows(lastWs).find(
|
|
(win) => win.assignedTile && win.get_monitor() === monitorIndex
|
|
);
|
|
if (!tiledWindows) {
|
|
selected[lastWs.index()][monitorIndex] = layoutToSelectId;
|
|
}
|
|
}
|
|
Settings.save_selected_layouts(selected);
|
|
this.menu.toggle();
|
|
}
|
|
newLayoutOnClick(showLegendOnly) {
|
|
this.menu.close(true);
|
|
const newLayout = new Layout(
|
|
[
|
|
new Tile2({ x: 0, y: 0, width: 0.3, height: 1, groups: [1] }),
|
|
new Tile2({ x: 0.3, y: 0, width: 0.7, height: 1, groups: [1] })
|
|
],
|
|
`${Shell.Global.get().get_current_time()}`
|
|
);
|
|
if (this._layoutEditor) {
|
|
this._layoutEditor.layout = newLayout;
|
|
} else {
|
|
this._layoutEditor = new LayoutEditor(
|
|
newLayout,
|
|
Main9.layoutManager.monitors[Main9.layoutManager.primaryIndex],
|
|
this._enableScaling
|
|
);
|
|
}
|
|
this._setState(2 /* CREATE_NEW */);
|
|
if (showLegendOnly)
|
|
this.openMenu(true);
|
|
}
|
|
openMenu(showLegend) {
|
|
if (this._editorDialog)
|
|
return;
|
|
this._editorDialog = new EditorDialog({
|
|
enableScaling: this._enableScaling,
|
|
onNewLayout: () => {
|
|
this.newLayoutOnClick(false);
|
|
},
|
|
onDeleteLayout: (ind, lay) => {
|
|
GlobalState.get().deleteLayout(lay);
|
|
if (this._layoutEditor && this._layoutEditor.layout.id === lay.id)
|
|
this.cancelLayoutOnClick();
|
|
},
|
|
onSelectLayout: (ind, lay) => {
|
|
const layCopy = new Layout(
|
|
lay.tiles.map(
|
|
(t) => new Tile2({
|
|
x: t.x,
|
|
y: t.y,
|
|
width: t.width,
|
|
height: t.height,
|
|
groups: [...t.groups]
|
|
})
|
|
),
|
|
lay.id
|
|
);
|
|
if (this._layoutEditor) {
|
|
this._layoutEditor.layout = layCopy;
|
|
} else {
|
|
this._layoutEditor = new LayoutEditor(
|
|
layCopy,
|
|
Main9.layoutManager.monitors[Main9.layoutManager.primaryIndex],
|
|
this._enableScaling
|
|
);
|
|
}
|
|
this._setState(3 /* EDITING_LAYOUT */);
|
|
},
|
|
onClose: () => {
|
|
this._editorDialog?.destroy();
|
|
this._editorDialog = null;
|
|
},
|
|
path: this._path,
|
|
legend: showLegend
|
|
});
|
|
this._editorDialog.open();
|
|
}
|
|
openLayoutEditor() {
|
|
this.openMenu(false);
|
|
}
|
|
saveLayoutOnClick() {
|
|
if (this._layoutEditor === null || this._state === 1 /* DEFAULT */)
|
|
return;
|
|
const newLayout = this._layoutEditor.layout;
|
|
if (this._state === 2 /* CREATE_NEW */)
|
|
GlobalState.get().addLayout(newLayout);
|
|
else
|
|
GlobalState.get().editLayout(newLayout);
|
|
this.menu.toggle();
|
|
this._layoutEditor.destroy();
|
|
this._layoutEditor = null;
|
|
this._setState(1 /* DEFAULT */);
|
|
}
|
|
cancelLayoutOnClick() {
|
|
if (this._layoutEditor === null || this._state === 1 /* DEFAULT */)
|
|
return;
|
|
this.menu.toggle();
|
|
this._layoutEditor.destroy();
|
|
this._layoutEditor = null;
|
|
this._setState(1 /* DEFAULT */);
|
|
}
|
|
_setState(newState) {
|
|
if (this._state === newState)
|
|
return;
|
|
this._state = newState;
|
|
this._currentMenu?.destroy();
|
|
switch (newState) {
|
|
case 1 /* DEFAULT */:
|
|
this._currentMenu = new DefaultMenu(this, this._enableScaling);
|
|
if (!Settings.SHOW_INDICATOR)
|
|
this.hide();
|
|
if (this._keyPressEvent) {
|
|
global.stage.disconnect(this._keyPressEvent);
|
|
this._keyPressEvent = null;
|
|
}
|
|
break;
|
|
case 2 /* CREATE_NEW */:
|
|
case 3 /* EDITING_LAYOUT */:
|
|
this._currentMenu = new EditingMenu(this);
|
|
this.show();
|
|
if (this._keyPressEvent)
|
|
global.stage.disconnect(this._keyPressEvent);
|
|
this._keyPressEvent = global.stage.connect_after(
|
|
"key-press-event",
|
|
(_2, event) => {
|
|
const symbol = event.get_key_symbol();
|
|
if (symbol === Clutter.KEY_Escape)
|
|
this.cancelLayoutOnClick();
|
|
return Clutter.EVENT_PROPAGATE;
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
_onDestroy() {
|
|
this._editorDialog?.destroy();
|
|
this._editorDialog = null;
|
|
this._layoutEditor?.destroy();
|
|
this._layoutEditor = null;
|
|
this._currentMenu?.destroy();
|
|
this._currentMenu = null;
|
|
this.menu.removeAll();
|
|
}
|
|
};
|
|
Indicator3 = __decorateClass([
|
|
registerGObjectClass
|
|
], Indicator3);
|
|
|
|
// src/dbus.ts
|
|
var node = `<node>
|
|
<interface name="org.gnome.Shell.Extensions.TilingShell">
|
|
<method name="openLayoutEditor" />
|
|
</interface>
|
|
</node>`;
|
|
var DBus = class {
|
|
_dbus;
|
|
constructor() {
|
|
this._dbus = null;
|
|
}
|
|
enable(ext) {
|
|
if (this._dbus)
|
|
return;
|
|
this._dbus = Gio.DBusExportedObject.wrapJSObject(node, ext);
|
|
this._dbus.export(
|
|
Gio.DBus.session,
|
|
"/org/gnome/Shell/Extensions/TilingShell"
|
|
);
|
|
}
|
|
disable() {
|
|
this._dbus?.flush();
|
|
this._dbus?.unexport();
|
|
this._dbus = null;
|
|
}
|
|
};
|
|
|
|
// src/components/tilingsystem/resizeManager.ts
|
|
var ResizingManager = class {
|
|
_signals;
|
|
constructor() {
|
|
this._signals = null;
|
|
}
|
|
enable() {
|
|
if (this._signals)
|
|
this._signals.disconnect();
|
|
this._signals = new SignalHandling();
|
|
this._signals.connect(
|
|
global.display,
|
|
"grab-op-begin",
|
|
(_display, window, grabOp) => {
|
|
const moving = grabOp === Meta.GrabOp.KEYBOARD_MOVING || grabOp === Meta.GrabOp.MOVING;
|
|
if (moving || !Settings.RESIZE_COMPLEMENTING_WINDOWS)
|
|
return;
|
|
this._onWindowResizingBegin(window, grabOp & ~1024);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
global.display,
|
|
"grab-op-end",
|
|
(_display, window, grabOp) => {
|
|
const moving = grabOp === Meta.GrabOp.KEYBOARD_MOVING || grabOp === Meta.GrabOp.MOVING;
|
|
if (moving)
|
|
return;
|
|
this._onWindowResizingEnd(window);
|
|
}
|
|
);
|
|
}
|
|
destroy() {
|
|
if (this._signals)
|
|
this._signals.disconnect();
|
|
}
|
|
_onWindowResizingBegin(window, grabOp) {
|
|
if (!window || !window.assignedTile || !this._signals)
|
|
return;
|
|
const verticalSide = [false, 0];
|
|
const horizontalSide = [false, 0];
|
|
switch (grabOp) {
|
|
case Meta.GrabOp.RESIZING_N:
|
|
case Meta.GrabOp.RESIZING_NE:
|
|
case Meta.GrabOp.RESIZING_NW:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_N:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_NE:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_NW:
|
|
verticalSide[0] = true;
|
|
verticalSide[1] = St.Side.TOP;
|
|
break;
|
|
case Meta.GrabOp.RESIZING_S:
|
|
case Meta.GrabOp.RESIZING_SE:
|
|
case Meta.GrabOp.RESIZING_SW:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_S:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_SE:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_SW:
|
|
verticalSide[0] = true;
|
|
verticalSide[1] = St.Side.BOTTOM;
|
|
break;
|
|
}
|
|
switch (grabOp) {
|
|
case Meta.GrabOp.RESIZING_E:
|
|
case Meta.GrabOp.RESIZING_NE:
|
|
case Meta.GrabOp.RESIZING_SE:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_E:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_NE:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_SE:
|
|
horizontalSide[0] = true;
|
|
horizontalSide[1] = St.Side.RIGHT;
|
|
break;
|
|
case Meta.GrabOp.RESIZING_W:
|
|
case Meta.GrabOp.RESIZING_NW:
|
|
case Meta.GrabOp.RESIZING_SW:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_W:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_NW:
|
|
case Meta.GrabOp.KEYBOARD_RESIZING_SW:
|
|
horizontalSide[0] = true;
|
|
horizontalSide[1] = St.Side.LEFT;
|
|
break;
|
|
}
|
|
if (!verticalSide[0] && !horizontalSide[0])
|
|
return;
|
|
const otherTiledWindows = getWindows().filter(
|
|
(otherWindow) => otherWindow && otherWindow.assignedTile && otherWindow !== window && !otherWindow.minimized
|
|
);
|
|
if (otherTiledWindows.length === 0)
|
|
return;
|
|
const verticalAdjacentWindows = verticalSide[0] ? this._findAdjacent(
|
|
window,
|
|
verticalSide[1],
|
|
new Set(otherTiledWindows)
|
|
) : [];
|
|
const horizontalAdjacentWindows = horizontalSide[0] ? this._findAdjacent(
|
|
window,
|
|
horizontalSide[1],
|
|
new Set(otherTiledWindows)
|
|
) : [];
|
|
const windowsMap = /* @__PURE__ */ new Map();
|
|
verticalAdjacentWindows.forEach(([otherWin, sideOtherWin]) => {
|
|
windowsMap.set(otherWin, [
|
|
otherWin,
|
|
otherWin.get_frame_rect().copy(),
|
|
sideOtherWin,
|
|
// resize vertically
|
|
-1
|
|
// resize horizontally
|
|
]);
|
|
});
|
|
horizontalAdjacentWindows.forEach(([otherWin, sideOtherWin]) => {
|
|
const val = windowsMap.get(otherWin);
|
|
if (val) {
|
|
val[3] = sideOtherWin;
|
|
} else {
|
|
windowsMap.set(otherWin, [
|
|
otherWin,
|
|
otherWin.get_frame_rect().copy(),
|
|
-1,
|
|
// resize vertically
|
|
sideOtherWin
|
|
// resize horizontally
|
|
]);
|
|
}
|
|
});
|
|
const windowsToResize = Array.from(windowsMap.values());
|
|
this._signals.connect(
|
|
window,
|
|
"size-changed",
|
|
this._onResizingWindow.bind(
|
|
this,
|
|
window,
|
|
window.get_frame_rect().copy(),
|
|
verticalSide[1],
|
|
horizontalSide[1],
|
|
windowsToResize
|
|
)
|
|
);
|
|
}
|
|
_oppositeSide(side) {
|
|
switch (side) {
|
|
case St.Side.TOP:
|
|
return St.Side.BOTTOM;
|
|
case St.Side.BOTTOM:
|
|
return St.Side.TOP;
|
|
case St.Side.LEFT:
|
|
return St.Side.RIGHT;
|
|
case St.Side.RIGHT:
|
|
return St.Side.LEFT;
|
|
}
|
|
}
|
|
_findAdjacent(window, side, remainingWindows) {
|
|
const result = [];
|
|
const adjacentWindows = [];
|
|
const windowRect = window.get_frame_rect();
|
|
const borderRect = windowRect.copy();
|
|
const innerGaps = Settings.get_inner_gaps();
|
|
if (innerGaps.top === 0)
|
|
innerGaps.top = 2;
|
|
if (innerGaps.bottom === 0)
|
|
innerGaps.bottom = 2;
|
|
if (innerGaps.left === 0)
|
|
innerGaps.left = 2;
|
|
if (innerGaps.right === 0)
|
|
innerGaps.right = 2;
|
|
const errorFactor = innerGaps.right * 4;
|
|
switch (side) {
|
|
case St.Side.TOP:
|
|
borderRect.height = innerGaps.top + errorFactor;
|
|
borderRect.y -= innerGaps.top + errorFactor;
|
|
break;
|
|
case St.Side.BOTTOM:
|
|
borderRect.y += borderRect.height;
|
|
borderRect.height = innerGaps.bottom + errorFactor;
|
|
break;
|
|
case St.Side.LEFT:
|
|
borderRect.width = innerGaps.left + errorFactor;
|
|
borderRect.x -= innerGaps.left + errorFactor;
|
|
break;
|
|
case St.Side.RIGHT:
|
|
borderRect.x += borderRect.width;
|
|
borderRect.width = innerGaps.right + errorFactor;
|
|
break;
|
|
}
|
|
const oppositeSide = this._oppositeSide(side);
|
|
const newRemainingWindows = /* @__PURE__ */ new Set();
|
|
remainingWindows.forEach((otherWin) => {
|
|
const otherWinRect = otherWin.get_frame_rect();
|
|
let [hasIntersection, intersection] = otherWin.get_frame_rect().intersect(borderRect);
|
|
switch (side) {
|
|
case St.Side.RIGHT:
|
|
hasIntersection && (hasIntersection = intersection.x <= otherWinRect.x);
|
|
break;
|
|
case St.Side.LEFT:
|
|
hasIntersection && (hasIntersection = intersection.x + intersection.width >= otherWinRect.x + otherWinRect.width);
|
|
break;
|
|
case St.Side.BOTTOM:
|
|
hasIntersection && (hasIntersection = intersection.y <= otherWinRect.y);
|
|
break;
|
|
case St.Side.TOP:
|
|
hasIntersection && (hasIntersection = intersection.y + intersection.height >= otherWinRect.y + otherWinRect.height);
|
|
break;
|
|
}
|
|
if (hasIntersection) {
|
|
result.push([otherWin, oppositeSide]);
|
|
adjacentWindows.push(otherWin);
|
|
} else {
|
|
newRemainingWindows.add(otherWin);
|
|
}
|
|
});
|
|
adjacentWindows.forEach((otherWin) => {
|
|
this._findAdjacent(
|
|
otherWin,
|
|
oppositeSide,
|
|
newRemainingWindows
|
|
).forEach((recursionResult) => {
|
|
result.push(recursionResult);
|
|
newRemainingWindows.delete(recursionResult[0]);
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
_onWindowResizingEnd(window) {
|
|
if (this._signals)
|
|
this._signals.disconnect(window);
|
|
}
|
|
_onResizingWindow(window, startingRect, resizeVerticalSide, resizeHorizontalSide, windowsToResize) {
|
|
const currentRect = window.get_frame_rect();
|
|
const resizedRect = {
|
|
x: currentRect.x - startingRect.x,
|
|
y: currentRect.y - startingRect.y,
|
|
width: currentRect.width - startingRect.width,
|
|
height: currentRect.height - startingRect.height
|
|
};
|
|
windowsToResize.forEach(
|
|
([otherWindow, otherWindowRect, verticalSide, horizontalSide]) => {
|
|
const isSameVerticalSide = verticalSide !== -1 && verticalSide === resizeVerticalSide;
|
|
const isSameHorizontalSide = horizontalSide !== -1 && horizontalSide === resizeHorizontalSide;
|
|
const rect = [
|
|
otherWindowRect.x,
|
|
otherWindowRect.y,
|
|
otherWindowRect.width,
|
|
otherWindowRect.height
|
|
];
|
|
if (horizontalSide === St.Side.LEFT) {
|
|
rect[2] = otherWindowRect.width - (isSameHorizontalSide ? resizedRect.x : resizedRect.width);
|
|
rect[0] = otherWindowRect.x + (isSameHorizontalSide ? resizedRect.x : resizedRect.width);
|
|
} else if (horizontalSide === St.Side.RIGHT) {
|
|
rect[2] = otherWindowRect.width + (isSameHorizontalSide ? resizedRect.width : resizedRect.x);
|
|
}
|
|
if (verticalSide === St.Side.TOP) {
|
|
rect[3] = otherWindowRect.height - (isSameVerticalSide ? resizedRect.y : resizedRect.height);
|
|
rect[1] = otherWindowRect.y + (isSameVerticalSide ? resizedRect.y : resizedRect.height);
|
|
} else if (verticalSide === St.Side.BOTTOM) {
|
|
rect[3] = otherWindowRect.height + (isSameVerticalSide ? resizedRect.height : resizedRect.y);
|
|
}
|
|
otherWindow.move_resize_frame(
|
|
false,
|
|
Math.max(0, rect[0]),
|
|
Math.max(0, rect[1]),
|
|
Math.max(0, rect[2]),
|
|
Math.max(0, rect[3])
|
|
);
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
// src/components/snapassist/snapAssistTileButton.ts
|
|
var SnapAssistTileButton = class extends SnapAssistTile {
|
|
_btn;
|
|
constructor(params) {
|
|
super(params);
|
|
this._btn = new St.Button({
|
|
xExpand: true,
|
|
yExpand: true,
|
|
trackHover: true
|
|
});
|
|
this.add_child(this._btn);
|
|
this._btn.set_size(this.innerWidth, this.innerHeight);
|
|
this._btn.connect(
|
|
"notify::hover",
|
|
() => this.set_hover(this._btn.hover)
|
|
);
|
|
}
|
|
get tile() {
|
|
return this._tile;
|
|
}
|
|
get checked() {
|
|
return this._btn.checked;
|
|
}
|
|
set_checked(newVal) {
|
|
this._btn.set_checked(newVal);
|
|
}
|
|
connect(signal, callback) {
|
|
if (signal === "clicked")
|
|
return this._btn.connect(signal, callback);
|
|
return super.connect(signal, callback);
|
|
}
|
|
};
|
|
SnapAssistTileButton = __decorateClass([
|
|
registerGObjectClass
|
|
], SnapAssistTileButton);
|
|
|
|
// src/components/window_menu/layoutTileButtons.ts
|
|
var LayoutTileButtons = class extends LayoutWidget {
|
|
constructor(parent, layout, gapSize, height, width) {
|
|
super({
|
|
parent,
|
|
layout,
|
|
containerRect: buildRectangle(),
|
|
innerGaps: buildMarginOf(gapSize),
|
|
outerGaps: buildMarginOf(gapSize),
|
|
styleClass: "window-menu-layout"
|
|
});
|
|
const [, scalingFactor] = getScalingFactorOf(this);
|
|
this.relayout({
|
|
containerRect: buildRectangle({
|
|
x: 0,
|
|
y: 0,
|
|
width: width * scalingFactor,
|
|
height: height * scalingFactor
|
|
})
|
|
});
|
|
this._fixFloatingPointErrors();
|
|
}
|
|
buildTile(parent, rect, gaps, tile) {
|
|
return new SnapAssistTileButton({ parent, rect, gaps, tile });
|
|
}
|
|
get buttons() {
|
|
return this._previews;
|
|
}
|
|
_fixFloatingPointErrors() {
|
|
const xMap = /* @__PURE__ */ new Map();
|
|
const yMap = /* @__PURE__ */ new Map();
|
|
this._previews.forEach((prev) => {
|
|
const tile = prev.tile;
|
|
const newX = xMap.get(tile.x);
|
|
if (!newX)
|
|
xMap.set(tile.x, prev.rect.x);
|
|
const newY = yMap.get(tile.y);
|
|
if (!newY)
|
|
yMap.set(tile.y, prev.rect.y);
|
|
if (newX || newY) {
|
|
prev.open(
|
|
false,
|
|
buildRectangle({
|
|
x: newX ?? prev.rect.x,
|
|
y: newY ?? prev.rect.y,
|
|
width: prev.rect.width,
|
|
height: prev.rect.height
|
|
})
|
|
);
|
|
}
|
|
xMap.set(
|
|
tile.x + tile.width,
|
|
xMap.get(tile.x + tile.width) ?? prev.rect.x + prev.rect.width
|
|
);
|
|
yMap.set(
|
|
tile.y + tile.height,
|
|
yMap.get(tile.y + tile.height) ?? prev.rect.y + prev.rect.height
|
|
);
|
|
});
|
|
}
|
|
};
|
|
LayoutTileButtons = __decorateClass([
|
|
registerGObjectClass
|
|
], LayoutTileButtons);
|
|
|
|
// src/components/window_menu/layoutIcon.ts
|
|
var LayoutIcon = class extends LayoutWidget {
|
|
constructor(parent, importantTiles, tiles, innerGaps, outerGaps, width, height) {
|
|
super({
|
|
parent,
|
|
layout: new Layout(tiles, ""),
|
|
innerGaps: innerGaps.copy(),
|
|
outerGaps: outerGaps.copy(),
|
|
containerRect: buildRectangle(),
|
|
styleClass: "layout-icon button"
|
|
});
|
|
const [, scalingFactor] = getScalingFactorOf(this);
|
|
width *= scalingFactor;
|
|
height *= scalingFactor;
|
|
super.relayout({
|
|
containerRect: buildRectangle({ x: 0, y: 0, width, height })
|
|
});
|
|
this.set_size(width, height);
|
|
this.set_x_expand(false);
|
|
this.set_y_expand(false);
|
|
importantTiles.forEach((t) => {
|
|
const preview = this._previews.find(
|
|
(snap) => snap.tile.x === t.x && snap.tile.y === t.y
|
|
);
|
|
if (preview)
|
|
preview.add_style_class_name("important");
|
|
});
|
|
}
|
|
buildTile(parent, rect, gaps, tile) {
|
|
return new SnapAssistTile({ parent, rect, gaps, tile });
|
|
}
|
|
};
|
|
LayoutIcon = __decorateClass([
|
|
registerGObjectClass
|
|
], LayoutIcon);
|
|
|
|
// src/components/window_menu/overriddenWindowMenu.ts
|
|
import * as windowMenu from "resource:///org/gnome/shell/ui/windowMenu.js";
|
|
import * as PopupMenu4 from "resource:///org/gnome/shell/ui/popupMenu.js";
|
|
import * as Main10 from "resource:///org/gnome/shell/ui/main.js";
|
|
var LAYOUT_ICON_WIDTH = 46;
|
|
var LAYOUT_ICON_HEIGHT = 32;
|
|
var INNER_GAPS = 2;
|
|
function buildMenuWithLayoutIcon(title, popupMenu, importantTiles, tiles, innerGaps) {
|
|
popupMenu.add_child(
|
|
new St.Label({
|
|
text: title,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true
|
|
})
|
|
);
|
|
const layoutIcon = new LayoutIcon(
|
|
popupMenu,
|
|
importantTiles,
|
|
tiles,
|
|
buildMarginOf(innerGaps),
|
|
buildMarginOf(4),
|
|
LAYOUT_ICON_WIDTH,
|
|
LAYOUT_ICON_HEIGHT
|
|
);
|
|
layoutIcon.set_x_align(Clutter.ActorAlign.END);
|
|
}
|
|
var OverriddenWindowMenu = class extends GObject.Object {
|
|
static get() {
|
|
if (this._instance === null)
|
|
this._instance = new OverriddenWindowMenu();
|
|
return this._instance;
|
|
}
|
|
static enable() {
|
|
if (this._enabled)
|
|
return;
|
|
const owm = this.get();
|
|
OverriddenWindowMenu._old_buildMenu = windowMenu.WindowMenu.prototype._buildMenu;
|
|
windowMenu.WindowMenu.prototype._buildMenu = owm.newBuildMenu;
|
|
this._enabled = true;
|
|
}
|
|
static disable() {
|
|
if (!this._enabled)
|
|
return;
|
|
windowMenu.WindowMenu.prototype._buildMenu = OverriddenWindowMenu._old_buildMenu;
|
|
this._old_buildMenu = null;
|
|
this._enabled = false;
|
|
}
|
|
static destroy() {
|
|
this.disable();
|
|
this._instance = null;
|
|
}
|
|
// the function will be treated as a method of class WindowMenu
|
|
newBuildMenu(window) {
|
|
const oldFunction = OverriddenWindowMenu._old_buildMenu?.bind(this);
|
|
if (oldFunction)
|
|
oldFunction(window);
|
|
const layouts = GlobalState.get().layouts;
|
|
if (layouts.length === 0)
|
|
return;
|
|
const workArea = Main10.layoutManager.getWorkAreaForMonitor(
|
|
window.get_monitor()
|
|
);
|
|
const tiledWindows = getWindows().map((otherWindow) => {
|
|
return otherWindow && !otherWindow.minimized && otherWindow.assignedTile ? otherWindow : void 0;
|
|
}).filter((w) => w !== void 0);
|
|
const tiles = GlobalState.get().getSelectedLayoutOfMonitor(
|
|
window.get_monitor(),
|
|
global.workspaceManager.get_active_workspace_index()
|
|
).tiles;
|
|
const vacantTiles = tiles.filter((t) => {
|
|
const tileRect = TileUtils.apply_props(t, workArea);
|
|
return !tiledWindows.find(
|
|
(win) => tileRect.overlap(win.get_frame_rect())
|
|
);
|
|
});
|
|
const enableScaling = window.get_monitor() === Main10.layoutManager.primaryIndex;
|
|
const scalingFactor = getMonitorScalingFactor(window.get_monitor());
|
|
if (vacantTiles.length > 0) {
|
|
vacantTiles.sort((a, b) => a.x - b.x);
|
|
let bestTileIndex = 0;
|
|
let bestDistance = Math.abs(
|
|
0.5 - vacantTiles[bestTileIndex].x + vacantTiles[bestTileIndex].width / 2
|
|
);
|
|
for (let index = 1; index < vacantTiles.length; index++) {
|
|
const distance = Math.abs(
|
|
0.5 - (vacantTiles[index].x + vacantTiles[index].width / 2)
|
|
);
|
|
if (bestDistance > distance) {
|
|
bestTileIndex = index;
|
|
bestDistance = distance;
|
|
}
|
|
}
|
|
this.addMenuItem(new PopupMenu4.PopupSeparatorMenuItem());
|
|
const vacantPopupMenu = new PopupMenu4.PopupBaseMenuItem();
|
|
this.addMenuItem(vacantPopupMenu);
|
|
if (enableScaling)
|
|
enableScalingFactorSupport(vacantPopupMenu, scalingFactor);
|
|
buildMenuWithLayoutIcon(
|
|
_("Move to best tile"),
|
|
vacantPopupMenu,
|
|
[vacantTiles[bestTileIndex]],
|
|
tiles,
|
|
INNER_GAPS
|
|
);
|
|
vacantPopupMenu.connect("activate", () => {
|
|
OverriddenWindowMenu.get().emit(
|
|
"tile-clicked",
|
|
vacantTiles[bestTileIndex],
|
|
window
|
|
);
|
|
});
|
|
}
|
|
if (vacantTiles.length > 1) {
|
|
const vacantLeftPopupMenu = new PopupMenu4.PopupBaseMenuItem();
|
|
this.addMenuItem(vacantLeftPopupMenu);
|
|
if (enableScaling)
|
|
enableScalingFactorSupport(vacantLeftPopupMenu, scalingFactor);
|
|
buildMenuWithLayoutIcon(
|
|
_("Move to leftmost tile"),
|
|
vacantLeftPopupMenu,
|
|
[vacantTiles[0]],
|
|
tiles,
|
|
INNER_GAPS
|
|
);
|
|
vacantLeftPopupMenu.connect("activate", () => {
|
|
OverriddenWindowMenu.get().emit(
|
|
"tile-clicked",
|
|
vacantTiles[0],
|
|
window
|
|
);
|
|
});
|
|
const tilesFromRightToLeft = vacantTiles.slice(0).sort((a, b) => b.x === a.x ? a.y - b.y : b.x - a.x);
|
|
const vacantRightPopupMenu = new PopupMenu4.PopupBaseMenuItem();
|
|
this.addMenuItem(vacantRightPopupMenu);
|
|
if (enableScaling)
|
|
enableScalingFactorSupport(vacantRightPopupMenu, scalingFactor);
|
|
buildMenuWithLayoutIcon(
|
|
_("Move to rightmost tile"),
|
|
vacantRightPopupMenu,
|
|
[tilesFromRightToLeft[0]],
|
|
tiles,
|
|
INNER_GAPS
|
|
);
|
|
vacantRightPopupMenu.connect("activate", () => {
|
|
OverriddenWindowMenu.get().emit(
|
|
"tile-clicked",
|
|
tilesFromRightToLeft[0],
|
|
window
|
|
);
|
|
});
|
|
}
|
|
this.addMenuItem(new PopupMenu4.PopupSeparatorMenuItem());
|
|
const layoutsPopupMenu = new PopupMenu4.PopupBaseMenuItem();
|
|
this.addMenuItem(layoutsPopupMenu);
|
|
const container = new St.BoxLayout({
|
|
xAlign: Clutter.ActorAlign.START,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
yExpand: true,
|
|
style: "spacing: 16px !important"
|
|
});
|
|
setWidgetOrientation(container, true);
|
|
layoutsPopupMenu.add_child(container);
|
|
const layoutsPerRow = 4;
|
|
const rows = [];
|
|
for (let index = 0; index < layouts.length; index += layoutsPerRow) {
|
|
const box = new St.BoxLayout({
|
|
xAlign: Clutter.ActorAlign.CENTER,
|
|
yAlign: Clutter.ActorAlign.CENTER,
|
|
xExpand: true,
|
|
yExpand: true,
|
|
style: "spacing: 6px;"
|
|
});
|
|
rows.push(box);
|
|
container.add_child(box);
|
|
}
|
|
if (enableScaling)
|
|
enableScalingFactorSupport(layoutsPopupMenu, scalingFactor);
|
|
const layoutHeight = 30;
|
|
const layoutWidth = 52;
|
|
layouts.forEach((lay, ind) => {
|
|
const row = rows[Math.floor(ind / layoutsPerRow)];
|
|
const layoutWidget = new LayoutTileButtons(
|
|
row,
|
|
lay,
|
|
INNER_GAPS,
|
|
layoutHeight,
|
|
layoutWidth
|
|
);
|
|
layoutWidget.set_x_align(Clutter.ActorAlign.END);
|
|
layoutWidget.buttons.forEach((btn) => {
|
|
btn.connect("clicked", () => {
|
|
OverriddenWindowMenu.get().emit(
|
|
"tile-clicked",
|
|
btn.tile,
|
|
window
|
|
);
|
|
layoutsPopupMenu.activate(Clutter.get_current_event());
|
|
});
|
|
});
|
|
});
|
|
}
|
|
static connect(key, func) {
|
|
return this.get().connect(key, func) || -1;
|
|
}
|
|
static disconnect(id) {
|
|
this.get().disconnect(id);
|
|
}
|
|
};
|
|
__publicField(OverriddenWindowMenu, "metaInfo", {
|
|
GTypeName: "OverriddenWindowMenu",
|
|
Signals: {
|
|
"tile-clicked": {
|
|
param_types: [Tile2.$gtype, Meta.Window.$gtype]
|
|
}
|
|
}
|
|
});
|
|
__publicField(OverriddenWindowMenu, "_instance", null);
|
|
__publicField(OverriddenWindowMenu, "_old_buildMenu");
|
|
__publicField(OverriddenWindowMenu, "_enabled", false);
|
|
OverriddenWindowMenu = __decorateClass([
|
|
registerGObjectClass
|
|
], OverriddenWindowMenu);
|
|
|
|
// src/components/windowBorderManager.ts
|
|
Gio._promisify(Shell.Screenshot, "composite_to_stream");
|
|
var DEFAULT_BORDER_RADIUS = 11;
|
|
var SMART_BORDER_RADIUS_DELAY = 460;
|
|
var SMART_BORDER_RADIUS_FIRST_FRAME_DELAY = 240;
|
|
var debug13 = logger("WindowBorderManager");
|
|
var WindowBorder = class extends St.Bin {
|
|
_signals;
|
|
_window;
|
|
_windowMonitor;
|
|
_bindings;
|
|
_enableScaling;
|
|
_borderRadiusValue;
|
|
_timeout;
|
|
_delayedSmartBorderRadius;
|
|
_borderWidth;
|
|
constructor(win, enableScaling) {
|
|
super({
|
|
style_class: "window-border"
|
|
});
|
|
this._signals = new SignalHandling();
|
|
this._bindings = [];
|
|
this._borderWidth = 1;
|
|
this._window = win;
|
|
this._windowMonitor = win.get_monitor();
|
|
this._enableScaling = enableScaling;
|
|
this._delayedSmartBorderRadius = false;
|
|
const smartRadius = Settings.ENABLE_SMART_WINDOW_BORDER_RADIUS;
|
|
this._borderRadiusValue = [
|
|
DEFAULT_BORDER_RADIUS,
|
|
DEFAULT_BORDER_RADIUS,
|
|
smartRadius ? 0 : DEFAULT_BORDER_RADIUS,
|
|
smartRadius ? 0 : DEFAULT_BORDER_RADIUS
|
|
];
|
|
this.close();
|
|
global.windowGroup.add_child(this);
|
|
this.trackWindow(win, true);
|
|
this.connect("destroy", () => {
|
|
this._bindings.forEach((b) => b.unbind());
|
|
this._bindings = [];
|
|
this._signals.disconnect();
|
|
if (this._timeout)
|
|
clearTimeout(this._timeout);
|
|
this._timeout = void 0;
|
|
});
|
|
}
|
|
trackWindow(win, force = false) {
|
|
if (!force && this._window === win)
|
|
return;
|
|
this._bindings.forEach((b) => b.unbind());
|
|
this._bindings = [];
|
|
this._signals.disconnect();
|
|
this._window = win;
|
|
this.close();
|
|
const winActor = this._window.get_compositor_private();
|
|
this._bindings = [
|
|
"scale-x",
|
|
"scale-y",
|
|
"translation_x",
|
|
"translation_y"
|
|
].map(
|
|
(prop) => winActor.bind_property(
|
|
prop,
|
|
this,
|
|
prop,
|
|
GObject.BindingFlags.DEFAULT
|
|
// if winActor changes, this will change
|
|
)
|
|
);
|
|
const winRect = this._window.get_frame_rect();
|
|
this.set_position(
|
|
winRect.x - this._borderWidth,
|
|
winRect.y - this._borderWidth
|
|
);
|
|
this.set_size(
|
|
winRect.width + 2 * this._borderWidth,
|
|
winRect.height + 2 * this._borderWidth
|
|
);
|
|
if (Settings.ENABLE_SMART_WINDOW_BORDER_RADIUS) {
|
|
const cached_radius = this._window.__ts_cached_radius;
|
|
if (cached_radius) {
|
|
this._borderRadiusValue[St.Corner.TOPLEFT] = cached_radius[St.Corner.TOPLEFT];
|
|
this._borderRadiusValue[St.Corner.TOPRIGHT] = cached_radius[St.Corner.TOPRIGHT];
|
|
this._borderRadiusValue[St.Corner.BOTTOMLEFT] = cached_radius[St.Corner.BOTTOMLEFT];
|
|
this._borderRadiusValue[St.Corner.BOTTOMRIGHT] = cached_radius[St.Corner.BOTTOMRIGHT];
|
|
}
|
|
}
|
|
this.updateStyle();
|
|
const isMaximized = this._window.maximizedVertically && this._window.maximizedHorizontally;
|
|
if (this._window.is_fullscreen() || isMaximized || this._window.minimized || !winActor.visible)
|
|
this.close();
|
|
else
|
|
this.open();
|
|
this._signals.connect(global.display, "restacked", () => {
|
|
global.windowGroup.set_child_above_sibling(this, null);
|
|
});
|
|
this._signals.connect(this._window, "position-changed", () => {
|
|
if (this._window.maximizedVertically || this._window.maximizedHorizontally || this._window.minimized || this._window.is_fullscreen()) {
|
|
this.remove_all_transitions();
|
|
this.close();
|
|
return;
|
|
}
|
|
if (this._delayedSmartBorderRadius && Settings.ENABLE_SMART_WINDOW_BORDER_RADIUS) {
|
|
this._delayedSmartBorderRadius = false;
|
|
this._runComputeBorderRadiusTimeout(winActor);
|
|
}
|
|
const rect = this._window.get_frame_rect();
|
|
this.set_position(
|
|
rect.x - this._borderWidth,
|
|
rect.y - this._borderWidth
|
|
);
|
|
if (this._windowMonitor !== win.get_monitor()) {
|
|
this._windowMonitor = win.get_monitor();
|
|
this.updateStyle();
|
|
}
|
|
this.open();
|
|
});
|
|
this._signals.connect(this._window, "size-changed", () => {
|
|
if (this._window.maximizedVertically || this._window.maximizedHorizontally || this._window.minimized || this._window.is_fullscreen()) {
|
|
this.remove_all_transitions();
|
|
this.close();
|
|
return;
|
|
}
|
|
if (this._delayedSmartBorderRadius && Settings.ENABLE_SMART_WINDOW_BORDER_RADIUS) {
|
|
this._delayedSmartBorderRadius = false;
|
|
this._runComputeBorderRadiusTimeout(winActor);
|
|
}
|
|
const rect = this._window.get_frame_rect();
|
|
this.set_size(
|
|
rect.width + 2 * this._borderWidth,
|
|
rect.height + 2 * this._borderWidth
|
|
);
|
|
if (this._windowMonitor !== win.get_monitor()) {
|
|
this._windowMonitor = win.get_monitor();
|
|
this.updateStyle();
|
|
}
|
|
this.open();
|
|
});
|
|
if (Settings.ENABLE_SMART_WINDOW_BORDER_RADIUS) {
|
|
const firstFrameId = winActor.connect_after("first-frame", () => {
|
|
if (this._window.maximizedHorizontally || this._window.maximizedVertically || this._window.is_fullscreen()) {
|
|
this._delayedSmartBorderRadius = true;
|
|
return;
|
|
}
|
|
this._runComputeBorderRadiusTimeout(winActor);
|
|
winActor.disconnect(firstFrameId);
|
|
});
|
|
}
|
|
}
|
|
_runComputeBorderRadiusTimeout(winActor) {
|
|
if (this._timeout)
|
|
clearTimeout(this._timeout);
|
|
this._timeout = void 0;
|
|
this._timeout = setTimeout(() => {
|
|
this._computeBorderRadius(winActor).then(() => this.updateStyle());
|
|
if (this._timeout)
|
|
clearTimeout(this._timeout);
|
|
this._timeout = void 0;
|
|
}, SMART_BORDER_RADIUS_FIRST_FRAME_DELAY);
|
|
}
|
|
async _computeBorderRadius(winActor) {
|
|
const width = 3;
|
|
const height = winActor.metaWindow.get_frame_rect().height;
|
|
if (height <= 0)
|
|
return;
|
|
const content = winActor.paint_to_content(
|
|
buildRectangle({
|
|
x: winActor.metaWindow.get_frame_rect().x,
|
|
y: winActor.metaWindow.get_frame_rect().y,
|
|
height,
|
|
width
|
|
})
|
|
);
|
|
if (!content)
|
|
return;
|
|
const texture = content.get_texture();
|
|
const stream = Gio.MemoryOutputStream.new_resizable();
|
|
const x = 0;
|
|
const y = 0;
|
|
const pixbuf = await Shell.Screenshot.composite_to_stream(
|
|
texture,
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
1,
|
|
null,
|
|
0,
|
|
0,
|
|
1,
|
|
stream
|
|
);
|
|
const pixels = pixbuf.get_pixels();
|
|
const alphaThreshold = 240;
|
|
for (let i = 0; i < height; i++) {
|
|
if (pixels[i * width * 4 + 3] > alphaThreshold) {
|
|
this._borderRadiusValue[St.Corner.TOPLEFT] = i;
|
|
this._borderRadiusValue[St.Corner.TOPRIGHT] = this._borderRadiusValue[St.Corner.TOPLEFT];
|
|
break;
|
|
}
|
|
}
|
|
for (let i = height - 1; i >= height - this._borderRadiusValue[St.Corner.TOPLEFT] - 2; i--) {
|
|
if (pixels[i * width * 4 + 3] > alphaThreshold) {
|
|
this._borderRadiusValue[St.Corner.BOTTOMLEFT] = height - i - 1;
|
|
this._borderRadiusValue[St.Corner.BOTTOMRIGHT] = this._borderRadiusValue[St.Corner.BOTTOMLEFT];
|
|
break;
|
|
}
|
|
}
|
|
stream.close(null);
|
|
const cached_radius = [
|
|
DEFAULT_BORDER_RADIUS,
|
|
DEFAULT_BORDER_RADIUS,
|
|
0,
|
|
0
|
|
];
|
|
cached_radius[St.Corner.TOPLEFT] = this._borderRadiusValue[St.Corner.TOPLEFT];
|
|
cached_radius[St.Corner.TOPRIGHT] = this._borderRadiusValue[St.Corner.TOPRIGHT];
|
|
cached_radius[St.Corner.BOTTOMLEFT] = this._borderRadiusValue[St.Corner.BOTTOMLEFT];
|
|
cached_radius[St.Corner.BOTTOMRIGHT] = this._borderRadiusValue[St.Corner.BOTTOMRIGHT];
|
|
this._window.__ts_cached_radius = cached_radius;
|
|
}
|
|
updateStyle() {
|
|
const monitorScalingFactor = this._enableScaling ? getMonitorScalingFactor(this._window.get_monitor()) : void 0;
|
|
enableScalingFactorSupport(this, monitorScalingFactor);
|
|
const [alreadyScaled, scalingFactor] = getScalingFactorOf(this);
|
|
const borderWidth = (alreadyScaled ? 1 : scalingFactor) * (Settings.WINDOW_BORDER_WIDTH / (alreadyScaled ? scalingFactor : 1));
|
|
const radius = this._borderRadiusValue.map((val) => {
|
|
const valWithBorder = val === 0 ? val : val + borderWidth;
|
|
return (alreadyScaled ? 1 : scalingFactor) * (valWithBorder / (alreadyScaled ? scalingFactor : 1));
|
|
});
|
|
const scalingFactorSupportString = monitorScalingFactor ? `${getScalingFactorSupportString(monitorScalingFactor)};` : "";
|
|
this.set_style(
|
|
`border-color: ${Settings.WINDOW_BORDER_COLOR}; border-width: ${borderWidth}px; border-radius: ${radius[St.Corner.TOPLEFT]}px ${radius[St.Corner.TOPRIGHT]}px ${radius[St.Corner.BOTTOMRIGHT]}px ${radius[St.Corner.BOTTOMLEFT]}px; ${scalingFactorSupportString}`
|
|
);
|
|
if (this._borderWidth !== borderWidth) {
|
|
const diff = this._borderWidth - borderWidth;
|
|
this._borderWidth = borderWidth;
|
|
this.set_size(
|
|
this.get_width() - 2 * diff,
|
|
this.get_height() - 2 * diff
|
|
);
|
|
this.set_position(this.get_x() + diff, this.get_y() + diff);
|
|
}
|
|
}
|
|
open() {
|
|
if (this.visible)
|
|
return;
|
|
this.show();
|
|
this.ease({
|
|
opacity: 255,
|
|
duration: 200,
|
|
mode: Clutter.AnimationMode.EASE,
|
|
delay: 130
|
|
});
|
|
}
|
|
close() {
|
|
this.set_opacity(0);
|
|
this.hide();
|
|
}
|
|
};
|
|
WindowBorder = __decorateClass([
|
|
registerGObjectClass
|
|
], WindowBorder);
|
|
var WindowBorderManager = class {
|
|
_signals;
|
|
_border;
|
|
_enableScaling;
|
|
constructor(enableScaling) {
|
|
this._signals = new SignalHandling();
|
|
this._border = null;
|
|
this._enableScaling = enableScaling;
|
|
}
|
|
enable() {
|
|
if (Settings.ENABLE_WINDOW_BORDER)
|
|
this._turnOn();
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_ENABLE_WINDOW_BORDER,
|
|
() => {
|
|
if (Settings.ENABLE_WINDOW_BORDER)
|
|
this._turnOn();
|
|
else
|
|
this._turnOff();
|
|
}
|
|
);
|
|
}
|
|
_turnOn() {
|
|
this._onWindowFocused();
|
|
this._signals.connect(
|
|
global.display,
|
|
"notify::focus-window",
|
|
this._onWindowFocused.bind(this)
|
|
);
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_WINDOW_BORDER_COLOR,
|
|
() => this._border?.updateStyle()
|
|
);
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_WINDOW_BORDER_WIDTH,
|
|
() => this._border?.updateStyle()
|
|
);
|
|
}
|
|
_turnOff() {
|
|
this.destroy();
|
|
this.enable();
|
|
}
|
|
destroy() {
|
|
this._signals.disconnect();
|
|
this._border?.destroy();
|
|
this._border = null;
|
|
}
|
|
_onWindowFocused() {
|
|
const metaWindow = global.display.focus_window;
|
|
if (!metaWindow || metaWindow.get_wm_class() === null || metaWindow.get_wm_class() === "gjs") {
|
|
this._border?.destroy();
|
|
this._border = null;
|
|
return;
|
|
}
|
|
if (!this._border)
|
|
this._border = new WindowBorder(metaWindow, this._enableScaling);
|
|
else
|
|
this._border.trackWindow(metaWindow);
|
|
}
|
|
};
|
|
|
|
// src/extension.ts
|
|
import * as Main12 from "resource:///org/gnome/shell/ui/main.js";
|
|
var debug14 = logger("extension");
|
|
var TilingShellExtension = class extends Extension {
|
|
_indicator;
|
|
_tilingManagers;
|
|
_fractionalScalingEnabled;
|
|
_dbus;
|
|
_signals;
|
|
_keybindings;
|
|
_resizingManager;
|
|
_windowBorderManager;
|
|
constructor(metadata) {
|
|
super(metadata);
|
|
this._signals = null;
|
|
this._fractionalScalingEnabled = false;
|
|
this._tilingManagers = [];
|
|
this._indicator = null;
|
|
this._dbus = null;
|
|
this._keybindings = null;
|
|
this._resizingManager = null;
|
|
this._windowBorderManager = null;
|
|
}
|
|
createIndicator() {
|
|
this._indicator = new Indicator3(this.path, this.uuid);
|
|
this._indicator.enableScaling = !this._fractionalScalingEnabled;
|
|
this._indicator.enable();
|
|
}
|
|
_validateSettings() {
|
|
if (Settings.LAST_VERSION_NAME_INSTALLED === "14.0") {
|
|
debug14("apply compatibility changes");
|
|
Settings.save_selected_layouts([]);
|
|
}
|
|
if (this.metadata["version-name"]) {
|
|
Settings.LAST_VERSION_NAME_INSTALLED = this.metadata["version-name"] || "0";
|
|
}
|
|
}
|
|
enable() {
|
|
if (this._signals)
|
|
this._signals.disconnect();
|
|
this._signals = new SignalHandling();
|
|
Settings.initialize(this.getSettings());
|
|
this._validateSettings();
|
|
TilingShellWindowManager.get();
|
|
this._fractionalScalingEnabled = this._isFractionalScalingEnabled(
|
|
new Gio.Settings({ schema: "org.gnome.mutter" })
|
|
);
|
|
if (this._keybindings)
|
|
this._keybindings.destroy();
|
|
this._keybindings = new KeyBindings(this.getSettings());
|
|
if (Settings.ACTIVE_SCREEN_EDGES) {
|
|
SettingsOverride.get().override(
|
|
new Gio.Settings({ schema_id: "org.gnome.mutter" }),
|
|
"edge-tiling",
|
|
new GLib.Variant("b", false)
|
|
);
|
|
}
|
|
if (Main12.layoutManager._startingUp) {
|
|
this._signals.connect(
|
|
Main12.layoutManager,
|
|
"startup-complete",
|
|
() => {
|
|
this._createTilingManagers();
|
|
this._setupSignals();
|
|
}
|
|
);
|
|
} else {
|
|
this._createTilingManagers();
|
|
this._setupSignals();
|
|
}
|
|
this._resizingManager = new ResizingManager();
|
|
this._resizingManager.enable();
|
|
if (this._windowBorderManager)
|
|
this._windowBorderManager.destroy();
|
|
this._windowBorderManager = new WindowBorderManager(
|
|
!this._fractionalScalingEnabled
|
|
);
|
|
this._windowBorderManager.enable();
|
|
this.createIndicator();
|
|
if (this._dbus)
|
|
this._dbus.disable();
|
|
this._dbus = new DBus();
|
|
this._dbus.enable(this);
|
|
if (Settings.OVERRIDE_WINDOW_MENU)
|
|
OverriddenWindowMenu.enable();
|
|
debug14("extension is enabled");
|
|
}
|
|
openLayoutEditor() {
|
|
this._indicator?.openLayoutEditor();
|
|
}
|
|
_createTilingManagers() {
|
|
debug14("building a tiling manager for each monitor");
|
|
this._tilingManagers.forEach((tm) => tm.destroy());
|
|
this._tilingManagers = getMonitors().map(
|
|
(monitor) => new TilingManager(monitor, !this._fractionalScalingEnabled)
|
|
);
|
|
this._tilingManagers.forEach((tm) => tm.enable());
|
|
}
|
|
_setupSignals() {
|
|
if (!this._signals)
|
|
return;
|
|
this._signals.connect(global.display, "workareas-changed", () => {
|
|
const allMonitors = getMonitors();
|
|
if (this._tilingManagers.length !== allMonitors.length) {
|
|
GlobalState.get().validate_selected_layouts();
|
|
this._createTilingManagers();
|
|
} else {
|
|
this._tilingManagers.forEach((tm, index) => {
|
|
tm.workArea = Main12.layoutManager.getWorkAreaForMonitor(index);
|
|
});
|
|
}
|
|
});
|
|
this._signals.connect(
|
|
new Gio.Settings({ schema: "org.gnome.mutter" }),
|
|
"changed::experimental-features",
|
|
(_mutterSettings) => {
|
|
if (!_mutterSettings)
|
|
return;
|
|
const fractionalScalingEnabled = this._isFractionalScalingEnabled(_mutterSettings);
|
|
if (this._fractionalScalingEnabled === fractionalScalingEnabled)
|
|
return;
|
|
this._fractionalScalingEnabled = fractionalScalingEnabled;
|
|
this._createTilingManagers();
|
|
if (this._indicator) {
|
|
this._indicator.enableScaling = !this._fractionalScalingEnabled;
|
|
}
|
|
if (this._windowBorderManager)
|
|
this._windowBorderManager.destroy();
|
|
this._windowBorderManager = new WindowBorderManager(
|
|
this._fractionalScalingEnabled
|
|
);
|
|
this._windowBorderManager.enable();
|
|
}
|
|
);
|
|
if (this._keybindings) {
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"move-window",
|
|
(kb, dp, dir) => {
|
|
this._onKeyboardMoveWin(dp, dir, false);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"span-window",
|
|
(kb, dp, dir) => {
|
|
this._onKeyboardMoveWin(dp, dir, true);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"span-window-all-tiles",
|
|
(kb, dp) => {
|
|
const window = dp.focus_window;
|
|
const monitorIndex = window.get_monitor();
|
|
const manager = this._tilingManagers[monitorIndex];
|
|
if (manager)
|
|
manager.onSpanAllTiles(window);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"untile-window",
|
|
this._onKeyboardUntileWindow.bind(this)
|
|
);
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"move-window-center",
|
|
(kb, dp) => {
|
|
this._onKeyboardMoveWin(
|
|
dp,
|
|
1 /* NODIRECTION */,
|
|
false
|
|
);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"focus-window",
|
|
(kb, dp, dir) => {
|
|
this._onKeyboardFocusWin(dp, dir);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"focus-window-direction",
|
|
(kb, dp, dir) => {
|
|
this._onKeyboardFocusWinDirection(dp, dir);
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
this._keybindings,
|
|
"highlight-current-window",
|
|
(kb, dp) => {
|
|
const focus_window = dp.get_focus_window();
|
|
getWindows(
|
|
global.workspaceManager.get_active_workspace()
|
|
).forEach((win) => {
|
|
if (win !== focus_window && win.can_minimize())
|
|
win.minimize();
|
|
});
|
|
Main12.activateWindow(
|
|
focus_window,
|
|
global.get_current_time()
|
|
);
|
|
}
|
|
);
|
|
}
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_ACTIVE_SCREEN_EDGES,
|
|
() => {
|
|
const gioSettings = new Gio.Settings({
|
|
schema_id: "org.gnome.mutter"
|
|
});
|
|
if (Settings.ACTIVE_SCREEN_EDGES) {
|
|
debug14("disable native edge tiling");
|
|
SettingsOverride.get().override(
|
|
gioSettings,
|
|
"edge-tiling",
|
|
new GLib.Variant("b", false)
|
|
);
|
|
} else {
|
|
debug14("bring back native edge tiling");
|
|
SettingsOverride.get().restoreKey(
|
|
gioSettings,
|
|
"edge-tiling"
|
|
);
|
|
}
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
Settings,
|
|
Settings.KEY_OVERRIDE_WINDOW_MENU,
|
|
() => {
|
|
if (Settings.OVERRIDE_WINDOW_MENU)
|
|
OverriddenWindowMenu.enable();
|
|
else
|
|
OverriddenWindowMenu.disable();
|
|
}
|
|
);
|
|
this._signals.connect(
|
|
OverriddenWindowMenu,
|
|
"tile-clicked",
|
|
(_2, tile, window) => {
|
|
const monitorIndex = window.get_monitor();
|
|
const manager = this._tilingManagers[monitorIndex];
|
|
if (manager)
|
|
manager.onTileFromWindowMenu(tile, window);
|
|
}
|
|
);
|
|
}
|
|
/* todo private _moveMaximizedToWorkspace(
|
|
wm: Shell.WM,
|
|
winActor: Meta.WindowActor,
|
|
change: Meta.SizeChange,
|
|
) {
|
|
const window = winActor.metaWindow;
|
|
if (
|
|
window.wmClass === null ||
|
|
change !== Meta.SizeChange.MAXIMIZE || // handle maximize changes only
|
|
window.get_maximized() !== Meta.MaximizeFlags.BOTH || // handle maximized window only
|
|
window.is_attached_dialog() || // skip dialogs
|
|
window.is_on_all_workspaces() ||
|
|
window.windowType !== Meta.WindowType.NORMAL || // handle normal windows only
|
|
window.wmClass === 'gjs'
|
|
)
|
|
return;
|
|
|
|
const prevWorkspace = window.get_workspace();
|
|
// if it is the only window in the workspace, no new workspace is needed
|
|
if (
|
|
!prevWorkspace
|
|
.list_windows()
|
|
.find(
|
|
(otherWin) =>
|
|
otherWin !== window &&
|
|
otherWin.windowType === Meta.WindowType.NORMAL &&
|
|
!otherWin.is_always_on_all_workspaces() &&
|
|
otherWin.wmClass !== null &&
|
|
otherWin.wmClass !== 'gjs',
|
|
)
|
|
)
|
|
return;
|
|
|
|
// disable GNOME default fade out animation
|
|
// @ts-expect-error Main.wm has "_sizeChangeWindowDone" function
|
|
Main.wm._sizeChangeWindowDone(global.windowManager, winActor);
|
|
|
|
const wasActive = prevWorkspace.active;
|
|
// create a new workspace, do not focus it
|
|
const newWorkspace = global.workspace_manager.append_new_workspace(
|
|
false,
|
|
global.get_current_time(),
|
|
);
|
|
// place the workspace after the current one
|
|
global.workspace_manager.reorder_workspace(
|
|
newWorkspace,
|
|
prevWorkspace.index() + 1,
|
|
);
|
|
// queue focus the workspace, focusing the window too. This will trigger workspace slide-in animation
|
|
if (wasActive) window._queue_focus_ws = newWorkspace;
|
|
}
|
|
|
|
private _onSizeChanged(wm: Shell.WM, winActor: Meta.WindowActor) {
|
|
const window = winActor.metaWindow;
|
|
|
|
if (!window._queue_focus_ws) return;
|
|
const ws = window._queue_focus_ws;
|
|
delete window._queue_focus_ws;
|
|
|
|
console.log(`_onSizeChanged ${ws}`);
|
|
// move the window
|
|
ws.activate_with_focus(window, global.get_current_time());
|
|
window.change_workspace(ws);
|
|
// todo check the following
|
|
// If the selected window is on a different workspace, we don't
|
|
// want it to disappear, then slide in with the workspace; instead,
|
|
// always activate it on the active workspace ...
|
|
activeWs.activate_with_focus(window, global.get_current_time());
|
|
|
|
// ... then slide it over to the original workspace if necessary
|
|
Main.wm.actionMoveWindow(window, ws);
|
|
|
|
}*/
|
|
_onKeyboardMoveWin(display, direction, spanFlag) {
|
|
const focus_window = display.get_focus_window();
|
|
if (!focus_window || !focus_window.has_focus() || focus_window.get_wm_class() && focus_window.get_wm_class() === "gjs" || focus_window.is_fullscreen())
|
|
return;
|
|
if ((focus_window.maximizedHorizontally || focus_window.maximizedVertically) && spanFlag)
|
|
return;
|
|
if ((focus_window.maximizedHorizontally || focus_window.maximizedVertically) && direction === 3 /* DOWN */) {
|
|
focus_window.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
return;
|
|
}
|
|
const monitorTilingManager = this._tilingManagers[focus_window.get_monitor()];
|
|
if (!monitorTilingManager)
|
|
return;
|
|
if (Settings.ENABLE_AUTO_TILING && (focus_window.maximizedHorizontally || focus_window.maximizedVertically)) {
|
|
focus_window.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
return;
|
|
}
|
|
let displayDirection = Meta.DisplayDirection.DOWN;
|
|
switch (direction) {
|
|
case 4 /* LEFT */:
|
|
displayDirection = Meta.DisplayDirection.LEFT;
|
|
break;
|
|
case 5 /* RIGHT */:
|
|
displayDirection = Meta.DisplayDirection.RIGHT;
|
|
break;
|
|
case 2 /* UP */:
|
|
displayDirection = Meta.DisplayDirection.UP;
|
|
break;
|
|
}
|
|
const neighborMonitorIndex = display.get_monitor_neighbor_index(
|
|
focus_window.get_monitor(),
|
|
displayDirection
|
|
);
|
|
const success = monitorTilingManager.onKeyboardMoveWindow(
|
|
focus_window,
|
|
direction,
|
|
false,
|
|
spanFlag,
|
|
neighborMonitorIndex === -1
|
|
// clamp if there is NOT a monitor in this direction
|
|
);
|
|
if (success || direction === 1 /* NODIRECTION */ || neighborMonitorIndex === -1)
|
|
return;
|
|
if ((focus_window.maximizedHorizontally || focus_window.maximizedVertically) && direction === 2 /* UP */) {
|
|
Main12.wm.skipNextEffect(focus_window.get_compositor_private());
|
|
focus_window.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
focus_window.assignedTile = void 0;
|
|
}
|
|
const neighborTilingManager = this._tilingManagers[neighborMonitorIndex];
|
|
if (!neighborTilingManager)
|
|
return;
|
|
neighborTilingManager.onKeyboardMoveWindow(
|
|
focus_window,
|
|
direction,
|
|
true,
|
|
spanFlag,
|
|
false
|
|
);
|
|
}
|
|
_onKeyboardFocusWinDirection(display, direction) {
|
|
const focus_window = display.get_focus_window();
|
|
if (!focus_window || !focus_window.has_focus() || focus_window.get_wm_class() && focus_window.get_wm_class() === "gjs")
|
|
return;
|
|
let bestWindow;
|
|
let bestWindowDistance = -1;
|
|
const focusWindowRect = focus_window.get_frame_rect();
|
|
const focusWindowCenter = {
|
|
x: focusWindowRect.x + focusWindowRect.width / 2,
|
|
y: focusWindowRect.y + focusWindowRect.height / 2
|
|
};
|
|
const windowList = filterUnfocusableWindows(
|
|
focus_window.get_workspace().list_windows()
|
|
);
|
|
windowList.filter((win) => {
|
|
if (win === focus_window || win.minimized)
|
|
return false;
|
|
const winRect = win.get_frame_rect();
|
|
switch (direction) {
|
|
case 5 /* RIGHT */:
|
|
return winRect.x > focusWindowRect.x;
|
|
case 4 /* LEFT */:
|
|
return winRect.x < focusWindowRect.x;
|
|
case 2 /* UP */:
|
|
return winRect.y < focusWindowRect.y;
|
|
case 3 /* DOWN */:
|
|
return winRect.y > focusWindowRect.y;
|
|
}
|
|
return false;
|
|
}).forEach((win) => {
|
|
const winRect = win.get_frame_rect();
|
|
const winCenter = {
|
|
x: winRect.x + winRect.width / 2,
|
|
y: winRect.y + winRect.height / 2
|
|
};
|
|
const euclideanDistance = squaredEuclideanDistance(
|
|
winCenter,
|
|
focusWindowCenter
|
|
);
|
|
if (!bestWindow || euclideanDistance < bestWindowDistance || euclideanDistance === bestWindowDistance && bestWindow.get_frame_rect().y > winRect.y) {
|
|
bestWindow = win;
|
|
bestWindowDistance = euclideanDistance;
|
|
}
|
|
});
|
|
if (!bestWindow)
|
|
return;
|
|
bestWindow.activate(global.get_current_time());
|
|
}
|
|
_onKeyboardFocusWin(display, direction) {
|
|
const focus_window = display.get_focus_window();
|
|
if (!focus_window || !focus_window.has_focus() || focus_window.get_wm_class() && focus_window.get_wm_class() === "gjs")
|
|
return;
|
|
const windowList = filterUnfocusableWindows(
|
|
focus_window.get_workspace().list_windows()
|
|
);
|
|
const focusParent = focus_window.get_transient_for() || focus_window;
|
|
const focusedIdx = windowList.findIndex((win) => {
|
|
return win === focusParent;
|
|
});
|
|
let nextIndex = -1;
|
|
switch (direction) {
|
|
case 2 /* PREV */:
|
|
if (focusedIdx === 0 && Settings.WRAPAROUND_FOCUS) {
|
|
windowList[windowList.length - 1].activate(
|
|
global.get_current_time()
|
|
);
|
|
} else {
|
|
windowList[focusedIdx - 1].activate(
|
|
global.get_current_time()
|
|
);
|
|
}
|
|
break;
|
|
case 1 /* NEXT */:
|
|
nextIndex = (focusedIdx + 1) % windowList.length;
|
|
if (nextIndex > 0 || Settings.WRAPAROUND_FOCUS)
|
|
windowList[nextIndex].activate(global.get_current_time());
|
|
break;
|
|
}
|
|
}
|
|
_onKeyboardUntileWindow(kb, display) {
|
|
const focus_window = display.get_focus_window();
|
|
if (!focus_window || !focus_window.has_focus() || focus_window.windowType !== Meta.WindowType.NORMAL || focus_window.get_wm_class() && focus_window.get_wm_class() === "gjs")
|
|
return;
|
|
if (focus_window.get_maximized())
|
|
focus_window.unmaximize(Meta.MaximizeFlags.BOTH);
|
|
const monitorTilingManager = this._tilingManagers[focus_window.get_monitor()];
|
|
if (!monitorTilingManager)
|
|
return;
|
|
monitorTilingManager.onUntileWindow(focus_window, true);
|
|
}
|
|
_isFractionalScalingEnabled(_mutterSettings) {
|
|
return _mutterSettings.get_strv("experimental-features").find(
|
|
(feat) => feat === "scale-monitor-framebuffer" || feat === "x11-randr-fractional-scaling"
|
|
) !== void 0;
|
|
}
|
|
disable() {
|
|
this._keybindings?.destroy();
|
|
this._keybindings = null;
|
|
this._indicator?.destroy();
|
|
this._indicator = null;
|
|
this._tilingManagers.forEach((tm) => tm.destroy());
|
|
this._tilingManagers = [];
|
|
this._signals?.disconnect();
|
|
this._signals = null;
|
|
this._resizingManager?.destroy();
|
|
this._resizingManager = null;
|
|
this._windowBorderManager?.destroy();
|
|
this._windowBorderManager = null;
|
|
this._dbus?.disable();
|
|
this._dbus = null;
|
|
this._fractionalScalingEnabled = false;
|
|
OverriddenWindowMenu.destroy();
|
|
SettingsOverride.destroy();
|
|
GlobalState.destroy();
|
|
Settings.destroy();
|
|
TilingShellWindowManager.destroy();
|
|
debug14("extension is disabled");
|
|
}
|
|
};
|
|
export {
|
|
TilingShellExtension as default
|
|
};
|