#include "platform-funcs.hpp" #include "hotkey.hpp" #include "log-helper.hpp" #include #include #include #include #include #include #undef Bool #undef CursorShape #undef Expose #undef KeyPress #undef KeyRelease #undef FocusIn #undef FocusOut #undef FontChange #undef None #undef Status #undef Unsorted #include #include #include #include #include #include #include #ifdef PROCPS_AVAILABLE #include #endif #ifdef PROCPS2_AVAILABLE #include #endif #include #include namespace advss { static Display *xdisplay = 0; static QLibrary *libXtstHandle = nullptr; typedef int (*keyPressFunc)(Display *, unsigned int, bool, unsigned long); static keyPressFunc pressFunc = nullptr; bool canSimulateKeyPresses = false; static QLibrary *libXssHandle = nullptr; typedef XScreenSaverInfo *(*XScreenSaverAllocInfoFunc)(); typedef int (*XScreenSaverQueryInfoFunc)(Display *, Window, XScreenSaverInfo *); static XScreenSaverAllocInfoFunc allocSSFunc = nullptr; static XScreenSaverQueryInfoFunc querySSFunc = nullptr; bool canGetIdleTime = false; std::chrono::high_resolution_clock::time_point lastMouseLeftClickTime{}; std::chrono::high_resolution_clock::time_point lastMouseMiddleClickTime{}; std::chrono::high_resolution_clock::time_point lastMouseRightClickTime{}; static QLibrary *libprocps = nullptr; #ifdef PROCPS_AVAILABLE typedef PROCTAB *(*openproc_func)(int flags); typedef void (*closeproc_func)(PROCTAB *PT); typedef proc_t *(*readproc_func)(PROCTAB *__restrict const PT, proc_t *__restrict p); static openproc_func openproc_ = nullptr; static closeproc_func closeproc_ = nullptr; static readproc_func readproc_ = nullptr; #endif static bool libprocpsSupported = false; static QLibrary *libproc2 = nullptr; #ifdef PROCPS2_AVAILABLE typedef int (*procps_pids_new_func)(struct pids_info **info, enum pids_item *items, int numitems); typedef struct pids_stack *(*procps_pids_get_func)(struct pids_info *info, enum pids_fetch_type which); typedef int (*procps_pids_unref_func)(struct pids_info **info); static procps_pids_new_func procps_pids_new_ = nullptr; static procps_pids_get_func procps_pids_get_ = nullptr; static procps_pids_unref_func procps_pids_unref_ = nullptr; #endif static bool libprocps2Supported = false; Display *disp() { if (!xdisplay) { xdisplay = XOpenDisplay(NULL); } return xdisplay; } void cleanupDisplay() { if (!xdisplay) { return; } XCloseDisplay(xdisplay); xdisplay = 0; } static bool ewmhIsSupported() { Display *display = disp(); if (!display) { return false; } auto window = DefaultRootWindow(display); if (!window) { return false; } Atom netSupportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", true); Atom actualType; int format = 0; unsigned long num = 0, bytes = 0; unsigned char *data = NULL; Window ewmhWindow = 0; int status = XGetWindowProperty(display, window, netSupportingWmCheck, 0L, 1L, false, XA_WINDOW, &actualType, &format, &num, &bytes, &data); if (status == Success) { if (num > 0) { ewmhWindow = ((Window *)data)[0]; } if (data) { XFree(data); data = NULL; } } if (ewmhWindow) { status = XGetWindowProperty(display, ewmhWindow, netSupportingWmCheck, 0L, 1L, false, XA_WINDOW, &actualType, &format, &num, &bytes, &data); if (status != Success || num == 0 || ewmhWindow != ((Window *)data)[0]) { ewmhWindow = 0; } if (status == Success && data) { XFree(data); } } return ewmhWindow != 0; } static QStringList getStates(Window window) { QStringList states; if (!window || !ewmhIsSupported()) { return states; } Atom wmState = XInternAtom(disp(), "_NET_WM_STATE", true), type; int format; unsigned long num, bytes; unsigned char *data; int status = XGetWindowProperty(disp(), window, wmState, 0, ~0L, false, AnyPropertyType, &type, &format, &num, &bytes, &data); if (status == Success) { for (unsigned long i = 0; i < num; i++) { states.append(QString( XGetAtomName(disp(), ((Atom *)data)[i]))); } XFree(data); } return states; } static std::vector getTopLevelWindows() { std::vector res; if (!ewmhIsSupported()) { return res; } Atom netClList = XInternAtom(disp(), "_NET_CLIENT_LIST", true); Atom actualType; int format; unsigned long num, bytes; Window *data = 0; for (int i = 0; i < ScreenCount(disp()); ++i) { Window rootWindow = RootWindow(disp(), i); if (!rootWindow) { continue; } int status = XGetWindowProperty(disp(), rootWindow, netClList, 0L, ~0L, false, AnyPropertyType, &actualType, &format, &num, &bytes, (uint8_t **)&data); if (status != Success) { continue; } for (unsigned long i = 0; i < num; ++i) { res.emplace_back(data[i]); } XFree(data); } return res; } std::string getWindowName(Window window) { auto display = disp(); if (!display || !window) { return ""; } std::string windowTitle; char *name; int status = XFetchName(display, window, &name); if (status >= Success && name != nullptr) { std::string str(name); windowTitle = str; XFree(name); } else { XTextProperty xtp_new_name; if (XGetWMName(display, window, &xtp_new_name) != 0 && xtp_new_name.value != nullptr) { std::string str((const char *)xtp_new_name.value); windowTitle = str; XFree(xtp_new_name.value); } } return windowTitle; } void GetWindowList(std::vector &windows) { windows.resize(0); for (auto window : getTopLevelWindows()) { auto name = getWindowName(window); if (name.empty()) { continue; } windows.emplace_back(name); } } void GetWindowList(QStringList &windows) { windows.clear(); for (auto window : getTopLevelWindows()) { auto name = getWindowName(window); if (name.empty()) { continue; } windows << QString::fromStdString(name); } } int getActiveWindow(Window *&window) { if (!ewmhIsSupported()) { return -1; } Atom active = XInternAtom(disp(), "_NET_ACTIVE_WINDOW", true); Atom actualType; int format; unsigned long num, bytes; auto rootWindow = DefaultRootWindow(disp()); if (!rootWindow) { return -2; } return XGetWindowProperty(disp(), rootWindow, active, 0L, ~0L, false, AnyPropertyType, &actualType, &format, &num, &bytes, (uint8_t **)&window); } void GetCurrentWindowTitle(std::string &title) { Window *data = 0; if (getActiveWindow(data) != Success || !data) { return; } if (!data[0]) { XFree(data); return; } auto name = getWindowName(data[0]); XFree(data); if (name.empty()) { return; } title = name; } bool windowStatesAreSet(const std::string &windowTitle, std::vector &expectedStates) { if (!ewmhIsSupported()) { return false; } std::vector windows = getTopLevelWindows(); for (auto &window : windows) { auto name = getWindowName(window); if (name.empty()) { continue; } bool equals = windowTitle == name; bool matches = QString::fromStdString(name).contains( QRegularExpression( QString::fromStdString(windowTitle))); if (!(equals || matches)) { continue; } QStringList states = getStates(window); if (states.isEmpty()) { if (expectedStates.empty()) { return true; } return false; } for (const auto &state : expectedStates) { if (!states.contains(state)) { return false; } } return true; } return false; } bool IsMaximized(const std::string &title) { std::vector states; states.emplace_back("_NET_WM_STATE_MAXIMIZED_VERT"); states.emplace_back("_NET_WM_STATE_MAXIMIZED_HORZ"); return windowStatesAreSet(title, states); } bool IsFullscreen(const std::string &title) { std::vector states; states.emplace_back("_NET_WM_STATE_FULLSCREEN"); return windowStatesAreSet(title, states); } std::optional GetTextInWindow(const std::string &) { // Not implemented return {}; } static void getProcessListProcps(QStringList &processes) { #ifdef PROCPS_AVAILABLE PROCTAB *proc = openproc_(PROC_FILLSTAT); proc_t proc_info; memset(&proc_info, 0, sizeof(proc_info)); while (readproc_(proc, &proc_info) != NULL) { QString procName(proc_info.cmd); if (!procName.isEmpty() && !processes.contains(procName)) { processes << procName; } } closeproc_(proc); #endif } static void getProcessListProcps2(QStringList &processes) { #ifdef PROCPS2_AVAILABLE struct pids_info *info = NULL; struct pids_stack *stack; enum pids_item Items[] = { PIDS_CMD, }; if (procps_pids_new_(&info, Items, sizeof(Items) / sizeof(Items[0])) < 0) { return; } while ((stack = procps_pids_get_(info, PIDS_FETCH_TASKS_ONLY))) { auto cmd = PIDS_VAL(0, str, stack, info); QString procName(cmd); if (!procName.isEmpty() && !processes.contains(procName)) { processes << procName; } } procps_pids_unref_(&info); #endif } void GetProcessList(QStringList &processes) { processes.clear(); if (libprocpsSupported) { getProcessListProcps(processes); return; } if (libprocps2Supported) { getProcessListProcps2(processes); } } long getForegroundProcessPid() { Window *window; if (getActiveWindow(window) != Success || !window || !*window) { return -1; } Atom atom, actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after; unsigned char *prop; long long pid; atom = XInternAtom(disp(), "_NET_WM_PID", True); auto status = XGetWindowProperty(disp(), *window, atom, 0, 1024, False, XA_CARDINAL, &actual_type, &actual_format, &nitems, &bytes_after, &prop); XFree(window); if (status != 0) { return -2; } if (!prop) { return -3; } pid = *((long *)prop); XFree(prop); return pid; } std::string getProcNameFromPid(long pid) { std::string path = "/proc/" + std::to_string(pid) + "/comm"; std::ifstream t(path); std::stringstream buffer; buffer << t.rdbuf(); std::string name = buffer.str(); if (!name.empty() && name[name.length() - 1] == '\n') { name.erase(name.length() - 1); } return name; } void GetForegroundProcessName(QString &proc) { std::string temp; GetForegroundProcessName(temp); proc = QString::fromStdString(temp); } void GetForegroundProcessName(std::string &proc) { proc.resize(0); auto pid = getForegroundProcessPid(); proc = getProcNameFromPid(pid); } bool IsInFocus(const QString &executable) { std::string current; GetForegroundProcessName(current); // True if executable switch equals current window bool equals = (executable.toStdString() == current); // True if executable switch matches current window bool matches = QString::fromStdString(current).contains( QRegularExpression(executable)); return (equals || matches); } int SecondsSinceLastInput() { if (!canGetIdleTime) { return 0; } auto display = disp(); if (!display) { return 0; } auto window = DefaultRootWindow(display); if (!window) { return 0; } time_t idle_time; auto mit_info = allocSSFunc(); auto status = querySSFunc(display, window, mit_info); idle_time = (mit_info->idle) / 1000; XFree(mit_info); return status != 0 ? idle_time : 0; } static std::unordered_map keyTable = { // Chars {HotkeyType::Key_A, XK_A}, {HotkeyType::Key_B, XK_B}, {HotkeyType::Key_C, XK_C}, {HotkeyType::Key_D, XK_D}, {HotkeyType::Key_E, XK_E}, {HotkeyType::Key_F, XK_F}, {HotkeyType::Key_G, XK_G}, {HotkeyType::Key_H, XK_H}, {HotkeyType::Key_I, XK_I}, {HotkeyType::Key_J, XK_J}, {HotkeyType::Key_K, XK_K}, {HotkeyType::Key_L, XK_L}, {HotkeyType::Key_M, XK_M}, {HotkeyType::Key_N, XK_N}, {HotkeyType::Key_O, XK_O}, {HotkeyType::Key_P, XK_P}, {HotkeyType::Key_Q, XK_Q}, {HotkeyType::Key_R, XK_R}, {HotkeyType::Key_S, XK_S}, {HotkeyType::Key_T, XK_T}, {HotkeyType::Key_U, XK_U}, {HotkeyType::Key_V, XK_V}, {HotkeyType::Key_W, XK_W}, {HotkeyType::Key_X, XK_X}, {HotkeyType::Key_Y, XK_Y}, {HotkeyType::Key_Z, XK_Z}, // Numbers {HotkeyType::Key_0, XK_0}, {HotkeyType::Key_1, XK_1}, {HotkeyType::Key_2, XK_2}, {HotkeyType::Key_3, XK_3}, {HotkeyType::Key_4, XK_4}, {HotkeyType::Key_5, XK_5}, {HotkeyType::Key_6, XK_6}, {HotkeyType::Key_7, XK_7}, {HotkeyType::Key_8, XK_8}, {HotkeyType::Key_9, XK_9}, {HotkeyType::Key_F1, XK_F1}, {HotkeyType::Key_F2, XK_F2}, {HotkeyType::Key_F3, XK_F3}, {HotkeyType::Key_F4, XK_F4}, {HotkeyType::Key_F5, XK_F5}, {HotkeyType::Key_F6, XK_F6}, {HotkeyType::Key_F7, XK_F7}, {HotkeyType::Key_F8, XK_F8}, {HotkeyType::Key_F9, XK_F9}, {HotkeyType::Key_F10, XK_F10}, {HotkeyType::Key_F11, XK_F11}, {HotkeyType::Key_F12, XK_F12}, {HotkeyType::Key_F13, XK_F13}, {HotkeyType::Key_F14, XK_F14}, {HotkeyType::Key_F15, XK_F15}, {HotkeyType::Key_F16, XK_F16}, {HotkeyType::Key_F17, XK_F17}, {HotkeyType::Key_F18, XK_F18}, {HotkeyType::Key_F19, XK_F19}, {HotkeyType::Key_F20, XK_F20}, {HotkeyType::Key_F21, XK_F21}, {HotkeyType::Key_F22, XK_F22}, {HotkeyType::Key_F23, XK_F23}, {HotkeyType::Key_F24, XK_F24}, {HotkeyType::Key_Escape, XK_Escape}, {HotkeyType::Key_Space, XK_space}, {HotkeyType::Key_Return, XK_Return}, {HotkeyType::Key_Backspace, XK_BackSpace}, {HotkeyType::Key_Tab, XK_Tab}, {HotkeyType::Key_Shift_L, XK_Shift_L}, {HotkeyType::Key_Shift_R, XK_Shift_R}, {HotkeyType::Key_Control_L, XK_Control_L}, {HotkeyType::Key_Control_R, XK_Control_R}, {HotkeyType::Key_Alt_L, XK_Alt_L}, {HotkeyType::Key_Alt_R, XK_Alt_R}, {HotkeyType::Key_Win_L, XK_Super_L}, {HotkeyType::Key_Win_R, XK_Super_R}, {HotkeyType::Key_Apps, XK_Hyper_L}, {HotkeyType::Key_CapsLock, XK_Caps_Lock}, {HotkeyType::Key_NumLock, XK_Num_Lock}, {HotkeyType::Key_ScrollLock, XK_Scroll_Lock}, {HotkeyType::Key_PrintScreen, XK_Print}, {HotkeyType::Key_Pause, XK_Pause}, {HotkeyType::Key_Insert, XK_Insert}, {HotkeyType::Key_Delete, XK_Delete}, {HotkeyType::Key_PageUP, XK_Page_Up}, {HotkeyType::Key_PageDown, XK_Page_Down}, {HotkeyType::Key_Home, XK_Home}, {HotkeyType::Key_End, XK_End}, {HotkeyType::Key_Left, XK_Left}, {HotkeyType::Key_Up, XK_Up}, {HotkeyType::Key_Right, XK_Right}, {HotkeyType::Key_Down, XK_Down}, {HotkeyType::Key_Numpad0, XK_KP_0}, {HotkeyType::Key_Numpad1, XK_KP_1}, {HotkeyType::Key_Numpad2, XK_KP_2}, {HotkeyType::Key_Numpad3, XK_KP_3}, {HotkeyType::Key_Numpad4, XK_KP_4}, {HotkeyType::Key_Numpad5, XK_KP_5}, {HotkeyType::Key_Numpad6, XK_KP_6}, {HotkeyType::Key_Numpad7, XK_KP_7}, {HotkeyType::Key_Numpad8, XK_KP_8}, {HotkeyType::Key_Numpad9, XK_KP_9}, {HotkeyType::Key_NumpadAdd, XK_KP_Add}, {HotkeyType::Key_NumpadSubtract, XK_KP_Subtract}, {HotkeyType::Key_NumpadMultiply, XK_KP_Multiply}, {HotkeyType::Key_NumpadDivide, XK_KP_Divide}, {HotkeyType::Key_NumpadDecimal, XK_KP_Decimal}, {HotkeyType::Key_NumpadEnter, XK_KP_Enter}, }; void PressKeys(const std::vector keys, int duration) { if (!canSimulateKeyPresses) { return; } Display *display = disp(); if (!display) { return; } // Press keys for (auto &key : keys) { auto it = keyTable.find(key); if (it == keyTable.end()) { continue; } pressFunc(display, XKeysymToKeycode(display, it->second), true, CurrentTime); } XFlush(display); std::this_thread::sleep_for(std::chrono::milliseconds(duration)); // Release keys for (auto &key : keys) { auto it = keyTable.find(key); if (it == keyTable.end()) { continue; } pressFunc(display, XKeysymToKeycode(display, it->second), false, CurrentTime); } XFlush(display); } static void initXtst() { libXtstHandle = new QLibrary("libXtst", nullptr); pressFunc = (keyPressFunc)libXtstHandle->resolve("XTestFakeKeyEvent"); int _; canSimulateKeyPresses = pressFunc && XQueryExtension(disp(), "XTEST", &_, &_, &_); } static void initXss() { libXssHandle = new QLibrary("libXss", nullptr); allocSSFunc = (XScreenSaverAllocInfoFunc)libXssHandle->resolve( "XScreenSaverAllocInfo"); querySSFunc = (XScreenSaverQueryInfoFunc)libXssHandle->resolve( "XScreenSaverQueryInfo"); int _; canGetIdleTime = allocSSFunc && querySSFunc && XQueryExtension(disp(), ScreenSaverName, &_, &_, &_); } static void initProcps() { #ifdef PROCPS_AVAILABLE libprocps = new QLibrary("libprocps", nullptr); openproc_ = (openproc_func)libprocps->resolve("openproc"); closeproc_ = (closeproc_func)libprocps->resolve("closeproc"); readproc_ = (readproc_func)libprocps->resolve("readproc"); if (openproc_ && closeproc_ && readproc_) { libprocpsSupported = true; blog(LOG_INFO, "libprocps symbols resolved successfully!"); } #endif } static void initProc2() { #ifdef PROCPS2_AVAILABLE libproc2 = new QLibrary("libproc2", nullptr); procps_pids_new_ = (procps_pids_new_func)libproc2->resolve("procps_pids_new"); procps_pids_get_ = (procps_pids_get_func)libproc2->resolve("procps_pids_get"); procps_pids_unref_ = (procps_pids_unref_func)libproc2->resolve("procps_pids_unref"); if (procps_pids_new_ && procps_pids_get_ && procps_pids_unref_) { libprocps2Supported = true; blog(LOG_INFO, "libproc2 symbols resolved successfully!"); } #endif } void PlatformInit() { auto display = disp(); if (!display) { return; } initXtst(); initXss(); initProcps(); initProc2(); } static void cleanupHelper(QLibrary *lib) { if (lib) { delete lib; lib = nullptr; } } void PlatformCleanup() { cleanupHelper(libXtstHandle); cleanupHelper(libXssHandle); cleanupHelper(libprocps); cleanupHelper(libproc2); cleanupDisplay(); } } // namespace advss