diff --git a/src/_c_internal_utils.c b/src/_c_internal_utils.c deleted file mode 100644 index 49e5057f1349..000000000000 --- a/src/_c_internal_utils.c +++ /dev/null @@ -1,217 +0,0 @@ -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -// Windows 10, for latest HiDPI API support. -#define WINVER 0x0A00 -#define _WIN32_WINNT 0x0A00 -#endif -#define PY_SSIZE_T_CLEAN -#include -#ifdef __linux__ -#include -#endif -#ifdef _WIN32 -#include -#include -#include -#endif - -static PyObject* -mpl_display_is_valid(PyObject* module) -{ -#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 = NULL; - struct Display* (* XOpenDisplay)(char const*) = - dlsym(libX11, "XOpenDisplay"); - int (* XCloseDisplay)(struct Display*) = - dlsym(libX11, "XCloseDisplay"); - if (XOpenDisplay && XCloseDisplay - && (display = XOpenDisplay(NULL))) { - XCloseDisplay(display); - } - if (dlclose(libX11)) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - return NULL; - } - if (display) { - Py_RETURN_TRUE; - } - } - void* libwayland_client; - if (getenv("WAYLAND_DISPLAY") - && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { - struct wl_display* display = NULL; - struct wl_display* (* wl_display_connect)(char const*) = - dlsym(libwayland_client, "wl_display_connect"); - void (* wl_display_disconnect)(struct wl_display*) = - dlsym(libwayland_client, "wl_display_disconnect"); - if (wl_display_connect && wl_display_disconnect - && (display = wl_display_connect(NULL))) { - wl_display_disconnect(display); - } - if (dlclose(libwayland_client)) { - PyErr_SetString(PyExc_RuntimeError, dlerror()); - return NULL; - } - if (display) { - Py_RETURN_TRUE; - } - } - Py_RETURN_FALSE; -#else - Py_RETURN_TRUE; -#endif -} - -static PyObject* -mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module) -{ -#ifdef _WIN32 - wchar_t* appid = NULL; - HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid); - if (FAILED(hr)) { - return PyErr_SetFromWindowsErr(hr); - } - PyObject* py_appid = PyUnicode_FromWideChar(appid, -1); - CoTaskMemFree(appid); - return py_appid; -#else - Py_RETURN_NONE; -#endif -} - -static PyObject* -mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg) -{ -#ifdef _WIN32 - wchar_t* appid = PyUnicode_AsWideCharString(arg, NULL); - if (!appid) { - return NULL; - } - HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid); - PyMem_Free(appid); - if (FAILED(hr)) { - return PyErr_SetFromWindowsErr(hr); - } - Py_RETURN_NONE; -#else - Py_RETURN_NONE; -#endif -} - -static PyObject* -mpl_GetForegroundWindow(PyObject* module) -{ -#ifdef _WIN32 - return PyLong_FromVoidPtr(GetForegroundWindow()); -#else - Py_RETURN_NONE; -#endif -} - -static PyObject* -mpl_SetForegroundWindow(PyObject* module, PyObject *arg) -{ -#ifdef _WIN32 - HWND handle = PyLong_AsVoidPtr(arg); - if (PyErr_Occurred()) { - return NULL; - } - if (!SetForegroundWindow(handle)) { - return PyErr_Format(PyExc_RuntimeError, "Error setting window"); - } - Py_RETURN_NONE; -#else - Py_RETURN_NONE; -#endif -} - -static PyObject* -mpl_SetProcessDpiAwareness_max(PyObject* module) -{ -#ifdef _WIN32 -#ifdef _DPI_AWARENESS_CONTEXTS_ - // These functions and options were added in later Windows 10 updates, so - // must be loaded dynamically. - typedef BOOL (WINAPI *IsValidDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); - typedef BOOL (WINAPI *SetProcessDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); - - HMODULE user32 = LoadLibrary("user32.dll"); - IsValidDpiAwarenessContext_t IsValidDpiAwarenessContextPtr = - (IsValidDpiAwarenessContext_t)GetProcAddress( - user32, "IsValidDpiAwarenessContext"); - SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContextPtr = - (SetProcessDpiAwarenessContext_t)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 (IsValidDpiAwarenessContextPtr != NULL - && SetProcessDpiAwarenessContextPtr != NULL) { - for (int i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { - if (IsValidDpiAwarenessContextPtr(ctxs[i])) { - SetProcessDpiAwarenessContextPtr(ctxs[i]); - break; - } - } - } else { - // Added in Windows Vista. - SetProcessDPIAware(); - } - FreeLibrary(user32); -#else - // Added in Windows Vista. - SetProcessDPIAware(); -#endif -#endif - Py_RETURN_NONE; -} - -static PyMethodDef functions[] = { - {"display_is_valid", (PyCFunction)mpl_display_is_valid, METH_NOARGS, - "display_is_valid()\n--\n\n" - "Check whether the current X11 or Wayland display is valid.\n\n" - "On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL)\n" - "succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL)\n" - "succeeds.\n\n" - "On other platforms, always returns True."}, - {"Win32_GetCurrentProcessExplicitAppUserModelID", - (PyCFunction)mpl_GetCurrentProcessExplicitAppUserModelID, METH_NOARGS, - "Win32_GetCurrentProcessExplicitAppUserModelID()\n--\n\n" - "Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID.\n\n" - "On non-Windows platforms, always returns None."}, - {"Win32_SetCurrentProcessExplicitAppUserModelID", - (PyCFunction)mpl_SetCurrentProcessExplicitAppUserModelID, METH_O, - "Win32_SetCurrentProcessExplicitAppUserModelID(appid, /)\n--\n\n" - "Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID.\n\n" - "On non-Windows platforms, does nothing."}, - {"Win32_GetForegroundWindow", - (PyCFunction)mpl_GetForegroundWindow, METH_NOARGS, - "Win32_GetForegroundWindow()\n--\n\n" - "Wrapper for Windows' GetForegroundWindow.\n\n" - "On non-Windows platforms, always returns None."}, - {"Win32_SetForegroundWindow", - (PyCFunction)mpl_SetForegroundWindow, METH_O, - "Win32_SetForegroundWindow(hwnd, /)\n--\n\n" - "Wrapper for Windows' SetForegroundWindow.\n\n" - "On non-Windows platforms, does nothing."}, - {"Win32_SetProcessDpiAwareness_max", - (PyCFunction)mpl_SetProcessDpiAwareness_max, METH_NOARGS, - "Win32_SetProcessDpiAwareness_max()\n--\n\n" - "Set Windows' process DPI awareness to best option available.\n\n" - "On non-Windows platforms, does nothing."}, - {NULL, NULL}}; // sentinel. -static PyModuleDef util_module = { - PyModuleDef_HEAD_INIT, "_c_internal_utils", NULL, 0, functions -}; - -#pragma GCC visibility push(default) -PyMODINIT_FUNC PyInit__c_internal_utils(void) -{ - return PyModule_Create(&util_module); -} diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp new file mode 100644 index 000000000000..813aeb6f7d5a --- /dev/null +++ b/src/_c_internal_utils.cpp @@ -0,0 +1,218 @@ +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +// Windows 10, for latest HiDPI API support. +#define WINVER 0x0A00 +#define _WIN32_WINNT 0x0A00 +#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_display_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))) { + typedef struct Display* (*XOpenDisplay_t)(char const*); + typedef int (*XCloseDisplay_t)(struct Display*); + struct Display* display = NULL; + XOpenDisplay_t XOpenDisplay = (XOpenDisplay_t)dlsym(libX11, "XOpenDisplay"); + XCloseDisplay_t XCloseDisplay = (XCloseDisplay_t)dlsym(libX11, "XCloseDisplay"); + if (XOpenDisplay && XCloseDisplay + && (display = XOpenDisplay(NULL))) { + XCloseDisplay(display); + } + if (dlclose(libX11)) { + throw std::runtime_error(dlerror()); + } + if (display) { + return true; + } + } + void* libwayland_client; + if (getenv("WAYLAND_DISPLAY") + && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { + typedef struct wl_display* (*wl_display_connect_t)(char const*); + typedef void (*wl_display_disconnect_t)(struct wl_display*); + struct wl_display* display = NULL; + wl_display_connect_t wl_display_connect = + (wl_display_connect_t)dlsym(libwayland_client, "wl_display_connect"); + wl_display_disconnect_t wl_display_disconnect = + (wl_display_disconnect_t)dlsym(libwayland_client, "wl_display_disconnect"); + if (wl_display_connect && wl_display_disconnect + && (display = wl_display_connect(NULL))) { + 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 + return py::capsule(GetForegroundWindow(), "HWND"); +#else + return py::none(); +#endif +} + +static void +mpl_SetForegroundWindow(py::capsule UNUSED_ON_NON_WINDOWS(handle_p)) +{ +#ifdef _WIN32 + if (handle_p.name() != "HWND") { + 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. + typedef BOOL (WINAPI *IsValidDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); + typedef BOOL (WINAPI *SetProcessDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); + + HMODULE user32 = LoadLibrary("user32.dll"); + IsValidDpiAwarenessContext_t IsValidDpiAwarenessContextPtr = + (IsValidDpiAwarenessContext_t)GetProcAddress( + user32, "IsValidDpiAwarenessContext"); + SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContextPtr = + (SetProcessDpiAwarenessContext_t)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 (IsValidDpiAwarenessContextPtr != NULL + && SetProcessDpiAwarenessContextPtr != NULL) { + for (int i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { + if (IsValidDpiAwarenessContextPtr(ctxs[i])) { + SetProcessDpiAwarenessContextPtr(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) +{ + 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( + "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.)"""); +} diff --git a/src/meson.build b/src/meson.build index 6a33d2dfb12a..54457d74aceb 100644 --- a/src/meson.build +++ b/src/meson.build @@ -82,9 +82,9 @@ extension_data = { '_c_internal_utils': { 'subdir': 'matplotlib', 'sources': files( - '_c_internal_utils.c', + '_c_internal_utils.cpp', ), - 'dependencies': [py3_dep, dl, ole32, shell32, user32], + 'dependencies': [pybind11_dep, dl, ole32, shell32, user32], }, 'ft2font': { 'subdir': 'matplotlib',