Feature: Add user idle and screen lock detection for macOS
This commit is contained in:
106
macos/main.mm
106
macos/main.mm
@@ -141,9 +141,113 @@ static std::string getTimeString()
|
||||
return std::string([dateString UTF8String]);
|
||||
}
|
||||
|
||||
// Get active application name
|
||||
// Get user idle time in seconds (time since last keyboard/mouse input)
|
||||
static double getUserIdleTime()
|
||||
{
|
||||
// CGEventSourceSecondsSinceLastEventType returns seconds since last event
|
||||
// kCGEventSourceStateCombinedSessionState includes all input sources
|
||||
CFTimeInterval idleTime = CGEventSourceSecondsSinceLastEventType(
|
||||
kCGEventSourceStateCombinedSessionState,
|
||||
kCGAnyInputEventType
|
||||
);
|
||||
// Defensive: ensure non-negative (edge case protection)
|
||||
return idleTime > 0 ? idleTime : 0;
|
||||
}
|
||||
|
||||
// Check if screen is locked
|
||||
static bool isScreenLocked()
|
||||
{
|
||||
// Method 1: Check CGSession dictionary for screen lock status
|
||||
CFDictionaryRef sessionDict = CGSessionCopyCurrentDictionary();
|
||||
if (sessionDict) {
|
||||
// Check for "CGSSessionScreenIsLocked" key
|
||||
CFBooleanRef screenLocked = (CFBooleanRef)CFDictionaryGetValue(
|
||||
sessionDict, CFSTR("CGSSessionScreenIsLocked"));
|
||||
if (screenLocked && CFBooleanGetValue(screenLocked)) {
|
||||
CFRelease(sessionDict);
|
||||
return true;
|
||||
}
|
||||
CFRelease(sessionDict);
|
||||
}
|
||||
|
||||
// Method 2: Check if loginwindow is frontmost (screen saver / lock screen)
|
||||
NSRunningApplication* frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
if (frontApp) {
|
||||
NSString* bundleId = [frontApp bundleIdentifier];
|
||||
if ([bundleId isEqualToString:@"com.apple.loginwindow"] ||
|
||||
[bundleId isEqualToString:@"com.apple.ScreenSaver.Engine"]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Format time as HH:MM:SS with prefix
|
||||
static std::string formatStatusTime(const char* prefix, double seconds)
|
||||
{
|
||||
int totalSecs = (int)seconds;
|
||||
int hours = totalSecs / 3600;
|
||||
int mins = (totalSecs % 3600) / 60;
|
||||
int secs = totalSecs % 60;
|
||||
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "%s: %02d:%02d:%02d", prefix, hours, mins, secs);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
// Get active application name or idle/locked status (works for background processes)
|
||||
static std::string getActiveApp()
|
||||
{
|
||||
double idleTime = getUserIdleTime();
|
||||
|
||||
// Check if screen is locked first
|
||||
if (isScreenLocked()) {
|
||||
return formatStatusTime("Locked", idleTime);
|
||||
}
|
||||
|
||||
// Check user idle time (matches Windows/Linux: 6 seconds threshold)
|
||||
// If idle for more than 6 seconds, report inactive status
|
||||
if (idleTime >= 6.0) {
|
||||
return formatStatusTime("Inactive", idleTime);
|
||||
}
|
||||
|
||||
// Use CGWindowListCopyWindowInfo to get the frontmost window
|
||||
// This works reliably even when running as a background/nohup process
|
||||
CFArrayRef windowList = CGWindowListCopyWindowInfo(
|
||||
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
|
||||
kCGNullWindowID
|
||||
);
|
||||
|
||||
if (windowList) {
|
||||
CFIndex count = CFArrayGetCount(windowList);
|
||||
for (CFIndex i = 0; i < count; i++) {
|
||||
CFDictionaryRef window = (CFDictionaryRef)CFArrayGetValueAtIndex(windowList, i);
|
||||
|
||||
// Get window layer - layer 0 is normal windows
|
||||
CFNumberRef layerRef = (CFNumberRef)CFDictionaryGetValue(window, kCGWindowLayer);
|
||||
int layer = 0;
|
||||
if (layerRef) {
|
||||
CFNumberGetValue(layerRef, kCFNumberIntType, &layer);
|
||||
}
|
||||
|
||||
// Skip non-normal windows (menu bar, dock, etc.)
|
||||
if (layer != 0) continue;
|
||||
|
||||
// Get owner name (application name)
|
||||
CFStringRef ownerName = (CFStringRef)CFDictionaryGetValue(window, kCGWindowOwnerName);
|
||||
if (ownerName) {
|
||||
char buffer[256] = {};
|
||||
if (CFStringGetCString(ownerName, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
|
||||
CFRelease(windowList);
|
||||
return std::string(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
CFRelease(windowList);
|
||||
}
|
||||
|
||||
// Fallback to NSWorkspace (may not work for background processes)
|
||||
NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
if (app) {
|
||||
NSString* name = [app localizedName];
|
||||
|
||||
Reference in New Issue
Block a user