diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index d11f68c1e..89b5ca4e5 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -46,7 +46,6 @@ 4D36BA780CA2F00800A63CA5 /* peer-mgr.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D36BA690CA2F00800A63CA5 /* peer-mgr.h */; }; 4D36BA790CA2F00800A63CA5 /* peer-msgs.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D36BA6A0CA2F00800A63CA5 /* peer-msgs.cc */; }; 4D36BA7A0CA2F00800A63CA5 /* peer-msgs.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D36BA6B0CA2F00800A63CA5 /* peer-msgs.h */; }; - 4D3EA0AA08AE13C600EA10C2 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */; }; 4D4ADFC70DA1631500A68297 /* blocklist.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2D3078E0D9EC45F0051FD27 /* blocklist.cc */; }; 4D8017EA10BBC073008A4AF2 /* torrent-magnet.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D8017E810BBC073008A4AF2 /* torrent-magnet.cc */; }; 4D8017EB10BBC073008A4AF2 /* torrent-magnet.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D8017E910BBC073008A4AF2 /* torrent-magnet.h */; }; @@ -481,6 +480,7 @@ EDBAAC8C29E486BC00D9495F /* ip-cache.h in Headers */ = {isa = PBXBuildFile; fileRef = EDBAAC8B29E486BC00D9495F /* ip-cache.h */; }; EDBAAC8E29E486C200D9495F /* ip-cache.cc in Sources */ = {isa = PBXBuildFile; fileRef = EDBAAC8D29E486C200D9495F /* ip-cache.cc */; }; EDBDFA9E25AFCCA60093D9C1 /* evutil_time.c in Sources */ = {isa = PBXBuildFile; fileRef = EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */; }; + EDC749F92D98AE3000A12D0F /* PowerManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDC749F82D98AE2900A12D0F /* PowerManager.mm */; }; F11545ACA7C4D7A464F703AB /* block-info.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A044CBD8C049AFCBD4DB411 /* block-info.h */; settings = {ATTRIBUTES = (Project, ); }; }; F63480631E1D7274005B9E09 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F63480621E1D7274005B9E09 /* Images.xcassets */; }; /* End PBXBuildFile section */ @@ -828,7 +828,6 @@ 4D36BA690CA2F00800A63CA5 /* peer-mgr.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "peer-mgr.h"; sourceTree = ""; }; 4D36BA6A0CA2F00800A63CA5 /* peer-msgs.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-msgs.cc"; sourceTree = ""; }; 4D36BA6B0CA2F00800A63CA5 /* peer-msgs.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "peer-msgs.h"; sourceTree = ""; }; - 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 4D8017E810BBC073008A4AF2 /* torrent-magnet.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "torrent-magnet.cc"; sourceTree = ""; }; 4D8017E910BBC073008A4AF2 /* torrent-magnet.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "torrent-magnet.h"; sourceTree = ""; }; 4D80185710BBC0B0008A4AF2 /* magnet-metainfo.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "magnet-metainfo.cc"; sourceTree = ""; }; @@ -1474,6 +1473,8 @@ EDBAAC8B29E486BC00D9495F /* ip-cache.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "ip-cache.h"; sourceTree = ""; }; EDBAAC8D29E486C200D9495F /* ip-cache.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "ip-cache.cc"; sourceTree = ""; }; EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = evutil_time.c; sourceTree = ""; }; + EDC749F72D98ADE200A12D0F /* PowerManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PowerManager.h; sourceTree = ""; }; + EDC749F82D98AE2900A12D0F /* PowerManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PowerManager.mm; sourceTree = ""; }; F63480621E1D7274005B9E09 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Images/Images.xcassets; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1503,7 +1504,6 @@ files = ( C87369652809984200573C90 /* UserNotifications.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, - 4D3EA0AA08AE13C600EA10C2 /* IOKit.framework in Frameworks */, 4D1838DD09DEC0E80047D688 /* libtransmission.a in Frameworks */, A24F19080A3A790800C9C145 /* Sparkle.framework in Frameworks */, C88771B52803EEB1005C7523 /* libiconv.tbd in Frameworks */, @@ -1789,6 +1789,8 @@ A222EA7A0E6C32C4009FB003 /* BlocklistScheduler.mm */, ED86936D2ADAE34D00342B1A /* DefaultAppHelper.h */, ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */, + EDC749F72D98ADE200A12D0F /* PowerManager.h */, + EDC749F82D98AE2900A12D0F /* PowerManager.mm */, ED9862952B979AA2002F3035 /* Utils.h */, ED9862962B979AA2002F3035 /* Utils.mm */, A2AB883916A399A6008FAD50 /* VDKQueue */, @@ -1898,7 +1900,6 @@ 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 29B97325FDCFA39411CA2CEA /* Foundation.framework */, - 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */, A2E669780F5B8E5A00B4251A /* Security.framework */, A22CFB810FB66EF30009BD3E /* Carbon.framework */, A221DCC7104B3660008A642D /* Quartz.framework */, @@ -3556,6 +3557,7 @@ A2ED7D8F0CEF431B00970975 /* FilterButton.mm in Sources */, 45A7D32C2843B55F00F0C32A /* PriorityPopUpButtonCell.mm in Sources */, A25892640CF1F7E800CCCDDF /* StatsWindowController.mm in Sources */, + EDC749F92D98AE3000A12D0F /* PowerManager.mm in Sources */, A2C89D600CFCBF57004CC2BC /* ButtonToolbarItem.mm in Sources */, A219798B0D07B78400438EA7 /* GroupToolbarItem.mm in Sources */, 4521532B29AF891F009331B0 /* GroupCell.mm in Sources */, diff --git a/macosx/CMakeLists.txt b/macosx/CMakeLists.txt index ed8c35815..d21d6885a 100644 --- a/macosx/CMakeLists.txt +++ b/macosx/CMakeLists.txt @@ -143,6 +143,8 @@ target_sources(${TR_NAME}-mac PiecesView.mm PortChecker.h PortChecker.mm + PowerManager.h + PowerManager.mm PredicateEditorRowTemplateAny.h PredicateEditorRowTemplateAny.mm PrefsController.h @@ -412,7 +414,6 @@ target_link_libraries(${TR_NAME}-mac "-framework AppKit" "-framework Carbon" "-framework Foundation" - "-framework IOKit" "-framework Quartz" "-framework Security" "-weak_framework UserNotifications") diff --git a/macosx/Controller.h b/macosx/Controller.h index 34debc17d..045e699be 100644 --- a/macosx/Controller.h +++ b/macosx/Controller.h @@ -143,8 +143,6 @@ typedef NS_ENUM(NSUInteger, AddType) { // - (void)beginCreateFile:(NSNotification*)notification; -- (void)sleepCallback:(natural_t)messageType argument:(void*)messageArgument; - @property(nonatomic, readonly) VDKQueue* fileWatcherQueue; - (void)torrentTableViewSelectionDidChange:(NSNotification*)notification; diff --git a/macosx/Controller.mm b/macosx/Controller.mm index 07c2ef1aa..735499093 100644 --- a/macosx/Controller.mm +++ b/macosx/Controller.mm @@ -2,8 +2,6 @@ // It may be used under the MIT (SPDX: MIT) license. // License text can be found in the licenses/ folder. -@import IOKit; -@import IOKit.pwr_mgt; @import Carbon; @import UserNotifications; @@ -56,6 +54,7 @@ #import "ExpandedPathToPathTransformer.h" #import "ExpandedPathToIconTransformer.h" #import "VersionComparator.h" +#import "PowerManager.h" typedef NSString* ToolbarItemIdentifier NS_TYPED_EXTENSIBLE_ENUM; @@ -176,11 +175,6 @@ static tr_rpc_callback_status rpcCallback([[maybe_unused]] tr_session* handle, t return TR_RPC_NOREMOVE; //we'll do the remove manually } -static void sleepCallback(void* controller, io_service_t /*y*/, natural_t messageType, void* messageArgument) -{ - [(__bridge Controller*)controller sleepCallback:messageType argument:messageArgument]; -} - // 2.90 was infected with ransomware which we now check for and attempt to remove static void removeKeRangerRansomware() { @@ -271,7 +265,7 @@ static void removeKeRangerRansomware() NSLog(@"OSX.KeRanger.A ransomware removal completed, proceeding to normal operation"); } -@interface Controller () +@interface Controller () @property(nonatomic) IBOutlet NSWindow* fWindow; @property(nonatomic) IBOutlet NSStackView* fStackView; @@ -311,7 +305,6 @@ static void removeKeRangerRansomware() @property(nonatomic) DragOverlayWindow* fOverlayWindow; -@property(nonatomic) io_connect_t fRootPort; @property(nonatomic) NSTimer* fTimer; @property(nonatomic) StatusBarController* fStatusBar; @@ -338,7 +331,6 @@ static void removeKeRangerRansomware() @property(nonatomic) BOOL fGlobalPopoverShown; @property(nonatomic) NSView* fPositioningView; @property(nonatomic) BOOL fSoundPlaying; -@property(nonatomic) id fNoNapActivity; @end @@ -702,18 +694,6 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool //this must be called after showStatusBar: [self.fStatusBar updateWithDownload:0.0 upload:0.0]; - //register for sleep notifications - IONotificationPortRef notify; - io_object_t iterator; - if ((self.fRootPort = IORegisterForSystemPower((__bridge void*)(self), ¬ify, sleepCallback, &iterator))) - { - CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes); - } - else - { - NSLog(@"Could not IORegisterForSystemPower"); - } - auto* const session = self.fLib; //load previous transfers @@ -889,8 +869,8 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool NSApp.servicesProvider = self; - self.fNoNapActivity = [NSProcessInfo.processInfo beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep - reason:@"No napping on the job!"]; + [PowerManager.shared setDelegate:self]; + [PowerManager.shared start]; //register for dock icon drags (has to be in applicationDidFinishLaunching: to work) [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleOpenContentsEvent:replyEvent:) @@ -1038,7 +1018,7 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool { self.fQuitting = YES; - [NSProcessInfo.processInfo endActivity:self.fNoNapActivity]; + [PowerManager.shared stop]; //stop the Bonjour service if (BonjourController.defaultControllerExists) @@ -2378,6 +2358,8 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool { CGFloat dlRate = 0.0, ulRate = 0.0; BOOL anyCompleted = NO; + BOOL anyActive = NO; + for (Torrent* torrent in self.fTorrents) { [torrent update]; @@ -2387,8 +2369,11 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool ulRate += torrent.uploadRate; anyCompleted |= torrent.finishedSeeding; + anyActive |= torrent.active && !torrent.stalled && !torrent.error; } + PowerManager.shared.shouldPreventSleep = anyActive && [self.fDefaults boolForKey:@"SleepPrevent"]; + if (!NSApp.hidden) { if (self.fWindow.visible) @@ -5007,57 +4992,32 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool return YES; } -- (void)sleepCallback:(natural_t)messageType argument:(void*)messageArgument +- (void)systemWillSleep { - switch (messageType) + //stop all transfers (since some are active) before going to sleep and remember to resume when we wake up + BOOL anyActive = NO; + for (Torrent* torrent in self.fTorrents) { - case kIOMessageSystemWillSleep: + if (torrent.active) { - //stop all transfers (since some are active) before going to sleep and remember to resume when we wake up - BOOL anyActive = NO; - for (Torrent* torrent in self.fTorrents) - { - if (torrent.active) - { - anyActive = YES; - } - [torrent sleep]; //have to call on all, regardless if they are active - } - - //if there are any running transfers, wait 15 seconds for them to stop - if (anyActive) - { - sleep(15); - } - - IOAllowPowerChange(self.fRootPort, (long)messageArgument); - break; + anyActive = YES; } + [torrent sleep]; //have to call on all, regardless if they are active + } - case kIOMessageCanSystemSleep: - if ([self.fDefaults boolForKey:@"SleepPrevent"]) - { - //prevent idle sleep unless no torrents are active - for (Torrent* torrent in self.fTorrents) - { - if (torrent.active && !torrent.stalled && !torrent.error) - { - IOCancelPowerChange(self.fRootPort, (long)messageArgument); - return; - } - } - } + //if there are any running transfers, wait 15 seconds for them to stop + if (anyActive) + { + sleep(15); + } +} - IOAllowPowerChange(self.fRootPort, (long)messageArgument); - break; - - case kIOMessageSystemHasPoweredOn: - //resume sleeping transfers after we wake up - for (Torrent* torrent in self.fTorrents) - { - [torrent wakeUp]; - } - break; +- (void)systemDidWakeUp +{ + //resume sleeping transfers after we wake up + for (Torrent* torrent in self.fTorrents) + { + [torrent wakeUp]; } } diff --git a/macosx/PowerManager.h b/macosx/PowerManager.h new file mode 100644 index 000000000..2a44d6188 --- /dev/null +++ b/macosx/PowerManager.h @@ -0,0 +1,26 @@ +// This file Copyright © Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import + +@protocol PowerManagerDelegate + +- (void)systemWillSleep; +- (void)systemDidWakeUp; + +@end + +@interface PowerManager : NSObject + +@property(nonatomic, class, readonly) PowerManager* shared; + +@property(nonatomic, weak) id delegate; +@property(nonatomic) BOOL shouldPreventSleep; + +- (instancetype)init NS_UNAVAILABLE; + +- (void)start; +- (void)stop; + +@end diff --git a/macosx/PowerManager.mm b/macosx/PowerManager.mm new file mode 100644 index 000000000..35b2e0e23 --- /dev/null +++ b/macosx/PowerManager.mm @@ -0,0 +1,172 @@ +// This file Copyright © Transmission authors and contributors. +// It may be used under the MIT (SPDX: MIT) license. +// License text can be found in the licenses/ folder. + +#import "PowerManager.h" + +#include + +@interface PowerManager () + +@property(nonatomic, readonly) os_log_t log; +@property(getter=isListening) BOOL listening; + +@property(nonatomic) id noNapActivity; +@property(nonatomic) id noSleepActivity; + +- (void)systemWillSleep:(NSNotification*)notification; +- (void)systemDidWakeUp:(NSNotification*)notification; + +- (void)powerStateDidChange:(NSNotification*)notification NS_AVAILABLE_MAC(12_0); + +@end + +@implementation PowerManager + ++ (instancetype)shared +{ + static PowerManager* sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[PowerManager alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init +{ + if ((self = [super init])) + { + _log = os_log_create("org.transmission", "power"); + _listening = NO; + } + + return self; +} + +- (void)dealloc +{ + [self stop]; +} + +- (void)start +{ + os_log_info(self.log, "Starting power manager"); + if (!self.isListening) + { + os_log_debug(self.log, "Registering sleep/wake/low power mode notifications"); + [NSWorkspace.sharedWorkspace.notificationCenter addObserver:self selector:@selector(systemWillSleep:) + name:NSWorkspaceWillSleepNotification + object:nil]; + [NSWorkspace.sharedWorkspace.notificationCenter addObserver:self selector:@selector(systemDidWakeUp:) + name:NSWorkspaceDidWakeNotification + object:nil]; + if (@available(macOS 12.0, *)) + { + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(powerStateDidChange:) + name:NSProcessInfoPowerStateDidChangeNotification + object:nil]; + } + self.listening = YES; + } + + if (self.noNapActivity == nil) + { + os_log_debug(self.log, "Starting no-nap activity"); + self.noNapActivity = [NSProcessInfo.processInfo beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep + reason:@"Transmission: Application is active"]; + } +} + +- (void)stop +{ + os_log_info(self.log, "Stopping power manager"); + if (self.isListening) + { + os_log_debug(self.log, "Unregistering sleep/wake/low power mode notifications"); + [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self name:NSWorkspaceWillSleepNotification object:nil]; + [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self name:NSWorkspaceDidWakeNotification object:nil]; + if (@available(macOS 12.0, *)) + { + [NSNotificationCenter.defaultCenter removeObserver:self name:NSProcessInfoPowerStateDidChangeNotification object:nil]; + } + self.listening = NO; + } + + if (self.noNapActivity != nil) + { + os_log_debug(self.log, "Ending no-nap activity"); + [NSProcessInfo.processInfo endActivity:self.noNapActivity]; + self.noNapActivity = nil; + } + + if (self.noSleepActivity != nil) + { + os_log_debug(self.log, "Ending no-sleep activity"); + [NSProcessInfo.processInfo endActivity:self.noSleepActivity]; + self.noSleepActivity = nil; + } +} + +- (void)systemWillSleep:(NSNotification*)notification +{ + os_log_info(self.log, "System will sleep notification received"); + [self.delegate systemWillSleep]; +} + +- (void)systemDidWakeUp:(NSNotification*)notification +{ + os_log_info(self.log, "System did wake up notification received"); + [self.delegate systemDidWakeUp]; +} + +- (void)powerStateDidChange:(NSNotification*)notification +{ + os_log_info(self.log, "Power state did change notification received"); + if (NSProcessInfo.processInfo.lowPowerModeEnabled) + { + os_log_info(self.log, "Low power mode enabled, disabling sleep prevention"); + self.shouldPreventSleep = NO; + } +} + +- (void)setShouldPreventSleep:(BOOL)shouldPreventSleep +{ + if (@available(macOS 12.0, *)) + { + if (shouldPreventSleep && NSProcessInfo.processInfo.lowPowerModeEnabled) + { + return; + } + } + + if (shouldPreventSleep) + { + if (self.noSleepActivity != nil) + { + return; + } + + os_log_info(self.log, "Starting no-sleep activity"); + self.noSleepActivity = [NSProcessInfo.processInfo beginActivityWithOptions:NSActivityIdleSystemSleepDisabled + reason:@"Transmission: Active Torrents"]; + } + else + { + if (self.noSleepActivity == nil) + { + return; + } + + os_log_info(self.log, "Ending no-sleep activity"); + [NSProcessInfo.processInfo endActivity:self.noSleepActivity]; + self.noSleepActivity = nil; + } +} + +- (BOOL)shouldPreventSleep +{ + return self.noSleepActivity != nil; +} + +@end