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
39 changes: 29 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
UUID = "forge@jmmaranan.com"
UUID = forge@jmmaranan.com
INSTALL_PATH = $(HOME)/.local/share/gnome-shell/extensions/$(UUID)
MSGSRC = $(wildcard po/*.po)

Expand Down Expand Up @@ -26,7 +26,9 @@ patchcss:

metadata:
echo "export const developers = Object.entries([" > lib/prefs/metadata.js
git shortlog -sne || echo "" >> lib/prefs/metadata.js
if git rev-parse HEAD >/dev/null 2>&1; then \
git --no-pager shortlog -sne HEAD </dev/null >> lib/prefs/metadata.js; \
fi
awk -i inplace '!/dependabot|noreply/' lib/prefs/metadata.js
sed -i 's/^[[:space:]]*[0-9]*[[:space:]]*\(.*\) <\(.*\)>/ {name:"\1", email:"\2"},/g' lib/prefs/metadata.js
echo "].reduce((acc, x) => ({ ...acc, [x.email]: acc[x.email] ?? x.name }), {})).map(([email, name]) => name + ' <' + email + '>')" >> lib/prefs/metadata.js
Expand Down Expand Up @@ -70,15 +72,22 @@ compilemsgs: potfile $(MSGSRC:.po=.mo)

clean:
rm -f lib/prefs/metadata.js
rm "$(UUID).zip" || echo "Nothing to delete"
rm -f "$(UUID).zip"
rm -rf temp schemas/gschemas.compiled

enable:
gnome-extensions enable "$(UUID)"
if command -v gnome-extensions >/dev/null 2>&1; then \
gnome-extensions enable "$(UUID)" || echo "Extension "$(UUID)" could not be enabled"; \
else \
echo "gnome-extensions CLI is not available; skipping enable"; \
fi

disable:
gnome-extensions disable "$(UUID)"

if command -v gnome-extensions >/dev/null 2>&1; then \
gnome-extensions disable "$(UUID)" || echo "Extension "$(UUID)" is not installed"; \
else \
echo "gnome-extensions CLI is not available; skipping disable"; \
fi
install:
mkdir -p $(INSTALL_PATH)
cp -r temp/* $(INSTALL_PATH)
Expand All @@ -93,14 +102,24 @@ dist: build
zip -qr "../${UUID}.zip" .

restart:
if bash -c 'xprop -root &> /dev/null'; then \
killall -HUP gnome-shell; \
else \
if command -v xprop >/dev/null 2>&1 && bash -c 'xprop -root &> /dev/null'; then \
if command -v killall >/dev/null 2>&1; then \
killall -HUP gnome-shell || echo "Failed to signal gnome-shell"; \
else \
echo "killall not available; skipping gnome-shell restart"; \
fi; \
elif command -v gnome-session-quit >/dev/null 2>&1; then \
gnome-session-quit --logout; \
else \
echo "No GNOME restart command available; skipping restart"; \
fi

log:
journalctl -o cat -n 0 -f "$$(which gnome-shell)" | grep -v -E 'warning|g_variant'
if command -v gnome-shell >/dev/null 2>&1; then \
journalctl -o cat -n 0 -f "$$(command -v gnome-shell)" | grep -v -E 'warning|g_variant'; \
else \
echo "gnome-shell is not available; skipping journal log"; \
fi

journal:
journalctl -b 0 -r --since "1 hour ago"
Expand Down
105 changes: 103 additions & 2 deletions lib/extension/window.js
Original file line number Diff line number Diff line change
Expand Up @@ -1172,16 +1172,117 @@ export class WindowManager extends GObject.Object {
}

processFloats() {
const minimumTiledWindows = Math.max(1, this.ext.settings.get_uint("minimum-tiled-windows"));
const ultrawideLimits = this._getUltrawideLimits();
const workspaceCounts = this._countWorkspaceTileCandidates();

this.allNodeWindows.forEach((nodeWindow) => {
let metaWindow = nodeWindow.nodeValue;
if (this.isFloatingExempt(metaWindow) || !this.isActiveWindowWorkspaceTiled(metaWindow)) {
const metaWindow = nodeWindow.nodeValue;
if (!metaWindow) return;

const workspace = metaWindow.get_workspace();
const workspaceIndex = workspace ? workspace.index() : null;
const eligibleCount =
workspaceIndex !== null && workspaceCounts.has(workspaceIndex)
? workspaceCounts.get(workspaceIndex)
: 0;
const workspaceTiled = this.isActiveWindowWorkspaceTiled(metaWindow);
const floatingExempt = this.isFloatingExempt(metaWindow);
const meetsMinimum = eligibleCount >= minimumTiledWindows;

if (floatingExempt || !workspaceTiled || !meetsMinimum) {
const enforceLimits = !floatingExempt && workspaceTiled && !meetsMinimum;
nodeWindow.float = true;
if (enforceLimits) {
this._enforceUltrawideSize(metaWindow, ultrawideLimits);
}
} else {
nodeWindow.float = false;
}
});
}

_countWorkspaceTileCandidates() {
const counts = new Map();

this.allNodeWindows.forEach((nodeWindow) => {
const metaWindow = nodeWindow.nodeValue;
if (!this._shouldCountForTiling(metaWindow)) return;

const workspace = metaWindow.get_workspace();
if (!workspace) return;

const workspaceIndex = workspace.index();
const currentCount = counts.has(workspaceIndex) ? counts.get(workspaceIndex) : 0;
counts.set(workspaceIndex, currentCount + 1);
});

return counts;
}

_shouldCountForTiling(metaWindow) {
if (!metaWindow) return false;
if (metaWindow.minimized) return false;
if (!this.isActiveWindowWorkspaceTiled(metaWindow)) return false;
if (this.isFloatingExempt(metaWindow)) return false;
return true;
}

_getUltrawideLimits() {
return {
monitorWidth: this.ext.settings.get_uint("ultrawide-monitor-width"),
maxWidth: this.ext.settings.get_uint("ultrawide-max-window-width"),
maxHeight: this.ext.settings.get_uint("ultrawide-max-window-height"),
};
}

_enforceUltrawideSize(metaWindow, limits) {
if (!limits) return;
const { monitorWidth, maxWidth, maxHeight } = limits;

if (!monitorWidth || !maxWidth || !maxHeight) return;

const monitorIndex = metaWindow.get_monitor();
if (monitorIndex < 0) return;
const monitorRect = global.display.get_monitor_geometry(monitorIndex);
if (!monitorRect || monitorRect.width < monitorWidth) return;

const workArea = metaWindow.get_work_area_current_monitor();
if (!workArea) return;

const targetWidth = Math.min(maxWidth, workArea.width);
const targetHeight = Math.min(maxHeight, workArea.height);
if (targetWidth <= 0 || targetHeight <= 0) return;

const frameRect = metaWindow.get_frame_rect();
const width = Math.min(frameRect.width, targetWidth);
const height = Math.min(frameRect.height, targetHeight);

let x = frameRect.x;
let y = frameRect.y;

const maxX = workArea.x + workArea.width - width;
const maxY = workArea.y + workArea.height - height;
x = Math.min(Math.max(x, workArea.x), maxX);
y = Math.min(Math.max(y, workArea.y), maxY);

if (
frameRect.width === width &&
frameRect.height === height &&
frameRect.x === x &&
frameRect.y === y
) {
return;
}

this.move(metaWindow, {
x: Math.round(x),
y: Math.round(y),
width: Math.round(width),
height: Math.round(height),
});
}

get allNodeWindows() {
return this.tree.getNodeByType(NODE_TYPES.WINDOW);
}
Expand Down
38 changes: 37 additions & 1 deletion lib/prefs/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Logger } from "../shared/logger.js";
import { production } from "../shared/settings.js";

// Prefs UI
import { DropDownRow, SwitchRow, PreferencesPage, EntryRow } from "./widgets.js";
import { DropDownRow, SwitchRow, PreferencesPage, EntryRow, SpinButtonRow } from "./widgets.js";

// Extension imports
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
Expand Down Expand Up @@ -118,6 +118,42 @@ export class SettingsPage extends PreferencesPage {
settings,
bind: "float-always-on-top-enabled",
}),
new SpinButtonRow({
title: _("Minimum windows before tiling"),
subtitle: _("Keep windows floating until this number is reached"),
settings,
bind: "minimum-tiled-windows",
range: [1, 10, 1],
}),
],
});
this.add_group({
title: _("Ultrawide window limits"),
description: _("Restrict floating window dimensions on large monitors"),
children: [
new SpinButtonRow({
title: _("Ultrawide monitor width"),
subtitle: _(
"Apply limits when the monitor width meets or exceeds this value (0 disables)"
),
settings,
bind: "ultrawide-monitor-width",
range: [0, 10000, 10],
}),
new SpinButtonRow({
title: _("Max window width"),
subtitle: _("Maximum floating window width on ultrawide monitors"),
settings,
bind: "ultrawide-max-window-width",
range: [0, 10000, 10],
}),
new SpinButtonRow({
title: _("Max window height"),
subtitle: _("Maximum floating window height on ultrawide monitors"),
settings,
bind: "ultrawide-max-window-height",
range: [0, 10000, 10],
}),
],
});
this.add_group({
Expand Down
20 changes: 20 additions & 0 deletions schemas/org.gnome.shell.extensions.forge.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,26 @@
<summary>Whether to show the tab decoration or not</summary>
</key>

<key type="u" name="minimum-tiled-windows">
<default>3</default>
<summary>Minimum number of windows before tiling is enabled</summary>
</key>

<key type="u" name="ultrawide-monitor-width">
<default>5160</default>
<summary>Monitor width threshold for applying ultrawide window limits</summary>
</key>

<key type="u" name="ultrawide-max-window-width">
<default>1720</default>
<summary>Maximum width for windows on ultrawide monitors</summary>
</key>

<key type="u" name="ultrawide-max-window-height">
<default>1440</default>
<summary>Maximum height for windows on ultrawide monitors</summary>
</key>

</schema>
<schema id="org.gnome.shell.extensions.forge.keybindings" path="/org/gnome/shell/extensions/forge/keybindings/">
<!-- Keybinding Settings -->
Expand Down