diff --git a/extension.js b/extension.js index 138a3d8..9f5442a 100644 --- a/extension.js +++ b/extension.js @@ -1,5 +1,5 @@ // ---------------------------------------------------- // -// Simple-Tiling – GNOME Shell 3.38 (X11) - Version 3 // +// Simple-Tiling – GNOME Shell 3.38 (X11) - Version 4 // // © 2025 domoel – MIT // // ---------------------------------------------------- // @@ -14,10 +14,25 @@ const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const ExtensionUtils = imports.misc.extensionUtils; const ByteArray = imports.byteArray; +const TILING_DELAY_MS = 20; // Change Tiling Window Delay +const CENTERING_DELAY_MS = 5; // Change Centered Window Delay const Me = ExtensionUtils.getCurrentExtension(); -const SCHEMA_NAME = 'org.gnome.shell.extensions.simple-tiling.domoel'; -const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings'; +const SCHEMA_NAME = "org.gnome.shell.extensions.simple-tiling.domoel"; +const WM_SCHEMA = "org.gnome.desktop.wm.keybindings"; + +const KEYBINDINGS = { + "swap-master-window": (self) => self._swapWithMaster(), + "swap-left-window": (self) => self._swapInDirection("left"), + "swap-right-window": (self) => self._swapInDirection("right"), + "swap-up-window": (self) => self._swapInDirection("up"), + "swap-down-window": (self) => self._swapInDirection("down"), + + "focus-left": (self) => self._focusInDirection("left"), + "focus-right": (self) => self._focusInDirection("right"), + "focus-up": (self) => self._focusInDirection("up"), + "focus-down": (self) => self._focusInDirection("down"), +}; // ---------------------------------------------------- // // InteractionHandler // @@ -37,63 +52,97 @@ class InteractionHandler { enable() { if (this._wmKeysToDisable.length) { - this._wmKeysToDisable.forEach(key => this._wmSettings.set_value(key, new GLib.Variant('as', []))); + this._wmKeysToDisable.forEach((key) => + this._wmSettings.set_value(key, new GLib.Variant("as", [])) + ); } this._bindAllShortcuts(); - this._settingsChangedId = this._settings.connect('changed', this._onSettingsChanged); - this._grabOpIds.push(global.display.connect('grab-op-begin', (display, screen, window) => { - if (this.tiler.windows.includes(window)) { this.tiler.grabbedWindow = window; } - })); - this._grabOpIds.push(global.display.connect('grab-op-end', this._onGrabEnd.bind(this))); + this._settingsChangedId = this._settings.connect( + "changed", + this._onSettingsChanged + ); + this._grabOpIds.push( + global.display.connect( + "grab-op-begin", + (display, screen, window) => { + if (this.tiler.windows.includes(window)) { + this.tiler.grabbedWindow = window; + } + } + ) + ); + this._grabOpIds.push( + global.display.connect("grab-op-end", this._onGrabEnd.bind(this)) + ); } disable() { if (this._wmKeysToDisable.length) { - this._wmKeysToDisable.forEach(key => this._wmSettings.set_value(key, this._savedWmShortcuts[key])); + this._wmKeysToDisable.forEach((key) => + this._wmSettings.set_value(key, this._savedWmShortcuts[key]) + ); } this._unbindAllShortcuts(); if (this._settingsChangedId) { this._settings.disconnect(this._settingsChangedId); this._settingsChangedId = null; } - this._grabOpIds.forEach(id => global.display.disconnect(id)); - } - - _bind(key, callback) { - Main.wm.addKeybinding(key, this._settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL, callback.bind(this)); + this._grabOpIds.forEach((id) => global.display.disconnect(id)); } - _bindAllShortcuts() { - this._bind('swap-master-window', this._swapWithMaster); - this._bind('swap-left-window', () => this._swapInDirection('left')); - this._bind('swap-right-window', () => this._swapInDirection('right')); - this._bind('swap-up-window', () => this._swapInDirection('up')); - this._bind('swap-down-window', () => this._swapInDirection('down')); - } + _bind(key, callback) { + Main.wm.addKeybinding(key, this._settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL, + () => callback(this)); + } + + _bindAllShortcuts() { + for (const [key, handler] of Object.entries(KEYBINDINGS)) { + this._bind(key, handler); + } + } + _unbindAllShortcuts() { + for (const key in KEYBINDINGS) { + Main.wm.removeKeybinding(key); + } + } - _unbindAllShortcuts() { - ['swap-master-window', 'swap-left-window', 'swap-right-window', 'swap-up-window', 'swap-down-window'] - .forEach(key => Main.wm.removeKeybinding(key)); - } - _onSettingsChanged() { this._unbindAllShortcuts(); this._bindAllShortcuts(); } - _prepareWmShortcuts() { const schema = this._wmSettings.settings_schema; const keys = []; - if (schema.has_key('toggle-tiled-left')) keys.push('toggle-tiled-left', 'toggle-tiled-right'); - else if (schema.has_key('tile-left')) keys.push('tile-left', 'tile-right'); - if (schema.has_key('toggle-maximized')) keys.push('toggle-maximized'); + if (schema.has_key("toggle-tiled-left")) + keys.push("toggle-tiled-left", "toggle-tiled-right"); + else if (schema.has_key("tile-left")) + keys.push("tile-left", "tile-right"); + if (schema.has_key("toggle-maximized")) keys.push("toggle-maximized"); else { - if (schema.has_key('maximize')) keys.push('maximize'); - if (schema.has_key('unmaximize')) keys.push('unmaximize'); + if (schema.has_key("maximize")) keys.push("maximize"); + if (schema.has_key("unmaximize")) keys.push("unmaximize"); } if (keys.length) { this._wmKeysToDisable = keys; - keys.forEach(key => this._savedWmShortcuts[key] = this.tiler.wmSettings.get_value(key)); + keys.forEach( + (key) => + (this._savedWmShortcuts[ + key + ] = this._wmSettings.get_value(key)) + ); + } + } + + _focusInDirection(direction) { + const sourceWindow = global.display.get_focus_window(); + if (!sourceWindow || !this.tiler.windows.includes(sourceWindow)) return; + + const targetWindow = this._findTargetInDirection( + sourceWindow, + direction + ); + if (targetWindow) { + targetWindow.activate(global.get_current_time()); } } @@ -104,7 +153,10 @@ class InteractionHandler { if (!focusedWindow || !windows.includes(focusedWindow)) return; const focusedIndex = windows.indexOf(focusedWindow); if (focusedIndex > 0) { - [windows[0], windows[focusedIndex]] = [windows[focusedIndex], windows[0]]; + [windows[0], windows[focusedIndex]] = [ + windows[focusedIndex], + windows[0], + ]; } else if (focusedIndex === 0) { [windows[0], windows[1]] = [windows[1], windows[0]]; } @@ -117,18 +169,25 @@ class InteractionHandler { if (!sourceWindow || !this.tiler.windows.includes(sourceWindow)) return; let targetWindow = null; const sourceIndex = this.tiler.windows.indexOf(sourceWindow); - if (sourceIndex === 0 && direction === 'right' && this.tiler.windows.length > 1) { + if ( + sourceIndex === 0 && + direction === "right" && + this.tiler.windows.length > 1 + ) { targetWindow = this.tiler.windows[1]; } else { targetWindow = this._findTargetInDirection(sourceWindow, direction); } if (!targetWindow) return; const targetIndex = this.tiler.windows.indexOf(targetWindow); - [this.tiler.windows[sourceIndex], this.tiler.windows[targetIndex]] = [this.tiler.windows[targetIndex], this.tiler.windows[sourceIndex]]; + [this.tiler.windows[sourceIndex], this.tiler.windows[targetIndex]] = [ + this.tiler.windows[targetIndex], + this.tiler.windows[sourceIndex], + ]; this.tiler.tileNow(); sourceWindow.activate(global.get_current_time()); } - + _findTargetInDirection(source, direction) { const sourceRect = source.get_frame_rect(); let candidates = []; @@ -136,10 +195,18 @@ class InteractionHandler { if (win === source) continue; const targetRect = win.get_frame_rect(); switch (direction) { - case 'left': if (targetRect.x < sourceRect.x) candidates.push(win); break; - case 'right': if (targetRect.x > sourceRect.x) candidates.push(win); break; - case 'up': if (targetRect.y < sourceRect.y) candidates.push(win); break; - case 'down': if (targetRect.y > sourceRect.y) candidates.push(win); break; + case "left": + if (targetRect.x < sourceRect.x) candidates.push(win); + break; + case "right": + if (targetRect.x > sourceRect.x) candidates.push(win); + break; + case "up": + if (targetRect.y < sourceRect.y) candidates.push(win); + break; + case "down": + if (targetRect.y > sourceRect.y) candidates.push(win); + break; } } if (candidates.length === 0) return null; @@ -148,7 +215,7 @@ class InteractionHandler { for (const win of candidates) { const targetRect = win.get_frame_rect(); let deviation; - if (direction === 'left' || direction === 'right') { + if (direction === "left" || direction === "right") { deviation = Math.abs(sourceRect.y - targetRect.y); } else { deviation = Math.abs(sourceRect.x - targetRect.x); @@ -168,7 +235,13 @@ class InteractionHandler { if (targetWindow) { const sourceIndex = this.tiler.windows.indexOf(grabbedWindow); const targetIndex = this.tiler.windows.indexOf(targetWindow); - [this.tiler.windows[sourceIndex], this.tiler.windows[targetIndex]] = [this.tiler.windows[targetIndex], this.tiler.windows[sourceIndex]]; + [ + this.tiler.windows[sourceIndex], + this.tiler.windows[targetIndex], + ] = [ + this.tiler.windows[targetIndex], + this.tiler.windows[sourceIndex], + ]; } this.tiler.queueTile(); this.tiler.grabbedWindow = null; @@ -176,22 +249,48 @@ class InteractionHandler { _findTargetUnderPointer(excludeWindow) { let [pointerX, pointerY] = global.get_pointer(); - let windows = global.get_window_actors().map(actor => actor.meta_window).filter(win => { - if (!win || win === excludeWindow || !this.tiler.windows.includes(win)) return false; - let frame = win.get_frame_rect(); - return pointerX >= frame.x && pointerX < frame.x + frame.width && - pointerY >= frame.y && pointerY < frame.y + frame.height; - }); - if (windows.length > 0) { return windows[windows.length - 1]; } - + let windows = global + .get_window_actors() + .map((actor) => actor.meta_window) + .filter((win) => { + if ( + !win || + win === excludeWindow || + !this.tiler.windows.includes(win) + ) + return false; + let frame = win.get_frame_rect(); + return ( + pointerX >= frame.x && + pointerX < frame.x + frame.width && + pointerY >= frame.y && + pointerY < frame.y + frame.height + ); + }); + if (windows.length > 0) { + return windows[windows.length - 1]; + } + let bestTarget = null; let maxOverlap = 0; const sourceFrame = excludeWindow.get_frame_rect(); for (const win of this.tiler.windows) { if (win === excludeWindow) continue; const targetFrame = win.get_frame_rect(); - const overlapX = Math.max(0, Math.min(sourceFrame.x + sourceFrame.width, targetFrame.x + targetFrame.width) - Math.max(sourceFrame.x, targetFrame.x)); - const overlapY = Math.max(0, Math.min(sourceFrame.y + sourceFrame.height, targetFrame.y + targetFrame.height) - Math.max(sourceFrame.y, targetFrame.y)); + const overlapX = Math.max( + 0, + Math.min( + sourceFrame.x + sourceFrame.width, + targetFrame.x + targetFrame.width + ) - Math.max(sourceFrame.x, targetFrame.x) + ); + const overlapY = Math.max( + 0, + Math.min( + sourceFrame.y + sourceFrame.height, + targetFrame.y + targetFrame.height + ) - Math.max(sourceFrame.y, targetFrame.y) + ); const overlapArea = overlapX * overlapY; if (overlapArea > maxOverlap) { maxOverlap = overlapArea; @@ -211,96 +310,136 @@ class Tiler { this.windows = []; this.grabbedWindow = null; this._settings = ExtensionUtils.getSettings(SCHEMA_NAME); - this.wmSettings = new Gio.Settings({ schema: WM_SCHEMA }); this._signalIds = new Map(); this._tileInProgress = false; - - this._innerGap = this._settings.get_int('inner-gap'); - this._outerGapVertical = this._settings.get_int('outer-gap-vertical'); - this._outerGapHorizontal = this._settings.get_int('outer-gap-horizontal'); - // Window Delay Settings - this._tilingDelay = 20; // General Tiling Window Delay - this._centeringDelay = 5; //Delay for centered Apps on the Exception List + this._innerGap = this._settings.get_int("inner-gap"); + this._outerGapVertical = this._settings.get_int("outer-gap-vertical"); + this._outerGapHorizontal = this._settings.get_int( + "outer-gap-horizontal" + ); + + this._tilingDelay = TILING_DELAY_MS; + this._centeringDelay = CENTERING_DELAY_MS; this._exceptions = []; this._interactionHandler = new InteractionHandler(this); this._onWindowAdded = this._onWindowAdded.bind(this); this._onWindowRemoved = this._onWindowRemoved.bind(this); - this._onActiveWorkspaceChanged = this._onActiveWorkspaceChanged.bind(this); - this._onWindowMinimizedStateChanged = this._onWindowMinimizedStateChanged.bind(this); + this._onActiveWorkspaceChanged = this._onActiveWorkspaceChanged.bind( + this + ); + this._onWindowMinimizedStateChanged = this._onWindowMinimizedStateChanged.bind( + this + ); this._onSettingsChanged = this._onSettingsChanged.bind(this); } - + enable() { this._loadExceptions(); const workspaceManager = global.workspace_manager; - this._signalIds.set('workspace-changed', { object: workspaceManager, id: workspaceManager.connect('active-workspace-changed', this._onActiveWorkspaceChanged) }); + this._signalIds.set("workspace-changed", { + object: workspaceManager, + id: workspaceManager.connect( + "active-workspace-changed", + this._onActiveWorkspaceChanged + ), + }); this._connectToWorkspace(); this._interactionHandler.enable(); - this._signalIds.set('settings-changed', { object: this._settings, id: this._settings.connect('changed', this._onSettingsChanged) }); + this._signalIds.set("settings-changed", { + object: this._settings, + id: this._settings.connect("changed", this._onSettingsChanged), + }); } disable() { this._interactionHandler.disable(); this._disconnectFromWorkspace(); - for (const [, signal] of this._signalIds) { - try { signal.object.disconnect(signal.id); } catch(e) {} + for (const [, signal] of this._signalIds) { + try { + signal.object.disconnect(signal.id); + } catch (e) {} } this._signalIds.clear(); this.windows = []; } - + _onSettingsChanged() { - this._innerGap = this._settings.get_int('inner-gap'); - this._outerGapVertical = this._settings.get_int('outer-gap-vertical'); - this._outerGapHorizontal = this._settings.get_int('outer-gap-horizontal'); + this._innerGap = this._settings.get_int("inner-gap"); + this._outerGapVertical = this._settings.get_int("outer-gap-vertical"); + this._outerGapHorizontal = this._settings.get_int( + "outer-gap-horizontal" + ); this.queueTile(); } - + _loadExceptions() { - const file = Gio.file_new_for_path(Me.path + '/exceptions.txt'); - if (!file.query_exists(null)) { this._exceptions = []; return; } + const file = Gio.file_new_for_path(Me.path + "/exceptions.txt"); + if (!file.query_exists(null)) { + this._exceptions = []; + return; + } const [ok, data] = file.load_contents(null); - this._exceptions = ok ? ByteArray.toString(data).split('\n') - .map(l => l.trim()).filter(l => l && !l.startsWith('#')) - .map(l => l.toLowerCase()) : []; + this._exceptions = ok + ? ByteArray.toString(data) + .split("\n") + .map((l) => l.trim()) + .filter((l) => l && !l.startsWith("#")) + .map((l) => l.toLowerCase()) + : []; } _isException(win) { - return !!win && this._exceptions.includes((win.get_wm_class() || '').toLowerCase()); + return ( + !!win && + this._exceptions.includes((win.get_wm_class() || "").toLowerCase()) + ); } _isTileable(win) { - return win && !win.minimized && !this._isException(win) && win.get_window_type() === Meta.WindowType.NORMAL; + return ( + win && + !win.minimized && + !this._isException(win) && + win.get_window_type() === Meta.WindowType.NORMAL + ); } _centerWindow(win) { Mainloop.timeout_add(this._centeringDelay, () => { if (!win || !win.get_display()) return GLib.SOURCE_REMOVE; - if (win.get_maximized()) { win.unmaximize(Meta.MaximizeFlags.BOTH); } + if (win.get_maximized()) { + win.unmaximize(Meta.MaximizeFlags.BOTH); + } const monitorIndex = win.get_monitor(); - const workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex); + const workArea = Main.layoutManager.getWorkAreaForMonitor( + monitorIndex + ); const frame = win.get_frame_rect(); - win.move_frame(true, + win.move_frame( + true, workArea.x + Math.floor((workArea.width - frame.width) / 2), - workArea.y + Math.floor((workArea.height - frame.height) / 2)); + workArea.y + Math.floor((workArea.height - frame.height) / 2) + ); Mainloop.idle_add(() => { if (win.get_display()) { - if (typeof win.set_keep_above === 'function') win.set_keep_above(true); - else if (typeof win.make_above === 'function') win.make_above(); + if (typeof win.set_keep_above === "function") + win.set_keep_above(true); + else if (typeof win.make_above === "function") + win.make_above(); } return GLib.SOURCE_REMOVE; }); return GLib.SOURCE_REMOVE; }); } - + _onWindowMinimizedStateChanged() { this.queueTile(); } - + _onWindowAdded(workspace, win) { if (this.windows.includes(win)) return; @@ -308,40 +447,58 @@ class Tiler { this._centerWindow(win); return; } - + if (this._isTileable(win)) { - if (this._settings.get_string('new-window-behavior') === 'master') { + if (this._settings.get_string("new-window-behavior") === "master") { this.windows.unshift(win); } else { this.windows.push(win); } const id = win.get_id(); - this._signalIds.set(`unmanaged-${id}`, { object: win, id: win.connect('unmanaged', () => this._onWindowRemoved(null, win)) }); - this._signalIds.set(`size-changed-${id}`, { object: win, id: win.connect('size-changed', () => { if (!this.grabbedWindow) this.queueTile(); }) }); - this._signalIds.set(`minimized-${id}`, { object: win, id: win.connect('notify::minimized', this._onWindowMinimizedStateChanged) }); - + this._signalIds.set(`unmanaged-${id}`, { + object: win, + id: win.connect("unmanaged", () => + this._onWindowRemoved(null, win) + ), + }); + this._signalIds.set(`size-changed-${id}`, { + object: win, + id: win.connect("size-changed", () => { + if (!this.grabbedWindow) this.queueTile(); + }), + }); + this._signalIds.set(`minimized-${id}`, { + object: win, + id: win.connect( + "notify::minimized", + this._onWindowMinimizedStateChanged + ), + }); + this.queueTile(); } } - + _onWindowRemoved(workspace, win) { const index = this.windows.indexOf(win); if (index > -1) { this.windows.splice(index, 1); } - ['unmanaged', 'size-changed', 'minimized'].forEach(prefix => { + ["unmanaged", "size-changed", "minimized"].forEach((prefix) => { const key = `${prefix}-${win.get_id()}`; if (this._signalIds.has(key)) { const { object, id } = this._signalIds.get(key); - try { object.disconnect(id); } catch(e) {} + try { + object.disconnect(id); + } catch (e) {} this._signalIds.delete(key); } }); this.queueTile(); } - + _onActiveWorkspaceChanged() { this._disconnectFromWorkspace(); this._connectToWorkspace(); @@ -349,19 +506,29 @@ class Tiler { _connectToWorkspace() { const workspace = global.workspace_manager.get_active_workspace(); - workspace.list_windows().forEach(win => this._onWindowAdded(workspace, win)); - this._signalIds.set('window-added', { object: workspace, id: workspace.connect('window-added', this._onWindowAdded) }); - this._signalIds.set('window-removed', { object: workspace, id: workspace.connect('window-removed', this._onWindowRemoved) }); + workspace + .list_windows() + .forEach((win) => this._onWindowAdded(workspace, win)); + this._signalIds.set("window-added", { + object: workspace, + id: workspace.connect("window-added", this._onWindowAdded), + }); + this._signalIds.set("window-removed", { + object: workspace, + id: workspace.connect("window-removed", this._onWindowRemoved), + }); this.queueTile(); } _disconnectFromWorkspace() { - this.windows.slice().forEach(win => this._onWindowRemoved(null, win)); - - ['window-added', 'window-removed'].forEach(key => { + this.windows.slice().forEach((win) => this._onWindowRemoved(null, win)); + + ["window-added", "window-removed"].forEach((key) => { if (this._signalIds.has(key)) { const { object, id } = this._signalIds.get(key); - try { object.disconnect(id); } catch(e) {} + try { + object.disconnect(id); + } catch (e) {} this._signalIds.delete(key); } }); @@ -386,10 +553,16 @@ class Tiler { _splitLayout(windows, area) { if (windows.length === 0) return; if (windows.length === 1) { - windows[0].move_resize_frame(true, area.x, area.y, area.width, area.height); + windows[0].move_resize_frame( + true, + area.x, + area.y, + area.width, + area.height + ); return; } - + const gap = Math.floor(this._innerGap / 2); const primaryWindows = [windows[0]]; const secondaryWindows = windows.slice(1); @@ -397,46 +570,82 @@ class Tiler { if (area.width > area.height) { const primaryWidth = Math.floor(area.width / 2) - gap; - primaryArea = { x: area.x, y: area.y, width: primaryWidth, height: area.height }; - secondaryArea = { x: area.x + primaryWidth + this._innerGap, y: area.y, width: area.width - primaryWidth - this._innerGap, height: area.height }; + primaryArea = { + x: area.x, + y: area.y, + width: primaryWidth, + height: area.height, + }; + secondaryArea = { + x: area.x + primaryWidth + this._innerGap, + y: area.y, + width: area.width - primaryWidth - this._innerGap, + height: area.height, + }; } else { const primaryHeight = Math.floor(area.height / 2) - gap; - primaryArea = { x: area.x, y: area.y, width: area.width, height: primaryHeight }; - secondaryArea = { x: area.x, y: area.y + primaryHeight + this._innerGap, width: area.width, height: area.height - primaryHeight - this._innerGap }; + primaryArea = { + x: area.x, + y: area.y, + width: area.width, + height: primaryHeight, + }; + secondaryArea = { + x: area.x, + y: area.y + primaryHeight + this._innerGap, + width: area.width, + height: area.height - primaryHeight - this._innerGap, + }; } - + this._splitLayout(primaryWindows, primaryArea); this._splitLayout(secondaryWindows, secondaryArea); } _tileWindows() { - const windowsToTile = this.windows.filter(win => !win.minimized); + const windowsToTile = this.windows.filter((win) => !win.minimized); if (windowsToTile.length === 0) return; const monitor = Main.layoutManager.primaryMonitor; - const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); + const workArea = Main.layoutManager.getWorkAreaForMonitor( + monitor.index + ); const innerArea = { x: workArea.x + this._outerGapHorizontal, y: workArea.y + this._outerGapVertical, width: workArea.width - 2 * this._outerGapHorizontal, - height: workArea.height - 2 * this._outerGapVertical + height: workArea.height - 2 * this._outerGapVertical, }; - windowsToTile.forEach(win => { if (win.get_maximized()) win.unmaximize(Meta.MaximizeFlags.BOTH); }); + windowsToTile.forEach((win) => { + if (win.get_maximized()) win.unmaximize(Meta.MaximizeFlags.BOTH); + }); if (windowsToTile.length === 1) { - windowsToTile[0].move_resize_frame(true, innerArea.x, innerArea.y, innerArea.width, innerArea.height); + windowsToTile[0].move_resize_frame( + true, + innerArea.x, + innerArea.y, + innerArea.width, + innerArea.height + ); return; } const gap = Math.floor(this._innerGap / 2); const masterWidth = Math.floor(innerArea.width / 2) - gap; const master = windowsToTile[0]; - master.move_resize_frame(true, innerArea.x, innerArea.y, masterWidth, innerArea.height); + master.move_resize_frame( + true, + innerArea.x, + innerArea.y, + masterWidth, + innerArea.height + ); const stackArea = { x: innerArea.x + masterWidth + this._innerGap, y: innerArea.y, width: innerArea.width - masterWidth - this._innerGap, - height: innerArea.height + height: innerArea.height, }; this._splitLayout(windowsToTile.slice(1), stackArea); }