diff options
author | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 14:39:34 +0300 |
---|---|---|
committer | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 16:42:24 +0300 |
commit | 77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch) | |
tree | c51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py3/src/_tkagg.cpp | |
parent | dd6d20cadb65582270ac23f4b3b14ae189704b9d (diff) | |
download | ydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz |
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py3/src/_tkagg.cpp')
-rw-r--r-- | contrib/python/matplotlib/py3/src/_tkagg.cpp | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py3/src/_tkagg.cpp b/contrib/python/matplotlib/py3/src/_tkagg.cpp new file mode 100644 index 0000000000..5c36b3f07f --- /dev/null +++ b/contrib/python/matplotlib/py3/src/_tkagg.cpp @@ -0,0 +1,370 @@ +/* -*- mode: c++; c-basic-offset: 4 -*- */ + +// Where is PIL? +// +// Many years ago, Matplotlib used to include code from PIL (the Python Imaging +// Library). Since then, the code has changed a lot - the organizing principle +// and methods of operation are now quite different. Because our review of +// the codebase showed that all the code that came from PIL was removed or +// rewritten, we have removed the PIL licensing information. If you want PIL, +// you can get it at https://python-pillow.org/ + +#define PY_SSIZE_T_CLEAN +#include <Python.h> + +#ifdef _WIN32 +#define WIN32_DLL +#endif +#ifdef __CYGWIN__ +/* + * Unfortunately cygwin's libdl inherits restrictions from the underlying + * Windows OS, at least currently. Therefore, a symbol may be loaded from a + * module by dlsym() only if it is really located in the given module, + * dependencies are not included. So we have to use native WinAPI on Cygwin + * also. + */ +#define WIN32_DLL +static inline PyObject *PyErr_SetFromWindowsErr(int ierr) { + PyErr_SetString(PyExc_OSError, "Call to EnumProcessModules failed"); + return NULL; +} +#endif + +#ifdef WIN32_DLL +#include <string> +#include <windows.h> +#include <commctrl.h> +#define PSAPI_VERSION 1 +#include <psapi.h> // Must be linked with 'psapi' library +#define dlsym GetProcAddress +#else +#include <dlfcn.h> +#endif + +// Include our own excerpts from the Tcl / Tk headers +#include "_tkmini.h" + +static int convert_voidptr(PyObject *obj, void *p) +{ + void **val = (void **)p; + *val = PyLong_AsVoidPtr(obj); + return *val != NULL ? 1 : !PyErr_Occurred(); +} + +// Global vars for Tk functions. We load these symbols from the tkinter +// extension module or loaded Tk libraries at run-time. +static Tk_FindPhoto_t TK_FIND_PHOTO; +static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK; +// Global vars for Tcl functions. We load these symbols from the tkinter +// extension module or loaded Tcl libraries at run-time. +static Tcl_SetVar_t TCL_SETVAR; + +static PyObject *mpl_tk_blit(PyObject *self, PyObject *args) +{ + Tcl_Interp *interp; + char const *photo_name; + int height, width; + unsigned char *data_ptr; + int comp_rule; + int put_retval; + int o0, o1, o2, o3; + int x1, x2, y1, y2; + Tk_PhotoHandle photo; + Tk_PhotoImageBlock block; + if (!PyArg_ParseTuple(args, "O&s(iiO&)i(iiii)(iiii):blit", + convert_voidptr, &interp, &photo_name, + &height, &width, convert_voidptr, &data_ptr, + &comp_rule, + &o0, &o1, &o2, &o3, + &x1, &x2, &y1, &y2)) { + goto exit; + } + if (!(photo = TK_FIND_PHOTO(interp, photo_name))) { + PyErr_SetString(PyExc_ValueError, "Failed to extract Tk_PhotoHandle"); + goto exit; + } + if (0 > y1 || y1 > y2 || y2 > height || 0 > x1 || x1 > x2 || x2 > width) { + PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds"); + goto exit; + } + if (comp_rule != TK_PHOTO_COMPOSITE_OVERLAY && comp_rule != TK_PHOTO_COMPOSITE_SET) { + PyErr_SetString(PyExc_ValueError, "Invalid comp_rule argument"); + goto exit; + } + + Py_BEGIN_ALLOW_THREADS + block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1); + block.width = x2 - x1; + block.height = y2 - y1; + block.pitch = 4 * width; + block.pixelSize = 4; + block.offset[0] = o0; + block.offset[1] = o1; + block.offset[2] = o2; + block.offset[3] = o3; + put_retval = TK_PHOTO_PUT_BLOCK( + interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule); + Py_END_ALLOW_THREADS + if (put_retval == TCL_ERROR) { + return PyErr_NoMemory(); + } + +exit: + if (PyErr_Occurred()) { + return NULL; + } else { + Py_RETURN_NONE; + } +} + +#ifdef WIN32_DLL +LRESULT CALLBACK +DpiSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, + UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + switch (uMsg) { + case WM_DPICHANGED: + // This function is a subclassed window procedure, and so is run during + // the Tcl/Tk event loop. Unfortunately, Tkinter has a *second* lock on + // Tcl threading that is not exposed publicly, but is currently taken + // while we're in the window procedure. So while we can take the GIL to + // call Python code, we must not also call *any* Tk code from Python. + // So stay with Tcl calls in C only. + { + // This variable naming must match the name used in + // lib/matplotlib/backends/_backend_tk.py:FigureManagerTk. + std::string var_name("window_dpi"); + var_name += std::to_string((unsigned long long)hwnd); + + // X is high word, Y is low word, but they are always equal. + std::string dpi = std::to_string(LOWORD(wParam)); + + Tcl_Interp* interp = (Tcl_Interp*)dwRefData; + TCL_SETVAR(interp, var_name.c_str(), dpi.c_str(), 0); + } + return 0; + case WM_NCDESTROY: + RemoveWindowSubclass(hwnd, DpiSubclassProc, uIdSubclass); + break; + } + + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} +#endif + +static PyObject* +mpl_tk_enable_dpi_awareness(PyObject* self, PyObject*const* args, + Py_ssize_t nargs) +{ + if (nargs != 2) { + return PyErr_Format(PyExc_TypeError, + "enable_dpi_awareness() takes 2 positional " + "arguments but %zd were given", + nargs); + } + +#ifdef WIN32_DLL + HWND frame_handle = NULL; + Tcl_Interp *interp = NULL; + + if (!convert_voidptr(args[0], &frame_handle)) { + return NULL; + } + if (!convert_voidptr(args[1], &interp)) { + return NULL; + } + +#ifdef _DPI_AWARENESS_CONTEXTS_ + HMODULE user32 = LoadLibrary("user32.dll"); + + typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext_t)(HWND); + GetWindowDpiAwarenessContext_t GetWindowDpiAwarenessContextPtr = + (GetWindowDpiAwarenessContext_t)GetProcAddress( + user32, "GetWindowDpiAwarenessContext"); + if (GetWindowDpiAwarenessContextPtr == NULL) { + FreeLibrary(user32); + Py_RETURN_FALSE; + } + + typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual_t)(DPI_AWARENESS_CONTEXT, + DPI_AWARENESS_CONTEXT); + AreDpiAwarenessContextsEqual_t AreDpiAwarenessContextsEqualPtr = + (AreDpiAwarenessContextsEqual_t)GetProcAddress( + user32, "AreDpiAwarenessContextsEqual"); + if (AreDpiAwarenessContextsEqualPtr == NULL) { + FreeLibrary(user32); + Py_RETURN_FALSE; + } + + DPI_AWARENESS_CONTEXT ctx = GetWindowDpiAwarenessContextPtr(frame_handle); + bool per_monitor = ( + AreDpiAwarenessContextsEqualPtr( + ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) || + AreDpiAwarenessContextsEqualPtr( + ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)); + + if (per_monitor) { + // Per monitor aware means we need to handle WM_DPICHANGED by wrapping + // the Window Procedure, and the Python side needs to trace the Tk + // window_dpi variable stored on interp. + SetWindowSubclass(frame_handle, DpiSubclassProc, 0, (DWORD_PTR)interp); + } + FreeLibrary(user32); + return PyBool_FromLong(per_monitor); +#endif +#endif + + Py_RETURN_NONE; +} + +static PyMethodDef functions[] = { + { "blit", (PyCFunction)mpl_tk_blit, METH_VARARGS }, + { "enable_dpi_awareness", (PyCFunction)mpl_tk_enable_dpi_awareness, + METH_FASTCALL }, + { NULL, NULL } /* sentinel */ +}; + +// Functions to fill global Tcl/Tk function pointers by dynamic loading. + +template <class T> +bool load_tcl_tk(T lib) +{ + // Try to fill Tcl/Tk global vars with function pointers. Return whether + // all of them have been filled. + if (auto ptr = dlsym(lib, "Tcl_SetVar")) { + TCL_SETVAR = (Tcl_SetVar_t)ptr; + } + if (auto ptr = dlsym(lib, "Tk_FindPhoto")) { + TK_FIND_PHOTO = (Tk_FindPhoto_t)ptr; + } + if (auto ptr = dlsym(lib, "Tk_PhotoPutBlock")) { + TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)ptr; + } + return TCL_SETVAR && TK_FIND_PHOTO && TK_PHOTO_PUT_BLOCK; +} + +#ifdef WIN32_DLL + +/* On Windows, we can't load the tkinter module to get the Tcl/Tk symbols, + * because Windows does not load symbols into the library name-space of + * importing modules. So, knowing that tkinter has already been imported by + * Python, we scan all modules in the running process for the Tcl/Tk function + * names. + */ + +void load_tkinter_funcs(void) +{ + HANDLE process = GetCurrentProcess(); // Pseudo-handle, doesn't need closing. + HMODULE* modules = NULL; + DWORD size; + if (!EnumProcessModules(process, NULL, 0, &size)) { + PyErr_SetFromWindowsErr(0); + goto exit; + } + if (!(modules = static_cast<HMODULE*>(malloc(size)))) { + PyErr_NoMemory(); + goto exit; + } + if (!EnumProcessModules(process, modules, size, &size)) { + PyErr_SetFromWindowsErr(0); + goto exit; + } + for (unsigned i = 0; i < size / sizeof(HMODULE); ++i) { + if (load_tcl_tk(modules[i])) { + return; + } + } +exit: + free(modules); +} + +#else // not Windows + +/* + * On Unix, we can get the Tk symbols from the tkinter module, because tkinter + * uses these symbols, and the symbols are therefore visible in the tkinter + * dynamic library (module). + */ + +void load_tkinter_funcs(void) +{ + // Load tkinter global funcs from tkinter compiled module. + void *main_program = NULL, *tkinter_lib = NULL; + PyObject *module = NULL, *py_path = NULL, *py_path_b = NULL; + char *path; + + // Try loading from the main program namespace first. + main_program = dlopen(NULL, RTLD_LAZY); + if (load_tcl_tk(main_program)) { + goto exit; + } + // Clear exception triggered when we didn't find symbols above. + PyErr_Clear(); + + // Handle PyPy first, as that import will correctly fail on CPython. + module = PyImport_ImportModule("_tkinter.tklib_cffi"); // PyPy + if (!module) { + PyErr_Clear(); + module = PyImport_ImportModule("_tkinter"); // CPython + } + if (!(module && + (py_path = PyObject_GetAttrString(module, "__file__")) && + (py_path_b = PyUnicode_EncodeFSDefault(py_path)) && + (path = PyBytes_AsString(py_path_b)))) { + goto exit; + } + tkinter_lib = dlopen(path, RTLD_LAZY); + if (!tkinter_lib) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + goto exit; + } + if (load_tcl_tk(tkinter_lib)) { + goto exit; + } + +exit: + // We don't need to keep a reference open as the main program & tkinter + // have been imported. Try to close each library separately (otherwise the + // second dlclose could clear a dlerror from the first dlclose). + bool raised_dlerror = false; + if (main_program && dlclose(main_program) && !raised_dlerror) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + raised_dlerror = true; + } + if (tkinter_lib && dlclose(tkinter_lib) && !raised_dlerror) { + PyErr_SetString(PyExc_RuntimeError, dlerror()); + raised_dlerror = true; + } + Py_XDECREF(module); + Py_XDECREF(py_path); + Py_XDECREF(py_path_b); +} +#endif // end not Windows + +static PyModuleDef _tkagg_module = { + PyModuleDef_HEAD_INIT, "_tkagg", NULL, -1, functions +}; + +PyMODINIT_FUNC PyInit__tkagg(void) +{ + load_tkinter_funcs(); + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + // Always raise ImportError (normalizing a previously set exception if + // needed) to interact properly with backend auto-fallback. + if (value) { + PyErr_NormalizeException(&type, &value, &traceback); + PyErr_SetObject(PyExc_ImportError, value); + return NULL; + } else if (!TCL_SETVAR) { + PyErr_SetString(PyExc_ImportError, "Failed to load Tcl_SetVar"); + return NULL; + } else if (!TK_FIND_PHOTO) { + PyErr_SetString(PyExc_ImportError, "Failed to load Tk_FindPhoto"); + return NULL; + } else if (!TK_PHOTO_PUT_BLOCK) { + PyErr_SetString(PyExc_ImportError, "Failed to load Tk_PhotoPutBlock"); + return NULL; + } + return PyModule_Create(&_tkagg_module); +} |