#include /* Python.h must be included before any system headers, to ensure visibility macros are properly set. */ #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN // Windows 10, for latest HiDPI API support. #define WINVER 0x0A00 #if defined(_WIN32_WINNT) #if _WIN32_WINNT < WINVER #undef _WIN32_WINNT #define _WIN32_WINNT WINVER #endif #else #define _WIN32_WINNT WINVER #endif #endif #include #ifdef __linux__ #include #endif #ifdef _WIN32 #include #include #include #define UNUSED_ON_NON_WINDOWS(x) x #else #define UNUSED_ON_NON_WINDOWS Py_UNUSED #endif namespace py = pybind11; using namespace pybind11::literals; static bool mpl_xdisplay_is_valid(void) { #ifdef __linux__ void* libX11; // The getenv check is redundant but helps performance as it is much faster // than dlopen(). if (getenv("DISPLAY") && (libX11 = dlopen("libX11.so.6", RTLD_LAZY))) { struct Display* display = nullptr; auto XOpenDisplay = (struct Display* (*)(char const*)) dlsym(libX11, "XOpenDisplay"); auto XCloseDisplay = (int (*)(struct Display*)) dlsym(libX11, "XCloseDisplay"); if (XOpenDisplay && XCloseDisplay && (display = XOpenDisplay(nullptr))) { XCloseDisplay(display); } if (dlclose(libX11)) { throw std::runtime_error(dlerror()); } if (display) { return true; } } return false; #else return true; #endif } static bool mpl_display_is_valid(void) { #ifdef __linux__ if (mpl_xdisplay_is_valid()) { return true; } void* libwayland_client; if (getenv("WAYLAND_DISPLAY") && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { struct wl_display* display = nullptr; auto wl_display_connect = (struct wl_display* (*)(char const*)) dlsym(libwayland_client, "wl_display_connect"); auto wl_display_disconnect = (void (*)(struct wl_display*)) dlsym(libwayland_client, "wl_display_disconnect"); if (wl_display_connect && wl_display_disconnect && (display = wl_display_connect(nullptr))) { wl_display_disconnect(display); } if (dlclose(libwayland_client)) { throw std::runtime_error(dlerror()); } if (display) { return true; } } return false; #else return true; #endif } static py::object mpl_GetCurrentProcessExplicitAppUserModelID(void) { #ifdef _WIN32 wchar_t* appid = NULL; HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid); if (FAILED(hr)) { PyErr_SetFromWindowsErr(hr); throw py::error_already_set(); } auto py_appid = py::cast(appid); CoTaskMemFree(appid); return py_appid; #else return py::none(); #endif } static void mpl_SetCurrentProcessExplicitAppUserModelID(const wchar_t* UNUSED_ON_NON_WINDOWS(appid)) { #ifdef _WIN32 HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid); if (FAILED(hr)) { PyErr_SetFromWindowsErr(hr); throw py::error_already_set(); } #endif } static py::object mpl_GetForegroundWindow(void) { #ifdef _WIN32 if (HWND hwnd = GetForegroundWindow()) { return py::capsule(hwnd, "HWND"); } else { return py::none(); } #else return py::none(); #endif } static void mpl_SetForegroundWindow(py::capsule UNUSED_ON_NON_WINDOWS(handle_p)) { #ifdef _WIN32 if (strcmp(handle_p.name(), "HWND") != 0) { throw std::runtime_error("Handle must be a value returned from Win32_GetForegroundWindow"); } HWND handle = static_cast(handle_p.get_pointer()); if (!SetForegroundWindow(handle)) { throw std::runtime_error("Error setting window"); } #endif } static void mpl_SetProcessDpiAwareness_max(void) { #ifdef _WIN32 #ifdef _DPI_AWARENESS_CONTEXTS_ // These functions and options were added in later Windows 10 updates, so // must be loaded dynamically. HMODULE user32 = LoadLibrary("user32.dll"); auto IsValidDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT)) GetProcAddress(user32, "IsValidDpiAwarenessContext"); auto SetProcessDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT)) GetProcAddress(user32, "SetProcessDpiAwarenessContext"); DPI_AWARENESS_CONTEXT ctxs[3] = { DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, // Win10 Creators Update DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, // Win10 DPI_AWARENESS_CONTEXT_SYSTEM_AWARE}; // Win10 if (IsValidDpiAwarenessContext && SetProcessDpiAwarenessContext) { for (size_t i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { if (IsValidDpiAwarenessContext(ctxs[i])) { SetProcessDpiAwarenessContext(ctxs[i]); break; } } } else { // Added in Windows Vista. SetProcessDPIAware(); } FreeLibrary(user32); #else // Added in Windows Vista. SetProcessDPIAware(); #endif #endif } PYBIND11_MODULE(_c_internal_utils, m, py::mod_gil_not_used()) { m.def( "display_is_valid", &mpl_display_is_valid, R"""( -- Check whether the current X11 or Wayland display is valid. On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL) succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL) succeeds. On other platforms, always returns True.)"""); m.def( "xdisplay_is_valid", &mpl_xdisplay_is_valid, R"""( -- Check whether the current X11 display is valid. On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL) succeeds. Use this function if you need to specifically check for X11 only (e.g., for Tkinter). On other platforms, always returns True.)"""); m.def( "Win32_GetCurrentProcessExplicitAppUserModelID", &mpl_GetCurrentProcessExplicitAppUserModelID, R"""( -- Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID. On non-Windows platforms, always returns None.)"""); m.def( "Win32_SetCurrentProcessExplicitAppUserModelID", &mpl_SetCurrentProcessExplicitAppUserModelID, "appid"_a, py::pos_only(), R"""( -- Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID. On non-Windows platforms, does nothing.)"""); m.def( "Win32_GetForegroundWindow", &mpl_GetForegroundWindow, R"""( -- Wrapper for Windows' GetForegroundWindow. On non-Windows platforms, always returns None.)"""); m.def( "Win32_SetForegroundWindow", &mpl_SetForegroundWindow, "hwnd"_a, R"""( -- Wrapper for Windows' SetForegroundWindow. On non-Windows platforms, does nothing.)"""); m.def( "Win32_SetProcessDpiAwareness_max", &mpl_SetProcessDpiAwareness_max, R"""( -- Set Windows' process DPI awareness to best option available. On non-Windows platforms, does nothing.)"""); }