107 lines
4.5 KiB
C
107 lines
4.5 KiB
C
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "commands_native.h"
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xutil.h>
|
|
#include <string.h>
|
|
|
|
static int steam_active_window(Display *display, Window *window) {
|
|
Atom type; int format;
|
|
unsigned long count, remaining;
|
|
unsigned char *data = NULL;
|
|
int known = XGetWindowProperty(display, DefaultRootWindow(display), XInternAtom(display, "_NET_ACTIVE_WINDOW", False),
|
|
0, 1, False, XA_WINDOW, &type, &format, &count, &remaining, &data) == Success &&
|
|
data != NULL && type == XA_WINDOW && format == 32 && count == 1;
|
|
if (known) *window = ((Window *)data)[0];
|
|
if (data != NULL) XFree(data);
|
|
return known;
|
|
}
|
|
|
|
static int steam_window_pid(Display *display, Window window) {
|
|
Atom type; int format;
|
|
unsigned long count, remaining;
|
|
unsigned char *data = NULL;
|
|
int pid = 0;
|
|
if (XGetWindowProperty(display, window, XInternAtom(display, "_NET_WM_PID", False),
|
|
0, 1, False, XA_CARDINAL, &type, &format, &count,
|
|
&remaining, &data) == Success && data != NULL &&
|
|
type == XA_CARDINAL && format == 32 && count == 1) {
|
|
pid = (int)((unsigned long *)data)[0];
|
|
}
|
|
if (data != NULL) XFree(data);
|
|
return pid;
|
|
}
|
|
|
|
static int steam_wrapper(Display *display, Window window) {
|
|
XClassHint hint = {0};
|
|
int matches = XGetClassHint(display, window, &hint) &&
|
|
hint.res_name != NULL && hint.res_class != NULL &&
|
|
strcmp(hint.res_name, "steamwebhelper") == 0 && strcmp(hint.res_class, "steam") == 0;
|
|
if (hint.res_name != NULL) XFree(hint.res_name);
|
|
if (hint.res_class != NULL) XFree(hint.res_class);
|
|
return matches;
|
|
}
|
|
|
|
static int steam_browser_child(Display *display, Window wrapper, int pid, Window child) {
|
|
XWindowAttributes attributes;
|
|
if (child == None || child == PointerRoot || child == wrapper ||
|
|
!XGetWindowAttributes(display, child, &attributes) || attributes.map_state != IsViewable ||
|
|
attributes.class != InputOutput || steam_window_pid(display, child) != pid) return 0;
|
|
for (int depth = 0; depth < 32; depth++) {
|
|
Window root, parent, *children = NULL;
|
|
unsigned int count;
|
|
if (!XQueryTree(display, child, &root, &parent, &children, &count)) return 0;
|
|
if (children != NULL) XFree(children);
|
|
if (parent == wrapper) return 1;
|
|
if (parent == None || parent == root) return 0;
|
|
child = parent;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
CommandFocusSample cthulhu_read_command_focus(Display *display, int steam_enabled) {
|
|
CommandFocusSample result = {0};
|
|
SteamFocusSample *sample = &result.steam;
|
|
cthulhu_x11_begin_read(display);
|
|
result.xterm = cthulhu_focus_metadata_unchecked(display, steam_enabled ? sample : NULL);
|
|
if (sample->steam) {
|
|
sample->pid = steam_window_pid(display, sample->window);
|
|
sample->steam = sample->pid > 0;
|
|
int revert;
|
|
XGetInputFocus(display, &sample->focus, &revert);
|
|
if (sample->steam) sample->innerFocus = steam_browser_child(display, sample->window, sample->pid, sample->focus);
|
|
}
|
|
if (cthulhu_x11_end_read(display)) result = (CommandFocusSample){0};
|
|
return result;
|
|
}
|
|
|
|
int cthulhu_restore_steam_focus(Display *display, Window wrapper, int pid, Window target) {
|
|
cthulhu_x11_begin_read(display);
|
|
int restored = 0, revert;
|
|
Window focus, active = None;
|
|
XGetInputFocus(display, &focus, &revert);
|
|
char heldKeys[32];
|
|
XQueryKeymap(display, heldKeys);
|
|
int keyHeld = 0;
|
|
for (int i = 0; i < 32; i++) keyHeld |= heldKeys[i];
|
|
if (!keyHeld && focus == wrapper && steam_active_window(display, &active) && active == wrapper &&
|
|
steam_wrapper(display, wrapper) &&
|
|
steam_window_pid(display, wrapper) == pid && steam_browser_child(display, wrapper, pid, target)) {
|
|
// Recheck foreground immediately before changing input focus: cached
|
|
// children must never pull keyboard input out of a different client.
|
|
if (steam_active_window(display, &active) && active == wrapper) {
|
|
XGetInputFocus(display, &focus, &revert);
|
|
if (focus == wrapper) {
|
|
XSetInputFocus(display, target, RevertToParent, CurrentTime);
|
|
XSync(display, False);
|
|
XGetInputFocus(display, &focus, &revert);
|
|
restored = focus == target;
|
|
}
|
|
}
|
|
}
|
|
if (cthulhu_x11_end_read(display)) restored = 0;
|
|
return restored;
|
|
}
|