From 26a9e750dc6f1313413524cd97e60a94e25a56ec Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 4 Nov 2024 20:37:26 +0100 Subject: Add option to save replay/recording to a folder with the name of the game --- gsr-window-name/main.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 gsr-window-name/main.c (limited to 'gsr-window-name/main.c') diff --git a/gsr-window-name/main.c b/gsr-window-name/main.c new file mode 100644 index 0000000..a46aff2 --- /dev/null +++ b/gsr-window-name/main.c @@ -0,0 +1,190 @@ +#include +#include +#include + +#include +#include +#include +#include + +typedef enum { + CAPTURE_TYPE_FOCUSED, + CAPTURE_TYPE_CURSOR +} capture_type; + +static bool window_has_atom(Display *dpy, Window window, Atom atom) { + Atom type; + unsigned long len, bytes_left; + int format; + unsigned char *properties = NULL; + if(XGetWindowProperty(dpy, window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success) + return false; + + if(properties) + XFree(properties); + + return type != None; +} + +static bool window_is_user_program(Display *dpy, Window window) { + const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False); + const Atom wm_state_atom = XInternAtom(dpy, "WM_STATE", False); + return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom); +} + +static Window get_window_at_cursor_position(Display *dpy) { + Window root_window = None; + Window window = None; + int dummy_i; + unsigned int dummy_u; + int cursor_pos_x = 0; + int cursor_pos_y = 0; + XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &dummy_i, &dummy_i, &cursor_pos_x, &cursor_pos_y, &dummy_u); + return window; +} + +static Window get_focused_window(Display *dpy, capture_type cap_type) { + const Atom net_active_window_atom = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + Window focused_window = None; + + if(cap_type == CAPTURE_TYPE_FOCUSED) { + Atom type = None; + int format = 0; + unsigned long num_items = 0; + unsigned long bytes_left = 0; + unsigned char *data = NULL; + XGetWindowProperty(dpy, DefaultRootWindow(dpy), net_active_window_atom, 0, 1, False, XA_WINDOW, &type, &format, &num_items, &bytes_left, &data); + + if(type == XA_WINDOW && num_items == 1 && data) + return *(Window*)data; + + int revert_to = 0; + XGetInputFocus(dpy, &focused_window, &revert_to); + if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window)) + return focused_window; + } + + focused_window = get_window_at_cursor_position(dpy); + if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window)) + return focused_window; + + return None; +} + +static char* get_window_title(Display *dpy, Window window) { + const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); + const Atom wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); + const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False); + + Atom type = None; + int format = 0; + unsigned long num_items = 0; + unsigned long bytes_left = 0; + unsigned char *data = NULL; + XGetWindowProperty(dpy, window, net_wm_name_atom, 0, 1024, False, utf8_string_atom, &type, &format, &num_items, &bytes_left, &data); + + if(type == utf8_string_atom && format == 8 && data) + return (char*)data; + + type = None; + format = 0; + num_items = 0; + bytes_left = 0; + data = NULL; + XGetWindowProperty(dpy, window, wm_name_atom, 0, 1024, False, 0, &type, &format, &num_items, &bytes_left, &data); + + if((type == XA_STRING || type == utf8_string_atom) && data) + return (char*)data; + + return NULL; +} + +static bool is_steam_game(Display *dpy, Window window) { + return window_has_atom(dpy, window, XInternAtom(dpy, "STEAM_GAME", False)); +} + +static const char* strip(const char *str, int *len) { + int str_len = strlen(str); + for(int i = 0; i < str_len; ++i) { + if(str[i] != ' ') { + str += i; + str_len -= i; + break; + } + } + + for(int i = str_len - 1; i >= 0; --i) { + if(str[i] != ' ') { + str_len = i + 1; + break; + } + } + + *len = str_len; + return str; +} + +static void print_str_strip(const char *str) { + int len = 0; + str = strip(str, &len); + printf("%.*s", len, str); +} + +static int x11_ignore_error(Display *dpy, XErrorEvent *error_event) { + (void)dpy; + (void)error_event; + return 0; +} + +static void usage(void) { + fprintf(stderr, "usage: gsr-window-name \n"); + fprintf(stderr, "options:\n"); + fprintf(stderr, " focused The class/name of the focused window is returned. If no window is focused then the window beneath the cursor is returned instead\n"); + fprintf(stderr, " cursor The class/name of the window beneath the cursor is returned\n"); + exit(1); +} + +int main(int argc, char **argv) { + if(argc != 2) + usage(); + + const char *cap_type_str = argv[1]; + capture_type cap_type = CAPTURE_TYPE_FOCUSED; + if(strcmp(cap_type_str, "focused") == 0) { + cap_type = CAPTURE_TYPE_FOCUSED; + } else if(strcmp(cap_type_str, "cursor") == 0) { + cap_type = CAPTURE_TYPE_CURSOR; + } else { + fprintf(stderr, "error: invalid option '%s', expected either 'focused' or 'cursor'\n", cap_type_str); + usage(); + } + + Display *dpy = XOpenDisplay(NULL); + if(!dpy) { + fprintf(stderr, "Error: failed to connect to the X11 server\n"); + exit(1); + } + + XSetErrorHandler(x11_ignore_error); + + const Window focused_window = get_focused_window(dpy, cap_type); + if(focused_window == None) + exit(2); + + if(!is_steam_game(dpy, focused_window)) { + XClassHint class_hint = {0}; + XGetClassHint(dpy, focused_window, &class_hint); + if(class_hint.res_class) { + print_str_strip(class_hint.res_class); + exit(0); + } + } + + char *window_title = get_window_title(dpy, focused_window); + if(window_title) { + print_str_strip(window_title); + exit(0); + } + + return 2; +} -- cgit v1.2.3