diff --git a/package.json b/package.json
index 9e6cbc5f..b5ce8025 100644
--- a/package.json
+++ b/package.json
@@ -54,5 +54,12 @@
},
"dependencies": {
"@girs/gnome-shell": "^49.1.0"
+ },
+ "overrides": {
+ "@girs/mtk-17": "17.0.0-4.0.0-rc.3",
+ "@girs/st-17": "17.0.0-4.0.0-rc.3",
+ "@girs/meta-17": "17.0.0-4.0.0-rc.3",
+ "@girs/clutter-17": "17.0.0-4.0.0-rc.3",
+ "@girs/shell-17": "17.0.0-4.0.0-rc.3"
}
}
diff --git a/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml b/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml
index 67fe8399..1928423a 100644
--- a/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml
+++ b/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml
@@ -207,6 +207,11 @@
Edge tiling behavior
Controls how windows snap to screen edges:\n'default' (corners snap to quarters, edges to halves),\n'adaptive' (snap to layout tiles by column),\n'granular' (snap to exact tile under cursor).
+
+ 300
+ Minimum tile width threshold
+ Minimum width in pixels each tile must have for a layout to be usable on a monitor.
+
diff --git a/src/components/layout/Layout.ts b/src/components/layout/Layout.ts
index 29b90d63..dfda7448 100644
--- a/src/components/layout/Layout.ts
+++ b/src/components/layout/Layout.ts
@@ -1,11 +1,22 @@
import Tile from './Tile';
+export interface MinimumResolution {
+ width: number;
+ height: number;
+}
+
export default class Layout {
id: string;
tiles: Tile[];
+ minimumMonitorResolution?: MinimumResolution;
- constructor(tiles: Tile[], id: string) {
+ constructor(
+ tiles: Tile[],
+ id: string,
+ minimumMonitorResolution?: MinimumResolution,
+ ) {
this.tiles = tiles;
this.id = id;
+ this.minimumMonitorResolution = minimumMonitorResolution;
}
}
diff --git a/src/components/layoutSwitcher/layoutSwitcher.ts b/src/components/layoutSwitcher/layoutSwitcher.ts
index ed53a1d9..f33bae19 100644
--- a/src/components/layoutSwitcher/layoutSwitcher.ts
+++ b/src/components/layoutSwitcher/layoutSwitcher.ts
@@ -94,14 +94,15 @@ export class LayoutSwitcherPopup extends SwitcherPopup.SwitcherPopup {
private _backwardAction: number;
constructor(action: number, backwardAction: number, enableScaling: boolean) {
+ const monitorIndex =
+ LayoutSwitcherPopup._getMonitorIndex();
// @ts-expect-error "Parent can take a list"
- super(GlobalState.get().layouts);
+ super(GlobalState.get().getLayoutsForMonitor(monitorIndex));
this._action = action;
this._backwardAction = backwardAction;
- // handle scale factor of the monitor
const monitorScalingFactor = enableScaling
- ? getMonitorScalingFactor(this._getCurrentMonitorIndex())
+ ? getMonitorScalingFactor(monitorIndex)
: undefined;
this._switcherList = new LayoutSwitcherList(
this._items,
@@ -115,7 +116,7 @@ export class LayoutSwitcherPopup extends SwitcherPopup.SwitcherPopup {
this._getCurrentMonitorIndex(),
global.workspaceManager.get_active_workspace_index(),
);
- this._selectedIndex = GlobalState.get().layouts.findIndex(
+ this._selectedIndex = this._items.findIndex(
(lay) => lay.id === selectedLay.id,
);
// backward is the one passed to show function
@@ -147,10 +148,14 @@ export class LayoutSwitcherPopup extends SwitcherPopup.SwitcherPopup {
);
}
- private _getCurrentMonitorIndex(): number {
+ private static _getMonitorIndex(): number {
const focusWindow = global.display.focus_window;
if (focusWindow) return focusWindow.get_monitor();
return Main.layoutManager.primaryIndex;
}
+
+ private _getCurrentMonitorIndex(): number {
+ return LayoutSwitcherPopup._getMonitorIndex();
+ }
}
diff --git a/src/components/snapassist/snapAssist.ts b/src/components/snapassist/snapAssist.ts
index dd9fa52d..a47c2cc4 100644
--- a/src/components/snapassist/snapAssist.ts
+++ b/src/components/snapassist/snapAssist.ts
@@ -117,12 +117,18 @@ class SnapAssistContent extends St.BoxLayout {
},
);
- this._setLayouts(GlobalState.get().layouts);
+ this._setLayouts(
+ GlobalState.get().getLayoutsForMonitor(this._monitorIndex),
+ );
this._signals.connect(
GlobalState.get(),
GlobalState.SIGNAL_LAYOUTS_CHANGED,
() => {
- this._setLayouts(GlobalState.get().layouts);
+ this._setLayouts(
+ GlobalState.get().getLayoutsForMonitor(
+ this._monitorIndex,
+ ),
+ );
},
);
diff --git a/src/indicator/defaultMenu.ts b/src/indicator/defaultMenu.ts
index 26c793d2..8489eeaf 100644
--- a/src/indicator/defaultMenu.ts
+++ b/src/indicator/defaultMenu.ts
@@ -36,6 +36,7 @@ class LayoutsRow extends St.BoxLayout {
private _layoutsBox: St.BoxLayout;
private _layoutsButtons: LayoutButton[];
+ private _layouts: Layout[];
private _label: St.Label;
private _monitor: Monitor;
@@ -61,6 +62,7 @@ class LayoutsRow extends St.BoxLayout {
yExpand: true,
styleClass: 'layouts-box-layout',
});
+ this._layouts = layouts;
this._monitor = monitor;
this._label = new St.Label({
text: `Monitor ${this._monitor.index + 1}`,
@@ -96,8 +98,11 @@ class LayoutsRow extends St.BoxLayout {
}
public selectLayout(selectedId: string) {
- const selectedIndex = GlobalState.get().layouts.findIndex(
- (lay) => lay.id === selectedId,
+ const selectedIndex = this._layoutsButtons.findIndex(
+ (_, ind) => {
+ const lay = this._layouts[ind];
+ return lay && lay.id === selectedId;
+ },
);
this._layoutsButtons.forEach((btn, ind) =>
btn.set_checked(ind === selectedIndex),
@@ -200,6 +205,13 @@ export default class DefaultMenu implements CurrentMenu {
this._signals.connect(Settings, Settings.KEY_INNER_GAPS, () => {
this._drawLayouts();
});
+ this._signals.connect(
+ Settings,
+ Settings.KEY_MINIMUM_TILE_WIDTH_THRESHOLD,
+ () => {
+ this._drawLayouts();
+ },
+ );
// if the selected layout was changed externaly, update the selected button
this._signals.connect(
@@ -415,7 +427,6 @@ export default class DefaultMenu implements CurrentMenu {
}
private _drawLayouts() {
- const layouts = GlobalState.get().layouts;
this._container.destroy_all_children();
this._layoutsRows = [];
@@ -423,6 +434,8 @@ export default class DefaultMenu implements CurrentMenu {
const ws_index = global.workspaceManager.get_active_workspace_index();
const monitors = getMonitors();
this._layoutsRows = monitors.map((monitor) => {
+ const layouts =
+ GlobalState.get().getLayoutsForMonitor(monitor.index);
const ws_selected_layouts =
ws_index < selected_layouts.length
? selected_layouts[ws_index]
@@ -430,7 +443,7 @@ export default class DefaultMenu implements CurrentMenu {
const selectedId =
monitor.index < ws_selected_layouts.length
? ws_selected_layouts[monitor.index]
- : GlobalState.get().layouts[0].id;
+ : layouts[0].id;
const row = new LayoutsRow(
this._container,
layouts,
diff --git a/src/indicator/indicator.ts b/src/indicator/indicator.ts
index 213e83b0..42afd6a4 100644
--- a/src/indicator/indicator.ts
+++ b/src/indicator/indicator.ts
@@ -142,6 +142,9 @@ export default class Indicator extends PanelMenu.Button {
}),
),
lay.id,
+ lay.minimumMonitorResolution
+ ? { ...lay.minimumMonitorResolution }
+ : undefined,
);
if (this._layoutEditor) {
diff --git a/src/prefs.ts b/src/prefs.ts
index 16abe499..51e5fdf8 100644
--- a/src/prefs.ts
+++ b/src/prefs.ts
@@ -581,6 +581,17 @@ export default class TilingShellExtensionPreferences extends ExtensionPreference
);
layoutsGroup.add(resetBtn);
+ const minTileWidthRow = this._buildSpinButtonRow(
+ Settings.KEY_MINIMUM_TILE_WIDTH_THRESHOLD,
+ _('Minimum tile width'),
+ _(
+ 'Minimum tile pixel width for a layout to be shown on a monitor (0 to disable filtering)',
+ ),
+ 0,
+ 800,
+ );
+ layoutsGroup.add(minTileWidthRow);
+
// Keybindings section
const keybindingsGroup = new Adw.PreferencesGroup({
title: _('Keybindings'),
diff --git a/src/settings/settings.ts b/src/settings/settings.ts
index 59ff551c..4d75c1a4 100644
--- a/src/settings/settings.ts
+++ b/src/settings/settings.ts
@@ -123,6 +123,7 @@ export default class Settings {
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 KEY_EDGE_TILING_MODE = 'edge-tiling-mode';
+ static KEY_MINIMUM_TILE_WIDTH_THRESHOLD = 'minimum-tile-width-threshold';
static SETTING_MOVE_WINDOW_RIGHT = 'move-window-right';
static SETTING_MOVE_WINDOW_LEFT = 'move-window-left';
@@ -506,6 +507,14 @@ export default class Settings {
set_string(Settings.KEY_EDGE_TILING_MODE, val);
}
+ static get MINIMUM_TILE_WIDTH_THRESHOLD(): number {
+ return get_unsigned_number(Settings.KEY_MINIMUM_TILE_WIDTH_THRESHOLD);
+ }
+
+ static set MINIMUM_TILE_WIDTH_THRESHOLD(val: number) {
+ set_unsigned_number(Settings.KEY_MINIMUM_TILE_WIDTH_THRESHOLD, val);
+ }
+
static get_inner_gaps(scaleFactor: number = 1): {
top: number;
bottom: number;
diff --git a/src/utils/globalState.ts b/src/utils/globalState.ts
index 73996f38..3cfefa2c 100644
--- a/src/utils/globalState.ts
+++ b/src/utils/globalState.ts
@@ -7,6 +7,7 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { logger } from './logger';
import { getWindows } from './ui';
import ExtendedWindow from '../components/tilingsystem/extendedWindow';
+import { getLayoutsForMonitor } from './layoutFiltering';
const debug = logger('GlobalState');
@@ -80,6 +81,15 @@ export default class GlobalState extends GObject.Object {
},
);
+ this._signals.connect(
+ Settings,
+ Settings.KEY_MINIMUM_TILE_WIDTH_THRESHOLD,
+ () => {
+ this.validate_selected_layouts();
+ this.emit(GlobalState.SIGNAL_LAYOUTS_CHANGED);
+ },
+ );
+
this._signals.connect(
Settings,
Settings.KEY_SETTING_SELECTED_LAYOUTS,
@@ -210,13 +220,24 @@ export default class GlobalState extends GObject.Object {
monitors_layouts.push(this._layouts[0].id);
while (monitors_layouts.length > n_monitors) monitors_layouts.pop();
- monitors_layouts.forEach((_, ind) => {
+ monitors_layouts.forEach((_, monitorIndex) => {
if (
this._layouts.findIndex(
- (lay) => lay.id === monitors_layouts[ind],
+ (lay) => lay.id === monitors_layouts[monitorIndex],
) === -1
)
- monitors_layouts[ind] = monitors_layouts[0];
+ monitors_layouts[monitorIndex] = monitors_layouts[0];
+
+ const suitableLayouts =
+ this.getLayoutsForMonitor(monitorIndex);
+ if (
+ !suitableLayouts.find(
+ (lay) =>
+ lay.id === monitors_layouts[monitorIndex],
+ )
+ ) {
+ monitors_layouts[monitorIndex] = suitableLayouts[0].id;
+ }
});
this._selected_layouts.set(ws, monitors_layouts);
@@ -243,6 +264,16 @@ export default class GlobalState extends GObject.Object {
return this._layouts;
}
+ public getLayoutsForMonitor(monitorIndex: number): Layout[] {
+ const workArea =
+ Main.layoutManager.getWorkAreaForMonitor(monitorIndex);
+ return getLayoutsForMonitor(
+ this._layouts,
+ workArea.width,
+ workArea.height,
+ );
+ }
+
public addLayout(newLay: Layout) {
this._layouts.push(newLay);
// easy way to trigger save and signal emission
@@ -312,11 +343,16 @@ export default class GlobalState extends GObject.Object {
if (monitorIndex < 0 || monitorIndex >= monitors_selected.length)
monitorIndex = 0;
- return (
+ const selectedLayout =
this._layouts.find(
(lay) => lay.id === monitors_selected[monitorIndex],
- ) || this._layouts[0]
- );
+ ) || this._layouts[0];
+
+ const suitableLayouts = this.getLayoutsForMonitor(monitorIndex);
+ if (suitableLayouts.find((lay) => lay.id === selectedLayout.id)) {
+ return selectedLayout;
+ }
+ return suitableLayouts[0];
}
public get tilePreviewAnimationTime(): number {
diff --git a/src/utils/layoutFiltering.ts b/src/utils/layoutFiltering.ts
new file mode 100644
index 00000000..f3ab8cd2
--- /dev/null
+++ b/src/utils/layoutFiltering.ts
@@ -0,0 +1,43 @@
+import Layout from '../components/layout/Layout';
+import Settings from '../settings/settings';
+
+export function isLayoutSuitableForMonitor(
+ layout: Layout,
+ monitorWidth: number,
+ monitorHeight: number,
+): boolean {
+ if (layout.minimumMonitorResolution) {
+ return (
+ monitorWidth >= layout.minimumMonitorResolution.width &&
+ monitorHeight >= layout.minimumMonitorResolution.height
+ );
+ }
+
+ const minTileWidthThreshold = Settings.MINIMUM_TILE_WIDTH_THRESHOLD;
+ if (layout.tiles.length === 0 || minTileWidthThreshold === 0) return true;
+ const minTileHeightThreshold = Math.round((minTileWidthThreshold * 2) / 3);
+
+ const smallestTileWidth = Math.min(...layout.tiles.map((t) => t.width));
+ const smallestTileHeight = Math.min(...layout.tiles.map((t) => t.height));
+
+ return (
+ smallestTileWidth * monitorWidth >= minTileWidthThreshold &&
+ smallestTileHeight * monitorHeight >= minTileHeightThreshold
+ );
+}
+
+export function getLayoutsForMonitor(
+ layouts: Layout[],
+ monitorWidth: number,
+ monitorHeight: number,
+): Layout[] {
+ const suitable = layouts.filter((lay) =>
+ isLayoutSuitableForMonitor(lay, monitorWidth, monitorHeight),
+ );
+
+ if (suitable.length === 0 && layouts.length > 0) {
+ return [layouts[0]];
+ }
+
+ return suitable.length > 0 ? suitable : layouts;
+}