Skip to content
47 changes: 30 additions & 17 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ MSGSRC = $(wildcard po/*.po)

all: build install enable restart

# When developing locally
test-x: disable uninstall build debug install enable restart log

test-wayland: clean build debug install test-shell log

dev: build debug install

prod: build install enable restart log
Expand Down Expand Up @@ -77,14 +72,16 @@ enable:
gnome-extensions enable "$(UUID)"

disable:
gnome-extensions disable "$(UUID)"
gnome-extensions disable "$(UUID)" || echo "Nothing to disable"

install:
mkdir -p $(INSTALL_PATH)
cp -r temp/* $(INSTALL_PATH)

uninstall:
rm -rf $(INSTALL_PATH)

purge:
rm -rf .config/forge

# When releasing
Expand All @@ -99,31 +96,47 @@ restart:
gnome-session-quit --logout; \
fi

log:
journalctl -o cat -n 0 -f "$$(which gnome-shell)" | grep -v -E 'warning|g_variant'
horizontal-line:
@printf '%.s─' $$(seq 1 $$(tput cols)) && echo || true # Prints a line of dashes #

log: GNOME_SHELL_CMD=$(shell command -v gnome-shell)
log: horizontal-line
@echo 'HINT: type [Ctrl]+[C] to return to the prompt.'
journalctl --user --follow --output=short-iso --lines=10 --since='10 seconds ago' --grep 'warning|g_variant' "$(GNOME_SHELL_CMD)"

journal:
journalctl -b 0 -r --since "1 hour ago"

test-shell:
test-nested: horizontal-line
env GNOME_SHELL_SLOWDOWN_FACTOR=2 \
MUTTER_DEBUG_DUMMY_MODE_SPECS=1500x1000 \
MUTTER_DEBUG_DUMMY_MONITOR_SCALES=1 \
MUTTER_DEBUG_DUMMY_MONITOR_SCALES=1 \
GDK_BACKEND=wayland \
WAYLAND_DISPLAY=wayland-forge \
dbus-run-session -- gnome-shell --nested --wayland --wayland-display=wayland-forge

# Usage:
# make test-shell-open &
# make test-shell-open CMD=gnome-text-editor
# make test-shell-open CMD=gnome-terminal ARGS='--app-id app.x'
# make test-shell-open CMD=firefox ARGS='--safe-mode' ENVVARS='MOZ_DBUS_REMOTE=1 MOZ_ENABLE_WAYLAND=1'
# make test-open &
# make test-open CMD=gnome-text-editor
# make test-open CMD=gnome-terminal ARGS='--app-id app.x'
# make test-open CMD=gnome-gnome-www-browser
# make test-open CMD=firefox ARGS='--safe-mode' ENVVARS='MOZ_DBUS_REMOTE=1 MOZ_ENABLE_WAYLAND=1'
#
test-shell-open: CMD=nautilus
test-shell-open:
test-open: CMD=gnome-text-editor
test-open:
GDK_BACKEND=wayland WAYLAND_DISPLAY=wayland-forge $(ENVVARS) $(CMD) $(ARGS)&

# When developing locally
test: disable uninstall clean build debug install enable test-nested

# X-Window testing need gnome-shell restart
test-x: disable uninstall purge build debug install enable restart log

format:
npm run format

# npx prettier --list-different "./**/*.{js,jsx,ts,tsx,json}"
lint:
npm test

check:
npx prettier --check "./**/*.{js,jsx,ts,tsx,json}"
41 changes: 32 additions & 9 deletions lib/extension/window.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ export class WindowManager extends GObject.Object {
this.prefsTitle = `Forge ${_("Settings")} - ${
!production ? "DEV" : `${PACKAGE_VERSION}-${ext.metadata.version}`
}`;
this.windowProps = this.ext.configMgr.windowProps;
this.windowProps.overrides = this.windowProps.overrides.filter((override) => !override.wmId);
this.reloadWindowOverrides();
this._kbd = this.ext.keybindings;
this._tree = new Tree(this);
this.eventQueue = new Queue();
Expand Down Expand Up @@ -98,11 +97,12 @@ export class WindowManager extends GObject.Object {
}

addFloatOverride(metaWindow, withWmId) {
let overrides = this.windowProps.overrides;
let currentProps = this.ext.configMgr.windowProps;
let overrides = currentProps.overrides;
let wmClass = metaWindow.get_wm_class();
let wmId = metaWindow.get_id();

for (let override in overrides) {
for (let override of overrides) {
// if the window is already floating
if (override.wmClass === wmClass && override.mode === "float" && !override.wmTitle) return;
}
Expand All @@ -111,12 +111,15 @@ export class WindowManager extends GObject.Object {
wmId: withWmId ? wmId : undefined,
mode: "float",
});
this.windowProps.overrides = overrides;
this.ext.configMgr.windowProps = this.windowProps;

// Save the updated overrides back to the ConfigManager
currentProps.overrides = overrides;
this.ext.configMgr.windowProps = currentProps;
}

removeFloatOverride(metaWindow, withWmId) {
let overrides = this.windowProps.overrides;
let currentProps = this.ext.configMgr.windowProps;
let overrides = currentProps.overrides;
let wmClass = metaWindow.get_wm_class();
let wmId = metaWindow.get_id();
overrides = overrides.filter(
Expand All @@ -129,8 +132,9 @@ export class WindowManager extends GObject.Object {
)
);

this.windowProps.overrides = overrides;
this.ext.configMgr.windowProps = this.windowProps;
// Save the updated overrides back to the ConfigManager
currentProps.overrides = overrides;
this.ext.configMgr.windowProps = currentProps;
}

toggleFloatingMode(action, metaWindow) {
Expand Down Expand Up @@ -285,6 +289,11 @@ export class WindowManager extends GObject.Object {

settings.connect("changed", (_, settingName) => {
switch (settingName) {
case "window-overrides-reload-trigger":
// Reload window overrides when triggered by preferences
// This prevents the main extension from overwriting changes made by preferences
this.reloadWindowOverrides();
break;
case "focus-border-toggle":
this.renderTree(settingName);
break;
Expand Down Expand Up @@ -2768,6 +2777,20 @@ export class WindowManager extends GObject.Object {
return `mo${display.get_current_monitor()}`;
}

/**
* Reload window overrides from the configuration file
* This is called when the preferences page modifies the overrides
*/
reloadWindowOverrides() {
// Get fresh data from the ConfigManager
const freshProps = this.ext.configMgr.windowProps;
if (freshProps) {
this.windowProps = freshProps;
this.windowProps.overrides = this.windowProps.overrides.filter((override) => !override.wmId);
Logger.info(`Reloaded ${this.windowProps.overrides.length} window overrides from file`);
}
}

floatAllWindows() {
this.tree.getNodeByType(NODE_TYPES.WINDOW).forEach((w) => {
if (w.isFloat()) {
Expand Down
5 changes: 2 additions & 3 deletions lib/prefs/appearance.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensio

// Shared state
import { ConfigManager } from "../shared/settings.js";

import { PrefsThemeManager } from "./prefs-theme-manager.js";

// Prefs UI
Expand Down Expand Up @@ -42,8 +41,8 @@ export class AppearancePage extends PreferencesPage {
constructor({ settings, dir }) {
super({ title: _("Appearance"), icon_name: "brush-symbolic" });
this.settings = settings;
this.configMgr = new ConfigManager({ dir });
this.themeMgr = new PrefsThemeManager(this);
let configMgr = new ConfigManager({ dir });
this.themeMgr = new PrefsThemeManager({ configMgr: configMgr, settings: settings });
this.add_group({
title: _("Gaps"),
description: _("Change the gap size between windows"),
Expand Down
82 changes: 82 additions & 0 deletions lib/prefs/floating.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Gtk imports
import Gtk from "gi://Gtk";
import GObject from "gi://GObject";

// Gnome imports
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";

// Extension imports
import { PreferencesPage, RemoveItemRow, ResetButton } from "./widgets.js";
import { ConfigManager } from "../shared/settings.js";

export class FloatingPage extends PreferencesPage {
static {
GObject.registerClass(this);
}

constructor({ settings, dir }) {
super({ title: _("Windows"), icon_name: "window-symbolic" });

this.settings = settings;
this.configMgr = new ConfigManager({ dir });

let overrides = this.configMgr.windowProps.overrides;
this.rows = this.loadItemsFromConfig(overrides);

this.floatingWindowGroup = this.add_group({
title: _("Floating Windows"),
description: _("Windows that will not be tiled"),
header_suffix: new ResetButton({ onReset: () => this.onResetHandler() }),
children: this.rows,
});
}

loadItemsFromConfig(overrides) {
let children = [];
for (let override of overrides) {
if (override.mode === "float") {
let itemrow = new RemoveItemRow({
title: override.wmTitle ?? override.wmClass,
subtitle: override.wmClass,
onRemove: (item, parent) => this.onRemoveHandler(item, parent),
});
children.push(itemrow);
}
}
return children;
}

onRemoveHandler(item, parent) {
this.floatingWindowGroup.remove(parent);
this.rows = this.rows.filter((row) => row != parent);
const existing = this.configMgr.windowProps.overrides;
const modified = existing.filter((row) => item != row.wmClass);
this.saveOverrides(modified);
}

saveOverrides(modified) {
if (modified) {
this.configMgr.windowProps = {
overrides: modified,
};
// Signal the main extension to reload floating overrides
const changed = Math.floor(Date.now() / 1000);
this.settings.set_uint("window-overrides-reload-trigger", changed);
}
}

onResetHandler() {
const defaultWindowProps = this.configMgr.loadDefaultWindowConfigContents();
const original = defaultWindowProps.overrides;
this.saveOverrides(original);

for (const child of this.rows) {
this.floatingWindowGroup.remove(child);
}

this.rows = this.loadItemsFromConfig(original);
for (const item of this.rows) {
this.floatingWindowGroup.add(item);
}
}
}
1 change: 1 addition & 0 deletions lib/prefs/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { PACKAGE_VERSION } from "resource:///org/gnome/Shell/Extensions/js/misc/
import { developers } from "./metadata.js";

function showAboutWindow(parent, { version, description: comments }) {
version = version ?? "development";
const abt = new Adw.AboutWindow({
...(parent && { transient_for: parent }),
// TODO: fetch these from github at build time
Expand Down
35 changes: 35 additions & 0 deletions lib/prefs/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class PreferencesPage extends Adw.PreferencesPage {
for (const child of children) group.add(child);
if (header_suffix) group.set_header_suffix(header_suffix);
this.add(group);
return group;
}
}

Expand Down Expand Up @@ -247,6 +248,23 @@ export class ResetButton extends Gtk.Button {
}
}

export class RemoveButton extends Gtk.Button {
static {
GObject.registerClass(this);
}
constructor({ item, parent, onRemove }) {
super({
icon_name: "edit-delete-symbolic",
tooltip_text: _("Remove Item"),
css_classes: ["flat", "circular"],
valign: Gtk.Align.CENTER,
});
this.connect("clicked", () => {
onRemove?.(item, parent);
});
}
}

export class EntryRow extends Adw.EntryRow {
static {
GObject.registerClass(this);
Expand Down Expand Up @@ -319,3 +337,20 @@ export class RadioRow extends Adw.ActionRow {
this.add_suffix(hbox);
}
}

export class RemoveItemRow extends Adw.ActionRow {
static {
GObject.registerClass(this);
}

constructor({ title, subtitle = "", onRemove = undefined }) {
super({ title, subtitle });
const rmbutton = new RemoveButton({
item: subtitle,
parent: this,
onRemove: onRemove,
});

this.add_suffix(rmbutton);
}
}
17 changes: 14 additions & 3 deletions lib/shared/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export class ConfigManager extends GObject.Object {
if (defaultStylesheetFile.query_exists(null)) {
return defaultStylesheetFile;
}

return null;
}

Expand All @@ -75,7 +74,17 @@ export class ConfigManager extends GObject.Object {
if (defaultWindowConfigFile.query_exists(null)) {
return defaultWindowConfigFile;
}
return null;
}

loadDefaultWindowConfigContents() {
const defaultSettingFile = this.defaultWindowConfigFile;
if (defaultSettingFile) {
const contents = this.loadFileContents(defaultSettingFile);
if (contents) {
return JSON.parse(contents);
}
}
return null;
}

Expand Down Expand Up @@ -119,7 +128,8 @@ export class ConfigManager extends GObject.Object {
get windowProps() {
let windowConfigFile = this.windowConfigFile;
let windowProps = null;
if (!windowConfigFile || !production) {
// if (!windowConfigFile || !production) {
if (!windowConfigFile) {
windowConfigFile = this.defaultWindowConfigFile;
}

Expand All @@ -134,7 +144,8 @@ export class ConfigManager extends GObject.Object {

set windowProps(props) {
let windowConfigFile = this.windowConfigFile;
if (!windowConfigFile || !production) {
// if (!windowConfigFile || !production) {
if (!windowConfigFile) {
windowConfigFile = this.defaultWindowConfigFile;
}

Expand Down
Loading