Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 54 additions & 26 deletions AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ - (NSImage *)imageTintedWithColor:(NSColor *)tint
@end


@implementation AppDelegate
@implementation AppDelegate {
IOPMAssertionID _userActivityAssertion;
}

#pragma mark Launch and Termination

Expand Down Expand Up @@ -129,7 +131,12 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
[self fixMasterSwitchUI];

// Check that we have the Accessibility access we need; see https://stackoverflow.com/a/53617674/2752221
if (!AXIsProcessTrusted())
if (@available(macOS 15.0, *))
{
NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:(id)kCFBooleanFalse, (id)kAXTrustedCheckOptionPrompt, nil];
(void)AXIsProcessTrustedWithOptions((CFDictionaryRef)opts);
}
else if (!AXIsProcessTrusted())
{
NSModalResponse retval = SSRunCriticalAlertPanel(@"Turn on accessibility", @"Jiggler needs to control the mouse cursor to function. To enable this capability, please select the Jiggler checkbox in Security & Privacy > Accessibility, and then restart Jiggler (which will quit now).", @"Turn On Accessibility", @"Quit", nil);

Expand Down Expand Up @@ -239,9 +246,29 @@ - (void)fixTimedQuitMenuItem
[[self timedQuitItem] setTitle:timedQuitTitle];
}


#pragma mark Jiggling

- (void)declareUserActivity
{
// Release any previous assertion before creating a new one
if (_userActivityAssertion != kIOPMNullAssertionID) {
IOPMAssertionRelease(_userActivityAssertion);
_userActivityAssertion = kIOPMNullAssertionID;
}

// Create a short-lived "user is active" assertion to reset system idle timer
IOReturn result = IOPMAssertionCreateWithName(
kIOPMAssertionTypePreventUserIdleDisplaySleep, // tells macOS "the user just did something"
kIOPMAssertionLevelOn,
CFSTR("Jiggler Zen Jiggle Activity"),
&_userActivityAssertion
);

if (result != kIOReturnSuccess) {
NSLog(@"[Jiggler] Failed to declare user activity (IOReturn = 0x%x)", result);
}
}

- (BOOL)isInAScreen:(NSPoint)point
{
NSArray *screens = [NSScreen screens];
Expand Down Expand Up @@ -535,7 +562,7 @@ - (BOOL)checkRunningAppsForAppNameContaining:(NSArray *)nameComponents mustBeDoc
NSApplicationActivationPolicy activationPolicy = [app activationPolicy];
BOOL isActive = [app isActive];

//NSLog(@"process name: %@, is dock app == %@, is active == %@", processName, (activationPolicy == NSApplicationActivationPolicyRegular) ? @"YES" : @"NO", isActive ? @"YES" : @"NO");
NSLog(@"process name: %@, is dock app == %@, is active == %@", processName, (activationPolicy == NSApplicationActivationPolicyRegular) ? @"YES" : @"NO", isActive ? @"YES" : @"NO");

if (mustBeDockApp && (activationPolicy != NSApplicationActivationPolicyRegular))
continue;
Expand All @@ -557,19 +584,21 @@ - (BOOL)checkRunningAppsForAppNameContaining:(NSArray *)nameComponents mustBeDoc

- (BOOL)checkMountedVolumesForCandidateDisks
{
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSArray *mountedRemovables = [ws mountedRemovableMedia]; // the header suggests switching to mountedVolumeURLsIncludingResourceValuesForKeys:options:, but that looks annoying
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *volumeURLs = [fm mountedVolumeURLsIncludingResourceValuesForKeys:@[NSURLVolumeIsRemovableKey, NSURLVolumeIsReadOnlyKey] options:0];
int i, c;

for (i = 0, c = (int)[mountedRemovables count]; i < c; ++i)
for (i = 0, c = (int)[volumeURLs count]; i < c; ++i)
{
NSString *diskPath = [mountedRemovables objectAtIndex:i];
BOOL isRemovable, isWritable;
BOOL isMountPoint = [ws getFileSystemInfoForPath:diskPath isRemovable:&isRemovable isWritable:&isWritable isUnmountable:NULL description:NULL type:NULL];
NSURL *url = [volumeURLs objectAtIndex:i];
NSNumber *isRemovableNum = nil;
NSNumber *isReadOnlyNum = nil;
BOOL gotRemovable = [url getResourceValue:&isRemovableNum forKey:NSURLVolumeIsRemovableKey error:NULL];
BOOL gotReadOnly = [url getResourceValue:&isReadOnlyNum forKey:NSURLVolumeIsReadOnlyKey error:NULL];

if (isMountPoint)
if (gotRemovable && gotReadOnly)
{
if (isRemovable && isWritable)
if ([isRemovableNum boolValue] && ![isReadOnlyNum boolValue])
return YES;
}
}
Expand All @@ -581,7 +610,7 @@ - (BOOL)cpuUsageOverThreshold:(int)cpuUsageThreshold
{
int cpuBusyIndex = [SSCPU busyIndex];

//NSLog(@"busy index %d", cpuBusyIndex);
NSLog(@"busy index %d", cpuBusyIndex);

if (cpuBusyIndex >= cpuUsageThreshold)
return YES;
Expand All @@ -602,7 +631,7 @@ - (BOOL)iTunesIsRunningNow
NSString *runningAppLocalizedName = [runningApp localizedName];
NSString *runningAppBundleIdentifier = [runningApp bundleIdentifier];

//NSLog(@"index %d: name %@ bundle id %@", i, runningAppLocalizedName, runningAppBundleIdentifier);
NSLog(@"index %d: name %@ bundle id %@", i, runningAppLocalizedName, runningAppBundleIdentifier);

if ([runningAppLocalizedName isEqualToString:@"iTunes"] || [runningAppBundleIdentifier isEqualToString:@"com.apple.iTunes"])
{
Expand Down Expand Up @@ -673,27 +702,27 @@ - (BOOL)jiggleConditionsMet
// If we have conditions, check them; if any one is met, we return YES
if (onlyWithApplicationsNamedX && [self checkRunningAppsForAppNameContaining:applicationNameComponents mustBeDockApp:mustBeDockApp mustBeFront:NO])
{
//NSLog(@"jiggleConditionsMet: app matching name is running");
NSLog(@"jiggleConditionsMet: app matching name is running");
return YES;
}
if (onlyWithRemovableWritableDisks && [self checkMountedVolumesForCandidateDisks])
{
//NSLog(@"jiggleConditionsMet: mounted removable writable disk present");
NSLog(@"jiggleConditionsMet: mounted removable writable disk present");
return YES;
}
if (onlyWithCPUUsage && [self cpuUsageOverThreshold:cpuUsageThreshold])
{
//NSLog(@"jiggleConditionsMet: cpu usage is high");
NSLog(@"jiggleConditionsMet: cpu usage is high");
return YES;
}
if (onlyWithITunesPlaying && ([self iTunesIsRunningNow] && iTunesIsPlaying))
{
//NSLog(@"jiggleConditionsMet: iTunes is playing");
NSLog(@"jiggleConditionsMet: iTunes is playing");
return YES;
}

// If we have conditions and they are not met, then we return NO
//NSLog(@"jiggleConditionsMet: NO");
NSLog(@"jiggleConditionsMet: NO");
return NO;
}

Expand Down Expand Up @@ -750,7 +779,7 @@ - (void)jiggleMouse:(id)unused

if (jiggleOnlyWhenIdle && (idleTime < timeSinceLastJiggle - 0.4)) // the code below schedules mouse moves for up to 0.34 seconds beyond timeOfLastJiggle, so 0.4 gives us a little wiggle room
{
//NSLog(@"idleTime %f, timeSinceLastJiggle %f, delta = %f, deactivating", idleTime, timeSinceLastJiggle, timeSinceLastJiggle - idleTime);
NSLog(@"idleTime %f, timeSinceLastJiggle %f, delta = %f, deactivating", idleTime, timeSinceLastJiggle, timeSinceLastJiggle - idleTime);
[self setJigglingActive:NO];
}
else if (((callout_counter & 0x0F) == 0) || jiggleConditionsLikelyToHaveChanged)
Expand Down Expand Up @@ -866,10 +895,9 @@ - (void)jiggleMouse:(id)unused
// BCH 19 May 2016: Adding the new IOKit call IOPMAssertionDeclareUserActivity(), which appears to be equivalent
// to UpdateSystemActivity(UsrActivity). It may have slightly different effects, so I'm keeping the call to
// UpdateSystemActivity(UsrActivity) above as well, just to try to ensure the most complete coverage possible.
static IOPMAssertionID assertionID = kIOPMNullAssertionID;

IOPMAssertionDeclareUserActivity(CFSTR("Jiggler"), kIOPMUserActiveLocal, &assertionID);

[self declareUserActivity];

[timeOfLastJiggle release];
timeOfLastJiggle = [[NSDate alloc] init];

Expand Down Expand Up @@ -931,7 +959,7 @@ - (void)iTunesChanged:(NSNotification *)note
// Bump our jiggle code for immediate action if appropriate
[self jiggleMouse:nil];

//NSLog(@"iTunesChanged:");
NSLog(@"iTunesChanged:");
}

- (void)applicationListChanged:(NSNotification *)note
Expand All @@ -941,7 +969,7 @@ - (void)applicationListChanged:(NSNotification *)note
// Bump our jiggle code for immediate action if appropriate
[self jiggleMouse:nil];

//NSLog(@"applicationListChanged:");
NSLog(@"applicationListChanged:");
}

- (void)mountedDevicesChanged:(NSNotification *)note
Expand All @@ -951,7 +979,7 @@ - (void)mountedDevicesChanged:(NSNotification *)note
// Bump our jiggle code for immediate action if appropriate
[self jiggleMouse:nil];

//NSLog(@"mountedDevicesChanged:");
NSLog(@"mountedDevicesChanged:");
}


Expand Down
11 changes: 5 additions & 6 deletions CocoaExtra.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
}
else
{
// Uncomment this to log successful key lookups
//NSLog(@"key \"%@\" produced value \"%@\"", key, localizedString);
NSLog(@"key \"%@\" produced value \"%@\"", key, localizedString);
}

return localizedString;
Expand All @@ -46,8 +45,7 @@
}
else
{
// Uncomment this to log successful key lookups
//NSLog(@"key \"%@\" produced value \"%@\"", key, localizedString);
NSLog(@"key \"%@\" produced value \"%@\"", key, localizedString);
}

return localizedString;
Expand Down Expand Up @@ -271,7 +269,7 @@ - (void)centerOnPrimaryScreen
windowFrame.size.width,
windowFrame.size.height);

//NSLog(@"centerOnPrimaryScreen called for window %p with frame %@. New frame == %@.", self, NSStringFromRect(windowFrame), NSStringFromRect(newFrame));
NSLog(@"centerOnPrimaryScreen called for window %p with frame %@. New frame == %@.", self, NSStringFromRect(windowFrame), NSStringFromRect(newFrame));

if (!NSEqualRects(windowFrame, newFrame))
[self setFrame:newFrame display:YES];
Expand Down Expand Up @@ -394,7 +392,8 @@ BOOL RunningOnBatteryOnly(void)
CFRelease (list);
CFRelease (blob);

//NSLog(@"%d sources, onlyBattery == %@", count, onlyBattery ? @"YES" : @"NO");
NSLog(@"%ld sources, onlyBattery == %@", (long)count, onlyBattery ? @"YES" : @"NO");

return onlyBattery;
}

Expand Down
52 changes: 33 additions & 19 deletions JigglerOverlayWindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ + (JigglerOverlayWindow *)sharedOverlayWindow
- (void)createOverlayWindow
{
NSScreen *mainScreen = [[NSScreen screens] objectAtIndex:0];
// Prefer the mainScreen accessor on macOS 15+ to ensure proper centering
if (@available(macOS 15.0, *))
{
NSScreen *main = [NSScreen mainScreen];
if (main) mainScreen = main;
}
NSRect screenFrame = [mainScreen frame];
NSRect contentRect;
JigglerOverlayView *iconView;
Expand All @@ -81,29 +87,37 @@ - (void)createOverlayWindow
contentRect.origin.y = screenFrame.origin.y + floor((screenFrame.size.height - contentRect.size.height) / 2.0);

// Use our defaults window location if it exists
if (horizontalPosition && verticalPosition)
// On macOS 15+, skip restoring saved position to ensure centering
if (@available(macOS 15.0, *))
{
int horizontalInt = [horizontalPosition intValue];
int verticalInt = [verticalPosition intValue];
NSRect defaultBasedRect = NSMakeRect(horizontalInt, verticalInt, contentRect.size.width, contentRect.size.height);
NSArray *screens = [NSScreen screens];
int i, c;

for (i = 0, c = (int)[screens count]; i < c; ++i)
// do nothing; keep centered on main screen
}
else
{
if (horizontalPosition && verticalPosition)
{
NSScreen *screen = [screens objectAtIndex:i];
NSRect frame = [screen frame];
int horizontalInt = [horizontalPosition intValue];
int verticalInt = [verticalPosition intValue];
NSRect defaultBasedRect = NSMakeRect(horizontalInt, verticalInt, contentRect.size.width, contentRect.size.height);
NSArray *screens = [NSScreen screens];
int i, c;

if (NSIntersectsRect(frame, defaultBasedRect))
for (i = 0, c = (int)[screens count]; i < c; ++i)
{
NSRect intersection = NSIntersectionRect(frame, defaultBasedRect);
float intersectionSize = intersection.size.width * intersection.size.height;
NSScreen *screen = [screens objectAtIndex:i];
NSRect frame = [screen frame];

if (intersectionSize >= 400.0)
if (NSIntersectsRect(frame, defaultBasedRect))
{
// We have a screen on which our defaults-based frame will lie, so we can use it.
contentRect = defaultBasedRect;
break;
NSRect intersection = NSIntersectionRect(frame, defaultBasedRect);
float intersectionSize = intersection.size.width * intersection.size.height;

if (intersectionSize >= 400.0)
{
// We have a screen on which our defaults-based frame will lie, so we can use it.
contentRect = defaultBasedRect;
break;
}
}
}
}
Expand Down Expand Up @@ -221,7 +235,7 @@ - (void)scheduleTimer

- (void)activate
{
//NSLog(@"+++ activate");
NSLog(@"+++ activate");
if (!overlayWindow)
[self createOverlayWindow];

Expand All @@ -231,7 +245,7 @@ - (void)activate

- (void)deactivate
{
//NSLog(@"--- deactivate");
NSLog(@"--- deactivate");
activated = NO;
[self scheduleTimer];
}
Expand Down
5 changes: 5 additions & 0 deletions Jiggler_Prefix.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif

#if !defined(DEBUG)
#define NSLog(...) do {} while (0)
#define NSLogv(fmt, args) do {} while (0)
#endif
Loading