Repository navigation
Working on Gnome 51 - #590
Open
abdullah-rafiq wants to merge 61 commits into
Open
abdullah-rafiq wants to merge 61 commits into
abdullah-rafiq wants to merge 61 commits into
Conversation
A WINDOW node is allowed to hold a null rect, since its branch in the
setter breaks immediately and never touches an actor. split() then copies
that value onto a newly created CON node via `newConNode.rect = node.rect`,
and the CON branch does dereference it, so set_size() throws.
This surfaces as an unhandled `TypeError: rect is null` at tree.js:129,
reached from trackWindow() -> command("Split") -> split() when
auto-split-enabled is true and the window has not been through a
renderTree pass yet, so its rect is still unset.
The throw escapes a display "workareas-changed" handler, which runs while
mutter is reconfiguring monitors and work areas.
Guard the actor geometry calls instead of the assignment, since any caller
can reach the setter with a null rect. The node still records _rect = null
and the following renderTree assigns a real rect and sizes the actor, so
there is no visual change.
Refs forge-ext#540
With "make -j<n>" the prerequisites of "build: clean ..." run concurrently, so "clean" can delete schemas/gschemas.compiled right after it was compiled. The installed extension then fails to load with "gschemas.compiled ... not found". Serialize the Makefile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r broken
Reading ~/.config/forge/config/windows.json did JSON.parse() without a
fallback, so an empty file or a typo in it made enabling Forge fail ("an error
occurred while loading this extension", forge-ext#415). Forge itself could leave the
file empty: loadFile() wrote the default rules to a new file without closing
the stream.
When the file is empty, isn't valid JSON or has no overrides list, use the
default rules and say so with console.warn (Forge's Logger is silent in
release builds). The next change of the rules replaces the file, so first keep
a copy of the broken one as windows.json.bak. Close the stream after writing.
Fixes forge-ext#415
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…issions patchCss() named the backup after this.configMgr.stylesheetFileName, which doesn't exist, so it went to "undefined.bak" in GNOME Shell's working directory (the home folder). Both copies took their source's permissions: with a read-only install (e.g. NixOS) the user's stylesheet and the backup became read-only, and at the next update the copy failed with "Permission denied" and Forge didn't start. The backup is stylesheet.css.bak next to the stylesheet, the copies get default permissions, a read-only copy left by an earlier version is replaced, and a failed update is logged instead of stopping Forge. Fixes forge-ext#266 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reads
getCssRule() returned {} for a rule it didn't find, and getCssProperty()
read that as a rule ("cssRule.declarations is undefined"), so Forge
didn't start and the preferences didn't open when the user's
stylesheet was empty, lacked a rule (e.g. from an older Forge) or had
a comment in it (no selectors).
Lookups now return null / {} for what is missing. The rules and
properties the user's stylesheet lacks are taken from the default
stylesheet; a stylesheet that can't be parsed is replaced by the
default one. In both cases the completed stylesheet is saved, so the
shell styles everything, and the user's file is kept as
stylesheet.css.bak.
Fixes forge-ext#448
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXwsqMVgdEQYGec49zjaiP
Gio.File.replace_contents() writes to a temporary file and renames it over the destination when the stream is closed. When a write fails part way, for example with ENOSPC on a full disk, GLib still closes the stream normally, so the partially written temporary file replaces the original. On a full disk that file is empty. Forge saves windows.json every time a window is closed, so a full disk empties it and the next login fails with "JSON.parse: unexpected end of data" (forge-ext#415). The stylesheet is saved the same way. GLib.file_set_contents() removes its temporary file when a write fails and leaves the original in place. Like the REPLACE_DESTINATION flag used before, it replaces symlinks and does not keep the old permissions.
Use maximize flags so single-axis tiled windows are recognized, and avoid a second unmaximize call on Mutter 49+. Mutter 51 can restore an unmaximized window's old configured rectangle during that redundant call, causing tiled Wayland windows to bounce across monitors. Adapted from jcrussell/forge commit 6653216 and forge-ext/forge PR forge-ext#589.
Tree.split() pushed a window down into a new container by creating a new node for it, leaving the old node detached but with its parentNode still set. The new node lost the window's mode (a window being dragged turned back from GRAB_TILE to TILE) and state, and the old one stayed referenced, e.g. as the drag-and-drop target. When such a detached node was appended elsewhere, removeChild() looked it up by value, found the window's other node, and spliced at node.index, which is null for a detached node: splice(null, 1) removed the parent's first child, another window. The detached node then joined the tree as a second node of the same window, and every render moved the window to both tiles. Move the window's own node into the new container in split(), make removeChild() detach exactly the given node and clear its parentNode, and drop any duplicate window node before rendering. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The float toggle (window-toggle-float) saves a per-window rule, with the window id, in windows.json. Three problems made it fail intermittently: - addFloatOverride() returned early when any float rule existed for the window class, including a rule for another window of that class, so a second terminal, say, could not be floated. - Window ids only hold for a session, but the per-window rules stayed in windows.json across sessions, where they blocked the case above and could match an unrelated window. - reloadWindowOverrides() dropped the per-window rules from the cache, so a reload triggered from the preferences tiled back the windows floated with the toggle. Only treat a class-wide rule, or this window's own rule, as "already floating"; purge per-window rules left by previous sessions at startup; and keep the current session's per-window rules when reloading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Let focus-update and raise-float events opt into coalescing so a rapid focus burst cannot build a long queue of redundant renders. Their callbacks inspect the current focus when dispatched. Adapted from forge-ext/forge PR forge-ext#589 commit 0c67b1b.
Do not steal focus while the shell is modal or locked. Treat popup, notification, and override-redirect windows as occluders, and only focus when the pointer enters another eligible window. Adapted from jcrussell/forge commit 9354f87.
Window wrappers can outlive their compositor actor. Route focus, raise, and activate calls through a managed-window check so close and swap races do not invoke Mutter on an unmanaged window. Adapted from jcrussell/forge commit 7bb2af9.
Wait for unmapped Wayland toplevels to become mapped before adding them to the tiling tree, clean up their temporary signal watches on unmanage or disable, and skip redundant geometry requests within one pixel. Preserve the initial map animation on first placement. Adapted from forge-ext/forge PR forge-ext#589 and jcrussell/forge GNOME 51 fixes.
Wayland may report a temporary monitor crossing while a tiled window is being resized. Keep the tree node on its current monitor until the next render restores its tile, while allowing floats and user-driven drags to move normally. Adapted from forge-ext/forge PR forge-ext#589 commit b2202e5.
Auto-split only when the focused node has a computed rectangle and parent. Splitting an unplaced focused window could leave a new window untracked and break the next float toggle. Adapted from forge-ext/forge PR forge-ext#589 commit dd924c3.
Only clear the firstRender marker when a geometry request succeeds or the window is already in its tile. A new Wayland window may be rendered before its first frame, and clearing the marker on that skipped move made the next placement strip the opening animation. Adapted from jcrussell/forge first-placement animation handling.
Create missing user config files even when their parent directories already exist, and avoid falling back to writing the bundled windows.json or stylesheet.css when the user config cannot be created. Report failed custom-file creation without modifying extension defaults. Adapted from forge-ext/forge PR forge-ext#536 and PR forge-ext#587.
Synchronously re-home surviving windows before removing a doomed workspace subtree so windows are not stranded without tiling. Re-number workspace and monitor tree nodes on workspace removal and addition so index-keyed lookups remain valid. Rekey workspace signal entries in two phases and anchor them to their MetaWorkspace object so reorders disconnect the correct workspace without handler leaks. Tear down monitor bins and container decorations during workspace removal and scaffold cleanup on disable.
…clamping - utils.js: add getMonitorGeometrySafe() null-safe wrapper - window.js determineSplitLayout: use getMonitorGeometrySafe instead of raw call - window.js move(): clamp committed frame to work area; accept optional workArea param - tree.js move() edge-wrap branch: capture priorParent, call _finishMove before return - tree.js move() cross-monitor branch: do geometry move before reparent (finalized safety); pass workArea to extWm.move() - tree.js move(): refactor end to call shared _finishMove(parentNode, parentTarget) - tree.js: add _finishMove() shared epilogue for structural moves (resetSiblingPercent + resetLayoutSingleChild) - tree.js processStacked(): cap totalBars to nodeHeight-1 so content stays in container - tree.js processTabbed(): guard nodeHeight to Math.max(..., 1) preventing negative height
The workspace-removal port called Utils.isWindowAlive and Utils.createMonitorWorkspaceId, but neither helper existed, so the synchronous rehome threw and its catch skipped every window. Mutter also shifts workspace indices before emitting workspace-removed while the tree is renumbered after the rehome, so windows relocated into a later index resolved to the doomed workspace node and were removed with it. Map live indices back to the tree's old keys during removal, rebuild the scaffold when a workspace count change emits no add/remove signal, and stop MODE searches from falling through into LAYOUT matching. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
Applying a tile rectangle to a fullscreen window fought Mutter's fullscreen geometry on every render, and a window tracked while get_current_monitor() transiently reported -1 built an unresolvable monitor key and scheduled a reload that retried it. Skip fullscreen windows in placement, reject moves while fullscreen, and fall back to the window's monitor when the current monitor is invalid. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
Closing the last window of a single-child sub-container collapsed that container, but the auto-exit-tabbed check then inspected the detached sub-container instead of the tabbed grandparent, leaving a one-tab layout stuck in tabbed mode. Also stop the live-resize polling source in _removeSignals so disabling during a grab cannot recreate a tree. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
Closing the last window in one container reset the percentages of every sibling at the monitor and workspace levels, discarding split proportions the user had resized. Reset only within the container that actually lost its tiled children.
Validate get_monitor() through one helper before every remaining workspace/monitor lookup, fall back to the node's monitor while rehoming, and catch invalid per-monitor work areas. Stacked layout now uses the same DPI-scaled bar height as tabbed, gives every window the shared content area below a vertical tab column, and shares decoration placement with tabbed so both layouts stay aligned.
Skip finalized windows while cleaning the tree instead of reading their title and class, give drag-created containers the settings object the rest of the tree uses, and clear child tab references when a decoration is destroyed so stacked/tabbed containers can rebuild tabs after they are detached and reattached.
The Windows checkout wrote CRLF into the Makefile, so make inside WSL failed on carriage returns in every recipe; force LF for it in .gitattributes. Also drop the literal quotes around the UUID, replace the install directory instead of layering over stale files, skip gettext cleanly when it is not installed, and report a missing glib-compile-schemas instead of failing with a bare command error.
Disable Forge, rebuild and replace its install directory, then re-enable it, so testing a new build no longer needs three manual commands while the running Shell may still have old modules loaded.
A download without a .git directory made bare `git shortlog` read stdin and wait forever during the metadata step. Pass HEAD so git fails fast, skip shortlog outside a work tree, and emit a fallback contributor entry so the preferences About page still builds.
gnome-extensions resolves user extensions from XDG_DATA_HOME, so a custom data home installed Forge into a directory the Shell never scanned and reported that the extension did not exist.
The Tree constructor binds a signal to every workspace, but WindowManager created the Tree before allocating _workspaceSignals, so the first bindWorkspaceSignals() call threw on an undefined Map and left the extension in the ERROR state. Allocate the signal maps and pending window set before the Tree is constructed.
…n disable The tree binds workspace signals from its own constructor, so any code path that reaches bindWorkspaceSignals before the WindowManager constructor finishes threw on an undefined Map and left enable() in an error state. Create the bookkeeping lazily instead of relying on field order, and release the hover-focus timeout during disable instead of leaving it to expire.
On Wayland Desktop Icons NG draws the desktop as an ordinary NORMAL window titled @!<x>,<y>;BDHF. Forge tracked it as a tile, unmaximized it, and DING re-maximized and raised itself, putting the icons above every window and stealing clicks.
A workspace window-added timer or a queued event could fire after disable() tore down the tree and the extension nulled its settings, giving "TypeError: can't access property get_boolean, this.ext.settings is null" (repeatedly seen on GNOME 51 Wayland). Guard the render path and the scheduled callbacks so late work is dropped instead of resurrecting a tree.
Current DING titles the desktop window "Desktop Icons <monitor+1>" (hardcoded, not translated) and only calls set_type(DESKTOP) after the window exists, so Forge still tracked it before the type changed. Match that title too; the GNOME 51 journal showed "Meta Window Desktop Icons 1" being tiled.
Move the title rules into a dependency-free module so the DING formats can be tested without GNOME Shell, and run them with node:test via `npm run test:unit`. Covers both the current "Desktop Icons <n>" title and the older @!x,y;BDHF form, plus ordinary titles that must not match.
`tsconfig.json` already had allowJs/checkJs but nothing ran it and the compiler was not installed. Add TypeScript, stubs for the shell modules and gi://Shell (no typings package exists), and a script that fails CI only on new errors; the 52 known ones are recorded in types/typecheck-baseline.json so the check can ratchet down. The annotation pass also found two GLib callbacks that still reached into the tree after disable(): the reload idle and the live-resize tick. Both re-check `disabled`, because a source that is already dispatching cannot be removed. Typing createEnum() as Record<T, T> keeps the enum member names, which removed 239 spurious type errors.
Type-checking is now clean (0 errors, down from 448). Beyond annotations, the checker surfaced real defects: tabbed/stacked center drops and swaps could reach set_style_class_name(undefined), two grab-op branches compared against constants GNOME 51 does not define, and the deprecated enableListenForBindings() path referenced an undefined windowConfig.
The extension clears its services at teardown, so every consumer saw a nullable settings/theme/tree. Declaring those non-nullable and breaking the type once, in the teardown cast, lets the checker catch the real hazards instead: nullable GSettings.get_string()/File.get_path() results, unguarded createNode() results, and a border width that handed the "3" string from removePx() to a double GObject property.
…ng tab icons Ported from jcrussell/forge (forge-qy65, forge-hcbz, forge-2s37). At integer HiDPI the tab icon was scaled twice (create_icon_texture already takes a logical size), while the border inset and window gaps stayed unscaled against physical-pixel rects, so borders painted over content and gaps visually halved.
Three defects the reference fork had already closed: - Logger kept the Gio.Settings handed to init() past disable(), so a disabled extension kept reading logging-enabled off a stranded object (forge-l2f7). - _updateCss() would overwrite a user stylesheet with a truncated round-trip when the parser had recorded errors, and left the AST diverged from disk when the write failed (forge-y3jy, forge-rohk). - Finalized Meta.Window wrappers are truthy, so the swap/resize pair-walk could throw on .minimized before the prune ran (forge-3cfd, forge-ib39).
get_workspace() throws on a finalized wrapper, so re-reading it after the monitor and node lookups races the null guard above. Captured once, matching the existing isWindowManaged liveness probes (upstream forge-ib39).
Both were hardcoded: the bar height was a 35px literal and the bar always sat above the content. Height is now read live from a clamped schema key, and the bar can be pinned to the bottom of its container, moving the content anchor rather than the reserved height. Ported from jcrussell/forge (forge-1a5, forge-drf) with the metadata-driven re-render wired in.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I am working on it things still need to be fixed. But it's the first step, and its in working condition