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]);
|
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()
|
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];
|
NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||||
if (app) {
|
if (app) {
|
||||||
NSString* name = [app localizedName];
|
NSString* name = [app localizedName];
|
||||||
|
|||||||
Reference in New Issue
Block a user