From 6fa182ad972368b950dff14b54361bb64846e969 Mon Sep 17 00:00:00 2001
From: JMastro-OneMIT <126523346+JMastro-OneMIT@users.noreply.github.com>
Date: Sun, 21 Sep 2025 11:53:16 -0700
Subject: [PATCH 1/2] Prevent metadata generation from waiting on stdin
---
Makefile | 8 +-
lib/extension/window.js | 105 +++++++++++++++++-
lib/prefs/settings.js | 38 ++++++-
...g.gnome.shell.extensions.forge.gschema.xml | 20 ++++
4 files changed, 165 insertions(+), 6 deletions(-)
diff --git a/Makefile b/Makefile
index 651f077..daa75e1 100644
--- a/Makefile
+++ b/Makefile
@@ -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)
@@ -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 > 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
@@ -70,7 +72,7 @@ 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:
diff --git a/lib/extension/window.js b/lib/extension/window.js
index 24356f5..ec5430a 100644
--- a/lib/extension/window.js
+++ b/lib/extension/window.js
@@ -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);
}
diff --git a/lib/prefs/settings.js b/lib/prefs/settings.js
index a178a1d..906766d 100644
--- a/lib/prefs/settings.js
+++ b/lib/prefs/settings.js
@@ -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";
@@ -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({
diff --git a/schemas/org.gnome.shell.extensions.forge.gschema.xml b/schemas/org.gnome.shell.extensions.forge.gschema.xml
index 241cc89..b205a4c 100644
--- a/schemas/org.gnome.shell.extensions.forge.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.forge.gschema.xml
@@ -148,6 +148,26 @@
Whether to show the tab decoration or not
+
+ 3
+ Minimum number of windows before tiling is enabled
+
+
+
+ 5160
+ Monitor width threshold for applying ultrawide window limits
+
+
+
+ 1720
+ Maximum width for windows on ultrawide monitors
+
+
+
+ 1440
+ Maximum height for windows on ultrawide monitors
+
+
From 0e9b077686edcbfbcffe69f1f7025fd853c409e7 Mon Sep 17 00:00:00 2001
From: JMastro-OneMIT <126523346+JMastro-OneMIT@users.noreply.github.com>
Date: Sun, 21 Sep 2025 12:01:18 -0700
Subject: [PATCH 2/2] Gracefully skip GNOME tooling when unavailable
---
Makefile | 31 ++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/Makefile b/Makefile
index daa75e1..e3321ac 100644
--- a/Makefile
+++ b/Makefile
@@ -76,11 +76,18 @@ clean:
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)
@@ -95,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"