Skip to content

Auto-dismount volumes on system sleep and screen lock on macOS#1818

Open
damianrickard wants to merge 1 commit into
veracrypt:masterfrom
damianrickard:feature/macos-dismount-on-sleep-and-lock
Open

Auto-dismount volumes on system sleep and screen lock on macOS#1818
damianrickard wants to merge 1 commit into
veracrypt:masterfrom
damianrickard:feature/macos-dismount-on-sleep-and-lock

Conversation

@damianrickard

Copy link
Copy Markdown
Contributor

On macOS, mounted volumes stayed mounted (and their keys resident in memory) when the system went to sleep or the screen was locked. The existing auto-dismount triggers do not cover these on macOS:

  • Power-suspend dismount is connected only under wxHAS_POWER_EVENTS, which wxWidgets does not define on macOS, so OnPowerSuspending never fires.
  • Screen-saver dismount in MainFrame::OnTimer is inside #ifdef TC_WINDOWS (SPI_GETSCREENSAVERRUNNING) and has no macOS path.

This adds a small Objective-C++ helper (MacOSXSleepLock) that observes NSWorkspaceWillSleepNotification and the com.apple.screenIsLocked distributed notification, and calls back into GraphicUserInterface to run the existing OnAutoDismountAllEvent() path, gated by the existing DismountOnPowerSaving / DismountOnScreenSaver / BackgroundTaskEnabled preferences. The observer is installed in OnInit and removed in the destructor. The two preference checkboxes, previously hidden on macOS, are now shown so users can enable the behaviour; the "screen saver" checkbox drives screen-lock dismount on macOS.

All new code is macOS-only (guarded by TC_MACOSX, built solely via the MacOSX block of Main.make); no other platform is affected.

Note: sleep dismount is best-effort within the short window NSWorkspace grants before sleep; delaying sleep via an IOKit power assertion to guarantee completion could be a follow-up.

@idrassi

idrassi commented Jul 8, 2026

Copy link
Copy Markdown
Member

Thanks for the PR. I agree this addresses a real macOS gap: the existing wx power event path is not active on macOS, and the screen saver auto dismount logic is Windows only.

Before merging, I think a few points need to be addressed:

  1. Please add an exception boundary around the native macOS notification callbacks. The callbacks currently enter the VeraCrypt dismount path directly from Cocoa notification dispatch, and failures from GetMountedVolumes or the dismount handling should not escape through AppKit notification delivery.

  2. The macOS UI shouldn't keep the existing "Screen saver is launched" wording if the behavior is actually "screen is locked". There is already an IDC_PREF_UNMOUNT_SESSION_LOCKED language key, so please use that on macOS, or also handle the screen saver notification so the preference text matches the behavior. Also please fix or avoid the existing LINUX_ENTERING_POVERSAWING typo now that the power saving checkbox is visible on macOS.

  3. Please explicitly address the forced dismount behavior. This path reuses OnAutoDismountAllEvent, and AutoDismountVolumes currently defaults to alwaysForce=true, which bypasses the ForceAutoDismount preference. That is important for screen lock behavior because it can detach busy volumes.

  4. Since com.apple.screenIsLocked is a de facto but undocumented distributed notification, please treat it as untrusted and verify that the session is actually locked before dismounting. Otherwise another local process may be able to trigger VeraCrypt auto dismount by posting the same notification.

  5. Please include validation notes for the macOS versions we support, and ideally both MacFUSE and FUSE-T on Intel and Apple Silicon. In particular, please cover lock screen, screen saver both with and without immediate lock, Apple menu sleep, lid close sleep, and idle sleep.

The overall approach is reasonable, but I would prefer these points fixed or explicitly validated before merging.

Comment on lines +22 to +32
- (void) systemWillSleep: (NSNotification *) notification
{
(void) notification;
VeraCrypt::OnMacOSXSystemWillSleep ();
}

- (void) screenLocked: (NSNotification *) notification
{
(void) notification;
VeraCrypt::OnMacOSXScreenLocked ();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an exception boundary before returning to Cocoa notification delivery. These callbacks enter the VeraCrypt dismount path, and GetMountedVolumes can still throw before or after DismountVolumes. Exceptions shouldn't escape through AppKit notification dispatch.

Comment on lines +57 to +65
// Screen lock. macOS exposes no public notification for this; the
// com.apple.screenIsLocked distributed notification is the long-standing
// de-facto mechanism used to detect it.
[[NSDistributedNotificationCenter defaultCenter]
addObserver: SleepLockObserver
selector: @selector (screenLocked:)
name: @"com.apple.screenIsLocked"
object: nil];
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please treat this distributed notification as untrusted and verify that the session is actually locked before dismounting. Otherwise another local process may be able to post the same notification and trigger VeraCrypt forced dismount.

Comment on lines +1046 to +1059
void OnMacOSXSystemWillSleep ()
{
if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnPowerSaving)
Gui->OnAutoDismountAllEvent();
}

// Called from MacOSXSleepLock.mm on the main thread when the screen is
// locked. macOS has no equivalent of the Windows screen-saver polling used
// by MainFrame::OnTimer, so screen-lock dismount is wired up here instead.
void OnMacOSXScreenLocked ()
{
if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnScreenSaver)
Gui->OnAutoDismountAllEvent();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calls OnAutoDismountAllEvent, which currently uses AutoDismountVolumes with alwaysForce=true. Please make that behavior explicit and intentional for macOS sleep and screen lock, or use a path that honors ForceAutoDismount. Screen lock can otherwise detach busy volumes.

Comment on lines +298 to 307
// On macOS, wxHAS_POWER_EVENTS is undefined but sleep is handled by a
// native observer (see MacOSXSleepLock), so keep the checkbox visible.
#if !defined(wxHAS_POWER_EVENTS) && !defined(TC_MACOSX)
DismountOnPowerSavingCheckBox->Show (false);
#endif

#ifdef TC_MACOSX
DismountOnScreenSaverCheckBox->Show (false);
// The "screen saver" checkbox drives screen-lock dismount on macOS (see
// MacOSXSleepLock), so it is left visible here.
DismountOnLogOffCheckBox->SetLabel (LangString["LINUX_VC_QUITS"]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please align the visible preference text with the macOS behavior. If this means screen lock, please use the existing IDC_PREF_UNMOUNT_SESSION_LOCKED label on macOS, or also handle the actual screen saver notification. Also, making the power saving checkbox visible exposes the existing LINUX_ENTERING_POVERSAWING typo, which should be fixed or avoided.

On macOS, mounted volumes stayed mounted (and their keys resident in
memory) when the system went to sleep or the screen was locked. The
existing auto-dismount triggers do not cover these on macOS:

- Power-suspend dismount is connected only under wxHAS_POWER_EVENTS,
  which wxWidgets does not define on macOS, so OnPowerSuspending never
  fires.
- Screen-saver dismount in MainFrame::OnTimer is inside #ifdef
  TC_WINDOWS (SPI_GETSCREENSAVERRUNNING) and has no macOS path.

Add a small Objective-C++ helper (MacOSXSleepLock) that observes
NSWorkspaceWillSleepNotification and the com.apple.screenIsLocked
distributed notification and calls back into GraphicUserInterface,
gated by the existing DismountOnPowerSaving / DismountOnScreenSaver /
BackgroundTaskEnabled preferences. The observer is installed in OnInit
and removed in the destructor.

Details:

- The notification callbacks are exception boundaries: no C++ exception
  can unwind into AppKit notification dispatch (GetMountedVolumes and
  the dismount path can throw). The dismount is best-effort; UI cannot
  be shown reliably while the session is locking or the system is going
  to sleep.

- com.apple.screenIsLocked is a de facto but undocumented distributed
  notification that any local process can post. It is treated only as a
  hint: the handler verifies via CGSessionCopyCurrentDictionary that the
  session is actually locked before dismounting, re-checking once after
  a short delay to tolerate notification/state ordering.

- The dismount honors the ForceAutoDismount preference (alwaysForce =
  false), matching the Windows auto-dismount events, which all pass
  bForceAutoDismount. Volumes with open files are only detached if the
  user enabled forced auto-dismount.

- The two preference checkboxes, previously hidden on macOS, are now
  shown. Because the checkbox drives screen-lock dismount on macOS, it
  is relabeled with the existing IDC_PREF_UNMOUNT_SESSION_LOCKED string
  (translated in all languages). The power-saving checkbox referenced
  the nonexistent key LINUX_ENTERING_POVERSAWING; point it at the
  correctly spelled LINUX_ENTERING_POWERSAVING, which exists in
  Language.xml and all translations (this also fixes the label on
  Linux builds that show this checkbox).

All new code is macOS-only (guarded by TC_MACOSX and built solely via
the MacOSX block of Main.make); the only cross-platform change is the
Forms label key correction. No new framework link flags are needed:
ApplicationServices/CoreGraphics symbols already resolve through the
wxWidgets link line.

Note: sleep dismount is best-effort within the short window NSWorkspace
grants before sleep; delaying sleep via an IOKit power assertion to
guarantee completion could be a follow-up.
@damianrickard damianrickard force-pushed the feature/macos-dismount-on-sleep-and-lock branch from 03b0047 to af355cc Compare July 8, 2026 16:18
@damianrickard

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — all five points are addressed:

  1. Exception boundary: the Cocoa notification callbacks now wrap the VeraCrypt work in try { … } catch (...), so nothing (including GetMountedVolumes throwing) can unwind through AppKit notification dispatch. The dismount is best-effort, since UI can't be shown reliably mid-lock/mid-sleep.
  2. Untrusted notification: com.apple.screenIsLocked is now treated as a hint only — the handler verifies the real session state via CGSessionCopyCurrentDictionary (CGSSessionScreenIsLocked) before dismounting, with a short re-check to tolerate notification/state ordering.
  3. Force behavior: the sleep/lock paths now call AutoDismountVolumes with alwaysForce = false, so the ForceAutoDismount preference decides whether busy volumes are detached — matching the Windows auto-dismount events, which all pass bForceAutoDismount.
  4. UI text: on macOS the checkbox is relabeled with the existing IDC_PREF_UNMOUNT_SESSION_LOCKED string (already translated everywhere). I also fixed the LINUX_ENTERING_POVERSAWING typo → LINUX_ENTERING_POWERSAVING, which also corrects the label on the Linux builds that show this checkbox.

Rebased onto current master; builds clean and --text --test passes.

Validation so far (Apple Silicon, macOS 26.5.1, FUSE-T 1.2.7), end-to-end with a real mounted volume:

  • Real screen lock, idle volume → dismounted.
  • Spoofed com.apple.screenIsLocked posted from a separate local process → not dismounted (the session-lock check rejects it).
  • Real lock, busy volume, ForceAutoDismount off → stays mounted (preference honored).
  • The GUI handled every callback with no exception escaping and a clean stderr.

I don't have MacFUSE or an Intel machine set up here, so I haven't yet covered MacFUSE, Intel, or the individual sleep variants (Apple-menu / lid-close / idle). I'll report on those separately rather than claim them now.

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.

2 participants