From 651e9e142a51ebf5999c190c8d968c7dab4a619f Mon Sep 17 00:00:00 2001 From: Justin van Son Date: Fri, 2 Aug 2024 01:30:23 +0200 Subject: [PATCH 1/7] Support grabbing RS window through title --- native/os_x11_linux.cc | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/native/os_x11_linux.cc b/native/os_x11_linux.cc index 8791d71..46703eb 100644 --- a/native/os_x11_linux.cc +++ b/native/os_x11_linux.cc @@ -141,9 +141,48 @@ bool IsRsWindow(const xcb_window_t window) { if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0 || strcmp(classname, "rs2client.exe") == 0) { auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { + std::cout << "Found client: " << classname << std::endl; free(replyProp); return true; } + } else if (strcmp(classname, "steam_proton") == 0) { + auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); + xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100); + std::unique_ptr reply { xcb_get_property_reply(connection, cookie, NULL), &free }; + if(reply) { + char* title = reinterpret_cast(xcb_get_property_value(reply.get())); + int length = xcb_get_property_value_length(reply.get()); + auto str_title = std::string(title, length); + /* Covers both normal and compatibility mode */ + if (str_title.find("RuneScape") != std::string::npos) { + if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { + xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry(connection, window); + xcb_get_geometry_reply_t* geomReply = xcb_get_geometry_reply(connection, geomCookie, NULL); + if (geomReply) { + uint16_t width = geomReply->width; + uint16_t height = geomReply->height; + free(geomReply); + if (width == 720 && height == 480) { + /* The launcher window doesn't actually get killed on closing, it still exists in xwayland invisibly (Also not visible to the compositor, only to xwayland). + * A workaround is to kill this window with xdotool if it matches this exact window size (it does not change) in for instance a .desktop file of the jagex launcher. + * This native code could also possibly do this, but it's probably out of scope. + * The window has few other ways to identify it as the window's class and title are exactly the same. + * */ + std::cout << "Found RuneScape Launcher phantom window: " << width << "x" << height << "\n"; + } else { + std::cout << "Found correct RuneScape window: " << str_title << std::endl; + free(replyProp); + return true; + } + } + } else { + std::cout << "NonTransient window found: " << str_title << std::endl; + } + } else { + std::cout << "Wrong title: " << str_title << std::endl; + } + } + } } } From c01c7b78a37b4a3664624134a5f112e6c9284531 Mon Sep 17 00:00:00 2001 From: Justin van Son Date: Mon, 30 Jun 2025 14:03:17 +0200 Subject: [PATCH 2/7] Fetch window title only for specific classname --- native/os_x11_linux.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/os_x11_linux.cc b/native/os_x11_linux.cc index 46703eb..2382758 100644 --- a/native/os_x11_linux.cc +++ b/native/os_x11_linux.cc @@ -138,14 +138,14 @@ bool IsRsWindow(const xcb_window_t window) { memcpy(buffer, xcb_get_property_value(replyProp), len); // first is instance name, then class name - both null terminated. we want class name. const char* classname = buffer + strlen(buffer) + 1; - if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0 || strcmp(classname, "rs2client.exe") == 0) { + if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0) { auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { std::cout << "Found client: " << classname << std::endl; free(replyProp); return true; } - } else if (strcmp(classname, "steam_proton") == 0) { + } else if (strcmp(classname, "steam_proton") || strcmp(classname, "rs2client.exe") == 0) { auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100); std::unique_ptr reply { xcb_get_property_reply(connection, cookie, NULL), &free }; From b07495076866e21629e4d0dbaf138a227d889c5b Mon Sep 17 00:00:00 2001 From: Justin van Son Date: Tue, 1 Jul 2025 14:56:42 +0200 Subject: [PATCH 3/7] Always check title --- native/os_x11_linux.cc | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/native/os_x11_linux.cc b/native/os_x11_linux.cc index 2382758..5e3e713 100644 --- a/native/os_x11_linux.cc +++ b/native/os_x11_linux.cc @@ -138,14 +138,7 @@ bool IsRsWindow(const xcb_window_t window) { memcpy(buffer, xcb_get_property_value(replyProp), len); // first is instance name, then class name - both null terminated. we want class name. const char* classname = buffer + strlen(buffer) + 1; - if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0) { - auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); - if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { - std::cout << "Found client: " << classname << std::endl; - free(replyProp); - return true; - } - } else if (strcmp(classname, "steam_proton") || strcmp(classname, "rs2client.exe") == 0) { + if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0 || strcmp(classname, "steam_proton") || strcmp(classname, "rs2client.exe") == 0) { auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100); std::unique_ptr reply { xcb_get_property_reply(connection, cookie, NULL), &free }; @@ -153,7 +146,7 @@ bool IsRsWindow(const xcb_window_t window) { char* title = reinterpret_cast(xcb_get_property_value(reply.get())); int length = xcb_get_property_value_length(reply.get()); auto str_title = std::string(title, length); - /* Covers both normal and compatibility mode */ + /* Covers both normal and compatibility mode (substring of RuneScape (compatibility mode) )*/ if (str_title.find("RuneScape") != std::string::npos) { if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry(connection, window); @@ -163,10 +156,10 @@ bool IsRsWindow(const xcb_window_t window) { uint16_t height = geomReply->height; free(geomReply); if (width == 720 && height == 480) { - /* The launcher window doesn't actually get killed on closing, it still exists in xwayland invisibly (Also not visible to the compositor, only to xwayland). + /* The launcher window doesn't actually get killed on closing, it still exists in xwayland invisibly (Also not visible to the wayland compositor, only to xwayland). * A workaround is to kill this window with xdotool if it matches this exact window size (it does not change) in for instance a .desktop file of the jagex launcher. * This native code could also possibly do this, but it's probably out of scope. - * The window has few other ways to identify it as the window's class and title are exactly the same. + * The native code has no other ways to identify it as the window's class and title are exactly the same as the actual game window. * */ std::cout << "Found RuneScape Launcher phantom window: " << width << "x" << height << "\n"; } else { From fc27d8358e5efe4064421bd7cabcd719c82dbead Mon Sep 17 00:00:00 2001 From: Justin van Son Date: Thu, 3 Jul 2025 21:32:13 +0200 Subject: [PATCH 4/7] Fix bug where if checks for non 0 strcmp --- native/os_x11_linux.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/native/os_x11_linux.cc b/native/os_x11_linux.cc index 5e3e713..58c2fc8 100644 --- a/native/os_x11_linux.cc +++ b/native/os_x11_linux.cc @@ -138,7 +138,8 @@ bool IsRsWindow(const xcb_window_t window) { memcpy(buffer, xcb_get_property_value(replyProp), len); // first is instance name, then class name - both null terminated. we want class name. const char* classname = buffer + strlen(buffer) + 1; - if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0 || strcmp(classname, "steam_proton") || strcmp(classname, "rs2client.exe") == 0) { + if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0 || strcmp(classname, "steam_proton") == 0 || strcmp(classname, "rs2client.exe") == 0) { + std::cout << "Found classname: " << classname << std::endl; auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100); std::unique_ptr reply { xcb_get_property_reply(connection, cookie, NULL), &free }; From 2798829b570b727a691c519e686937a48bf8fce5 Mon Sep 17 00:00:00 2001 From: Justin van Son Date: Thu, 3 Jul 2025 21:32:29 +0200 Subject: [PATCH 5/7] Remove geometry checking, not needed after bugfix --- native/os_x11_linux.cc | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/native/os_x11_linux.cc b/native/os_x11_linux.cc index 58c2fc8..598786d 100644 --- a/native/os_x11_linux.cc +++ b/native/os_x11_linux.cc @@ -150,25 +150,9 @@ bool IsRsWindow(const xcb_window_t window) { /* Covers both normal and compatibility mode (substring of RuneScape (compatibility mode) )*/ if (str_title.find("RuneScape") != std::string::npos) { if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { - xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry(connection, window); - xcb_get_geometry_reply_t* geomReply = xcb_get_geometry_reply(connection, geomCookie, NULL); - if (geomReply) { - uint16_t width = geomReply->width; - uint16_t height = geomReply->height; - free(geomReply); - if (width == 720 && height == 480) { - /* The launcher window doesn't actually get killed on closing, it still exists in xwayland invisibly (Also not visible to the wayland compositor, only to xwayland). - * A workaround is to kill this window with xdotool if it matches this exact window size (it does not change) in for instance a .desktop file of the jagex launcher. - * This native code could also possibly do this, but it's probably out of scope. - * The native code has no other ways to identify it as the window's class and title are exactly the same as the actual game window. - * */ - std::cout << "Found RuneScape Launcher phantom window: " << width << "x" << height << "\n"; - } else { - std::cout << "Found correct RuneScape window: " << str_title << std::endl; - free(replyProp); - return true; - } - } + std::cout << "Found correct RuneScape window: " << str_title << std::endl; + free(replyProp); + return true; } else { std::cout << "NonTransient window found: " << str_title << std::endl; } From e655cae7fa499dc01f761ceb979602e152ac7d87 Mon Sep 17 00:00:00 2001 From: Justin van Son Date: Fri, 4 Jul 2025 00:05:21 +0200 Subject: [PATCH 6/7] Remove debug prints --- native/os_x11_linux.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/native/os_x11_linux.cc b/native/os_x11_linux.cc index 598786d..e4a7087 100644 --- a/native/os_x11_linux.cc +++ b/native/os_x11_linux.cc @@ -139,7 +139,6 @@ bool IsRsWindow(const xcb_window_t window) { // first is instance name, then class name - both null terminated. we want class name. const char* classname = buffer + strlen(buffer) + 1; if (strcmp(classname, "RuneScape") == 0 || strcmp(classname, "steam_app_1343400") == 0 || strcmp(classname, "steam_proton") == 0 || strcmp(classname, "rs2client.exe") == 0) { - std::cout << "Found classname: " << classname << std::endl; auto replyTransient = xcb_get_property_reply(connection, cookieTransient, NULL); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100); std::unique_ptr reply { xcb_get_property_reply(connection, cookie, NULL), &free }; @@ -150,14 +149,11 @@ bool IsRsWindow(const xcb_window_t window) { /* Covers both normal and compatibility mode (substring of RuneScape (compatibility mode) )*/ if (str_title.find("RuneScape") != std::string::npos) { if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { - std::cout << "Found correct RuneScape window: " << str_title << std::endl; free(replyProp); return true; } else { std::cout << "NonTransient window found: " << str_title << std::endl; } - } else { - std::cout << "Wrong title: " << str_title << std::endl; } } From ff49384d3d9f4513a95e459a5b8a926587960b49 Mon Sep 17 00:00:00 2001 From: Justin van Son Date: Fri, 4 Jul 2025 00:05:32 +0200 Subject: [PATCH 7/7] Use more robust title comparison --- native/os_x11_linux.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/os_x11_linux.cc b/native/os_x11_linux.cc index e4a7087..9cefad2 100644 --- a/native/os_x11_linux.cc +++ b/native/os_x11_linux.cc @@ -147,7 +147,7 @@ bool IsRsWindow(const xcb_window_t window) { int length = xcb_get_property_value_length(reply.get()); auto str_title = std::string(title, length); /* Covers both normal and compatibility mode (substring of RuneScape (compatibility mode) )*/ - if (str_title.find("RuneScape") != std::string::npos) { + if (str_title.compare(0, sizeof("RuneScape") - 1, "RuneScape") == 0) { if (replyTransient && xcb_get_property_value_length(replyTransient) == 0) { free(replyProp); return true;