#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; }