diff --git a/src/Main/Forms/Forms.cpp b/src/Main/Forms/Forms.cpp index c64dbbfbc4..4f2fb5923a 100644 --- a/src/Main/Forms/Forms.cpp +++ b/src/Main/Forms/Forms.cpp @@ -1832,7 +1832,7 @@ PreferencesDialogBase::PreferencesDialogBase( wxWindow* parent, wxWindowID id, c DismountOnScreenSaverCheckBox = new wxCheckBox( sbSizer13->GetStaticBox(), wxID_ANY, _("IDC_PREF_UNMOUNT_SCREENSAVER"), wxDefaultPosition, wxDefaultSize, 0 ); sbSizer13->Add( DismountOnScreenSaverCheckBox, 0, wxALL, 5 ); - DismountOnPowerSavingCheckBox = new wxCheckBox( sbSizer13->GetStaticBox(), wxID_ANY, _("LINUX_ENTERING_POVERSAWING"), wxDefaultPosition, wxDefaultSize, 0 ); + DismountOnPowerSavingCheckBox = new wxCheckBox( sbSizer13->GetStaticBox(), wxID_ANY, _("LINUX_ENTERING_POWERSAVING"), wxDefaultPosition, wxDefaultSize, 0 ); sbSizer13->Add( DismountOnPowerSavingCheckBox, 0, wxALL, 5 ); diff --git a/src/Main/Forms/PreferencesDialog.cpp b/src/Main/Forms/PreferencesDialog.cpp index 879394cac7..3d2a8d733e 100644 --- a/src/Main/Forms/PreferencesDialog.cpp +++ b/src/Main/Forms/PreferencesDialog.cpp @@ -295,12 +295,16 @@ namespace VeraCrypt CloseExplorerWindowsOnDismountCheckBox->Show (false); #endif -#ifndef wxHAS_POWER_EVENTS + // 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); + // On macOS this checkbox drives dismount on screen lock (see + // MacOSXSleepLock), so relabel it accordingly. + DismountOnScreenSaverCheckBox->SetLabel (LangString["IDC_PREF_UNMOUNT_SESSION_LOCKED"]); DismountOnLogOffCheckBox->SetLabel (LangString["LINUX_VC_QUITS"]); OpenExplorerWindowAfterMountCheckBox->SetLabel (LangString["LINUX_OPEN_FINDER"]); diff --git a/src/Main/Forms/TrueCrypt.fbp b/src/Main/Forms/TrueCrypt.fbp index a100bf1f74..6c457935e9 100644 --- a/src/Main/Forms/TrueCrypt.fbp +++ b/src/Main/Forms/TrueCrypt.fbp @@ -11134,7 +11134,7 @@ 0 0 wxID_ANY - LINUX_ENTERING_POVERSAWING + LINUX_ENTERING_POWERSAVING 0 diff --git a/src/Main/GraphicUserInterface.cpp b/src/Main/GraphicUserInterface.cpp index 321a939627..cf38325a43 100644 --- a/src/Main/GraphicUserInterface.cpp +++ b/src/Main/GraphicUserInterface.cpp @@ -30,6 +30,7 @@ #include "FatalErrorHandler.h" #ifdef TC_MACOSX #include "MacOSXSecureTextFieldHotkeys.h" +#include "MacOSXSleepLock.h" #endif #include "Forms/DeviceSelectionDialog.h" #include "Forms/KeyfileGeneratorDialog.h" @@ -184,6 +185,7 @@ namespace VeraCrypt { #ifdef TC_MACOSX UninstallMacOSXSecureTextFieldHotkeys(); + UninstallMacOSXSleepLockHandler(); #endif try { @@ -1038,6 +1040,38 @@ namespace VeraCrypt } } +#ifdef TC_MACOSX + // Called from MacOSXSleepLock.mm on the main thread when the system is about + // to sleep. wxWidgets does not deliver wxEVT_POWER_SUSPENDING on macOS + // (wxHAS_POWER_EVENTS is undefined there), so this observer fills that gap. + // AutoDismountVolumes is called with alwaysForce=false so that the + // ForceAutoDismount preference decides whether volumes with open files are + // detached, matching the Windows auto-dismount events (bForceAutoDismount). + void OnMacOSXSystemWillSleep () + { + if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnPowerSaving) + { + VolumeInfoList mountedVolumes = Core->GetMountedVolumes(); + if (!mountedVolumes.empty()) + Gui->AutoDismountVolumes (mountedVolumes, false); + } + } + + // Called from MacOSXSleepLock.mm on the main thread when the user session + // has been verified as locked. macOS has no equivalent of the Windows + // screen-saver polling used by MainFrame::OnTimer, so screen-lock dismount + // is wired up here instead. Force semantics as above. + void OnMacOSXScreenLocked () + { + if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnScreenSaver) + { + VolumeInfoList mountedVolumes = Core->GetMountedVolumes(); + if (!mountedVolumes.empty()) + Gui->AutoDismountVolumes (mountedVolumes, false); + } + } +#endif + bool GraphicUserInterface::OnInit () { Gui = this; @@ -1193,6 +1227,11 @@ namespace VeraCrypt #ifdef wxHAS_POWER_EVENTS Gui->Connect (wxEVT_POWER_SUSPENDING, wxPowerEventHandler (GraphicUserInterface::OnPowerSuspending)); #endif +#ifdef TC_MACOSX + // macOS lacks wxHAS_POWER_EVENTS; use native observers for sleep and + // screen lock so volumes can be auto-dismounted on those events. + InstallMacOSXSleepLockHandler(); +#endif mMainFrame = new MainFrame (nullptr); #if defined(TC_UNIX) diff --git a/src/Main/MacOSXSleepLock.h b/src/Main/MacOSXSleepLock.h new file mode 100644 index 0000000000..28e634e18c --- /dev/null +++ b/src/Main/MacOSXSleepLock.h @@ -0,0 +1,28 @@ +/* + Copyright (c) 2013-2026 AM Crypto. All rights reserved. + + Governed by the Apache License 2.0 the full text of which is + contained in the file License.txt included in VeraCrypt binary and source + code distribution packages. +*/ + +#ifndef TC_HEADER_Main_MacOSXSleepLock +#define TC_HEADER_Main_MacOSXSleepLock + +#ifdef TC_MACOSX +namespace VeraCrypt +{ + // Register/unregister observers for system sleep and screen lock. + // Implemented in MacOSXSleepLock.mm. + void InstallMacOSXSleepLockHandler (); + void UninstallMacOSXSleepLockHandler (); + + // Invoked by the observers above when the corresponding event occurs. + // Implemented on the C++ side (GraphicUserInterface.cpp) so that all + // wxWidgets / Core logic stays out of the Objective-C++ file. + void OnMacOSXSystemWillSleep (); + void OnMacOSXScreenLocked (); +} +#endif + +#endif // TC_HEADER_Main_MacOSXSleepLock diff --git a/src/Main/MacOSXSleepLock.mm b/src/Main/MacOSXSleepLock.mm new file mode 100644 index 0000000000..40ea53b7e6 --- /dev/null +++ b/src/Main/MacOSXSleepLock.mm @@ -0,0 +1,133 @@ +/* + Copyright (c) 2013-2026 AM Crypto. All rights reserved. + + Governed by the Apache License 2.0 the full text of which is + contained in the file License.txt included in VeraCrypt binary and source + code distribution packages. +*/ + +#include "System.h" +#include "MacOSXSleepLock.h" + +#ifdef TC_MACOSX +#import +#import + +// Queries the window server for the actual lock state of the current session. +// The com.apple.screenIsLocked distributed notification can be posted by any +// local process, so it is treated only as a hint and the dismount is performed +// only if the session really is locked. +static bool SessionIsLocked () +{ + bool locked = false; + CFDictionaryRef session = CGSessionCopyCurrentDictionary (); + if (session) + { + CFTypeRef value = CFDictionaryGetValue (session, CFSTR ("CGSSessionScreenIsLocked")); + locked = value && CFGetTypeID (value) == CFBooleanGetTypeID () && CFBooleanGetValue ((CFBooleanRef) value); + CFRelease (session); + } + return locked; +} + +@interface VCSleepLockObserver : NSObject +- (void) systemWillSleep: (NSNotification *) notification; +- (void) screenLocked: (NSNotification *) notification; +- (void) dismountIfSessionLocked; +@end + +@implementation VCSleepLockObserver + +// The callbacks below are entered from AppKit notification dispatch; a C++ +// exception must not unwind through those Objective-C frames, so every call +// into VeraCrypt code is guarded here. The dismount is best-effort: showing +// UI is not reliable while the session is locking or the system is going to +// sleep, so failures are ignored. + +- (void) systemWillSleep: (NSNotification *) notification +{ + (void) notification; + try + { + VeraCrypt::OnMacOSXSystemWillSleep (); + } + catch (...) { } +} + +- (void) screenLocked: (NSNotification *) notification +{ + (void) notification; + // The window server can update the session dictionary slightly after the + // notification is delivered, so if the session does not report itself as + // locked yet, re-check once after a short delay instead of giving up. + if (SessionIsLocked ()) + { + [self dismountIfSessionLocked]; + } + else + { + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector (dismountIfSessionLocked) object: nil]; + [self performSelector: @selector (dismountIfSessionLocked) withObject: nil afterDelay: 1.0]; + } +} + +- (void) dismountIfSessionLocked +{ + if (!SessionIsLocked ()) + return; + + try + { + VeraCrypt::OnMacOSXScreenLocked (); + } + catch (...) { } +} + +@end + +namespace VeraCrypt +{ + // Non-ARC build (see the .mm compile rule in Build/Include/Makefile.inc), + // so the observer is retained by alloc/init and released in Uninstall. + static VCSleepLockObserver *SleepLockObserver = nil; + + void InstallMacOSXSleepLockHandler () + { + if (SleepLockObserver) + return; + + SleepLockObserver = [[VCSleepLockObserver alloc] init]; + + // System sleep (lid close, idle sleep, Apple menu > Sleep). Delivered on + // the main thread by NSWorkspace's own notification center, which only + // the system feeds (it is per-process, unlike the distributed center). + [[[NSWorkspace sharedWorkspace] notificationCenter] + addObserver: SleepLockObserver + selector: @selector (systemWillSleep:) + name: NSWorkspaceWillSleepNotification + object: nil]; + + // 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. It is untrusted input: the + // handler verifies the actual session lock state before dismounting. + [[NSDistributedNotificationCenter defaultCenter] + addObserver: SleepLockObserver + selector: @selector (screenLocked:) + name: @"com.apple.screenIsLocked" + object: nil]; + } + + void UninstallMacOSXSleepLockHandler () + { + if (!SleepLockObserver) + return; + + [NSObject cancelPreviousPerformRequestsWithTarget: SleepLockObserver]; + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: SleepLockObserver]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver: SleepLockObserver]; + [SleepLockObserver release]; + SleepLockObserver = nil; + } +} +#endif diff --git a/src/Main/Main.make b/src/Main/Main.make index 36b8c1f6e9..ae9fdc37c2 100755 --- a/src/Main/Main.make +++ b/src/Main/Main.make @@ -28,6 +28,7 @@ OBJS += FatalErrorHandler.o OBJS += GraphicUserInterface.o ifeq "$(PLATFORM)" "MacOSX" OBJS += MacOSXSecureTextFieldHotkeys.o +OBJS += MacOSXSleepLock.o endif OBJS += VolumeHistory.o OBJS += Forms/AboutDialog.o