Skip to content
This repository was archived by the owner on Jan 30, 2025. It is now read-only.

Commit

Permalink
vm_tools: sommelier: Parse steam_game_id from WM_CLASS as a fallback.
Browse files Browse the repository at this point in the history
Some games have the STEAM_GAME property set on their launcher window,
but not on their game window. But in at least some cases, the
WM_CLASS *is* set on the game window with the format
"steam_app_<id>\0steam_app_<id>". We can use this to extract the
steam_game_id in cases where STEAM_GAME is missing.

BUG=b:281593467
TEST=run unit tests: meson setup builddir -Dquirks=true; pushd builddir; ninja && ./sommelier_test; popd

Change-Id: Ia59d49ce7e7a91d380c07ba2b85ab46d1e1ce404
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/4930950
Reviewed-by: Lucy Qu <[email protected]>
Commit-Queue: Lucy Qu <[email protected]>
Auto-Submit: Chloe Pelling <[email protected]>
Tested-by: Chloe Pelling <[email protected]>
  • Loading branch information
cpelling authored and Chromeos LUCI committed Oct 12, 2023
1 parent 2600078 commit 9cf98e9
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 2 deletions.
82 changes: 82 additions & 0 deletions sommelier-x11event-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,87 @@ TEST_F(X11EventTest, PropertyNotifyStoresSteamId) {
EXPECT_EQ(window->steam_game_id, steam_game_id);
}

TEST_F(X11EventTest, MapRequestParsesSteamIdFromClass) {
xcb.DelegateToFake();
sl_window* window = CreateWindowWithoutRole();
xcb.create_window(nullptr, 32, window->id, XCB_WINDOW_NONE, 0, 0, 800, 600, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0,
nullptr);
const char clazz[] = "steam_app_7890\0steam_app_7890";
xcb.change_property(nullptr, XCB_PROP_MODE_REPLACE, window->id,
XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, sizeof(clazz),
clazz);

xcb_map_request_event_t event;
event.response_type = XCB_MAP_REQUEST;
event.window = window->id;
sl_handle_map_request(&ctx, &event);

EXPECT_EQ(window->steam_game_id, 7890);
}

TEST_F(X11EventTest, MapRequestPrefersSteamGameIdOverClass) {
uint32_t steam_game_id = 123456;
xcb.DelegateToFake();
sl_window* window = CreateWindowWithoutRole();
xcb.create_window(nullptr, 32, window->id, XCB_WINDOW_NONE, 0, 0, 800, 600, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0,
nullptr);
const char clazz[] = "steam_app_7890\0steam_app_7890";
xcb.change_property(nullptr, XCB_PROP_MODE_REPLACE, window->id,
XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, sizeof(clazz),
clazz);
xcb.change_property(nullptr, XCB_PROP_MODE_REPLACE, window->id,
ctx.atoms[ATOM_STEAM_GAME].value, XCB_ATOM_CARDINAL, 32,
1, &steam_game_id);

// Act: map the window
xcb_map_request_event_t event;
event.response_type = XCB_MAP_REQUEST;
event.window = window->id;
sl_handle_map_request(&ctx, &event);

// Assert: Uses the ID from the STEAM_GAME property, not from WM_CLASS
EXPECT_EQ(window->steam_game_id, steam_game_id);
}

TEST_F(X11EventTest, PropertyNotifyParsesSteamIdFromClassAsFallback) {
uint32_t steam_game_id = 123456;
xcb.DelegateToFake();
sl_window* window = CreateWindowWithoutRole();
xcb.create_window(nullptr, 32, window->id, XCB_WINDOW_NONE, 0, 0, 800, 600, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0,
nullptr);

// Act: set STEAM_GAME to one ID, and WM_CLASS to specify another
xcb.change_property(nullptr, XCB_PROP_MODE_REPLACE, window->id,
ctx.atoms[ATOM_STEAM_GAME].value, XCB_ATOM_CARDINAL, 32,
1, &steam_game_id);
{
xcb_property_notify_event_t event;
event.response_type = XCB_PROPERTY_NOTIFY;
event.window = window->id;
event.atom = ctx.atoms[ATOM_STEAM_GAME].value;
event.state = XCB_PROPERTY_NEW_VALUE;
sl_handle_property_notify(&ctx, &event);
}

const char clazz[] = "steam_app_7890\0steam_app_7890";
xcb.change_property(nullptr, XCB_PROP_MODE_REPLACE, window->id,
XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, sizeof(clazz),
clazz);
{
xcb_property_notify_event_t event;
event.response_type = XCB_PROPERTY_NOTIFY;
event.window = window->id;
event.atom = XCB_ATOM_WM_CLASS;
event.state = XCB_PROPERTY_NEW_VALUE;
sl_handle_property_notify(&ctx, &event);
}

// Assert: STEAM_GAME is preferred, even though WM_CLASS was changed last.
EXPECT_EQ(window->steam_game_id, steam_game_id);
}

} // namespace sommelier
} // namespace vm_tools
19 changes: 17 additions & 2 deletions sommelier.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "xcb/xcb-shim.h"

#include <assert.h>
#include <cstdlib>
#include <errno.h>
#include <fcntl.h>
#include <gbm.h>
Expand Down Expand Up @@ -97,6 +98,8 @@ struct sl_data_source {
#define MIN_AURA_SHELL_VERSION 6
#define MAX_AURA_SHELL_VERSION 38

static const char STEAM_APP_CLASS_PREFIX[] = "steam_app_";

int sl_open_wayland_socket(const char* socket_name,
struct sockaddr_un* addr,
int* lock_fd,
Expand Down Expand Up @@ -1125,12 +1128,24 @@ static const char* sl_decode_wm_class(struct sl_window* window,
// WM_CLASS property contains two consecutive null-terminated strings.
// These specify the Instance and Class names. If a global app ID is
// not set then use Class name for app ID.
const char* value = static_cast<char*>(xcb_get_property_value(reply));
int value_length = xcb_get_property_value_length(reply);
const char* value = static_cast<char*>(xcb()->get_property_value(reply));
int value_length = xcb()->get_property_value_length(reply);
int instance_length = strnlen(value, value_length);
if (value_length > instance_length) {
window->clazz = strndup(value + instance_length + 1,
value_length - instance_length - 1);

if (!window->steam_game_id) {
// If there's no known Steam Game ID for this window,
// attempt to parse one from the class name.
if (strncmp(window->clazz, STEAM_APP_CLASS_PREFIX,
strlen(STEAM_APP_CLASS_PREFIX)) == 0) {
// atoi() returns 0 on error, in which case steam_game_id
// simply remains effectively unset.
window->steam_game_id =
atoi(window->clazz + strlen(STEAM_APP_CLASS_PREFIX));
}
}
return window->clazz;
}
return nullptr;
Expand Down

0 comments on commit 9cf98e9

Please sign in to comment.