Compare commits
3 Commits
36ba9ccc1d
...
56419f8ecb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56419f8ecb | ||
|
|
bb6fd7b1b9 | ||
|
|
3607f1d768 |
@@ -2,6 +2,7 @@
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <dispatch/dispatch.h>
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#import "../client/IOCPClient.h"
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
@@ -132,4 +133,7 @@ private:
|
||||
|
||||
// Input handler for mouse/keyboard control
|
||||
std::unique_ptr<InputHandler> m_inputHandler;
|
||||
|
||||
// Power management: prevent display sleep during remote desktop
|
||||
IOPMAssertionID m_displayAssertionID;
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ ScreenHandler::ScreenHandler(IOCPClient* client)
|
||||
, m_maxFPS(15)
|
||||
, m_qualityLevel(QUALITY_GOOD) // Use fixed QUALITY_GOOD (H264) for web compatibility
|
||||
, m_h264Bitrate(3000000) // 3 Mbps (matches Windows QUALITY_GOOD)
|
||||
, m_displayAssertionID(0)
|
||||
{
|
||||
memset(&m_bmpHeader, 0, sizeof(m_bmpHeader));
|
||||
|
||||
@@ -115,6 +116,21 @@ void ScreenHandler::start(IOCPClient* client, uint64_t clientID)
|
||||
m_clientID = clientID;
|
||||
m_running = true;
|
||||
|
||||
// Prevent display sleep during remote desktop session
|
||||
if (m_displayAssertionID == 0) {
|
||||
IOReturn result = IOPMAssertionCreateWithName(
|
||||
kIOPMAssertionTypeNoDisplaySleep,
|
||||
kIOPMAssertionLevelOn,
|
||||
CFSTR("SimpleRemoter - remote desktop session active"),
|
||||
&m_displayAssertionID
|
||||
);
|
||||
if (result == kIOReturnSuccess) {
|
||||
NSLog(@"Display sleep disabled for remote desktop (ID: %u)", m_displayAssertionID);
|
||||
} else {
|
||||
NSLog(@"Warning: Failed to prevent display sleep (error: 0x%x)", result);
|
||||
}
|
||||
}
|
||||
|
||||
m_captureThread = std::thread(&ScreenHandler::captureLoop, this);
|
||||
}
|
||||
|
||||
@@ -130,6 +146,13 @@ void ScreenHandler::stop()
|
||||
m_h264Encoder->close();
|
||||
m_h264Encoder.reset();
|
||||
}
|
||||
|
||||
// Release display sleep assertion - allow screen to turn off
|
||||
if (m_displayAssertionID != 0) {
|
||||
IOPMAssertionRelease(m_displayAssertionID);
|
||||
NSLog(@"Display sleep re-enabled (released ID: %u)", m_displayAssertionID);
|
||||
m_displayAssertionID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenHandler::sendBitmapInfo()
|
||||
|
||||
140
macos/main.mm
140
macos/main.mm
@@ -6,6 +6,7 @@
|
||||
#import <signal.h>
|
||||
#import <unistd.h>
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#import <fstream>
|
||||
#import <thread>
|
||||
#import <atomic>
|
||||
@@ -140,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];
|
||||
@@ -478,6 +583,30 @@ int main(int argc, const char* argv[])
|
||||
@autoreleasepool {
|
||||
NSLog(@"=== macOS Ghost Client ===");
|
||||
|
||||
// ============== Power Management: Keep System Awake ==============
|
||||
// 1. Disable App Nap - prevent macOS from suspending this process
|
||||
id<NSObject> powerActivity = [[NSProcessInfo processInfo]
|
||||
beginActivityWithOptions:(NSActivityUserInitiated | NSActivityIdleSystemSleepDisabled)
|
||||
reason:@"Remote control client must maintain persistent connection"];
|
||||
NSLog(@"App Nap disabled, activity token acquired");
|
||||
|
||||
// 2. Prevent system idle sleep using IOKit power assertion
|
||||
IOPMAssertionID sleepAssertionID = 0;
|
||||
IOReturn result = IOPMAssertionCreateWithName(
|
||||
kIOPMAssertionTypeNoIdleSleep,
|
||||
kIOPMAssertionLevelOn,
|
||||
CFSTR("SimpleRemoter macOS client - maintaining remote connection"),
|
||||
&sleepAssertionID
|
||||
);
|
||||
if (result == kIOReturnSuccess) {
|
||||
NSLog(@"Power assertion created: system idle sleep disabled (ID: %u)", sleepAssertionID);
|
||||
} else {
|
||||
NSLog(@"Warning: Failed to create power assertion (error: 0x%x)", result);
|
||||
}
|
||||
|
||||
// 3. Display sleep: managed by ScreenHandler - only prevents display sleep
|
||||
// when remote desktop is actively connected (saves power when idle)
|
||||
|
||||
// Setup signal handlers
|
||||
setupSignals();
|
||||
|
||||
@@ -549,6 +678,15 @@ int main(int argc, const char* argv[])
|
||||
}
|
||||
|
||||
NSLog(@"Shutting down...");
|
||||
|
||||
// Release power assertions
|
||||
if (sleepAssertionID) {
|
||||
IOPMAssertionRelease(sleepAssertionID);
|
||||
NSLog(@"Released sleep assertion");
|
||||
}
|
||||
// Display assertion is managed by ScreenHandler (released in stop())
|
||||
// powerActivity is automatically released when exiting @autoreleasepool
|
||||
(void)powerActivity; // Suppress unused variable warning
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -2305,8 +2305,8 @@ BOOL CScreenSpyDlg::PreTranslateMessage(MSG* pMsg)
|
||||
MSG wheelMsg = *pMsg;
|
||||
wheelMsg.lParam = MAKELPARAM(pt.x, pt.y);
|
||||
SendScaledMouseMessage(&wheelMsg, true);
|
||||
return TRUE; // 已处理,阻止继续分发到 OnMouseWheel
|
||||
}
|
||||
break;
|
||||
case WM_KEYDOWN:
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYDOWN:
|
||||
@@ -2682,7 +2682,20 @@ void CScreenSpyDlg::OnLButtonUp(UINT nFlags, CPoint point)
|
||||
|
||||
BOOL CScreenSpyDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
|
||||
{
|
||||
return __super::OnMouseWheel(nFlags, zDelta, pt);
|
||||
// Convert screen coordinates to client coordinates
|
||||
ScreenToClient(&pt);
|
||||
|
||||
// Build MSG structure for SendScaledMouseMessage
|
||||
MSG msg = {};
|
||||
msg.hwnd = m_hWnd;
|
||||
msg.message = WM_MOUSEWHEEL;
|
||||
msg.wParam = MAKEWPARAM(nFlags, zDelta);
|
||||
msg.lParam = MAKELPARAM(pt.x, pt.y);
|
||||
msg.time = GetTickCount();
|
||||
msg.pt = { pt.x, pt.y };
|
||||
|
||||
SendScaledMouseMessage(&msg, true);
|
||||
return TRUE; // Message handled, don't pass to parent
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user