Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@
<summary>Edge tiling behavior</summary>
<description>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).</description>
</key>
<key name="minimum-tile-width-threshold" type="u">
<default>300</default>
<summary>Minimum tile width threshold</summary>
<description>Minimum width in pixels each tile must have for a layout to be usable on a monitor.</description>
</key>

<!-- keybindings -->
<key type="as" name="move-window-right">
Expand Down
13 changes: 12 additions & 1 deletion src/components/layout/Layout.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
15 changes: 10 additions & 5 deletions src/components/layoutSwitcher/layoutSwitcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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();
}
}
10 changes: 8 additions & 2 deletions src/components/snapassist/snapAssist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
);
},
);

Expand Down
21 changes: 17 additions & 4 deletions src/indicator/defaultMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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}`,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -415,22 +427,23 @@ export default class DefaultMenu implements CurrentMenu {
}

private _drawLayouts() {
const layouts = GlobalState.get().layouts;
this._container.destroy_all_children();
this._layoutsRows = [];

const selected_layouts = Settings.get_selected_layouts();
const ws_index = global.workspaceManager.get_active_workspace_index();
const monitors = getMonitors();
this._layoutsRows = monitors.map((monitor) => {
const layouts =
GlobalState.get().getLayoutsForMonitor(monitor.index);
const ws_selected_layouts =
ws_index < selected_layouts.length
? selected_layouts[ws_index]
: [];
const selectedId =
monitor.index < ws_selected_layouts.length
? ws_selected_layouts[monitor.index]
: GlobalState.get().layouts[0].id;
: layouts[0].id;
const row = new LayoutsRow(
this._container,
layouts,
Expand Down
3 changes: 3 additions & 0 deletions src/indicator/indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export default class Indicator extends PanelMenu.Button {
}),
),
lay.id,
lay.minimumMonitorResolution
? { ...lay.minimumMonitorResolution }
: undefined,
);

if (this._layoutEditor) {
Expand Down
11 changes: 11 additions & 0 deletions src/prefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
9 changes: 9 additions & 0 deletions src/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
48 changes: 42 additions & 6 deletions src/utils/globalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
43 changes: 43 additions & 0 deletions src/utils/layoutFiltering.ts
Original file line number Diff line number Diff line change
@@ -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;
}