Skip to content

Add Total Commander-style locked tabs (lock, protect from closing, restore at locked folder)#3804

Open
dzyla wants to merge 9 commits into
linuxmint:masterfrom
dzyla:locked-tabs-upstream
Open

Add Total Commander-style locked tabs (lock, protect from closing, restore at locked folder)#3804
dzyla wants to merge 9 commits into
linuxmint:masterfrom
dzyla:locked-tabs-upstream

Conversation

@dzyla

@dzyla dzyla commented Jul 7, 2026

Copy link
Copy Markdown

Adds Total Commander-style locked tabs, building on the tab session restore merged in #3661.

What it does

  • Right-clicking a tab shows a new check menu item, Lock Tab. Locking a tab captures its current directory as the tab's locked folder.
  • A locked tab shows a padlock icon (changes-prevent-symbolic) in its tab label and its close button is hidden.
  • All UI close paths silently ignore a locked tab: Ctrl+W / File > Close, the tab close button, middle-click on the tab, and the tab context menu's Close Tab item (which is also desensitized). To close a locked tab, unlock it first.
  • A locked tab has two behaviours, chosen by a second check item, Open Changes in New Tab:
    • default (return): navigation stays free, and the tab goes back to its locked folder whenever it becomes the current tab again.
    • new tab: the tab never leaves its locked folder; anything that would navigate it opens in a fresh, unlocked tab instead.
  • With org.nemo.preferences restore-tabs-on-startup enabled, lock state persists across restarts: a locked tab is restored at its locked folder (not the last visited directory) and comes back locked; unlocked tabs keep the existing behaviour of restoring at their last location.
  • Session state is now also saved when the session manager ends the session, so tabs survive a logout rather than only an explicit window close.

What it deliberately does not do

  • Window close/quit and internal cleanup paths (failed locations, session-restore's tab rebuild) are not blocked by locks — locked tabs are saved, not trapped.
  • Search is exempt from the new tab diversion: it is a view of the locked folder rather than a move away from it, and its completion callback assumes the search landed in the slot that started it.
  • Non-native locations (sftp, search) keep being filtered from the saved session by the existing uri_is_native_session_uri, unchanged from Restore Last Window Tabs on Startup #3661. A lock on such a tab works within the session but is not persisted.

Implementation

src/nemo-window-slot.{h,c}NemoWindowSlot gains NemoTabLockMode lock_mode (NONE / RETURN / NEW_TAB) and char *locked_uri, with nemo_window_slot_set_lock_mode (slot, mode, locked_uri) plus getters. Passing locked_uri == NULL captures the slot's pending or current location (menu toggle path); passing a URI locks explicitly (session-restore path). The setter triggers a tab-label resync; locked_uri is freed in dispose. A RETURN tab is sent back to its locked folder from the slot's active handler, on an idle, so the snap-back does not race the in-flight ::switch-page; doing it on activation rather than deactivation avoids navigating a background slot, whose fresh view would steal focus back.

src/nemo-notebook.{h,c}build_tab_label () packs a hidden padlock GtkImage into the tab-label hbox. New nemo_notebook_sync_lock () toggles padlock and close-button visibility, and sets a tooltip describing the active lock mode.

src/nemo-window-pane.c — the tab context menu gains a separator, the Loc_k Tab check item, and the Open Changes in New _Tab check item (sensitive only while locked). Check states are set before the signals are connected, so building the menu doesn't toggle anything. The Close Tab item is desensitized for locked tabs. notebook_tab_close_requested () — the funnel for the tab close button, middle-click, and the popup Close item — returns early for locked tabs.

src/nemo-window-menus.caction_close_window_slot_callback () (Ctrl+W / File > Close) returns early for a locked active tab.

src/nemo-window-manage-views.cnemo_window_slot_open_location_full () diverts a NEW_TAB slot's navigation into a fresh tab. nemo_window_back_or_forward () routes such a slot through open_location so a history step becomes a new tab rather than moving the locked one.

src/nemo-window.c + libnemo-private/org.nemo.gschema.xmlsaved-tabs-left / saved-tabs-right keep their existing as type. Lock modes travel in two new position-aligned ai keys, saved-tab-locks-left / saved-tab-locks-right. A lock list may be shorter than its tab list — anything it does not cover is an unlocked tab — so a session written by a Nemo without tab locking restores unchanged, and a pane with nothing locked writes [] exactly as today. Trailing unlocked entries are trimmed on save to keep that the common case.

Retyping the existing keys was tried first and is a bad idea: GSettings answers a type mismatch by discarding the stored value and returning the default, so changing saved-tabs-* from as to a tuple array silently throws away the saved tabs of everyone upgrading past #3661. The parallel-key layout avoids that, and is forward- and backward-compatible in both directions.

src/nemo-application.c — session state was only written from real_window_close () and the Quit action, so logging out with a window open saved nothing: the session manager sends SIGTERM and, with no handler installed, the process died where it stood. SIGTERM and SIGHUP now save through the same path Quit uses, followed by g_settings_sync (), since both callers are on their way out and an unflushed GSettings write dies with the process.

Testing

No unit harness covers window code, so this was verified behaviourally on a Mint system with packages built from this branch, with restore-tabs-on-startup enabled.

Interactive:

  • Lock a tab → padlock shown, close button hidden, Close Tab greyed out; Ctrl+W and middle-click do nothing; navigation inside the tab still works.
  • A return-locked tab navigated elsewhere snaps back to its locked folder when reselected; a new tab-locked tab never moves, and its navigation lands in a new unlocked tab (including Back/Forward).
  • Unlock → close button returns, Ctrl+W closes the tab.
  • Close the window and relaunch → both panes, tab order, active tabs, and split view restored; locked tabs reopen locked at their locked folder; unlocked tabs reopen at their last locations.
  • Log out and back in → same, which previously lost every tab.
  • nemo <path> still bypasses restore.

Scripted against the built binary, each run in a private session bus and dconf database (dbus-run-session + XDG_CONFIG_HOME), using timeout --signal=TERM to stand in for a logout:

PASS  SIGTERM saves session (was: saved nothing)
PASS  legacy 'as' data survives (was: wiped)
PASS  lock modes round-trip, no phantom tab
PASS  all-unlocked trims to empty lock list
PASS  out-of-range lock mode clamps to unlocked

Schema compiles with glib-compile-schemas --strict; build is warning-clean in the touched files.

🤖 Generated with Claude Code

dzyla and others added 9 commits July 6, 2026 18:46
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A locked tab previously only refused to close and reopened at its locked
folder on restart; nothing returned it there at runtime, so a locked tab
that had been navigated away stayed away.

Replace the pinned boolean with NemoTabLockMode:

  NONE     navigate freely, keep the last folder (default)
  RETURN   navigate freely, return to the locked folder when reselected
  NEW_TAB  never navigate, divert folder changes into a new unlocked tab

Both locked modes keep the existing close protection. Enforcement sits in
nemo_window_slot_open_location_full(), which every navigation call site
routes through, plus nemo_window_back_or_forward(), which reaches
begin_location_change() directly when flags are 0. The RETURN snap-back
hangs off ::switch-page, deferred to an idle so it does not navigate while
the notebook is still switching.

Search is exempt from the NEW_TAB diversion: sync_search_location_cb()
asserts the originating slot holds a search directory, so sending the
search to another tab would abort.

The tab context menu grows an "Open Changes in New Tab" check item,
sensitive only while the tab is locked.

Saved sessions change from a(sb) to a(si), each entry being (uri, lock
mode). All tabs are saved; locked ones save their locked folder. Sessions
written by the earlier a(sb) build are dropped once on type mismatch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The snap-back for NEMO_TAB_LOCK_RETURN hung off the notebook's
::switch-page, which only fires when a notebook's current page changes. A
locked tab can stop being the one in use without that happening: switching
to the other pane in split view, or being the only tab in its pane. In
those cases the tab never went home, so a tab locked at ~ and navigated to
~/Downloads still showed Downloads on return.

Move the hook to the slot's own "active" handler. nemo_window_set_active_slot()
emits it for every way a slot becomes current, covering tab switches and
pane switches alike.

Hooking "inactive" instead would park the tab the moment it is left, but
navigating a background slot makes its fresh view take focus - the icon and
list views call nemo_window_slot_make_hosting_pane_active() from their
focus-in handlers - which drags the user back to the tab they just left.
Activation-side navigation touches only the slot already in focus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commit 901f12b changed saved-tabs-left/right from "as" to "a(sb)", and
6ed2207 changed them again to "a(si)". Both retyped a key that already
ships in master, and GSettings answers a type mismatch by discarding the
stored value and handing back the default. Every tab a user had saved
went silently missing on upgrade -- twice.

The plan justified this as safe because the keys were "internal and
unreleased", which was wrong on the second count: they landed upstream
in PR linuxmint#3661.

Leave saved-tabs-left/right at "as" and carry the lock modes in new
position-aligned "ai" keys, saved-tab-locks-left/right. A lock list may
be shorter than its tab list -- anything it does not cover is unlocked --
so tabs written by a Nemo without tab locking restore unchanged, and a
pane with nothing locked writes [] exactly as before. Trailing unlocked
entries are trimmed on save to keep that the common case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Session state was only written from real_window_close and the Quit
action, so logging out with a window open saved nothing: the session
manager sends SIGTERM, and with no handler installed the process died
where it stood. Tabs only ever persisted if you closed the window by
hand first, which defeats the point of restoring them.

Handle SIGTERM and SIGHUP, saving through the same path Quit uses. Flush
with g_settings_sync() once the state is written, since both callers are
on their way out and an unflushed GSettings write dies with the process.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
notebook_switch_page_cb credited the slot's "inactive" handler for
sending a locked tab home, but d1a39ce deliberately moved that to the
"active" handler; the comment said the opposite of what the code does.
build_tab_label still called locked tabs "pinned", the name 6ed2207
replaced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dzyla dzyla changed the title Add Total Commander-style locked tabs (pin, protect from closing, restore pinned) Add Total Commander-style locked tabs (lock, protect from closing, restore at locked folder) Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant