Skip to content

Commit af355cc

Browse files
author
Damian Rickard
committed
Auto-dismount volumes on system sleep and screen lock on macOS
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.
1 parent 9cabe0d commit af355cc

7 files changed

Lines changed: 209 additions & 4 deletions

File tree

‎src/Main/Forms/Forms.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1832,7 +1832,7 @@ PreferencesDialogBase::PreferencesDialogBase( wxWindow* parent, wxWindowID id, c
18321832
DismountOnScreenSaverCheckBox = new wxCheckBox( sbSizer13->GetStaticBox(), wxID_ANY, _("IDC_PREF_UNMOUNT_SCREENSAVER"), wxDefaultPosition, wxDefaultSize, 0 );
18331833
sbSizer13->Add( DismountOnScreenSaverCheckBox, 0, wxALL, 5 );
18341834

1835-
DismountOnPowerSavingCheckBox = new wxCheckBox( sbSizer13->GetStaticBox(), wxID_ANY, _("LINUX_ENTERING_POVERSAWING"), wxDefaultPosition, wxDefaultSize, 0 );
1835+
DismountOnPowerSavingCheckBox = new wxCheckBox( sbSizer13->GetStaticBox(), wxID_ANY, _("LINUX_ENTERING_POWERSAVING"), wxDefaultPosition, wxDefaultSize, 0 );
18361836
sbSizer13->Add( DismountOnPowerSavingCheckBox, 0, wxALL, 5 );
18371837

18381838

‎src/Main/Forms/PreferencesDialog.cpp‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,16 @@ namespace VeraCrypt
295295
CloseExplorerWindowsOnDismountCheckBox->Show (false);
296296
#endif
297297

298-
#ifndef wxHAS_POWER_EVENTS
298+
// On macOS, wxHAS_POWER_EVENTS is undefined but sleep is handled by a
299+
// native observer (see MacOSXSleepLock), so keep the checkbox visible.
300+
#if !defined(wxHAS_POWER_EVENTS) && !defined(TC_MACOSX)
299301
DismountOnPowerSavingCheckBox->Show (false);
300302
#endif
301303

302304
#ifdef TC_MACOSX
303-
DismountOnScreenSaverCheckBox->Show (false);
305+
// On macOS this checkbox drives dismount on screen lock (see
306+
// MacOSXSleepLock), so relabel it accordingly.
307+
DismountOnScreenSaverCheckBox->SetLabel (LangString["IDC_PREF_UNMOUNT_SESSION_LOCKED"]);
304308
DismountOnLogOffCheckBox->SetLabel (LangString["LINUX_VC_QUITS"]);
305309
OpenExplorerWindowAfterMountCheckBox->SetLabel (LangString["LINUX_OPEN_FINDER"]);
306310

‎src/Main/Forms/TrueCrypt.fbp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11134,7 +11134,7 @@
1113411134
<property name="gripper">0</property>
1113511135
<property name="hidden">0</property>
1113611136
<property name="id">wxID_ANY</property>
11137-
<property name="label">LINUX_ENTERING_POVERSAWING</property>
11137+
<property name="label">LINUX_ENTERING_POWERSAVING</property>
1113811138
<property name="max_size"></property>
1113911139
<property name="maximize_button">0</property>
1114011140
<property name="maximum_size"></property>

‎src/Main/GraphicUserInterface.cpp‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "FatalErrorHandler.h"
3131
#ifdef TC_MACOSX
3232
#include "MacOSXSecureTextFieldHotkeys.h"
33+
#include "MacOSXSleepLock.h"
3334
#endif
3435
#include "Forms/DeviceSelectionDialog.h"
3536
#include "Forms/KeyfileGeneratorDialog.h"
@@ -184,6 +185,7 @@ namespace VeraCrypt
184185
{
185186
#ifdef TC_MACOSX
186187
UninstallMacOSXSecureTextFieldHotkeys();
188+
UninstallMacOSXSleepLockHandler();
187189
#endif
188190
try
189191
{
@@ -1038,6 +1040,38 @@ namespace VeraCrypt
10381040
}
10391041
}
10401042

1043+
#ifdef TC_MACOSX
1044+
// Called from MacOSXSleepLock.mm on the main thread when the system is about
1045+
// to sleep. wxWidgets does not deliver wxEVT_POWER_SUSPENDING on macOS
1046+
// (wxHAS_POWER_EVENTS is undefined there), so this observer fills that gap.
1047+
// AutoDismountVolumes is called with alwaysForce=false so that the
1048+
// ForceAutoDismount preference decides whether volumes with open files are
1049+
// detached, matching the Windows auto-dismount events (bForceAutoDismount).
1050+
void OnMacOSXSystemWillSleep ()
1051+
{
1052+
if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnPowerSaving)
1053+
{
1054+
VolumeInfoList mountedVolumes = Core->GetMountedVolumes();
1055+
if (!mountedVolumes.empty())
1056+
Gui->AutoDismountVolumes (mountedVolumes, false);
1057+
}
1058+
}
1059+
1060+
// Called from MacOSXSleepLock.mm on the main thread when the user session
1061+
// has been verified as locked. macOS has no equivalent of the Windows
1062+
// screen-saver polling used by MainFrame::OnTimer, so screen-lock dismount
1063+
// is wired up here instead. Force semantics as above.
1064+
void OnMacOSXScreenLocked ()
1065+
{
1066+
if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnScreenSaver)
1067+
{
1068+
VolumeInfoList mountedVolumes = Core->GetMountedVolumes();
1069+
if (!mountedVolumes.empty())
1070+
Gui->AutoDismountVolumes (mountedVolumes, false);
1071+
}
1072+
}
1073+
#endif
1074+
10411075
bool GraphicUserInterface::OnInit ()
10421076
{
10431077
Gui = this;
@@ -1193,6 +1227,11 @@ namespace VeraCrypt
11931227
#ifdef wxHAS_POWER_EVENTS
11941228
Gui->Connect (wxEVT_POWER_SUSPENDING, wxPowerEventHandler (GraphicUserInterface::OnPowerSuspending));
11951229
#endif
1230+
#ifdef TC_MACOSX
1231+
// macOS lacks wxHAS_POWER_EVENTS; use native observers for sleep and
1232+
// screen lock so volumes can be auto-dismounted on those events.
1233+
InstallMacOSXSleepLockHandler();
1234+
#endif
11961235

11971236
mMainFrame = new MainFrame (nullptr);
11981237
#if defined(TC_UNIX)

‎src/Main/MacOSXSleepLock.h‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright (c) 2013-2026 AM Crypto. All rights reserved.
3+
4+
Governed by the Apache License 2.0 the full text of which is
5+
contained in the file License.txt included in VeraCrypt binary and source
6+
code distribution packages.
7+
*/
8+
9+
#ifndef TC_HEADER_Main_MacOSXSleepLock
10+
#define TC_HEADER_Main_MacOSXSleepLock
11+
12+
#ifdef TC_MACOSX
13+
namespace VeraCrypt
14+
{
15+
// Register/unregister observers for system sleep and screen lock.
16+
// Implemented in MacOSXSleepLock.mm.
17+
void InstallMacOSXSleepLockHandler ();
18+
void UninstallMacOSXSleepLockHandler ();
19+
20+
// Invoked by the observers above when the corresponding event occurs.
21+
// Implemented on the C++ side (GraphicUserInterface.cpp) so that all
22+
// wxWidgets / Core logic stays out of the Objective-C++ file.
23+
void OnMacOSXSystemWillSleep ();
24+
void OnMacOSXScreenLocked ();
25+
}
26+
#endif
27+
28+
#endif // TC_HEADER_Main_MacOSXSleepLock

‎src/Main/MacOSXSleepLock.mm‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
Copyright (c) 2013-2026 AM Crypto. All rights reserved.
3+
4+
Governed by the Apache License 2.0 the full text of which is
5+
contained in the file License.txt included in VeraCrypt binary and source
6+
code distribution packages.
7+
*/
8+
9+
#include "System.h"
10+
#include "MacOSXSleepLock.h"
11+
12+
#ifdef TC_MACOSX
13+
#import <Cocoa/Cocoa.h>
14+
#import <ApplicationServices/ApplicationServices.h>
15+
16+
// Queries the window server for the actual lock state of the current session.
17+
// The com.apple.screenIsLocked distributed notification can be posted by any
18+
// local process, so it is treated only as a hint and the dismount is performed
19+
// only if the session really is locked.
20+
static bool SessionIsLocked ()
21+
{
22+
bool locked = false;
23+
CFDictionaryRef session = CGSessionCopyCurrentDictionary ();
24+
if (session)
25+
{
26+
CFTypeRef value = CFDictionaryGetValue (session, CFSTR ("CGSSessionScreenIsLocked"));
27+
locked = value && CFGetTypeID (value) == CFBooleanGetTypeID () && CFBooleanGetValue ((CFBooleanRef) value);
28+
CFRelease (session);
29+
}
30+
return locked;
31+
}
32+
33+
@interface VCSleepLockObserver : NSObject
34+
- (void) systemWillSleep: (NSNotification *) notification;
35+
- (void) screenLocked: (NSNotification *) notification;
36+
- (void) dismountIfSessionLocked;
37+
@end
38+
39+
@implementation VCSleepLockObserver
40+
41+
// The callbacks below are entered from AppKit notification dispatch; a C++
42+
// exception must not unwind through those Objective-C frames, so every call
43+
// into VeraCrypt code is guarded here. The dismount is best-effort: showing
44+
// UI is not reliable while the session is locking or the system is going to
45+
// sleep, so failures are ignored.
46+
47+
- (void) systemWillSleep: (NSNotification *) notification
48+
{
49+
(void) notification;
50+
try
51+
{
52+
VeraCrypt::OnMacOSXSystemWillSleep ();
53+
}
54+
catch (...) { }
55+
}
56+
57+
- (void) screenLocked: (NSNotification *) notification
58+
{
59+
(void) notification;
60+
// The window server can update the session dictionary slightly after the
61+
// notification is delivered, so if the session does not report itself as
62+
// locked yet, re-check once after a short delay instead of giving up.
63+
if (SessionIsLocked ())
64+
{
65+
[self dismountIfSessionLocked];
66+
}
67+
else
68+
{
69+
[NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector (dismountIfSessionLocked) object: nil];
70+
[self performSelector: @selector (dismountIfSessionLocked) withObject: nil afterDelay: 1.0];
71+
}
72+
}
73+
74+
- (void) dismountIfSessionLocked
75+
{
76+
if (!SessionIsLocked ())
77+
return;
78+
79+
try
80+
{
81+
VeraCrypt::OnMacOSXScreenLocked ();
82+
}
83+
catch (...) { }
84+
}
85+
86+
@end
87+
88+
namespace VeraCrypt
89+
{
90+
// Non-ARC build (see the .mm compile rule in Build/Include/Makefile.inc),
91+
// so the observer is retained by alloc/init and released in Uninstall.
92+
static VCSleepLockObserver *SleepLockObserver = nil;
93+
94+
void InstallMacOSXSleepLockHandler ()
95+
{
96+
if (SleepLockObserver)
97+
return;
98+
99+
SleepLockObserver = [[VCSleepLockObserver alloc] init];
100+
101+
// System sleep (lid close, idle sleep, Apple menu > Sleep). Delivered on
102+
// the main thread by NSWorkspace's own notification center, which only
103+
// the system feeds (it is per-process, unlike the distributed center).
104+
[[[NSWorkspace sharedWorkspace] notificationCenter]
105+
addObserver: SleepLockObserver
106+
selector: @selector (systemWillSleep:)
107+
name: NSWorkspaceWillSleepNotification
108+
object: nil];
109+
110+
// Screen lock. macOS exposes no public notification for this; the
111+
// com.apple.screenIsLocked distributed notification is the long-standing
112+
// de-facto mechanism used to detect it. It is untrusted input: the
113+
// handler verifies the actual session lock state before dismounting.
114+
[[NSDistributedNotificationCenter defaultCenter]
115+
addObserver: SleepLockObserver
116+
selector: @selector (screenLocked:)
117+
name: @"com.apple.screenIsLocked"
118+
object: nil];
119+
}
120+
121+
void UninstallMacOSXSleepLockHandler ()
122+
{
123+
if (!SleepLockObserver)
124+
return;
125+
126+
[NSObject cancelPreviousPerformRequestsWithTarget: SleepLockObserver];
127+
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: SleepLockObserver];
128+
[[NSDistributedNotificationCenter defaultCenter] removeObserver: SleepLockObserver];
129+
[SleepLockObserver release];
130+
SleepLockObserver = nil;
131+
}
132+
}
133+
#endif

‎src/Main/Main.make‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ OBJS += FatalErrorHandler.o
2828
OBJS += GraphicUserInterface.o
2929
ifeq "$(PLATFORM)" "MacOSX"
3030
OBJS += MacOSXSecureTextFieldHotkeys.o
31+
OBJS += MacOSXSleepLock.o
3132
endif
3233
OBJS += VolumeHistory.o
3334
OBJS += Forms/AboutDialog.o

0 commit comments

Comments
 (0)