GnomeSnapExtensions / gSnap

Gnome-shell extension that allows you to move windows into specific regions similiar to FancyZones on windows.
https://extensions.gnome.org/extension/4442/gsnap/
Other
166 stars 23 forks source link

Feature Request: Allow windows to span multiple zones #33

Closed apfelkuchen6 closed 1 year ago

apfelkuchen6 commented 2 years ago

I have multiple layouts that are refinements of each other (meaning that every zone of one layout is a union of zones of the other layout). I would like to replace these layouts with the finer one of them and have a way to strech windows over multiple zones.

I think the following userinterface makes sense: While previewing the snap (i.e. when the user presses Super, Shift and clicks the mouse) 'select' the zone the cursor currently is over when another key (let's say Shift) is pressed (or held). Color the selected zone in another color. When the cursor is moved to another zone, the selected zone remains highlighted. Then other zones can be selected the same way. When the user releases the mouse button, the window is resized to the smallest rectangle covering all the selected zones (maybe also draw the outline of this rectangle in the preview).

meronz commented 2 years ago

Yeah I'm actually working on this one, but it started as an experiment and is not yet ready to be merged.

https://github.com/meronz/gSnap/tree/adjacent-zones-snap

MrCaira commented 2 years ago

I second this and really really would love for this to happen. I've never used as nice a snapping tool as FancyZones and your extension gets it 98% there :) Can't wait to see this live!

auipga commented 2 years ago

Hi @meronz, do you think you'll continue to work on this feature within this year? I'm really looking forward to use this Fancyzone template from David Zhang's video. I tried your experiment but I couldn't get it to run. Would you try this and see whether it works as shown in the video?

layout definition (adapted from David Zhang) ```js { "name": "4k", "type": 1, "length": 100, "items": [ { "type": 0, "length": 15, "items": [ { "type": 0, "length": 30, "items": [] }, { "type": 0, "length": 40, "items": [] }, { "type": 0, "length": 30, "items": [] } ] }, { "type": 0, "length": 10, "items": [ { "type": 0, "length": 10, "items": [] }, { "type": 1, "length": 80, "items": [ { "type": 0, "length": 60, "items": [] }, { "type": 0, "length": 40, "items": [ { "type": 0, "length": 10, "items": [] }, { "type": 0, "length": 80, "items": [] }, { "type": 0, "length": 10, "items": [] } ] } ] }, { "type": 0, "length": 10, "items": [] } ] }, { "type": 0, "length": 75, "items": [ { "type": 0, "length": 15, "items": [] }, { "type": 0, "length": 15, "items": [] }, { "type": 0, "length": 20, "items": [] }, { "type": 0, "length": 20, "items": [] }, { "type": 0, "length": 15, "items": [] }, { "type": 0, "length": 15, "items": [] } ] } ] } ```
layout image ![image](https://i.imgur.com/ePBP2ax_d.webp?maxwidth=9999&shape=thumb&fidelity=medium)
meronz commented 2 years ago

I don't know. I'm currently really busy and I don't really have time to develop new features. I could still manage to do code reviews and help people implement the features they need, so if you want to try feel free to ask!

auipga commented 1 year ago

This is a very early version with "Shift to add all hovered zones"

Copy this into your extension.js ```js 'use strict'; /* Logging * Written by Sergey */ let debug = false; /** * If called with a false argument, log statements are suppressed. */ function setLoggingEnabled(enabled) { debug = enabled; } /** * Log logs the given message using the gnome shell logger (global.log) if the * debug variable is set to true. * * Debug messages may be viewed using the bash command `journalctl * /usr/bin/gnome-shell` and grepping the results for 'gSnap'. */ function log(message) { if (debug) { global.log("gSnap " + message); } } /* Determine if gnome-shell version newer than required * Written by Sergey */ function getConfig() { return imports.misc.config; } const VERSION_34 = { major: 3, minor: 34 }; const VERSION_36 = { major: 3, minor: 36 }; /** * ShellVersion is used to parse the version string */ class ShellVersion { constructor(version) { const parts = version.split('.').map((part) => Number(part)); if (parts.length < 2) { throw new Error(`invalid version supplied: ${version}`); } this.major = parts[0]; this.minor = parts[1]; // Tolerate "40.alpha.1" for example. See https://github.com/gSnap/gSnap/issues/187. if (isNaN(this.minor)) { this.minor = 0; } if (isNaN(this.major)) { throw new Error(`invalid version supplied: ${JSON.stringify(version)}; got major = ${this.major}, minor = ${this.minor}`); } this.rawVersion = version; } static defaultVersion() { return ShellVersion.parse(getConfig().PACKAGE_VERSION); } static parse(version) { return new ShellVersion(version); } version_at_least_34() { return versionGreaterThanOrEqualTo(this, VERSION_34); } version_at_least_36() { return versionGreaterThanOrEqualTo(this, VERSION_36); } print_version() { log("Init gnome-shell version " + this.rawVersion + " major " + this.major + " minor " + this.minor); } } /** * Returns true if a is >= b. */ function versionGreaterThanOrEqualTo(a, b) { return a.major > b.major || (a.major === b.major && a.minor >= b.minor); } // Library imports const Main$3 = imports.ui.main; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; // Extension imports imports.misc.extensionUtils.getCurrentExtension(); const ExtensionUtils$2 = imports.misc.extensionUtils; function bind(keyBindings) { // Globals let settings = ExtensionUtils$2.getSettings(); log("Binding keys"); keyBindings.forEach((callback, key) => { //const key = keyString as KeyBindingSettingName; if (Main$3.wm.addKeybinding && Shell.ActionMode) { // introduced in 3.16 Main$3.wm.addKeybinding(key, settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL, callback); } else if (Main$3.wm.addKeybinding && Shell.KeyBindingMode) { // introduced in 3.7.5 Main$3.wm.addKeybinding(key, settings, Meta.KeyBindingFlags.NONE, Shell.KeyBindingMode.NORMAL | Shell.KeyBindingMode.MESSAGE_TRAY, callback); } else { global.display.add_keybinding(key, settings, Meta.KeyBindingFlags.NONE, callback); } }); } function unbind(keyBindings) { log("Unbinding keys"); for (let key of keyBindings.keys()) { if (Main$3.wm.removeKeybinding) { // introduced in 3.7.2 Main$3.wm.removeKeybinding(key); } else { global.display.remove_keybinding(key); } } } /** * @fileoverview This file contains incomplete typings for gnome shell types. * * Probably the best source of definitive API documentation is here: * https://gjs-docs.gnome.org/ * * However, there are also some ways the GJS works that make the API docs above * slightly incomplete. * https://wiki.gnome.org/Projects/GnomeShell/Extensions/StepByStepTutorial * mentions that constructors can take a property map as an argument. This file * does not correctly type the constructors for these types. */ /** * From https://gjs-docs.gnome.org/meta4~4_api/meta.frametype. */ var FrameType; (function (FrameType) { FrameType[FrameType["NORMAL"] = 0] = "NORMAL"; FrameType[FrameType["DIALOG"] = 1] = "DIALOG"; FrameType[FrameType["MODAL_DIALOG"] = 2] = "MODAL_DIALOG"; FrameType[FrameType["UTILITY"] = 3] = "UTILITY"; FrameType[FrameType["MENU"] = 4] = "MENU"; FrameType[FrameType["BORDER"] = 5] = "BORDER"; FrameType[FrameType["ATTACHED"] = 6] = "ATTACHED"; FrameType[FrameType["LAST"] = 7] = "LAST"; })(FrameType || (FrameType = {})); var WindowType; (function (WindowType) { WindowType[WindowType["NORMAL"] = 0] = "NORMAL"; WindowType[WindowType["DESKTOP"] = 1] = "DESKTOP"; WindowType[WindowType["DOCK"] = 2] = "DOCK"; WindowType[WindowType["DIALOG"] = 3] = "DIALOG"; WindowType[WindowType["MODAL_DIALOG"] = 4] = "MODAL_DIALOG"; WindowType[WindowType["TOOLBAR"] = 5] = "TOOLBAR"; WindowType[WindowType["MENU"] = 6] = "MENU"; WindowType[WindowType["UTILITY"] = 7] = "UTILITY"; WindowType[WindowType["SPLASHSCREEN"] = 8] = "SPLASHSCREEN"; WindowType[WindowType["DROPDOWN_MENU"] = 9] = "DROPDOWN_MENU"; WindowType[WindowType["POPUP_MENU"] = 10] = "POPUP_MENU"; WindowType[WindowType["TOOLTIP"] = 11] = "TOOLTIP"; WindowType[WindowType["NOTIFICATION"] = 12] = "NOTIFICATION"; WindowType[WindowType["COMBO"] = 13] = "COMBO"; WindowType[WindowType["DND"] = 14] = "DND"; WindowType[WindowType["OVERRIDE_OTHER"] = 15] = "OVERRIDE_OTHER"; })(WindowType || (WindowType = {})); var MaximizeFlags; (function (MaximizeFlags) { MaximizeFlags[MaximizeFlags["HORIZONTAL"] = 1] = "HORIZONTAL"; MaximizeFlags[MaximizeFlags["VERTICAL"] = 2] = "VERTICAL"; MaximizeFlags[MaximizeFlags["BOTH"] = 3] = "BOTH"; })(MaximizeFlags || (MaximizeFlags = {})); // GENERATED CODE: DO NOT EDIT // // Run extract_settings_type_definitions instead. class ParsedSettings { constructor() { /** Put debug lines into global.log. To see, run journalctl /usr/bin/gnome-shell -f in terminal */ this["debug"] = false; /** Keyboard presets are always active (as opposed active only when tiling window is visible). */ this["global-presets"] = true; /** Bottom gap around border of screen for primary monitor */ this["insets-primary-bottom"] = 0; /** Left gap around border of screen for primary monitor */ this["insets-primary-left"] = 0; /** Right gap around border of screen for primary monitor */ this["insets-primary-right"] = 0; /** Top gap around border of screen for primary monitor */ this["insets-primary-top"] = 0; /** Bottom gap around border of screen for secondary monitor */ this["insets-secondary-bottom"] = 0; /** Left gap around border of screen for secondary monitor */ this["insets-secondary-left"] = 0; /** Right gap around border of screen for secondary monitor */ this["insets-secondary-right"] = 0; /** Top gap around border of screen for secondary monitor */ this["insets-secondary-top"] = 0; /** Enables shortcuts for moving and resizing the current window. */ this["moveresize-enabled"] = true; /** Preset resize 1. */ this["preset-resize-1"] = ['KP_1']; /** Preset resize 1. */ this["preset-resize-10"] = ['']; /** Preset resize 11. */ this["preset-resize-11"] = ['KP_1']; /** Preset resize 12. */ this["preset-resize-12"] = ['KP_2']; /** Preset resize 13. */ this["preset-resize-13"] = ['KP_3']; /** Preset resize 14. */ this["preset-resize-14"] = ['KP_4']; /** Preset resize 15. */ this["preset-resize-15"] = ['KP_5']; /** Preset resize 16. */ this["preset-resize-16"] = ['KP_6']; /** Preset resize 17. */ this["preset-resize-17"] = ['KP_7']; /** Preset resize 18. */ this["preset-resize-18"] = ['KP_8']; /** Preset resize 19. */ this["preset-resize-19"] = ['KP_9']; /** Preset resize 2. */ this["preset-resize-2"] = ['KP_2']; /** Preset resize 20. */ this["preset-resize-20"] = ['']; /** Preset resize 21. */ this["preset-resize-21"] = ['KP_1']; /** Preset resize 22. */ this["preset-resize-22"] = ['KP_2']; /** Preset resize 23. */ this["preset-resize-23"] = ['KP_3']; /** Preset resize 24. */ this["preset-resize-24"] = ['KP_4']; /** Preset resize 25. */ this["preset-resize-25"] = ['KP_5']; /** Preset resize 26. */ this["preset-resize-26"] = ['KP_6']; /** Preset resize 27. */ this["preset-resize-27"] = ['KP_7']; /** Preset resize 28. */ this["preset-resize-28"] = ['KP_8']; /** Preset resize 29. */ this["preset-resize-29"] = ['KP_9']; /** Preset resize 3. */ this["preset-resize-3"] = ['KP_3']; /** Preset resize 30. */ this["preset-resize-30"] = ['']; /** Preset resize 4. */ this["preset-resize-4"] = ['KP_4']; /** Preset resize 5. */ this["preset-resize-5"] = ['KP_5']; /** Preset resize 6. */ this["preset-resize-6"] = ['KP_6']; /** Preset resize 7. */ this["preset-resize-7"] = ['KP_7']; /** Preset resize 8. */ this["preset-resize-8"] = ['KP_8']; /** Preset resize 9. */ this["preset-resize-9"] = ['KP_9']; /** Show gSnap icon on a panel. */ this["show-icon"] = true; /** Show tabs for windows in each zone. */ this["show-tabs"] = true; /** Use shift modifier to snap windows. */ this["use-modifier"] = false; /** Gaps between windows in the middle of screen */ this["window-margin"] = 0; } } const DEBUG = "debug"; const GLOBAL_PRESETS = "global-presets"; const INSETS_PRIMARY_BOTTOM = "insets-primary-bottom"; const INSETS_PRIMARY_LEFT = "insets-primary-left"; const INSETS_PRIMARY_RIGHT = "insets-primary-right"; const INSETS_PRIMARY_TOP = "insets-primary-top"; const INSETS_SECONDARY_BOTTOM = "insets-secondary-bottom"; const INSETS_SECONDARY_LEFT = "insets-secondary-left"; const INSETS_SECONDARY_RIGHT = "insets-secondary-right"; const INSETS_SECONDARY_TOP = "insets-secondary-top"; const MOVERESIZE_ENABLED = "moveresize-enabled"; const PRESET_RESIZE_1 = "preset-resize-1"; const PRESET_RESIZE_10 = "preset-resize-10"; const PRESET_RESIZE_11 = "preset-resize-11"; const PRESET_RESIZE_12 = "preset-resize-12"; const PRESET_RESIZE_13 = "preset-resize-13"; const PRESET_RESIZE_14 = "preset-resize-14"; const PRESET_RESIZE_15 = "preset-resize-15"; const PRESET_RESIZE_16 = "preset-resize-16"; const PRESET_RESIZE_17 = "preset-resize-17"; const PRESET_RESIZE_18 = "preset-resize-18"; const PRESET_RESIZE_19 = "preset-resize-19"; const PRESET_RESIZE_2 = "preset-resize-2"; const PRESET_RESIZE_20 = "preset-resize-20"; const PRESET_RESIZE_21 = "preset-resize-21"; const PRESET_RESIZE_22 = "preset-resize-22"; const PRESET_RESIZE_23 = "preset-resize-23"; const PRESET_RESIZE_24 = "preset-resize-24"; const PRESET_RESIZE_25 = "preset-resize-25"; const PRESET_RESIZE_26 = "preset-resize-26"; const PRESET_RESIZE_27 = "preset-resize-27"; const PRESET_RESIZE_28 = "preset-resize-28"; const PRESET_RESIZE_29 = "preset-resize-29"; const PRESET_RESIZE_3 = "preset-resize-3"; const PRESET_RESIZE_30 = "preset-resize-30"; const PRESET_RESIZE_4 = "preset-resize-4"; const PRESET_RESIZE_5 = "preset-resize-5"; const PRESET_RESIZE_6 = "preset-resize-6"; const PRESET_RESIZE_7 = "preset-resize-7"; const PRESET_RESIZE_8 = "preset-resize-8"; const PRESET_RESIZE_9 = "preset-resize-9"; const SHOW_ICON = "show-icon"; const SHOW_TABS = "show-tabs"; const USE_MODIFIER = "use-modifier"; const WINDOW_MARGIN = "window-margin"; const ExtensionUtils$1 = imports.misc.extensionUtils; const gridSettings = new ParsedSettings(); let settings; let finalWidget; let settingsConnection = null; function getBoolSetting(settingName) { const value = settings.get_boolean(settingName); if (value === undefined) { log("Undefined settings " + settingName); return false; } return value; } function getIntSetting(settingsValue) { let iss = settings.get_int(settingsValue); if (iss === undefined) { log("Undefined settings " + settingsValue); return 0; } else { return iss; } } function initSettings(changed_settings) { settings = ExtensionUtils$1.getSettings(); settingsConnection = settings.connect('changed', changed_settings); setLoggingEnabled(getBoolSetting(DEBUG)); log("Init settings"); gridSettings[SHOW_ICON] = getBoolSetting(SHOW_ICON); gridSettings[SHOW_TABS] = getBoolSetting(SHOW_TABS); gridSettings[WINDOW_MARGIN] = getIntSetting(WINDOW_MARGIN); log("Init complete"); } function deinitSettings() { settings.disconnect(settingsConnection); } var Side; (function (Side) { Side["Top"] = "TOP"; Side["Right"] = "RIGHT"; Side["Bottom"] = "BOTTOM"; Side["Left"] = "LEFT"; })(Side || (Side = {})); const Main$2 = imports.ui.main; // Getter for accesing "get_active_workspace" on GNOME <=2.28 and >= 2.30 const WorkspaceManager$1 = (global.screen || global.workspace_manager); function areEqual(a, b) { return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; } function getMonitorTier(monitor) { return isPrimaryMonitor(monitor) ? 'primary' : 'secondary'; } function getMonitorInsets(tier) { switch (tier) { case 'primary': return { top: getIntSetting(INSETS_PRIMARY_TOP), bottom: getIntSetting(INSETS_PRIMARY_BOTTOM), left: getIntSetting(INSETS_PRIMARY_LEFT), right: getIntSetting(INSETS_PRIMARY_RIGHT) }; // Insets on primary monitor case 'secondary': return { top: getIntSetting(INSETS_SECONDARY_TOP), bottom: getIntSetting(INSETS_SECONDARY_BOTTOM), left: getIntSetting(INSETS_SECONDARY_LEFT), right: getIntSetting(INSETS_SECONDARY_RIGHT) }; default: throw new Error(`unknown monitor name ${JSON.stringify(tier)}`); } } function getWindowsOfMonitor(monitor) { let monitors = activeMonitors(); const useModifier = getBoolSetting(USE_MODIFIER); const trackedWindows = global.trackedWindows; let windows = WorkspaceManager$1 .get_active_workspace() .list_windows() .filter(w => w.get_window_type() == WindowType.NORMAL && !w.is_hidden() && (!useModifier || trackedWindows.includes(w)) && monitors[w.get_monitor()] == monitor); return windows; } function activeMonitors() { return Main$2.layoutManager.monitors; } /** * Determine if the given monitor is the primary monitor. * @param {Object} monitor The given monitor to evaluate. * @returns {boolean} True if the given monitor is the primary monitor. * */ function isPrimaryMonitor(monitor) { return Main$2.layoutManager.primaryMonitor.x == monitor.x && Main$2.layoutManager.primaryMonitor.y == monitor.y; } function getWorkAreaByMonitor(monitor) { const monitors = activeMonitors(); for (let i = 0; i < monitors.length; i++) { let mon = monitors[i]; if (mon.x == monitor.x && mon.y == monitor.y) { return getWorkArea(monitor); } } return null; } function getWorkArea(monitor) { const wkspace = WorkspaceManager$1.get_active_workspace(); const work_area = wkspace.get_work_area_for_monitor(monitor.index); const insets = getMonitorInsets(getMonitorTier(monitor)); const result = { x: work_area.x + insets.left, y: work_area.y + insets.top, width: work_area.width - insets.left - insets.right, height: work_area.height - insets.top - insets.bottom }; log(`getWorkArea m:${monitor.index}, x: ${result.x} y:${result.y} w:${result.width} h:${result.height}`); return result; } function getCurrentMonitorIndex() { return global.display.get_current_monitor(); } // Library imports const St = imports.gi.St; const Main$1 = imports.ui.main; const GLib$2 = imports.gi.GLib; const GObject$1 = imports.gi.GObject; const Clutter = imports.gi.Clutter; const ModalDialog = imports.ui.modalDialog; class ZoneBase { constructor(layoutItem, parent) { this._x = 0; this._y = 0; this._width = 0; this._height = 0; this.parent = null; this.margin = 0; this.layoutItem = layoutItem; this.parent = parent; } contains(x, y, width = 1, height = 1) { return (this.x <= x && this.y <= y && this.x + this.width >= x + width && this.y + this.height >= y + height); } get totalWidth() { return (this.margin * 2) + this.width; } get totalHeight() { return (this.margin * 2) + this.height; } get innerX() { return this.x + this.margin; } get innerY() { return this.y + this.margin; } get innerWidth() { return this.width - (this.margin * 2); } get innerHeight() { return this.height - (this.margin * 2); } get x() { return this._x; } set x(v) { if (this._x !== v) { this._x = v; this.positionChanged(); } } get y() { return this._y; } set y(v) { if (this._y !== v) { this._y = v; this.positionChanged(); } } get width() { return this._width; } set width(v) { if (this._width !== v) { this._width = v; this.sizeChanged(); } } get height() { return this._height; } set height(v) { if (this._height !== v) { this._height = v; this.sizeChanged(); } } applyPercentages() { if (this.parent) { if (this.parent.layoutItem.type == 0) { let factor = this.parent.width / this.width; this.layoutItem.length = 100 / factor; } else { let factor = this.parent.height / this.height; this.layoutItem.length = 100 / factor; } } } destroy() { } hide() { } show() { } positionChanged() { } sizeChanged() { } sizeLeft(delta) { this.x += delta; this.width -= delta; } sizeRight(delta) { this.width += delta; } sizeTop(delta) { this.y += delta; this.height -= delta; } sizeBottom(delta) { this.height += delta; } adjustWindows(windows) { } } class Zone extends ZoneBase { constructor(layoutItem, parent) { super(layoutItem, parent); this.createWidget(); Main$1.uiGroup.insert_child_above(this.widget, global.window_group); } createWidget(styleClass = 'grid-preview') { this.widget = new St.BoxLayout({ style_class: styleClass }); this.widget.visible = false; } positionChanged() { super.positionChanged(); this.widget.x = this.innerX; this.widget.y = this.innerY; } sizeChanged() { super.sizeChanged(); this.widget.height = this.innerHeight; this.widget.width = this.innerWidth; } hide() { this.widget.visible = false; this.widget.remove_style_pseudo_class('activate'); } show() { this.widget.visible = true; this.widget.add_style_pseudo_class('activate'); } set hover(hovering) { if (!this.widget) return; // this is needed to highlight windows on hover // while dragging a window in the zone hovering ? this.widget.add_style_pseudo_class('hover') : this.widget.remove_style_pseudo_class('hover'); } destroy() { this.hide(); Main$1.uiGroup.remove_actor(this.widget); } } class TabbedZone extends Zone { constructor() { super(...arguments); this.tabHeight = 50; this.tabWidth = 200; this.tabs = []; } get innerY() { if (this.tabs.length > 1) { return super.innerY + this.tabHeight; } return super.innerY; } get innerHeight() { if (this.tabs.length > 1) { return super.innerHeight - this.tabHeight; } return super.innerHeight; } createWidget(styleClass = 'grid-preview') { this.widget = new St.BoxLayout({ style_class: styleClass }); this.widget.visible = false; } layoutTabs() { } destroy() { super.destroy(); while (this.tabs.length > 0) { this.tabs[0].destroy(); } } adjustWindows(windows) { super.adjustWindows(windows); while (this.tabs.length > 0) { this.tabs[0].destroy(); } this.tabs = []; let x = this.x + this.margin; for (let i = 0; i < windows.length; i++) { let metaWindow = windows[i]; let outerRect = metaWindow.get_frame_rect(); let midX = outerRect.x + (outerRect.width / 2); let midY = outerRect.y + (outerRect.height / 2); if (this.contains(midX, midY)) { let zoneTab = new ZoneTab(this, metaWindow); zoneTab.buttonWidget.height = this.tabHeight - (this.margin * 2); zoneTab.buttonWidget.width = this.tabWidth; zoneTab.buttonWidget.x = x; zoneTab.buttonWidget.y = this.y + this.margin; zoneTab.buttonWidget.visible = true; x += zoneTab.buttonWidget.width + this.margin; } } for (let i = 0; i < windows.length; i++) { let metaWindow = windows[i]; let outerRect = metaWindow.get_frame_rect(); let midX = outerRect.x + (outerRect.width / 2); let midY = outerRect.y + (outerRect.height / 2); if (this.contains(midX, midY)) { metaWindow.move_frame(true, this.innerX, this.innerY); metaWindow.move_resize_frame(true, this.innerX, this.innerY, this.innerWidth, this.innerHeight); } } if (this.tabs.length < 2) { while (this.tabs.length > 0) { this.tabs[0].destroy(); } this.tabs = []; } log("Adjusted zone with " + this.tabs.length + " with window count " + windows.length); } } class ZoneTab { constructor(tabZone, metaWindow) { this.tabZone = tabZone; tabZone.tabs.push(this); this.window = metaWindow; this.buttonWidget = new St.Button({ style_class: 'tab-button' }); this.buttonWidget.label = metaWindow.title; this.buttonWidget.connect('button-press-event', () => { Main$1.activateWindow(this.window); }); Main$1.uiGroup.insert_child_above(this.buttonWidget, global.window_group); } destroy() { this.tabZone.tabs.splice(this.tabZone.tabs.indexOf(this), 1); this.buttonWidget.visible = false; Main$1.uiGroup.remove_child(this.buttonWidget); } } class EditableZone extends Zone { positionChanged() { super.positionChanged(); this.widget.label = this.layoutItem.length + "%"; } sizeChanged() { super.sizeChanged(); this.widget.label = this.layoutItem.length + "%"; } createWidget(styleClass = 'grid-preview') { this.widget = new St.Button({ style_class: styleClass }); this.widget.connect('button-press-event', (_actor, event) => { var _a, _b, _c; var btn = event.get_button(); if (btn == 1) { log("Splitting"); (_a = this.parent) === null || _a === void 0 ? void 0 : _a.split(this); } if (btn == 2) { (_b = this.parent) === null || _b === void 0 ? void 0 : _b.splitOtherDirection(this); } if (btn == 3) { (_c = this.parent) === null || _c === void 0 ? void 0 : _c.remove(this); } }); this.widget; } } class ZoneGroup extends ZoneBase { constructor(layoutItem, parent) { super(layoutItem, parent); this.children = []; } contains(x, y, width = 1, height = 1) { return false; } adjustWindows(windows) { super.adjustWindows(windows); for (var i = 0; i < this.children.length; i++) { this.children[i].adjustWindows(windows); } } remove(zone) { const index = this.layoutItem.items.indexOf(zone.layoutItem); if (index > -1) { if (index + 1 < this.layoutItem.items.length) { this.layoutItem.items[index + 1].length += zone.layoutItem.length; this.layoutItem.items.splice(index, 1); } else if (index - 1 > -1) { this.layoutItem.items[index - 1].length += zone.layoutItem.length; this.layoutItem.items.splice(index, 1); } if (this.layoutItem.items.length < 2) { if (this.layoutItem != this.root.layoutItem) { this.layoutItem.items = []; } } } this.root.reinit(); } splitOtherDirection(zone) { zone.layoutItem.items = []; zone.layoutItem.type = this.layoutItem.type == 1 ? 0 : 1; zone.layoutItem.items.push({ type: 0, length: 50, items: [] }); zone.layoutItem.items.push({ type: 0, length: 50, items: [] }); log(JSON.stringify(this.root.layoutItem)); this.root.reinit(); } split(zone) { let index = this.children.indexOf(zone); this.layoutItem.items.splice(index, 0, { type: 0, length: zone.layoutItem.length / 2, items: [] }); zone.layoutItem.length = zone.layoutItem.length / 2; log(JSON.stringify(this.root.layoutItem)); this.root.reinit(); } adjustLayout(root) { this.root = root; let x = this.x; let y = this.y; for (let i = 0; i < this.children.length; i++) { let child = this.children[i]; let item = child.layoutItem; let factor = this.layoutItem.type == 0 ? this.width : this.height; let length = (factor / 100) * item.length; let w = 0; let h = 0; if (this.layoutItem.type == 0) { w = length; h = this.height; } else { h = length; w = this.width; } if (child instanceof ZoneGroup) { child.layoutItem = item; child.x = x; child.y = y; child.width = w; child.height = h; child.adjustLayout(root); } else { child.x = x; child.y = y; child.width = w; child.height = h; } if (this.layoutItem.type == 0) { x += length; } else { y += length; } } } applyLayout(root) { this.destroy(); this.root = root; this.children = []; for (let i = 0; i < this.layoutItem.items.length; i++) { let item = this.layoutItem.items[i]; if (item.items && item.items.length > 0) { let z = new ZoneGroup(item, this); this.children.push(z); z.applyLayout(root); } else { let zone = root.createZone(item, this); zone.margin = root.margin; this.children.push(zone); root.zoneCreated(zone); } } root.zoneGroupCreated(this); } zoneGroupCreated(z) { } zoneCreated(zone) { } destroy() { super.destroy(); this.hide(); for (let i = 0; i < this.children.length; i++) { this.children[i].destroy(); } this.children = []; } sizeLeft(delta) { super.sizeLeft(delta); this.adjustLayout(this.root); } sizeRight(delta) { super.sizeRight(delta); this.adjustLayout(this.root); } sizeTop(delta) { super.sizeTop(delta); this.adjustLayout(this.root); } sizeBottom(delta) { super.sizeBottom(delta); this.adjustLayout(this.root); } hide() { super.destroy(); for (let i = 0; i < this.children.length; i++) { this.children[i].hide(); } } show() { super.destroy(); for (let i = 0; i < this.children.length; i++) { this.children[i].show(); } } recursiveChildren(list = []) { for (let i = 0; i < this.children.length; i++) { let item = this.children[i]; if (item instanceof ZoneGroup) { item.recursiveChildren(list); } list.push(item); } return list; } } class ZoneAnchor { constructor(zoneGroup, zoneA, zoneB, margin) { this.zoneGroup = zoneGroup; this.zoneA = zoneA; this.zoneB = zoneB; this.margin = margin; this.startX = 0; this.startY = 0; this.isMoving = false; this.motionConnection = null; this.widget = new St.Button({ style_class: 'size-button' }); this.widget.label = " = "; this.widget.visible = true; this.adjustSizes(); this.widget.connect('button-press-event', () => { let [x, y] = global.get_pointer(); this.startX = x; this.startY = y; this.isMoving = !this.isMoving; let a = this.zoneA instanceof ZoneGroup ? "zg" : "z"; let b = this.zoneB instanceof ZoneGroup ? "zg" : "z"; if (!this.isMoving) ; log("sizing " + a + ", " + b); }); this.widget.connect('button-release-event', () => { //this.isMoving = false; }); //this.widgets.push(sizeButton); Main$1.uiGroup.insert_child_above(this.widget, global.window_group); } adjustSizes() { if (this.zoneGroup.layoutItem.type == 0) { this.widget.x = this.zoneA.x + this.zoneA.width - this.margin; this.widget.y = this.zoneA.y + this.margin; this.widget.width = this.margin * 2; this.widget.height = this.zoneA.height - (this.margin * 2); } else { this.widget.y = this.zoneA.y + this.zoneA.height - this.margin; this.widget.x = this.zoneA.x + this.margin; this.widget.height = this.margin * 2; this.widget.width = this.zoneA.width - (this.margin * 2); } } x(v = null) { if (v != null) { return this.widget.x = v; } return this.widget.x; } y(v = null) { if (v != null) { return this.widget.y = v; } return this.widget.y; } width(v = null) { if (v != null) { return this.widget.width = v; } return this.widget.width; } height(v = null) { if (v != null) { return this.widget.height = v; } return this.widget.height; } hide() { this.widget.visible = false; this.widget.remove_style_pseudo_class('activate'); } show() { this.widget.visible = true; this.widget.add_style_pseudo_class('activate'); } destroy() { this.hide(); Main$1.uiGroup.remove_child(this.widget); } mouseMoved(x, y) { if (this.isMoving) { if (this.zoneGroup.layoutItem.type == 0) { let delta = x - this.startX; this.zoneA.sizeRight(delta); this.zoneB.sizeLeft(delta); this.startX = x; } else { let delta = y - this.startY; this.zoneA.sizeBottom(delta); this.zoneB.sizeTop(delta); this.startY = y; } } } } class ZoneDisplay extends ZoneGroup { constructor(monitor, layout, margin) { super(layout, null); this.monitor = monitor; this.margin = margin; this.workArea = getWorkAreaByMonitor(this.monitor); this.init(); } apply() { var c = this.recursiveChildren(); for (var i = 0; i < c.length; i++) { c[i].applyPercentages(); } } destroy() { super.destroy(); } moveWindowToWidgetAtCursor(win) { let [x, y] = global.get_pointer(); let c = this.recursiveChildren(); let f = finalWidget; f.hide(); for (let i = 0; i < c.length; i++) { c[i].hide(); if (!append && c[i].contains(x, y)) { win.move_frame(true, c[i].innerX, c[i].innerY); win.move_resize_frame(true, c[i].innerX, c[i].innerY, c[i].innerWidth, c[i].innerHeight); } } if (append) { // if (f.contains(x, y)) { f seems not to work as I want it to win.move_frame(true, f.x, f.y); win.move_resize_frame(true, f.x, f.y, f.width, f.height); // } } } createMarginItem() { } createZoneWidget() { } init() { if (!this.workArea) { log(`Could not get workArea for monitor ${this.monitor.index}`); return; } this.x = this.margin + this.workArea.x; this.y = this.margin + this.workArea.y; this.width = this.workArea.width - (this.margin * 2); this.height = this.workArea.height - (this.margin * 2); this.applyLayout(this); this.adjustLayout(this); } createZone(layout, parent) { return new Zone(layout, parent); } reinit() { let wa = getWorkAreaByMonitor(this.monitor); if (!this.workArea || !wa) { log(`Could not get workArea for monitor ${this.monitor.index}`); return; } if (!areEqual(this.workArea, wa)) { this.workArea = wa; this.init(); } else { this.applyLayout(this); this.adjustLayout(this); } } } class ZoneEditor extends ZoneDisplay { constructor(monitor, layout, margin) { super(monitor, layout, margin); this.stage = null; this.motionConnection = null; this.isMoving = false; this.anchors = []; } createZone(layout, parent) { return new EditableZone(layout, parent); } init() { if (this.anchors == null) { this.anchors = []; } super.init(); this.motionConnection = global.stage.connect("motion-event", () => { let [x, y] = global.get_pointer(); for (let i = 0; i < this.anchors.length; i++) { this.anchors[i].mouseMoved(x, y); } for (let i = 0; i < this.anchors.length; i++) { this.anchors[i].adjustSizes(); } this.apply(); }); } destroy() { global.stage.disconnect(this.motionConnection); for (let i = 0; i < this.anchors.length; i++) { this.anchors[i].destroy(); } this.anchors = []; super.destroy(); } zoneCreated(zone) { super.zoneCreated(zone); } zoneGroupCreated(z) { super.zoneGroupCreated(z); // if (this.anchors == null) { this.anchors = []; } for (let i = 1; i < z.children.length; i++) { let before = z.children[i - 1]; let after = z.children[i]; if (before == null) { log("--- --- BEFORE IS NULL --- ---" + i); log(JSON.stringify(z.children)); } if (after == null) { log("--- --- AFTER IS NULL --- ---" + i); log(JSON.stringify(z.children)); } let zoneAnchor = new ZoneAnchor(z, before, after, this.margin); this.anchors.push(zoneAnchor); } } applyLayout(root) { super.applyLayout(root); } hide() { super.hide(); for (let i = 0; i < this.anchors.length; i++) { this.anchors[i].hide(); } } show() { super.show(); for (let i = 0; i < this.anchors.length; i++) { this.anchors[i].show(); } } } class ZonePreview extends ZoneDisplay { constructor(monitor, layout, margin) { super(monitor, layout, margin); } } let append = false; // state of shift key class ZoneManager extends ZoneDisplay { constructor(monitor, layout, margin) { super(monitor, layout, margin); this.isShowing = false; this.trackCursorTimeoutId = null; } createWidgetFinal(styleClass = 'final-grid') { finalWidget = new St.BoxLayout({ style_class: styleClass }); finalWidget.visible = false; } adjustLayout(root) { super.adjustLayout(root); this.layoutWindows(); } layoutWindows() { let windows = getWindowsOfMonitor(this.monitor); for (let c = 0; c < this.children.length; c++) { let child = this.children[c]; child.adjustWindows(windows); } } highlightZonesUnderCursor() { let [x, y] = global.get_pointer(); var children = this.recursiveChildren(); let old_x1 = finalWidget.x; let old_y1 = finalWidget.y; let old_x2 = finalWidget.x + finalWidget.width; let old_y2 = finalWidget.y + finalWidget.height; for (const zone of children) { let contained = zone.contains(x, y); if (append) { // no unhover if (contained) { if (zone.hover != true) { // set the first tile as initial values of summed area if (finalWidget.x+finalWidget.y+finalWidget.width+finalWidget.height == 0) { finalWidget.x = zone.x; finalWidget.width = zone.width; finalWidget.y = zone.y; finalWidget.height = zone.height; zone.hover = true; continue; } // expanding to the left if (zone.x < old_x1) { let diff_x1 = old_x1 - zone.x; finalWidget.x = zone.x; finalWidget.width += diff_x1; } // expanding to the right if (zone.x + zone.width > old_x2) { let diff_x2 = zone.x + zone.width - old_x2; finalWidget.width += diff_x2; } // expanding to the top if (zone.y < old_y1) { let diff_y1 = old_y1 - zone.y; finalWidget.y = zone.y; finalWidget.height += diff_y1; } // expanding to the bottom if (zone.y + zone.height > old_y2) { let diff_y2 = zone.y + zone.height - old_y2; finalWidget.height += diff_y2; } // log(Math.floor(zone.x/3440*100) +" "+Math.floor(zone.width/3440*100)+" / "+Math.floor((zone.y+48)/1440*100)+" "+Math.floor((zone.height+48)/1440*100)) // log(Math.floor(finalWidget.x/3440*100) +" "+Math.floor(finalWidget.width/3440*100)+" / "+Math.floor((finalWidget.y+48)/1440*100)+" "+Math.floor((finalWidget.height+48)/1440*100)) // log("") } //finalWidget.label = this.layoutItem.length + "%"; maybe use a label too //this.enclosedChildren.push(zone); idea to remember each hovered tile //zone.add_style_class('previously-hovered'); zone.hover = true; break; } } else { zone.hover = contained; } } } show() { this.createWidgetFinal(); this.isShowing = true; super.show(); finalWidget.visible = true; this.trackCursorUpdates(); } forgetTrail() { var children = this.recursiveChildren(); for (const zone of children) { zone.hover = false; } } hide() { this.forgetTrail(); this.isShowing = false; super.hide(); this.cleanupTrackCursorUpdates(); } destroy() { super.destroy(); this.cleanupTrackCursorUpdates(); } trackCursorUpdates() { this.trackCursorTimeoutId = GLib$2.timeout_add(GLib$2.PRIORITY_DEFAULT, 25, () => { if (!this.isShowing) { return GLib$2.SOURCE_REMOVE; } this.highlightZonesUnderCursor(); return GLib$2.SOURCE_CONTINUE; }); } cleanupTrackCursorUpdates() { if (this.trackCursorTimeoutId) { GLib$2.Source.remove(this.trackCursorTimeoutId); this.trackCursorTimeoutId = null; } } } class TabbedZoneManager extends ZoneManager { constructor(monitor, layout, margin) { super(monitor, layout, margin); } createZone(layout, parent) { return new TabbedZone(layout, parent); } } class EntryDialogClass extends ModalDialog.ModalDialog { constructor(params) { super(params); log(JSON.stringify(params)); } _onClose() { try { this.onOkay(this.entry.text); } catch (e) { throw e; } } _init() { super._init({}); this.setButtons([{ label: "OK", action: () => { this.onOkay(this.entry.text); this.close(global.get_current_time()); }, key: Clutter.Escape }]); let box = new St.BoxLayout({ vertical: true }); this.contentLayout.add(box); // const MySelf = ExtensionUtils.getCurrentExtension(); // let gicon = new Gio.FileIcon({file: Gio.file_new_for_path(MySelf.path + "/icons/icon.png")}); // let icon = new St.Icon({gicon: gicon}); // box.add(icon); this.label = new St.Label({ text: "" }); box.add(this.label); box.add(this.entry = new St.Entry({ text: "" })); } } const EntryDialog = GObject$1.registerClass({ GTypeName: 'EntryDialogClass', }, EntryDialogClass); const GLib$1 = imports.gi.GLib; var MODIFIERS_ENUM; (function (MODIFIERS_ENUM) { MODIFIERS_ENUM[MODIFIERS_ENUM["SHIFT"] = 0] = "SHIFT"; MODIFIERS_ENUM[MODIFIERS_ENUM["CONTROL"] = 2] = "CONTROL"; MODIFIERS_ENUM[MODIFIERS_ENUM["ALT"] = 3] = "ALT"; MODIFIERS_ENUM[MODIFIERS_ENUM["SUPER"] = 6] = "SUPER"; })(MODIFIERS_ENUM || (MODIFIERS_ENUM = {})); const MODIFIERS = { 0: MODIFIERS_ENUM.SHIFT, 2: MODIFIERS_ENUM.CONTROL, 3: MODIFIERS_ENUM.ALT, 6: MODIFIERS_ENUM.SUPER }; class ModifiersManager { constructor() { this.connected = {}; this.modifiers = []; this.timeoutId = null; } enable() { this.timeoutId = GLib$1.timeout_add(GLib$1.PRIORITY_DEFAULT, 20, this.update.bind(this)); } destroy() { if (this.timeoutId) { GLib$1.Source.remove(this.timeoutId); this.timeoutId = null; } } isHolding(modifier) { return this.modifiers.includes(modifier); } connect(name, callback) { if (this.connected[name] === undefined) { this.connected[name] = []; } return this.connected[name].push(callback); } update() { const [x, y, m] = global.get_pointer(); if (m === undefined) { log('m === undefined'); return GLib$1.SOURCE_REMOVE; } this.state = m; if (this.state === this.previousState) { return GLib$1.SOURCE_CONTINUE; } this.modifiers = []; for (let i = 0; i < 6; i++) { if (this.state & 1 << i && MODIFIERS[i] !== undefined) { this.modifiers.push(MODIFIERS[i]); } } for (const callback of this.connected["changed"]) { try { callback(); } catch (e) { log(`error: ${e}`); } } this.previousState = this.state; return GLib$1.SOURCE_CONTINUE; } } const Gettext = imports.gettext; const _ = Gettext.gettext; /***************************************************************** This extension has been developed by micahosborne With the help of the gnome-shell community Edited by Kvis for gnome 3.8 Edited by Lundal for gnome 3.18 Edited by Sergey to add keyboard shortcuts and prefs dialog ******************************************************************/ /***************************************************************** CONST & VARS *****************************************************************/ // Library imports const Main = imports.ui.main; const GObject = imports.gi.GObject; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const GLib = imports.gi.GLib; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); // Getter for accesing "get_active_workspace" on GNOME <=2.28 and >= 2.30 const WorkspaceManager = (global.screen || global.workspace_manager); let launcher; let enabled = false; let monitorsChangedConnect = false; const trackedWindows = global.trackedWindows = []; const SHELL_VERSION = ShellVersion.defaultVersion(); // Hangouts workaround const keyBindings = new Map([]); const key_bindings_presets = new Map([ [PRESET_RESIZE_1, () => { globalApp.setLayout(0); }], [PRESET_RESIZE_2, () => { globalApp.setLayout(1); }], [PRESET_RESIZE_3, () => { globalApp.setLayout(2); }], [PRESET_RESIZE_4, () => { globalApp.setLayout(3); }], [PRESET_RESIZE_5, () => { globalApp.setLayout(4); }], [PRESET_RESIZE_6, () => { globalApp.setLayout(5); }], [PRESET_RESIZE_7, () => { globalApp.setLayout(6); }], [PRESET_RESIZE_8, () => { globalApp.setLayout(7); }], [PRESET_RESIZE_9, () => { globalApp.setLayout(8); }], [PRESET_RESIZE_10, () => { globalApp.setLayout(9); }], [PRESET_RESIZE_11, () => { globalApp.setLayout(10); }], [PRESET_RESIZE_12, () => { globalApp.setLayout(11); }], [PRESET_RESIZE_13, () => { globalApp.setLayout(12); }], [PRESET_RESIZE_14, () => { }], [PRESET_RESIZE_15, () => { }], [PRESET_RESIZE_16, () => { }], [PRESET_RESIZE_17, () => { }], [PRESET_RESIZE_18, () => { }], [PRESET_RESIZE_19, () => { }], [PRESET_RESIZE_20, () => { }], [PRESET_RESIZE_21, () => { }], [PRESET_RESIZE_22, () => { }], [PRESET_RESIZE_23, () => { }], [PRESET_RESIZE_24, () => { }], [PRESET_RESIZE_25, () => { }], [PRESET_RESIZE_26, () => { }], [PRESET_RESIZE_27, () => { }], [PRESET_RESIZE_28, () => { }], [PRESET_RESIZE_29, () => { }], [PRESET_RESIZE_30, () => { }], ]); const keyBindingGlobalResizes = new Map([]); class App { constructor() { this.isGrabbing = false; this.layoutsPath = `${Me.path}/layouts.json`; this.layoutsDefaultPath = `${Me.path}/layouts-default.json`; this.layouts = { // [workspaceindex][monitorindex] workspaces: [ [{ current: 0 }, { current: 0 }], [{ current: 0 }, { current: 0 }] ], definitions: [ { type: 0, name: "2 Column", length: 100, items: [ { type: 0, length: 50, items: [] }, { type: 0, length: 50, items: [] } ] }, ] }; const monitors = activeMonitors().length; this.editor = new Array(monitors); this.preview = new Array(monitors); this.tabManager = new Array(monitors); this.currentLayout = this.layouts.definitions[0]; this.modifiersManager = new ModifiersManager(); } setLayout(layoutIndex, monitorIndex = -1) { var _a, _b; if (this.layouts.definitions.length <= layoutIndex) { return; } this.currentLayout = this.layouts.definitions[layoutIndex]; if (this.layouts.workspaces == null) { this.layouts.workspaces = []; } if (monitorIndex === -1) { monitorIndex = getCurrentMonitorIndex(); } let workspaceIndex = WorkspaceManager.get_active_workspace().index(); this.layouts.workspaces[workspaceIndex][monitorIndex].current = layoutIndex; this.saveLayouts(); (_a = this.tabManager[monitorIndex]) === null || _a === void 0 ? void 0 : _a.destroy(); this.tabManager[monitorIndex] = null; if (gridSettings[SHOW_TABS]) { this.tabManager[monitorIndex] = new TabbedZoneManager(activeMonitors()[monitorIndex], this.currentLayout, gridSettings[WINDOW_MARGIN]); } else { this.tabManager[monitorIndex] = new ZoneManager(activeMonitors()[monitorIndex], this.currentLayout, gridSettings[WINDOW_MARGIN]); } (_b = this.tabManager[monitorIndex]) === null || _b === void 0 ? void 0 : _b.layoutWindows(); this.reloadMenu(); } showLayoutPreview(monitorIndex, layout) { var _a; (_a = this.preview[monitorIndex]) === null || _a === void 0 ? void 0 : _a.destroy(); this.preview[monitorIndex] = null; this.preview[monitorIndex] = new ZonePreview(activeMonitors()[monitorIndex], layout, gridSettings[WINDOW_MARGIN]); } hideLayoutPreview() { activeMonitors().forEach(monitor => { var _a; (_a = this.preview[monitor.index]) === null || _a === void 0 ? void 0 : _a.destroy(); this.preview[monitor.index] = null; }); } enable() { try { let [ok, contents] = GLib.file_get_contents(this.layoutsPath); if (ok) { log("Loaded contents " + contents); this.layouts = JSON.parse(contents); log(JSON.stringify(this.layouts)); if (this.refreshLayouts()) { this.saveLayouts(); } } } catch (exception) { log(JSON.stringify(exception)); let [ok, contents] = GLib.file_get_contents(this.layoutsDefaultPath); if (ok) { this.layouts = JSON.parse(contents); this.refreshLayouts(); this.saveLayouts(); } } this.setToCurrentWorkspace(); monitorsChangedConnect = Main.layoutManager.connect('monitors-changed', () => { activeMonitors().forEach(m => { var _a; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.layoutWindows(); }); this.reloadMenu(); }); const validWindow = (window) => window != null && window.get_window_type() == WindowType.NORMAL; global.display.connect('window-created', (_display, win) => { if (validWindow(win)) { activeMonitors().forEach(m => { var _a; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.layoutWindows(); }); } }); global.display.connect('in-fullscreen-changed', (_display) => { if (global.display.get_monitor_in_fullscreen(0)) { activeMonitors().forEach(m => { var _a; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.destroy(); this.tabManager[m.index] = null; }); } else { this.setToCurrentWorkspace(); } }); global.display.connect('grab-op-begin', (_display, win) => { // only start isGrabbing if is a valid window to avoid conflict // with dash-to-panel/appIcons.js:1021 where are emitting a grab-op-begin // without never emitting a grab-op-end if (!validWindow(win)) return; const useModifier = getBoolSetting(USE_MODIFIER); this.isGrabbing = true; if (useModifier && !this.modifiersManager.isHolding(MODIFIERS_ENUM.CONTROL)) return; activeMonitors().forEach(m => { var _a; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.show(); }); }); global.display.connect('grab-op-end', (_display, win) => { const useModifier = getBoolSetting(USE_MODIFIER); this.isGrabbing = false; if (!validWindow(win)) { return; } activeMonitors().forEach(m => { var _a, _b, _c; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.hide(); if (!useModifier || this.modifiersManager.isHolding(MODIFIERS_ENUM.CONTROL)) { if (!trackedWindows.includes(win)) { trackedWindows.push(win); } (_b = this.tabManager[m.index]) === null || _b === void 0 ? void 0 : _b.moveWindowToWidgetAtCursor(win); (_c = this.tabManager[m.index]) === null || _c === void 0 ? void 0 : _c.layoutWindows(); return; } if (useModifier && !this.modifiersManager.isHolding(MODIFIERS_ENUM.CONTROL) && trackedWindows.includes(win)) { trackedWindows.splice(trackedWindows.indexOf(win), 1); } }); }); if (getBoolSetting(USE_MODIFIER)) { this.modifiersManager.connect("changed", () => { if (!this.isGrabbing) { return; } if (this.modifiersManager.isHolding(MODIFIERS_ENUM.CONTROL)) { activeMonitors().forEach(m => { var _a; return (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.show(); }); } else { activeMonitors().forEach(m => { var _a; return (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.hide(); }); } }); } this.modifiersManager.connect("changed", () => { if (!this.isGrabbing) { return; } //enable/disable trail by shift key append = this.modifiersManager.isHolding(MODIFIERS_ENUM.SHIFT); }); this.restackConnection = global.display.connect('restacked', () => { activeMonitors().forEach(m => { var _a; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.layoutWindows(); }); }); this.workspaceSwitchedConnect = WorkspaceManager.connect('workspace-switched', () => { if (this.refreshLayouts()) { this.saveLayouts(); } activeMonitors().forEach(m => { var _a; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.destroy(); this.tabManager[m.index] = null; }); this.setToCurrentWorkspace(); }); this.workareasChangedConnect = global.display.connect('workareas-changed', () => { activeMonitors().forEach(m => { var _a, _b; (_a = this.tabManager[m.index]) === null || _a === void 0 ? void 0 : _a.reinit(); (_b = this.tabManager[m.index]) === null || _b === void 0 ? void 0 : _b.layoutWindows(); }); }); launcher = new GSnapStatusButton('tiling-icon'); launcher.label = "Layouts"; if (gridSettings[SHOW_ICON]) { Main.panel.addToStatusArea("GSnapStatusButton", launcher); this.reloadMenu(); } bind(keyBindings); if (gridSettings[GLOBAL_PRESETS]) { bind(key_bindings_presets); } if (gridSettings[MOVERESIZE_ENABLED]) { bind(keyBindingGlobalResizes); } this.modifiersManager.enable(); enabled = true; log("Extension enable completed"); } refreshLayouts() { let changed = false; // A workspace could have been added. Populate the layouts.workspace array let nWorkspaces = WorkspaceManager.get_n_workspaces(); log(`refreshLayouts ${this.layouts.workspaces.length} ${nWorkspaces}`); while (this.layouts.workspaces.length < nWorkspaces) { let wk = new Array(activeMonitors().length); wk.fill({ current: 0 }); this.layouts.workspaces.push(wk); changed = true; } return changed; } reloadMenu() { if (launcher == null) return; launcher.menu.removeAll(); let resetLayoutButton = new PopupMenu.PopupMenuItem(_("Reset Layout")); let editLayoutButton = new PopupMenu.PopupMenuItem(_("Edit Layout")); let saveLayoutButton = new PopupMenu.PopupMenuItem(_("Save Layout")); let cancelEditingButton = new PopupMenu.PopupMenuItem(_("Cancel Editing")); let newLayoutButton = new PopupMenu.PopupMenuItem(_("Create New Layout")); let renameLayoutButton = new PopupMenu.PopupMenuItem(_("Rename: " + this.currentLayout.name)); let currentMonitorIndex = getCurrentMonitorIndex(); if (this.editor[currentMonitorIndex] != null) { launcher.menu.addMenuItem(resetLayoutButton); launcher.menu.addMenuItem(saveLayoutButton); launcher.menu.addMenuItem(cancelEditingButton); } else { const monitorsCount = activeMonitors().length; for (let mI = 0; mI < monitorsCount; mI++) { if (monitorsCount > 1) { let monitorName = new PopupMenu.PopupSubMenuMenuItem(_(`Monitor ${mI}`)); launcher.menu.addMenuItem(monitorName); this.createLayoutMenuItems(mI).forEach(i => monitorName.menu.addMenuItem(i)); } else { this.createLayoutMenuItems(mI).forEach(i => launcher === null || launcher === void 0 ? void 0 : launcher.menu.addMenuItem(i)); } } let sep = new PopupMenu.PopupSeparatorMenuItem(); launcher.menu.addMenuItem(sep); launcher.menu.addMenuItem(editLayoutButton); launcher.menu.addMenuItem(renameLayoutButton); launcher.menu.addMenuItem(newLayoutButton); } renameLayoutButton.connect('activate', () => { let dialog = new EntryDialog({ label: "test" }); dialog.label.text = "Rename Layout " + this.currentLayout.name; dialog.entry.text = this.currentLayout.name; dialog.onOkay = (text) => { this.currentLayout.name = text; this.saveLayouts(); this.reloadMenu(); }; dialog.open(global.get_current_time()); }); newLayoutButton.connect('activate', () => { let dialog = new EntryDialog(); dialog.label.text = "Create New Layout"; dialog.onOkay = (text) => { this.layouts.definitions.push({ name: text, type: 0, length: 100, items: [ { type: 0, length: 100, items: [] } ] }); this.setLayout(this.layouts.definitions.length - 1); this.saveLayouts(); this.reloadMenu(); }; dialog.open(global.get_current_time()); }); editLayoutButton.connect('activate', () => { activeMonitors().forEach(m => { var _a; (_a = this.editor[m.index]) === null || _a === void 0 ? void 0 : _a.destroy(); this.editor[m.index] = new ZoneEditor(activeMonitors()[m.index], this.currentLayout, gridSettings[WINDOW_MARGIN]); }); var windows = WorkspaceManager.get_active_workspace().list_windows(); for (let i = 0; i < windows.length; i++) { windows[i].minimize(); } this.reloadMenu(); }); saveLayoutButton.connect('activate', () => { this.saveLayouts(); this.setToCurrentWorkspace(); this.reloadMenu(); }); resetLayoutButton.connect('activate', () => { activeMonitors().forEach(m => { let editor = this.editor[m.index]; if (editor) { editor.destroy(); editor.layoutItem = { type: 0, length: 100, items: [ { type: 0, length: 100, items: [], } ] }; editor.applyLayout(editor); this.reloadMenu(); } }); }); cancelEditingButton.connect('activate', () => { activeMonitors().forEach(m => { var _a; (_a = this.editor[m.index]) === null || _a === void 0 ? void 0 : _a.destroy(); this.editor[m.index] = null; }); var windows = WorkspaceManager.get_active_workspace().list_windows(); for (let i = 0; i < windows.length; i++) { windows[i].unminimize(); } this.reloadMenu(); }); } createLayoutMenuItems(monitorIndex) { let items = []; for (let i = 0; i < this.layouts.definitions.length; i++) { let item = new PopupMenu.PopupMenuItem(_(this.layouts.definitions[i].name == null ? "Layout " + i : this.layouts.definitions[i].name)); item.connect('activate', () => { this.setLayout(i, monitorIndex); this.hideLayoutPreview(); }); item.actor.connect('enter-event', () => { this.showLayoutPreview(monitorIndex, this.layouts.definitions[i]); }); item.actor.connect('leave-event', () => { this.hideLayoutPreview(); }); items.push(item); } return items; } saveLayouts() { activeMonitors().forEach(m => { var _a, _b; (_a = this.editor[m.index]) === null || _a === void 0 ? void 0 : _a.apply(); (_b = this.editor[m.index]) === null || _b === void 0 ? void 0 : _b.destroy(); this.editor[m.index] = null; }); GLib.file_set_contents(this.layoutsPath, JSON.stringify(this.layouts)); log(JSON.stringify(this.layouts)); var windows = WorkspaceManager.get_active_workspace().list_windows(); for (let i = 0; i < windows.length; i++) { windows[i].unminimize(); } } disable() { var _a, _b, _c; log("Extension disable begin"); enabled = false; this.modifiersManager.destroy(); (_a = this.preview) === null || _a === void 0 ? void 0 : _a.forEach(p => { p === null || p === void 0 ? void 0 : p.destroy(); p = null; }); (_b = this.editor) === null || _b === void 0 ? void 0 : _b.forEach(e => { e === null || e === void 0 ? void 0 : e.destroy(); e = null; }); (_c = this.tabManager) === null || _c === void 0 ? void 0 : _c.forEach(t => { t === null || t === void 0 ? void 0 : t.destroy(); t = null; }); if (this.workspaceSwitchedConnect) { WorkspaceManager.disconnect(this.workspaceSwitchedConnect); this.workspaceSwitchedConnect = false; } if (this.restackConnection) { global.display.disconnect(this.restackConnection); this.restackConnection = false; } if (monitorsChangedConnect) { log("Disconnecting monitors-changed"); Main.layoutManager.disconnect(monitorsChangedConnect); monitorsChangedConnect = false; } if (this.workareasChangedConnect) { global.display.disconnect(this.workareasChangedConnect); this.workareasChangedConnect = false; } unbind(keyBindings); unbind(key_bindings_presets); unbind(keyBindingGlobalResizes); launcher === null || launcher === void 0 ? void 0 : launcher.destroy(); launcher = null; } /** * onFocus is called when the global focus changes. */ onFocus() { } showMenu() { } setToCurrentWorkspace() { let currentWorkspaceIdx = WorkspaceManager.get_active_workspace().index(); activeMonitors().forEach(m => { let currentLayoutIdx = this.layouts.workspaces[currentWorkspaceIdx][m.index].current; this.setLayout(currentLayoutIdx, m.index); }); } } const globalApp = new App(); class GSnapStatusButtonClass extends PanelMenu.Button { _init(classname) { super._init(0.0, "gSnap", false); //Done by default in PanelMenuButton - Just need to override the method if (SHELL_VERSION.version_at_least_34()) { this.add_style_class_name(classname); this.connect('button-press-event', this._onButtonPress); } else { this.actor.add_style_class_name(classname); this.actor.connect('button-press-event', this._onButtonPress); } log("GSnapStatusButton _init done"); } reset() { this.activated = false; if (SHELL_VERSION.version_at_least_34()) { this.remove_style_pseudo_class('activate'); } else { this.actor.remove_style_pseudo_class('activate'); } } activate() { if (SHELL_VERSION.version_at_least_34()) { this.add_style_pseudo_class('activate'); } else { this.actor.add_style_pseudo_class('activate'); } } deactivate() { if (SHELL_VERSION.version_at_least_34()) { this.remove_style_pseudo_class('activate'); } else { this.actor.remove_style_pseudo_class('activate'); } } _onButtonPress(_actor, _event) { log(`_onButtonPress Click Toggle Status on system panel ${this}`); globalApp.showMenu(); } _destroy() { this.activated = null; } } const GSnapStatusButton = GObject.registerClass({ GTypeName: 'GSnapStatusButton', }, GSnapStatusButtonClass); function changed_settings() { log("changed_settings"); if (enabled) { disable(); enable(); } log("changed_settings complete"); } function enable() { initSettings(changed_settings); log("Extension enable begin"); SHELL_VERSION.print_version(); globalApp.enable(); } function disable() { deinitSettings(); globalApp.disable(); } ```
meronz commented 1 year ago

Hi @auipga, thanks for the code you submitted. Unfortunately, I cannot accept it due to various reasons: The extension,js file is not the "real source code" of the extension, but the product of a Typescript-to-javascript "translation". The edits you made should be ported to the source code in this repository, and as previously stated I really don't have time for this. One thing I could do is to rebase the adjacent-zones-snap branch onto the main branch (which includes fixes and new features such as CTRL to snap). After that, you could try working on that branch and make it work!

auipga commented 1 year ago

I cannot accept it

I know that.

The edits you made should be ported to the source code in this repository

I'll try. After the rebase.

Thank you.