/* -*- 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 #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 #include #include #define PSAPI_VERSION 1 #include // Must be linked with 'psapi' library #define dlsym GetProcAddress #else #include #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 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(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); }