diff --git a/include/DesktopEnvironment/DesktopEnvironment.hpp b/include/DesktopEnvironment/DesktopEnvironment.hpp new file mode 100644 index 0000000..0b4fccf --- /dev/null +++ b/include/DesktopEnvironment/DesktopEnvironment.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace gsr { + class DesktopEnvironment { + public: + DesktopEnvironment() = default; + DesktopEnvironment(const DesktopEnvironment&) = delete; + DesktopEnvironment& operator=(const DesktopEnvironment&) = delete; + virtual ~DesktopEnvironment() = default; + + virtual bool start() = 0; + virtual void update() = 0; + // Return an empty string if none + virtual std::string get_focused_window_title() = 0; + // Return an empty string if unknown + //virtual std::string get_focused_monitor_name() = 0; + }; +} \ No newline at end of file diff --git a/include/DesktopEnvironment/DesktopEnvironmentHyprland.hpp b/include/DesktopEnvironment/DesktopEnvironmentHyprland.hpp new file mode 100644 index 0000000..5ab5902 --- /dev/null +++ b/include/DesktopEnvironment/DesktopEnvironmentHyprland.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "DesktopEnvironment.hpp" +#include +#include + +namespace gsr { + class DesktopEnvironmentHyprland : public DesktopEnvironment { + public: + DesktopEnvironmentHyprland() = default; + DesktopEnvironmentHyprland(const DesktopEnvironmentHyprland&) = delete; + DesktopEnvironmentHyprland& operator=(const DesktopEnvironmentHyprland&) = delete; + ~DesktopEnvironmentHyprland(); + + bool start() override; + void update() override; + std::string get_focused_window_title() override; + //std::string get_focused_monitor_name() override; + private: + void shutdown(); + private: + pid_t process_id = -1; + FILE *stdout_file = nullptr; + int read_fd = -1; + char line_buffer[1024]; + std::string line; + + std::string window_title; + }; +} \ No newline at end of file diff --git a/include/DesktopEnvironment/DesktopEnvironmentKde.hpp b/include/DesktopEnvironment/DesktopEnvironmentKde.hpp new file mode 100644 index 0000000..17a6a97 --- /dev/null +++ b/include/DesktopEnvironment/DesktopEnvironmentKde.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "DesktopEnvironment.hpp" + +namespace gsr { + class DesktopEnvironmentKde : public DesktopEnvironment { + public: + DesktopEnvironmentKde() = default; + DesktopEnvironmentKde(const DesktopEnvironmentKde&) = delete; + DesktopEnvironmentKde& operator=(const DesktopEnvironmentKde&) = delete; + ~DesktopEnvironmentKde(); + + bool start() override; + void update() override; + std::string get_focused_window_title() override; + //std::string get_focused_monitor_name() override; + private: + void shutdown(); + private: + pid_t process_id = -1; + FILE *stdout_file = nullptr; + int read_fd = -1; + char line_buffer[1024]; + std::string line; + + std::string window_title; + std::string monitor_name; + }; +} \ No newline at end of file diff --git a/include/DesktopEnvironment/DesktopEnvironmentX11.hpp b/include/DesktopEnvironment/DesktopEnvironmentX11.hpp new file mode 100644 index 0000000..1d79f2c --- /dev/null +++ b/include/DesktopEnvironment/DesktopEnvironmentX11.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "DesktopEnvironment.hpp" +#include + +namespace gsr { + class DesktopEnvironmentX11 : public DesktopEnvironment { + public: + DesktopEnvironmentX11(Display *dpy) : dpy(dpy) {} + DesktopEnvironmentX11(const DesktopEnvironmentX11&) = delete; + DesktopEnvironmentX11& operator=(const DesktopEnvironmentX11&) = delete; + ~DesktopEnvironmentX11(); + + bool start() override; + void update() override; + std::string get_focused_window_title() override; + //std::string get_focused_monitor_name() override; + private: + Display *dpy = NULL; + }; +} \ No newline at end of file diff --git a/include/HyprlandWorkaround.hpp b/include/HyprlandWorkaround.hpp deleted file mode 100644 index a1b3a36..0000000 --- a/include/HyprlandWorkaround.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -namespace gsr { - struct ActiveHyprlandWindow { - std::string window_id = ""; - std::string title = "Game"; - }; - - void start_hyprland_listener_thread(); - std::string get_current_hyprland_window_title(); -} diff --git a/include/KwinWorkaround.hpp b/include/KwinWorkaround.hpp deleted file mode 100644 index 59b5e07..0000000 --- a/include/KwinWorkaround.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace gsr { - struct ActiveKwinWindow { - std::string title = "Game"; - bool fullscreen = false; - std::string monitorName = ""; - }; - - void start_kwin_helper_thread(); - std::string get_current_kwin_window_title(); - bool get_current_kwin_window_fullscreen(); - std::string get_current_kwin_window_monitor_name(); -} diff --git a/include/Overlay.hpp b/include/Overlay.hpp index 2baa3f2..4a05711 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -12,6 +12,7 @@ #include "ClipboardFile.hpp" #include "LedIndicator.hpp" #include "CursorTracker/CursorTracker.hpp" +#include "DesktopEnvironment/DesktopEnvironment.hpp" #include #include @@ -304,5 +305,6 @@ namespace gsr { std::unique_ptr led_indicator = nullptr; bool supports_window_title = false; + std::unique_ptr desktop_environment; }; } \ No newline at end of file diff --git a/include/WindowUtils.hpp b/include/WindowUtils.hpp index 7456206..f98cd83 100644 --- a/include/WindowUtils.hpp +++ b/include/WindowUtils.hpp @@ -30,8 +30,8 @@ namespace gsr { std::optional get_window_title(Display *dpy, Window window); Window get_focused_window(Display *dpy, WindowCaptureType cap_type, bool fallback_cursor_focused = true); std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type, bool fallback_cursor_focused = true); - std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window); - std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window); + std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, std::string_view ignore_window_title); + std::string get_window_name_at_cursor_position(Display *dpy, std::string_view ignore_window_title); void set_window_size_not_resizable(Display *dpy, Window window, int width, int height); Window window_get_target_window_child(Display *display, Window window); unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size); diff --git a/meson.build b/meson.build index 422f16c..9b12bc1 100644 --- a/meson.build +++ b/meson.build @@ -40,9 +40,10 @@ src = [ 'src/GlobalHotkeys/GlobalHotkeysJoystick.cpp', 'src/CursorTracker/CursorTrackerX11.cpp', 'src/CursorTracker/CursorTrackerWayland.cpp', + 'src/DesktopEnvironment/DesktopEnvironmentX11.cpp', + 'src/DesktopEnvironment/DesktopEnvironmentHyprland.cpp', + 'src/DesktopEnvironment/DesktopEnvironmentKde.cpp', 'src/Utils.cpp', - 'src/HyprlandWorkaround.cpp', - 'src/KwinWorkaround.cpp', 'src/WindowUtils.cpp', 'src/RegionSelector.cpp', 'src/Config.cpp', diff --git a/src/HyprlandWorkaround.cpp b/src/DesktopEnvironment/DesktopEnvironmentHyprland.cpp similarity index 55% rename from src/HyprlandWorkaround.cpp rename to src/DesktopEnvironment/DesktopEnvironmentHyprland.cpp index 92022ed..696f4d7 100644 --- a/src/HyprlandWorkaround.cpp +++ b/src/DesktopEnvironment/DesktopEnvironmentHyprland.cpp @@ -1,15 +1,11 @@ -#include "../include/HyprlandWorkaround.hpp" -#include "../include/Process.hpp" +#include "../../include/DesktopEnvironment/DesktopEnvironmentHyprland.hpp" +#include "../../include/Process.hpp" +#include #include -#include -#include -#include namespace gsr { - static ActiveHyprlandWindow active_hyprland_window; - static bool hyprland_listener_thread_started = false; - static std::mutex active_window_mutex; + static constexpr std::string_view prefix = "Window title changed: "; static bool get_hyprland_socket_path(char *path, int path_len) { const char* xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); @@ -27,22 +23,27 @@ namespace gsr { return true; } - static void hyprland_listener_thread() { + DesktopEnvironmentHyprland::~DesktopEnvironmentHyprland() { + shutdown(); + } + + bool DesktopEnvironmentHyprland::start() { + if(process_id > 0) { + fprintf(stderr, "Error: DesktopEnvironmentHyprland: already running\n"); + return false; + } + char hyprland_socket_path[256]; - char buffer[4096]; - const std::string prefix = "Window title changed: "; - std::string line; - FILE *stdout_file = nullptr; // Get path inside the flatpak before flatpak-spawn is called because of a bug in flatpak: // https://github.com/flatpak/flatpak/issues/6486 // where environment variables are missing in flatpak-spawn --host if(!get_hyprland_socket_path(hyprland_socket_path, sizeof(hyprland_socket_path))) { - fprintf(stderr, "Error: HyprlandWorkaround: failed to get hyprland socket path\n"); - return; + fprintf(stderr, "Error: DesktopEnvironmentHyprland: failed to get hyprland socket path\n"); + return false; } - const bool inside_flatpak = access("/app/manifest.json", F_OK) == 0; + const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; size_t arg_index = 0; const char *args[6]; @@ -59,44 +60,27 @@ namespace gsr { args[arg_index++] = hyprland_socket_path; args[arg_index++] = nullptr; - int read_fd = -1; - const pid_t process_id = exec_program(args, &read_fd, false); + process_id = exec_program(args, &read_fd, false); if(process_id == -1) { - fprintf(stderr, "Error: HyprlandWorkaround: failed to execute gsr-hyprland-helper\n"); - return; + fprintf(stderr, "Error: DesktopEnvironmentHyprland: failed to execute gsr-hyprland-helper\n"); + return false; } + fcntl(read_fd, F_SETFL, fcntl(read_fd, F_GETFL) | O_NONBLOCK); + stdout_file = fdopen(read_fd, "r"); if (!stdout_file) { - perror("Error: HyprlandWorkaround: fdopen"); - goto done; - return; + perror("Error: DesktopEnvironmentHyprland: fdopen"); + shutdown(); + return false; } read_fd = -1; - fprintf(stderr, "Info: HyprlandWorkaround: started Hyprland helper thread\n"); - - while (fgets(buffer, sizeof(buffer), stdout_file) != nullptr) { - line = buffer; - - if (!line.empty() && line.back() == '\n') { - line.pop_back(); - } - - size_t pos = line.find(prefix); - if (pos != std::string::npos) { - std::lock_guard lock(active_window_mutex); - active_hyprland_window.title = line.substr(pos + prefix.length()); - } - } - - done: - if(stdout_file) - fclose(stdout_file); - - if(read_fd > 0) - close(read_fd); + fprintf(stderr, "Info: DesktopEnvironmentHyprland: started Hyprland helper process\n"); + return true; + } + void DesktopEnvironmentHyprland::shutdown() { if(process_id > 0) { kill(process_id, SIGKILL); int status; @@ -104,23 +88,40 @@ namespace gsr { perror("waitpid failed"); /* Ignore... */ } + process_id = -1; + } + + if(stdout_file) { + fclose(stdout_file); + stdout_file = nullptr; + } + + if(read_fd > 0) { + close(read_fd); + read_fd = -1; } } - std::string get_current_hyprland_window_title() { - std::lock_guard lock(active_window_mutex); - return active_hyprland_window.title; - } + void DesktopEnvironmentHyprland::update() { + while (fgets(line_buffer, sizeof(line_buffer), stdout_file) != nullptr) { + line = line_buffer; - void start_hyprland_listener_thread() { - if (hyprland_listener_thread_started) { - return; + if (!line.empty() && line.back() == '\n') { + line.pop_back(); + } + + const size_t pos = line.find(prefix); + if (pos != std::string::npos) { + window_title = line.substr(pos + prefix.length()); + } } - - hyprland_listener_thread_started = true; - - std::thread([&] { - hyprland_listener_thread(); - }).detach(); } + + std::string DesktopEnvironmentHyprland::get_focused_window_title() { + return window_title; + } + + // std::string DesktopEnvironmentHyprland::get_focused_monitor_name() { + // return ""; + // } } diff --git a/src/DesktopEnvironment/DesktopEnvironmentKde.cpp b/src/DesktopEnvironment/DesktopEnvironmentKde.cpp new file mode 100644 index 0000000..a7b0350 --- /dev/null +++ b/src/DesktopEnvironment/DesktopEnvironmentKde.cpp @@ -0,0 +1,91 @@ +#include "../../include/DesktopEnvironment/DesktopEnvironmentKde.hpp" +#include "../../include/Process.hpp" + +#include +#include + +namespace gsr { + static constexpr std::string_view prefix_title = "Active window title set to: "; + static constexpr std::string_view prefix_fullscreen = "Active window fullscreen state set to: "; + static constexpr std::string_view prefix_monitor = "Active window monitor name set to: "; + + DesktopEnvironmentKde::~DesktopEnvironmentKde() { + shutdown(); + } + + bool DesktopEnvironmentKde::start() { + if(process_id > 0) { + fprintf(stderr, "Error: DesktopEnvironmentKde: already running\n"); + return false; + } + + const char *args[] = { "gsr-kwin-helper", NULL }; + process_id = exec_program(args, &read_fd, false); + if(process_id == -1) { + fprintf(stderr, "Error: DesktopEnvironmentKde: failed to execute gsr-kwin-helper\n"); + return false; + } + + fcntl(read_fd, F_SETFL, fcntl(read_fd, F_GETFL) | O_NONBLOCK); + + stdout_file = fdopen(read_fd, "r"); + if (!stdout_file) { + perror("Error: DesktopEnvironmentKde: fdopen"); + shutdown(); + return false; + } + read_fd = -1; + + fprintf(stderr, "Info: DesktopEnvironmentKde: started kwin helper process\n"); + return true; + } + + void DesktopEnvironmentKde::shutdown() { + if(process_id > 0) { + kill(process_id, SIGKILL); + int status; + if(waitpid(process_id, &status, 0) == -1) { + perror("waitpid failed"); + /* Ignore... */ + } + process_id = -1; + } + + if(stdout_file) { + fclose(stdout_file); + stdout_file = nullptr; + } + + if(read_fd > 0) { + close(read_fd); + read_fd = -1; + } + } + + void DesktopEnvironmentKde::update() { + while (fgets(line_buffer, sizeof(line_buffer), stdout_file) != nullptr) { + line = line_buffer; + + if (!line.empty() && line.back() == '\n') { + line.pop_back(); + } + + size_t pos = std::string::npos; + if ((pos = line.find(prefix_title)) != std::string::npos) { + window_title = line.substr(pos + prefix_title.length()); + } else if ((pos = line.find(prefix_fullscreen)) != std::string::npos) { + //fullscreen = line.substr(pos + prefix_fullscreen.length()) == "1"; + } else if ((pos = line.find(prefix_monitor)) != std::string::npos) { + monitor_name = line.substr(pos + prefix_monitor.length()); + } + } + } + + std::string DesktopEnvironmentKde::get_focused_window_title() { + return window_title; + } + + // std::string DesktopEnvironmentKde::get_focused_monitor_name() { + // return monitor_name; + // } +} \ No newline at end of file diff --git a/src/DesktopEnvironment/DesktopEnvironmentX11.cpp b/src/DesktopEnvironment/DesktopEnvironmentX11.cpp new file mode 100644 index 0000000..e141755 --- /dev/null +++ b/src/DesktopEnvironment/DesktopEnvironmentX11.cpp @@ -0,0 +1,34 @@ +#include "../../include/DesktopEnvironment/DesktopEnvironmentX11.hpp" +#include "../../include/WindowUtils.hpp" + +namespace gsr { + DesktopEnvironmentX11::~DesktopEnvironmentX11() { + + } + + bool DesktopEnvironmentX11::start() { + return true; + } + + void DesktopEnvironmentX11::update() { + + } + + std::string DesktopEnvironmentX11::get_focused_window_title() { + std::string focused_window_title; + if(!dpy) { + fprintf(stderr, "Error: DesktopEnvironmentX11: get_focused_window_title: display object is NULL, returning empty string\n"); + return focused_window_title; + } + + focused_window_title = get_window_name_at_cursor_position(dpy, "gsr-ui"); + if(focused_window_title.empty()) + focused_window_title = get_focused_window_name(dpy, WindowCaptureType::FOCUSED, false); + + return focused_window_title; + } + + // std::string DesktopEnvironmentX11::get_focused_monitor_name() { + // return ""; + // } +} diff --git a/src/KwinWorkaround.cpp b/src/KwinWorkaround.cpp deleted file mode 100644 index 6761a5b..0000000 --- a/src/KwinWorkaround.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "../include/KwinWorkaround.hpp" - -#include -#include -#include -#include -#include -#include - -namespace gsr { - static ActiveKwinWindow active_kwin_window; - static bool kwin_helper_thread_started = false; - static std::mutex active_window_mutex; - - void kwin_script_thread() { - FILE* pipe = popen("gsr-kwin-helper", "r"); - if (!pipe) { - std::cerr << "Failed to start gsr-kwin-helper process\n"; - return; - } - - std::cerr << "Started a KWin helper thread\n"; - - char buffer[4096]; - const std::string prefix_title = "Active window title set to: "; - const std::string prefix_fullscreen = "Active window fullscreen state set to: "; - const std::string prefix_monitor = "Active window monitor name set to: "; - - std::string line; - while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - line = buffer; - - if (!line.empty() && line.back() == '\n') { - line.pop_back(); - } - - std::lock_guard lock(active_window_mutex); - size_t pos = std::string::npos; - if ((pos = line.find(prefix_title)) != std::string::npos) { - std::string title = line.substr(pos + prefix_title.length()); - active_kwin_window.title = std::move(title); - } else if ((pos = line.find(prefix_fullscreen)) != std::string::npos) { - std::string fullscreen = line.substr(pos + prefix_fullscreen.length()); - active_kwin_window.fullscreen = fullscreen == "1"; - } else if ((pos = line.find(prefix_monitor)) != std::string::npos) { - std::string monitorName = line.substr(pos + prefix_monitor.length()); - active_kwin_window.monitorName = std::move(monitorName); - } - } - - pclose(pipe); - } - - std::string get_current_kwin_window_title() { - std::lock_guard lock(active_window_mutex); - return active_kwin_window.title; - } - - bool get_current_kwin_window_fullscreen() { - std::lock_guard lock(active_window_mutex); - return active_kwin_window.fullscreen; - } - - std::string get_current_kwin_window_monitor_name() { - std::lock_guard lock(active_window_mutex); - return active_kwin_window.monitorName; - } - - void start_kwin_helper_thread() { - if (kwin_helper_thread_started) { - return; - } - - kwin_helper_thread_started = true; - - std::thread([&] { - kwin_script_thread(); - }).detach(); - } -} \ No newline at end of file diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 3c60316..f2f4e43 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -11,8 +11,9 @@ #include "../include/gui/GlobalSettingsPage.hpp" #include "../include/gui/Utils.hpp" #include "../include/Translation.hpp" -#include "../include/KwinWorkaround.hpp" -#include "../include/HyprlandWorkaround.hpp" +#include "../include/DesktopEnvironment/DesktopEnvironmentX11.hpp" +#include "../include/DesktopEnvironment/DesktopEnvironmentHyprland.hpp" +#include "../include/DesktopEnvironment/DesktopEnvironmentKde.hpp" #include "../include/gui/PageStack.hpp" #include "../include/WindowUtils.hpp" #include "../include/GlobalHotkeys/GlobalHotkeys.hpp" @@ -573,11 +574,26 @@ namespace gsr { if(this->gsr_info.system_info.display_server == DisplayServer::X11) { cursor_tracker = std::make_unique((Display*)mgl_get_context()->connection); + desktop_environment = std::make_unique(x11_dpy); supports_window_title = true; } else if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) { + const std::string wm_name = x11_dpy ? get_window_manager_name(x11_dpy) : ""; + const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos; + const bool is_kwin_wayland = wm_name == "KWin" && gsr_info.system_info.display_server == DisplayServer::WAYLAND; + if(!this->gsr_info.gpu_info.card_path.empty()) cursor_tracker = std::make_unique(this->gsr_info.gpu_info.card_path.c_str(), wayland_dpy); + if(is_hyprland) { + desktop_environment = std::make_unique(); + supports_window_title = true; + } else if(is_kwin_wayland) { + desktop_environment = std::make_unique(); + supports_window_title = true; + } else { + desktop_environment = std::make_unique(x11_dpy); + } + if(!config.main_config.wayland_warning_shown) { config.main_config.wayland_warning_shown = true; save_config(config); @@ -585,6 +601,7 @@ namespace gsr { } } + desktop_environment->start(); update_led_indicator_after_settings_change(); gsr_game_tracker_process_id = launch_gsr_game_tracker(&gsr_game_tracker_process_output_fd); @@ -855,6 +872,7 @@ namespace gsr { cursor_tracker->update(); } + desktop_environment->update(); handle_keyboard_mapping_event(); region_selector.poll_events(); @@ -2087,16 +2105,9 @@ namespace gsr { } void Overlay::save_video_in_current_game_directory(std::string &video_filepath, NotificationType notification_type) { - mgl_context *context = mgl_get_context(); - Display *display = (Display*)context->connection; const std::string video_filename = filepath_get_filename(video_filepath.c_str()); - const Window gsr_ui_window = window ? (Window)window->get_system_handle() : None; - std::string focused_window_name = get_window_name_at_cursor_position(display, gsr_ui_window); - - if(focused_window_name.empty()) - focused_window_name = get_focused_window_name(display, WindowCaptureType::FOCUSED, false); - + std::string focused_window_name = desktop_environment->get_focused_window_title(); if(focused_window_name.empty()) focused_window_name = "Game"; diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp index 6ba22fe..7a53b49 100644 --- a/src/WindowUtils.cpp +++ b/src/WindowUtils.cpp @@ -476,7 +476,7 @@ namespace gsr { return result; } - std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window) { + std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, std::string_view ignore_window_title) { std::string result; Window root; @@ -487,7 +487,7 @@ namespace gsr { return result; for(int i = (int)num_children - 1; i >= 0; --i) { - if(children[i] == ignore_window) + if(get_window_title(dpy, children[i]) == ignore_window_title) continue; XWindowAttributes attr; @@ -498,10 +498,10 @@ namespace gsr { if(position.x >= attr.x && position.x <= attr.x + attr.width && position.y >= attr.y && position.y <= attr.y + attr.height) { const Window real_window = window_get_target_window_child(dpy, children[i]); - if(!real_window || real_window == ignore_window) + const std::optional window_title = get_window_title(dpy, real_window); + if(!real_window || window_title == ignore_window_title) continue; - const std::optional window_title = get_window_title(dpy, real_window); if(window_title) result = strip(window_title.value()); @@ -513,10 +513,10 @@ namespace gsr { return result; } - std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window) { + std::string get_window_name_at_cursor_position(Display *dpy, std::string_view ignore_window_title) { Window cursor_window; const mgl::vec2i cursor_position = get_cursor_position(dpy, &cursor_window); - return get_window_name_at_position(dpy, cursor_position, ignore_window); + return get_window_name_at_position(dpy, cursor_position, ignore_window_title); } void set_window_size_not_resizable(Display *dpy, Window window, int width, int height) {