diff --git a/meson.build b/meson.build index 11d6accd..251a3f77 100644 --- a/meson.build +++ b/meson.build @@ -85,11 +85,13 @@ vulkan_wsi_deps = [] if is_unixy dep_x11 = dependency('x11', required: get_option('with_x11')) + dep_xcb = dependency('xcb', required: get_option('with_x11')) dep_wayland_client = dependency('wayland-client', required: get_option('with_wayland'), version : '>=1.11') dbus_dep = dependency('dbus-1', required: get_option('with_dbus')).partial_dependency(compile_args : true, includes : true) else dep_x11 = null_dep + dep_xcb = null_dep dep_wayland_client = null_dep dbus_dep = null_dep endif @@ -98,6 +100,10 @@ if dep_x11.found() vulkan_wsi_args += ['-DVK_USE_PLATFORM_XLIB_KHR'] vulkan_wsi_deps += dep_x11.partial_dependency(compile_args : true, includes : true) endif +if dep_xcb.found() + vulkan_wsi_args += ['-DVK_USE_PLATFORM_XCB_KHR'] + vulkan_wsi_deps += dep_xcb #.partial_dependency(compile_args : true, includes : true) +endif if dep_wayland_client.found() vulkan_wsi_args += ['-DVK_USE_PLATFORM_WAYLAND_KHR'] vulkan_wsi_deps += dep_wayland_client diff --git a/meson_options.txt b/meson_options.txt index 093e47af..f8b14ac1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,7 +10,7 @@ option('include_doc', type : 'boolean', value : true, description: 'Include the option('with_nvml', type : 'combo', value : 'enabled', choices: ['enabled', 'system', 'disabled'], description: 'Enable NVML support') option('with_xnvctrl', type : 'feature', value : 'enabled', description: 'Enable XNVCtrl support') option('with_x11', type : 'feature', value : 'enabled') -option('with_wayland', type : 'feature', value : 'disabled') +option('with_wayland', type : 'feature', value : 'enabled') option('with_dbus', type : 'feature', value : 'enabled') option('with_dlsym', type : 'feature', value : 'disabled') option('loglevel', type: 'combo', choices : ['trace', 'debug', 'info', 'warn', 'err', 'critical', 'off'], value : 'info', description: 'Max log level in non-debug build') diff --git a/src/gl/gl_hud.cpp b/src/gl/gl_hud.cpp index 706f216e..1cc0979c 100644 --- a/src/gl/gl_hud.cpp +++ b/src/gl/gl_hud.cpp @@ -13,6 +13,7 @@ #include "file_utils.h" #include "notify.h" #include "blacklist.h" +#include "wsi_helpers.h" #ifdef HAVE_DBUS #include "dbus_info.h" @@ -53,6 +54,7 @@ struct state { static GLVec last_vp {}, last_sb {}; swapchain_stats sw_stats {}; +wsi_connection wsi_conn {}; static state state; static uint32_t vendorID; static std::string deviceName; @@ -75,6 +77,7 @@ void imgui_init() init_spdlog(); parse_overlay_config(¶ms, getenv("MANGOHUD_CONFIG")); _params = ¶ms; + sw_stats.wsi = &wsi_conn; //check for blacklist item in the config file for (auto& item : params.blacklist) { diff --git a/src/gl/inject_glx.cpp b/src/gl/inject_glx.cpp index 55a985de..023d1946 100644 --- a/src/gl/inject_glx.cpp +++ b/src/gl/inject_glx.cpp @@ -13,6 +13,7 @@ #include "mesa/util/macros.h" #include "mesa/util/os_time.h" #include "blacklist.h" +#include "wsi_helpers.h" #include #include @@ -22,6 +23,11 @@ using namespace MangoHud::GL; +namespace MangoHud { namespace GL { + extern swapchain_stats sw_stats; + extern wsi_connection wsi_conn; +}} + EXPORT_C_(void *) glXGetProcAddress(const unsigned char* procName); EXPORT_C_(void *) glXGetProcAddressARB(const unsigned char* procName); @@ -100,6 +106,7 @@ EXPORT_C_(int) glXMakeCurrent(void* dpy, void* drawable, void* ctx) { int ret = glx.MakeCurrent(dpy, drawable, ctx); if (!is_blacklisted()) { + if (ret) { imgui_set_context(ctx); SPDLOG_DEBUG("GL ref count: {}", refcnt); @@ -127,6 +134,9 @@ static void do_imgui_swap(void *dpy, void *drawable) if (!is_blacklisted()) { imgui_create(glx.GetCurrentContext()); + wsi_conn.xlib.dpy = (Display*)dpy; + wsi_conn.xlib.window = (Window)drawable; + unsigned int width = -1, height = -1; switch (params.gl_size_query) @@ -152,20 +162,27 @@ static void do_imgui_swap(void *dpy, void *drawable) } } -EXPORT_C_(void) glXSwapBuffers(void* dpy, void* drawable) { - glx.Load(); - - do_imgui_swap(dpy, drawable); - glx.SwapBuffers(dpy, drawable); +static void fps_limit() +{ + if (is_blacklisted()) + return; using namespace std::chrono_literals; - if (!is_blacklisted() && fps_limit_stats.targetFrameTime > 0s){ + if (fps_limit_stats.targetFrameTime > 0s || (sw_stats.lost_focus && fps_limit_stats.focusLossFrameTime > 0s)){ fps_limit_stats.frameStart = Clock::now(); - FpsLimiter(fps_limit_stats); + FpsLimiter(fps_limit_stats, sw_stats.lost_focus); fps_limit_stats.frameEnd = Clock::now(); } } +EXPORT_C_(void) glXSwapBuffers(void* dpy, void* drawable) { + glx.Load(); + + do_imgui_swap(dpy, drawable); + glx.SwapBuffers(dpy, drawable); + fps_limit(); +} + EXPORT_C_(int64_t) glXSwapBuffersMscOML(void* dpy, void* drawable, int64_t target_msc, int64_t divisor, int64_t remainder) { glx.Load(); @@ -174,13 +191,7 @@ EXPORT_C_(int64_t) glXSwapBuffersMscOML(void* dpy, void* drawable, int64_t targe do_imgui_swap(dpy, drawable); int64_t ret = glx.SwapBuffersMscOML(dpy, drawable, target_msc, divisor, remainder); - - using namespace std::chrono_literals; - if (!is_blacklisted() && fps_limit_stats.targetFrameTime > 0s){ - fps_limit_stats.frameStart = Clock::now(); - FpsLimiter(fps_limit_stats); - fps_limit_stats.frameEnd = Clock::now(); - } + fps_limit(); return ret; } diff --git a/src/loaders/loader_x11.cpp b/src/loaders/loader_x11.cpp index 4db6f78e..b35ffc68 100644 --- a/src/loaders/loader_x11.cpp +++ b/src/loaders/loader_x11.cpp @@ -69,6 +69,30 @@ bool libx11_loader::Load(const std::string& library_name) { return false; } + XGetInputFocus = + reinterpret_castXGetInputFocus)>( + dlsym(library_, "XGetInputFocus")); + if (!XGetInputFocus) { + CleanUp(true); + return false; + } + + XQueryTree = + reinterpret_castXQueryTree)>( + dlsym(library_, "XQueryTree")); + if (!XQueryTree) { + CleanUp(true); + return false; + } + + XFree = + reinterpret_castXFree)>( + dlsym(library_, "XFree")); + if (!XFree) { + CleanUp(true); + return false; + } + loaded_ = true; return true; } diff --git a/src/loaders/loader_x11.h b/src/loaders/loader_x11.h index 4b502428..df3bc59b 100644 --- a/src/loaders/loader_x11.h +++ b/src/loaders/loader_x11.h @@ -20,7 +20,9 @@ class libx11_loader { decltype(&::XKeysymToKeycode) XKeysymToKeycode; decltype(&::XStringToKeysym) XStringToKeysym; decltype(&::XGetGeometry) XGetGeometry; - + decltype(&::XGetInputFocus) XGetInputFocus; + decltype(&::XQueryTree) XQueryTree; + decltype(&::XFree) XFree; private: void CleanUp(bool unload); diff --git a/src/meson.build b/src/meson.build index be9c91e1..4ef439bd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -41,6 +41,11 @@ foreach s : ['overlay.frag', 'overlay.vert'] command : [glslang, '-V', '-x', '-o', '@OUTPUT@', '@INPUT@']) endforeach +# files not to be included with mangoapp etc. +vklayer_files_only = files( + 'wsi_helpers.cpp', +) + vklayer_files = files( 'hud_elements.cpp', 'overlay.cpp', @@ -164,6 +169,7 @@ vklayer_mesa_overlay = shared_library( util_files, vk_enum_to_str, vklayer_files, + vklayer_files_only, opengl_files, overlay_spv, c_args : [ @@ -216,8 +222,7 @@ if is_unixy endif if get_option('mangoapp') and sizeof_ptr == 8 - pre_args += '-DIMGUI_IMPL_OPENGL_LOADER_GLEW' - pre_args += '-DMANGOAPP' + pre_args += ['-DIMGUI_IMPL_OPENGL_LOADER_GLEW', '-DMANGOAPP'] mangoapp = executable( 'mangoapp', mangohud_version, diff --git a/src/overlay.cpp b/src/overlay.cpp index 2825071e..66089450 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -93,13 +93,17 @@ void init_spdlog() } -void FpsLimiter(struct fps_limit& stats){ - stats.sleepTime = stats.targetFrameTime - (stats.frameStart - stats.frameEnd); +void FpsLimiter(struct fps_limit& stats, bool lost_focus){ + auto targetFrameTime = stats.targetFrameTime; + if (lost_focus && stats.focusLossFrameTime > 0s) + targetFrameTime = stats.focusLossFrameTime; + + stats.sleepTime = targetFrameTime - (stats.frameStart - stats.frameEnd); if (stats.sleepTime > stats.frameOverhead) { auto adjustedSleep = stats.sleepTime - stats.frameOverhead; this_thread::sleep_for(adjustedSleep); stats.frameOverhead = ((Clock::now() - stats.frameStart) - adjustedSleep); - if (stats.frameOverhead > stats.targetFrameTime / 2) + if (stats.frameOverhead > targetFrameTime / 2) stats.frameOverhead = Clock::duration(0); } } @@ -246,6 +250,11 @@ void update_hud_info_with_frametime(struct swapchain_stats& sw_stats, const stru hw_update_thread = std::make_unique(); hw_update_thread->update(¶ms, vendorID); +#ifndef MANGOAPP + sw_stats.lost_focus = !window_has_focus(sw_stats.wsi); + SPDLOG_DEBUG("lost focus: {}", sw_stats.lost_focus); +#endif + sw_stats.fps = 1000000000.0 * sw_stats.n_frames_since_update / elapsed; if (params.enabled[OVERLAY_PARAM_ENABLED_time]) { diff --git a/src/overlay.h b/src/overlay.h index d1f2f2d9..3b7cd482 100644 --- a/src/overlay.h +++ b/src/overlay.h @@ -24,6 +24,7 @@ struct frame_stat { static const int kMaxGraphEntries = 50; +struct wsi_connection; struct swapchain_stats { uint64_t n_frames; enum overlay_plots stat_selector; @@ -57,12 +58,16 @@ struct swapchain_stats { std::string gpuName; std::string driverName; enum EngineTypes engine; + + wsi_connection *wsi; + bool lost_focus; }; struct fps_limit { Clock::time_point frameStart; Clock::time_point frameEnd; Clock::duration targetFrameTime; + Clock::duration focusLossFrameTime; Clock::duration frameOverhead; Clock::duration sleepTime; }; @@ -105,7 +110,7 @@ void init_gpu_stats(uint32_t& vendorID, uint32_t reported_deviceID, overlay_para void init_cpu_stats(overlay_params& params); void check_keybinds(overlay_params& params, uint32_t vendorID); void init_system_info(void); -void FpsLimiter(struct fps_limit& stats); +void FpsLimiter(struct fps_limit& stats, bool lost_focus = false); std::string get_device_name(uint32_t vendorID, uint32_t deviceID); void create_fonts(const overlay_params& params, ImFont*& small_font, ImFont*& text_font); void right_aligned_text(ImVec4& col, float off_x, const char *fmt, ...); @@ -119,5 +124,6 @@ extern void process_control_socket(int& control_client, overlay_params ¶ms); void render_mpris_metadata(const overlay_params& params, mutexed_metadata& meta, uint64_t frame_timing); #endif void update_fan(); +bool window_has_focus(const wsi_connection*); #endif //MANGOHUD_OVERLAY_H diff --git a/src/overlay_params.cpp b/src/overlay_params.cpp index b70eccc8..5cd987d4 100644 --- a/src/overlay_params.cpp +++ b/src/overlay_params.cpp @@ -410,6 +410,7 @@ parse_gl_size_query(const char *str) #define parse_round_corners(s) parse_unsigned(s) #define parse_fcat_overlay_width(s) parse_unsigned(s) #define parse_fcat_screen_edge(s) parse_unsigned(s) +#define parse_focus_loss_fps_limit(s) parse_unsigned(s) #define parse_cpu_color(s) parse_color(s) #define parse_gpu_color(s) parse_color(s) @@ -792,6 +793,11 @@ parse_overlay_config(struct overlay_params *params, else fps_limit_stats.targetFrameTime = {}; + if (params->focus_loss_fps_limit > 0) + fps_limit_stats.focusLossFrameTime = duration_cast(duration(1) / params->focus_loss_fps_limit); + else + fps_limit_stats.focusLossFrameTime = {}; + #ifdef HAVE_DBUS if (params->enabled[OVERLAY_PARAM_ENABLED_media_player]) { if (dbusmgr::dbus_mgr.init(dbusmgr::SRV_MPRIS)) diff --git a/src/overlay_params.h b/src/overlay_params.h index 7977edfe..33a1a8f1 100644 --- a/src/overlay_params.h +++ b/src/overlay_params.h @@ -101,6 +101,7 @@ typedef unsigned long KeySym; OVERLAY_PARAM_CUSTOM(no_display) \ OVERLAY_PARAM_CUSTOM(control) \ OVERLAY_PARAM_CUSTOM(fps_limit) \ + OVERLAY_PARAM_CUSTOM(focus_loss_fps_limit) \ OVERLAY_PARAM_CUSTOM(vsync) \ OVERLAY_PARAM_CUSTOM(gl_vsync) \ OVERLAY_PARAM_CUSTOM(gl_size_query) \ @@ -204,6 +205,7 @@ struct overlay_params { int control; uint32_t fps_sampling_period; /* ns */ std::vector fps_limit; + unsigned focus_loss_fps_limit; bool help; bool no_display; bool full; diff --git a/src/vulkan.cpp b/src/vulkan.cpp index ad85d464..918b04c6 100644 --- a/src/vulkan.cpp +++ b/src/vulkan.cpp @@ -49,6 +49,7 @@ #include "notify.h" #include "blacklist.h" #include "pci_ids.h" +#include "wsi_helpers.h" using namespace std; @@ -62,6 +63,12 @@ namespace MangoHud { namespace GL { }} #endif +struct surface_data { + VkSurfaceKHR surface; + bool changed_flags; + wsi_connection wsi; +}; + /* Mapped from VkInstace/VkPhysicalDevice */ struct instance_data { struct vk_instance_dispatch_table vtable; @@ -132,6 +139,7 @@ struct overlay_draw { /* Mapped from VkSwapchainKHR */ struct swapchain_data { struct device_data *device; + struct surface_data *surface_data; VkSwapchainKHR swapchain; unsigned width, height; @@ -398,6 +406,77 @@ static void destroy_swapchain_data(struct swapchain_data *data) delete data; } +static struct surface_data *new_surface_data(VkSurfaceKHR surface) +{ + struct surface_data *data = new surface_data(); + data->surface = surface; + map_object(HKEY(data->surface), data); + return data; +} + +static void destroy_surface_data(struct surface_data *data) +{ + unmap_object(HKEY(data->surface)); + delete data; +} + +#ifdef VK_USE_PLATFORM_XCB_KHR +static VkResult overlay_CreateXcbSurfaceKHR(VkInstance instance, const VkXcbSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface) +{ + SPDLOG_DEBUG("{}", __func__); + struct instance_data *instance_data = FIND(struct instance_data, instance); + VkResult result = instance_data->vtable.CreateXcbSurfaceKHR(instance, pCreateInfo, pAllocator, pSurface); + + if (result == VK_SUCCESS) { + struct surface_data *data = new_surface_data(*pSurface); + data->wsi.xcb.conn = pCreateInfo->connection; + data->wsi.xcb.window = pCreateInfo->window; + } + + return result; +} +#endif + +#ifdef VK_USE_PLATFORM_XLIB_KHR +static VkResult overlay_CreateXlibSurfaceKHR(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface) +{ + SPDLOG_DEBUG("{}", __func__); + struct instance_data *instance_data = FIND(struct instance_data, instance); + VkResult result = instance_data->vtable.CreateXlibSurfaceKHR(instance, pCreateInfo, pAllocator, pSurface); + + if (result == VK_SUCCESS) { + struct surface_data *data = new_surface_data(*pSurface); + data->wsi.xlib.dpy = pCreateInfo->dpy; + data->wsi.xlib.window = pCreateInfo->window; + } + return result; +} +#endif + +#ifdef VK_USE_PLATFORM_WAYLAND_KHR +static VkResult overlay_CreateWaylandSurfaceKHR(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface) +{ + SPDLOG_DEBUG("{}", __func__); + struct instance_data *instance_data = FIND(struct instance_data, instance); + VkResult result = instance_data->vtable.CreateWaylandSurfaceKHR(instance, pCreateInfo, pAllocator, pSurface); + + if (result == VK_SUCCESS) { + struct surface_data *data = new_surface_data(*pSurface); + data->wsi.wl.display = pCreateInfo->display; + data->wsi.wl.surface = pCreateInfo->surface; + } + return result; +} +#endif + +static void overlay_DestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* pAllocator) +{ + struct instance_data *instance_data = FIND(struct instance_data, instance); + struct surface_data *surface_data = FIND(struct surface_data, surface); + destroy_surface_data(surface_data); + instance_data->vtable.DestroySurfaceKHR(instance, surface, pAllocator); +} + struct overlay_draw *get_overlay_draw(struct swapchain_data *data) { struct device_data *device_data = data->device; @@ -1304,10 +1383,16 @@ static void setup_swapchain_data(struct swapchain_data *data, const VkSwapchainCreateInfoKHR *pCreateInfo) { struct device_data *device_data = data->device; + struct surface_data *surface_data = FIND(struct surface_data, pCreateInfo->surface); + data->surface_data = surface_data; data->width = pCreateInfo->imageExtent.width; data->height = pCreateInfo->imageExtent.height; data->format = pCreateInfo->imageFormat; +#ifndef MANGOAPP + data->sw_stats.wsi = &data->surface_data->wsi; +#endif + data->imgui_context = ImGui::CreateContext(); ImGui::SetCurrentContext(data->imgui_context); @@ -1573,6 +1658,7 @@ static VkResult overlay_QueuePresentKHR( VkQueue queue, const VkPresentInfoKHR* pPresentInfo) { + bool lost_focus = false; struct queue_data *queue_data = FIND(struct queue_data, queue); /* Otherwise we need to add our overlay drawing semaphore to the list of @@ -1608,6 +1694,7 @@ static VkResult overlay_QueuePresentKHR( present_info.waitSemaphoreCount = 1; } + lost_focus |= swapchain_data->sw_stats.lost_focus; VkResult chain_result = queue_data->device->vtable.QueuePresentKHR(queue, &present_info); if (pPresentInfo->pResults) pPresentInfo->pResults[i] = chain_result; @@ -1617,9 +1704,9 @@ static VkResult overlay_QueuePresentKHR( using namespace std::chrono_literals; - if (fps_limit_stats.targetFrameTime > 0s){ + if (fps_limit_stats.targetFrameTime > 0s || (lost_focus && fps_limit_stats.focusLossFrameTime > 0s)){ fps_limit_stats.frameStart = Clock::now(); - FpsLimiter(fps_limit_stats); + FpsLimiter(fps_limit_stats, lost_focus); fps_limit_stats.frameEnd = Clock::now(); } @@ -1978,6 +2065,11 @@ static const struct { ADD_HOOK(CreateDevice), ADD_HOOK(DestroyDevice), + ADD_HOOK(CreateXlibSurfaceKHR), + ADD_HOOK(CreateXcbSurfaceKHR), + ADD_HOOK(CreateWaylandSurfaceKHR), + ADD_HOOK(DestroySurfaceKHR), + ADD_HOOK(CreateInstance), ADD_HOOK(DestroyInstance), #undef ADD_HOOK diff --git a/src/wsi_helpers.cpp b/src/wsi_helpers.cpp new file mode 100644 index 00000000..d43f2de1 --- /dev/null +++ b/src/wsi_helpers.cpp @@ -0,0 +1,94 @@ +#include +#include +#include "wsi_helpers.h" + +#ifdef VK_USE_PLATFORM_XCB_KHR + +static bool check_window_focus(xcb_connection_t * connection, xcb_window_t window) +{ + auto reply = xcb_get_input_focus_reply(connection, xcb_get_input_focus(connection), nullptr); + if (reply) + { + SPDLOG_DEBUG("Window: {:08x} Focus WId: {:08x}", window, reply->focus); + bool has_focus = (window == reply->focus); + free(reply); + return has_focus; + } + +// xcb_query_tree_cookie_t cookie = xcb_query_tree(connection, reply->focus); +// xcb_query_tree_reply_t *tree_reply = nullptr; +// +// if ((tree_reply = xcb_query_tree_reply(connection, cookie, nullptr))) { +// printf("root = 0x%08x\n", tree_reply->root); +// printf("parent = 0x%08x\n", tree_reply->parent); +// +// xcb_window_t *children = xcb_query_tree_children(tree_reply); +// for (int i = 0; i < xcb_query_tree_children_length(tree_reply); i++) +// printf("child window = 0x%08x\n", children[i]); +// +// free(reply); +// } + + return true; +} +#endif + +#ifdef VK_USE_PLATFORM_XLIB_KHR +static bool check_window_focus(Display *disp, Window window) +{ + if (!g_x11 || !g_x11->IsLoaded()) + return true; + + Window focus; + int revert_to; + + if (!g_x11->XGetInputFocus(disp, &focus, &revert_to)) + return true; + + SPDLOG_DEBUG("Window: {:08x}, Focus: {:08x}", window, focus); + + // wine vulkan surface's window is a child of "main" window? + Window w = window; + Window parent = window; + Window root = None; + Window *children; + unsigned int nchildren; + Status s; + + while (parent != root) { + w = parent; + s = g_x11->XQueryTree(disp, w, &root, &parent, &children, &nchildren); + + if (s) + g_x11->XFree(children); + + if (w == focus || !root) + { + SPDLOG_DEBUG("we got focus"); + return true; + } + + SPDLOG_DEBUG(" get parent: window: {:08x}, parent: {:08x}, root: {:08x}", w, parent, root); + } + + SPDLOG_DEBUG("parent: {:08x}, focus: {:08x}", w, focus); + return false; +} +#endif + +bool window_has_focus(const wsi_connection* conn) +{ + if (!conn) + return true; + +#ifdef VK_USE_PLATFORM_XCB_KHR + if (conn->xcb.conn) + return check_window_focus(conn->xcb.conn, conn->xcb.window); +#endif + +#ifdef VK_USE_PLATFORM_XLIB_KHR + if (conn->xlib.dpy) + return check_window_focus(conn->xlib.dpy, conn->xlib.window); +#endif + return true; +} diff --git a/src/wsi_helpers.h b/src/wsi_helpers.h new file mode 100644 index 00000000..6ef93ace --- /dev/null +++ b/src/wsi_helpers.h @@ -0,0 +1,40 @@ +#pragma once +#ifdef VK_USE_PLATFORM_XLIB_KHR +#include "loaders/loader_x11.h" +#endif + +#ifdef VK_USE_PLATFORM_XCB_KHR +#include +#endif + +#ifdef VK_USE_PLATFORM_WAYLAND_KHR +#include +#endif + + +struct wsi_connection +{ +#ifdef VK_USE_PLATFORM_XCB_KHR + struct xcb { + xcb_connection_t *conn = nullptr; + xcb_window_t window = 0; + } xcb; +#endif +#ifdef VK_USE_PLATFORM_XLIB_KHR + struct xlib { + Display *dpy = nullptr; + Window window = 0; + int evmask = 0; + } xlib; +#endif +#ifdef VK_USE_PLATFORM_WAYLAND_KHR + struct wl { + wl_display *display; + wl_surface *surface; + bool has_focus; + } wl; +#endif +}; + +// struct wsi_connection; +// bool check_window_focus(const wsi_connection&);