diff --git a/.travis.yml b/.travis.yml index 991d07c2743e..ffa2f4dde1ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -119,6 +119,10 @@ script: # multiple processes - python -c "from matplotlib import font_manager" - | + echo Testing import of tkagg backend + MPLBACKEND="tkagg" python -c 'import matplotlib.pyplot as plt; print(plt.get_backend())' + echo Testing using $NPROC processes + echo The following args are passed to nose $NOSE_ARGS if [[ $BUILD_DOCS == false ]]; then export MPL_REPO_DIR=$PWD # needed for pep8-conformance test of the examples gdb -return-child-result -batch -ex r -ex bt --args python tests.py -s --processes=$NPROC --process-timeout=300 $TEST_ARGS diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000000..1e1fcfb755f3 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,136 @@ +# With infos from +# http://tjelvarolsson.com/blog/how-to-continuously-test-your-python-code-on-windows-using-appveyor/ +# https://packaging.python.org/en/latest/appveyor/ +# https://github.com/rmcgibbo/python-appveyor-conda-example + +# Backslashes in quotes need to be escaped: \ -> "\\" + +environment: + + global: + # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the + # /E:ON and /V:ON options are not enabled in the batch script intepreter + # See: http://stackoverflow.com/a/13751649/163740 + CMD_IN_ENV: "cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd" + # Workaround for https://github.com/conda/conda-build/issues/636 + PYTHONIOENCODING: "UTF-8" + + matrix: + # for testing purpose: numpy 1.8 on py2.7, for the rest use 1.10/latest + - TARGET_ARCH: "x86" + CONDA_PY: "27" + CONDA_NPY: "18" + PYTHON_VERSION: "2.7" + CONDA_INSTALL_LOCN: "C:\\Miniconda" + - TARGET_ARCH: "x64" + CONDA_PY: "27" + CONDA_NPY: "18" + PYTHON_VERSION: "2.7" + CONDA_INSTALL_LOCN: "C:\\Miniconda-x64" + - TARGET_ARCH: "x64" + CONDA_PY: "34" + CONDA_NPY: "110" + PYTHON_VERSION: "3.4" + CONDA_INSTALL_LOCN: "C:\\Miniconda3-x64" + - TARGET_ARCH: "x64" + CONDA_PY: "35" + CONDA_NPY: "110" + PYTHON_VERSION: "3.5" + CONDA_INSTALL_LOCN: "C:\\Miniconda35-x64" + + +# We always use a 64-bit machine, but can build x86 distributions +# with the PYTHON_ARCH variable (which is used by CMD_IN_ENV). +platform: + - x64 + +# all our python builds have to happen in tests_script... +build: false + +init: + - cmd: "ECHO %PYTHON_VERSION% %CONDA_INSTALL_LOCN%" + +install: + - cmd: set PATH=%CONDA_INSTALL_LOCN%;%CONDA_INSTALL_LOCN%\scripts;%PATH%; + - cmd: set PYTHONUNBUFFERED=1 + - cmd: conda install -c http://conda.anaconda.org/pelson/channel/development --yes --quiet obvious-ci + - cmd: obvci_install_conda_build_tools.py + - cmd: conda config --set show_channel_urls yes + # for msinttypes + - cmd: conda config --add channels conda-forge + # this is now the downloaded conda... + - conda info -a + + # Fix the appveyor build environment to work with conda build + # workaround for missing vcvars64.bat in py34 64bit + - cmd: copy ci\appveyor\vcvars64.bat "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64" + # workaround for conda build on py27 prefering the normal installed + # VS tools instead of the also installed Py27 VS compiler (which wouldn't need this workarounds...) + - cmd: copy "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat" "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64\vcvarsamd64.bat" + + # same things as the requirements in ci/conda_recipe/meta.yaml + - cmd: conda create -y -q -n test-environment python=%PYTHON_VERSION% pip setuptools numpy python-dateutil freetype msinttypes tk pyparsing pytz tornado libpng zlib pyqt cycler nose mock msvc_runtime + - activate test-environment + - cmd: echo %PYTHON_VERSION% %TARGET_ARCH% + - cmd: IF %PYTHON_VERSION% == 2.7 conda install -y functools32 + + # Let the install prefer the static builds of the libs + - set LIBRARY_LIB=%CONDA_DEFAULT_ENV%\Library\lib + - cmd: 'mkdir lib || cmd /c "exit /b 0"' + - copy %LIBRARY_LIB%\zlibstatic.lib lib\z.lib + - copy %LIBRARY_LIB%\libpng_static.lib lib\png.lib + - set MPLBASEDIRLIST=%CONDA_DEFAULT_ENV%\Library\;. + # enables the local freetype build + - copy ci\travis\setup.cfg . + # Show the installed packages + versions + - conda list + +test_script: + # Now build the thing.. + - '%CMD_IN_ENV% python setup.py develop' + # Test import of tkagg backend + - python -c "import matplotlib as m; m.use('tkagg'); import matplotlib.pyplot as plt; print(plt.get_backend())" + # tests + - python tests.py + # remove to get around libpng issue? + - python visual_tests.py + +after_test: + # After the tests were a success, build packages (wheels and conda) + + # Build the wheel + # Hide the output, the copied files really clutter the build log... + - cmd: '%CMD_IN_ENV% python setup.py bdist_wheel > NUL:' + + # And now the conda build after a cleanup... + # cleanup build files so that they don't pollute the conda build but keep the wheel in dist... + - cmd: git clean -d -x -f -e dist/ + # cleanup the environment so that the test-environment does not leak into the conda build... + - cmd: set MPLBASEDIRLIST= + - cmd: set LIBRARY_LIB= + - cmd: deactivate + - cmd: path + - cmd: where python + - cmd: '%CMD_IN_ENV% conda config --get channels' + # - cmd: '%CMD_IN_ENV% conda build .\ci\conda_recipe' + + # Move the conda package into the dist directory, to register it + # as an "artifact" for Appveyor. + - cmd: 'copy /Y %CONDA_INSTALL_LOCN%\conda-bld\win-32\*.bz2 dist || cmd /c "exit /b 0"' + - cmd: 'copy /Y %CONDA_INSTALL_LOCN%\conda-bld\win-64\*.bz2 dist || cmd /c "exit /b 0"' + - cmd: dir dist\ + - cmd: echo finished... + +artifacts: + - path: dist\* + name: packages + + - path: result_images\* + name: result_images + type: zip + +on_failure: + - python visual_tests.py + - echo zipping images after a failure... + - 7z a result_images.zip result_images\ |grep -v "Compressing" + - appveyor PushArtifact result_images.zip diff --git a/setupext.py b/setupext.py index ff6cbca54018..fc9ff3ec3f77 100755 --- a/setupext.py +++ b/setupext.py @@ -1283,6 +1283,7 @@ def get_install_requires(self): class BackendAgg(OptionalBackendPackage): name = "agg" + force = True def get_extension(self): sources = [ @@ -1300,36 +1301,10 @@ def get_extension(self): class BackendTkAgg(OptionalBackendPackage): name = "tkagg" + force = True - def __init__(self): - self.tcl_tk_cache = None - - def check_requirements(self): - try: - if PY3min: - import tkinter as Tkinter - else: - import Tkinter - except ImportError: - raise CheckFailed('TKAgg requires Tkinter.') - except RuntimeError: - raise CheckFailed('Tkinter present but import failed.') - else: - if Tkinter.TkVersion < 8.3: - raise CheckFailed("Tcl/Tk v8.3 or later required.") - - ext = self.get_extension() - check_include_file(ext.include_dirs, "tk.h", "Tk") - - try: - tk_v = Tkinter.__version__.split()[-2] - except (AttributeError, IndexError): - # Tkinter.__version__ has been removed in python 3 - tk_v = 'not identified' - - BackendAgg.force = True - - return "version %s" % tk_v + def check(self): + return "installing; run-time loading from Python Tcl / Tk" def get_extension(self): sources = [ @@ -1343,251 +1318,11 @@ def get_extension(self): LibAgg().add_flags(ext, add_sources=False) return ext - def query_tcltk(self): - """ - Tries to open a Tk window in order to query the Tk object - about its library paths. This should never be called more - than once by the same process, as Tk intricacies may cause the - Python interpreter to hang. The function also has a workaround - if no X server is running (useful for autobuild systems). - """ - # Use cached values if they exist, which ensures this function - # only executes once - if self.tcl_tk_cache is not None: - return self.tcl_tk_cache - - # By this point, we already know that Tkinter imports correctly - if PY3min: - import tkinter as Tkinter - else: - import Tkinter - tcl_lib_dir = '' - tk_lib_dir = '' - # First try to open a Tk window (requires a running X server) - try: - tk = Tkinter.Tk() - except Tkinter.TclError: - # Next, start Tcl interpreter without opening a Tk window - # (no need for X server) This feature is available in - # python version 2.4 and up - try: - tcl = Tkinter.Tcl() - except AttributeError: # Python version not high enough - pass - except Tkinter.TclError: # Something went wrong while opening Tcl - pass - else: - tcl_lib_dir = str(tcl.getvar('tcl_library')) - # Guess Tk location based on Tcl location - (head, tail) = os.path.split(tcl_lib_dir) - tail = tail.replace('Tcl', 'Tk').replace('tcl', 'tk') - tk_lib_dir = os.path.join(head, tail) - if not os.path.exists(tk_lib_dir): - tk_lib_dir = tcl_lib_dir.replace( - 'Tcl', 'Tk').replace('tcl', 'tk') - else: - # Obtain Tcl and Tk locations from Tk widget - tk.withdraw() - tcl_lib_dir = str(tk.getvar('tcl_library')) - tk_lib_dir = str(tk.getvar('tk_library')) - tk.destroy() - - # Save directories and version string to cache - self.tcl_tk_cache = tcl_lib_dir, tk_lib_dir, str(Tkinter.TkVersion)[:3] - return self.tcl_tk_cache - - def parse_tcl_config(self, tcl_lib_dir, tk_lib_dir): - try: - if PY3min: - import tkinter as Tkinter - else: - import Tkinter - except ImportError: - return None - - tcl_poss = [tcl_lib_dir, - os.path.normpath(os.path.join(tcl_lib_dir, '..')), - "/usr/lib/tcl" + str(Tkinter.TclVersion), - "/usr/lib"] - tk_poss = [tk_lib_dir, - os.path.normpath(os.path.join(tk_lib_dir, '..')), - "/usr/lib/tk" + str(Tkinter.TkVersion), - "/usr/lib"] - for ptcl, ptk in zip(tcl_poss, tk_poss): - tcl_config = os.path.join(ptcl, "tclConfig.sh") - tk_config = os.path.join(ptk, "tkConfig.sh") - if (os.path.exists(tcl_config) and os.path.exists(tk_config)): - break - if not (os.path.exists(tcl_config) and os.path.exists(tk_config)): - return None - - def get_var(file, varname): - p = subprocess.Popen( - '. %s ; eval echo ${%s}' % (file, varname), - shell=True, - executable="/bin/sh", - stdout=subprocess.PIPE) - result = p.communicate()[0] - return result.decode('ascii') - - tcl_lib_dir = get_var( - tcl_config, 'TCL_LIB_SPEC').split()[0][2:].strip() - tcl_inc_dir = get_var( - tcl_config, 'TCL_INCLUDE_SPEC')[2:].strip() - tcl_lib = get_var(tcl_config, 'TCL_LIB_FLAG')[2:].strip() - - tk_lib_dir = get_var(tk_config, 'TK_LIB_SPEC').split()[0][2:].strip() - tk_inc_dir = get_var(tk_config, 'TK_INCLUDE_SPEC').strip() - if tk_inc_dir == '': - tk_inc_dir = tcl_inc_dir - else: - tk_inc_dir = tk_inc_dir[2:] - tk_lib = get_var(tk_config, 'TK_LIB_FLAG')[2:].strip() - - if not os.path.exists(os.path.join(tk_inc_dir, 'tk.h')): - return None - - return (tcl_lib_dir, tcl_inc_dir, tcl_lib, - tk_lib_dir, tk_inc_dir, tk_lib) - - def guess_tcl_config(self, tcl_lib_dir, tk_lib_dir, tk_ver): - if not (os.path.exists(tcl_lib_dir) and os.path.exists(tk_lib_dir)): - return None - - tcl_lib = os.path.normpath(os.path.join(tcl_lib_dir, '../')) - tk_lib = os.path.normpath(os.path.join(tk_lib_dir, '../')) - - tcl_inc = os.path.normpath( - os.path.join(tcl_lib_dir, - '../../include/tcl' + tk_ver)) - if not os.path.exists(tcl_inc): - tcl_inc = os.path.normpath( - os.path.join(tcl_lib_dir, - '../../include')) - - tk_inc = os.path.normpath(os.path.join( - tk_lib_dir, - '../../include/tk' + tk_ver)) - if not os.path.exists(tk_inc): - tk_inc = os.path.normpath(os.path.join( - tk_lib_dir, - '../../include')) - - if not os.path.exists(os.path.join(tk_inc, 'tk.h')): - tk_inc = tcl_inc - - if not os.path.exists(tcl_inc): - # this is a hack for suse linux, which is broken - if (sys.platform.startswith('linux') and - os.path.exists('/usr/include/tcl.h') and - os.path.exists('/usr/include/tk.h')): - tcl_inc = '/usr/include' - tk_inc = '/usr/include' - - if not os.path.exists(os.path.join(tk_inc, 'tk.h')): - return None - - return tcl_lib, tcl_inc, 'tcl' + tk_ver, tk_lib, tk_inc, 'tk' + tk_ver - - def hardcoded_tcl_config(self): - tcl_inc = "/usr/local/include" - tk_inc = "/usr/local/include" - tcl_lib = "/usr/local/lib" - tk_lib = "/usr/local/lib" - return tcl_lib, tcl_inc, 'tcl', tk_lib, tk_inc, 'tk' - def add_flags(self, ext): + ext.include_dirs.extend(['src']) if sys.platform == 'win32': - major, minor1, minor2, s, tmp = sys.version_info - if sys.version_info[0:2] < (3, 4): - ext.include_dirs.extend(['win32_static/include/tcl85']) - ext.libraries.extend(['tk85', 'tcl85']) - else: - ext.include_dirs.extend(['win32_static/include/tcl86']) - ext.libraries.extend(['tk86t', 'tcl86t']) - ext.library_dirs.extend([os.path.join(sys.prefix, 'dlls')]) - - elif sys.platform == 'darwin': - # this config section lifted directly from Imaging - thanks to - # the effbot! - - # First test for a MacOSX/darwin framework install - from os.path import join, exists - framework_dirs = [ - join(os.getenv('HOME'), '/Library/Frameworks'), - '/Library/Frameworks', - '/System/Library/Frameworks/', - ] - - # Find the directory that contains the Tcl.framework and - # Tk.framework bundles. - tk_framework_found = 0 - for F in framework_dirs: - # both Tcl.framework and Tk.framework should be present - for fw in 'Tcl', 'Tk': - if not exists(join(F, fw + '.framework')): - break - else: - # ok, F is now directory with both frameworks. Continure - # building - tk_framework_found = 1 - break - if tk_framework_found: - # For 8.4a2, we must add -I options that point inside - # the Tcl and Tk frameworks. In later release we - # should hopefully be able to pass the -F option to - # gcc, which specifies a framework lookup path. - - tk_include_dirs = [ - join(F, fw + '.framework', H) - for fw in ('Tcl', 'Tk') - for H in ('Headers', 'Versions/Current/PrivateHeaders') - ] - - # For 8.4a2, the X11 headers are not included. Rather - # than include a complicated search, this is a - # hard-coded path. It could bail out if X11 libs are - # not found... - - # tk_include_dirs.append('/usr/X11R6/include') - frameworks = ['-framework', 'Tcl', '-framework', 'Tk'] - ext.include_dirs.extend(tk_include_dirs) - ext.extra_link_args.extend(frameworks) - ext.extra_compile_args.extend(frameworks) - - # you're still here? ok we'll try it this way... - else: - # There are 3 methods to try, in decreasing order of "smartness" - # - # 1. Parse the tclConfig.sh and tkConfig.sh files that have - # all the information we need - # - # 2. Guess the include and lib dirs based on the location of - # Tkinter's 'tcl_library' and 'tk_library' variables. - # - # 3. Use some hardcoded locations that seem to work on a lot - # of distros. - - # Query Tcl/Tk system for library paths and version string - try: - tcl_lib_dir, tk_lib_dir, tk_ver = self.query_tcltk() - except: - tk_ver = '' - result = self.hardcoded_tcl_config() - else: - result = self.parse_tcl_config(tcl_lib_dir, tk_lib_dir) - if result is None: - result = self.guess_tcl_config( - tcl_lib_dir, tk_lib_dir, tk_ver) - if result is None: - result = self.hardcoded_tcl_config() - - # Add final versions of directories and libraries to ext lists - (tcl_lib_dir, tcl_inc_dir, tcl_lib, - tk_lib_dir, tk_inc_dir, tk_lib) = result - ext.include_dirs.extend([tcl_inc_dir, tk_inc_dir]) - ext.library_dirs.extend([tcl_lib_dir, tk_lib_dir]) - ext.libraries.extend([tcl_lib, tk_lib]) + # PSAPI library needed for finding Tcl / Tk at run time + ext.libraries.extend(['psapi']) class BackendGtk(OptionalBackendPackage): @@ -1701,8 +1436,6 @@ def check(self): return super(BackendGtkAgg, self).check() except: raise - else: - BackendAgg.force = True def get_package_data(self): return {'matplotlib': ['mpl-data/*.glade']} @@ -1778,7 +1511,6 @@ def check_requirements(self): p.join() if success: - BackendAgg.force = True return msg else: raise CheckFailed(msg) @@ -1851,7 +1583,6 @@ def check_requirements(self): p.join() if success: - BackendAgg.force = True return msg else: raise CheckFailed(msg) @@ -1895,8 +1626,6 @@ def check_requirements(self): raise CheckFailed( "Requires wxPython 2.8, found %s" % backend_version) - BackendAgg.force = True - return "version %s" % backend_version @@ -1935,7 +1664,7 @@ def check_requirements(self): config = self.get_config() if config is False: raise CheckFailed("skipping due to configuration") - return "installing" + return "" def get_extension(self): sources = [ @@ -2010,7 +1739,6 @@ def backend_pyside_internal_check(self): except ImportError: raise CheckFailed("PySide not found") else: - BackendAgg.force = True return ("Qt: %s, PySide: %s" % (QtCore.__version__, __version__)) @@ -2027,7 +1755,6 @@ def backend_pyqt4_internal_check(self): except AttributeError: raise CheckFailed('PyQt4 not correctly imported') else: - BackendAgg.force = True return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str)) @@ -2069,7 +1796,6 @@ def backend_qt5_internal_check(self): except AttributeError: raise CheckFailed('PyQt5 not correctly imported') else: - BackendAgg.force = True return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str)) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index a193410a4514..9191992f8bca 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -6,11 +6,6 @@ * */ -/* This is needed for (at least) Tk 8.4.1, otherwise the signature of -** Tk_PhotoPutBlock changes. -*/ -#define USE_COMPOSITELESS_PHOTO_PUT_BLOCK - #include #include #include @@ -18,19 +13,8 @@ #include "py_converters.h" -extern "C" -{ -#ifdef __APPLE__ -# ifdef TK_FRAMEWORK -# include -# include -# else -# include -# endif -#else -# include -#endif -} +// Include our own excerpts from the Tcl / Tk headers +#include "_tkmini.h" #if defined(_MSC_VER) # define SIZE_T_FORMAT "%Iu" @@ -44,7 +28,17 @@ typedef struct Tcl_Interp *interp; } TkappObject; -static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, char **argv) +// Global vars for Tcl / Tk functions. We load these symbols from the tkinter +// extension module or loaded Tcl / Tk libraries at run-time. +static Tcl_CreateCommand_t TCL_CREATE_COMMAND; +static Tcl_AppendResult_t TCL_APPEND_RESULT; +static Tk_MainWindow_t TK_MAIN_WINDOW; +static Tk_FindPhoto_t TK_FIND_PHOTO; +static Tk_PhotoPutBlock_NoComposite_t TK_PHOTO_PUT_BLOCK_NO_COMPOSITE; +static Tk_PhotoBlank_t TK_PHOTO_BLANK; + +static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int + argc, char **argv) { Tk_PhotoHandle photo; Tk_PhotoImageBlock block; @@ -61,25 +55,25 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, long mode; long nval; - if (Tk_MainWindow(interp) == NULL) { + if (TK_MAIN_WINDOW(interp) == NULL) { // Will throw a _tkinter.TclError with "this isn't a Tk application" return TCL_ERROR; } if (argc != 5) { - Tcl_AppendResult(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); + TCL_APPEND_RESULT(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); return TCL_ERROR; } /* get Tcl PhotoImage handle */ - photo = Tk_FindPhoto(interp, argv[1]); + photo = TK_FIND_PHOTO(interp, argv[1]); if (photo == NULL) { - Tcl_AppendResult(interp, "destination photo must exist", (char *)NULL); + TCL_APPEND_RESULT(interp, "destination photo must exist", (char *)NULL); return TCL_ERROR; } /* get array (or object that can be converted to array) pointer */ if (sscanf(argv[2], SIZE_T_FORMAT, &aggl) != 1) { - Tcl_AppendResult(interp, "error casting pointer", (char *)NULL); + TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL); return TCL_ERROR; } bufferobj = (PyObject *)aggl; @@ -88,7 +82,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, try { buffer = numpy::array_view(bufferobj); } catch (...) { - Tcl_AppendResult(interp, "buffer is of wrong type", (char *)NULL); + TCL_APPEND_RESULT(interp, "buffer is of wrong type", (char *)NULL); PyErr_Clear(); return TCL_ERROR; } @@ -99,13 +93,13 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, /* get array mode (0=mono, 1=rgb, 2=rgba) */ mode = atol(argv[3]); if ((mode != 0) && (mode != 1) && (mode != 2)) { - Tcl_AppendResult(interp, "illegal image mode", (char *)NULL); + TCL_APPEND_RESULT(interp, "illegal image mode", (char *)NULL); return TCL_ERROR; } /* check for bbox/blitting */ if (sscanf(argv[4], SIZE_T_FORMAT, &bboxl) != 1) { - Tcl_AppendResult(interp, "error casting pointer", (char *)NULL); + TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL); return TCL_ERROR; } bboxo = (PyObject *)bboxl; @@ -126,7 +120,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, destbuffer = new agg::int8u[deststride * destheight]; if (destbuffer == NULL) { - Tcl_AppendResult(interp, "could not allocate memory", (char *)NULL); + TCL_APPEND_RESULT(interp, "could not allocate memory", (char *)NULL); return TCL_ERROR; } @@ -167,7 +161,8 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, block.pitch = deststride; block.pixelPtr = destbuffer; - Tk_PhotoPutBlock(photo, &block, destx, desty, destwidth, destheight); + TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, destx, desty, + destwidth, destheight); delete[] destbuffer; } else { @@ -177,9 +172,10 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, block.pixelPtr = buffer.data(); /* Clear current contents */ - Tk_PhotoBlank(photo); + TK_PHOTO_BLANK(photo); /* Copy opaque block to photo image, and leave the rest to TK */ - Tk_PhotoPutBlock(photo, &block, 0, 0, block.width, block.height); + TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, 0, 0, block.width, + block.height); } return TCL_OK; @@ -216,11 +212,11 @@ static PyObject *_tkinit(PyObject *self, PyObject *args) /* This will bomb if interp is invalid... */ - Tcl_CreateCommand(interp, - "PyAggImagePhoto", - (Tcl_CmdProc *)PyAggImagePhoto, - (ClientData)0, - (Tcl_CmdDeleteProc *)NULL); + TCL_CREATE_COMMAND(interp, + "PyAggImagePhoto", + (Tcl_CmdProc *)PyAggImagePhoto, + (ClientData)0, + (Tcl_CmdDeleteProc *)NULL); Py_INCREF(Py_None); return Py_None; @@ -232,6 +228,221 @@ static PyMethodDef functions[] = { { NULL, NULL } /* sentinel */ }; +// Functions to fill global TCL / Tk function pointers by dynamic loading +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) + +/* + * On Windows, we can't load the tkinter module to get the TCL or 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 and Tk + * function names. + */ +#include +#define PSAPI_VERSION 1 +#include +// Must be linked with 'psapi' library + +FARPROC _dfunc(HMODULE lib_handle, const char *func_name) +{ + // Load function `func_name` from `lib_handle`. + // Set Python exception if we can't find `func_name` in `lib_handle`. + // Returns function pointer or NULL if not present. + + char message[100]; + + FARPROC func = GetProcAddress(lib_handle, func_name); + if (func == NULL) { + sprintf(message, "Cannot load function %s", func_name); + PyErr_SetString(PyExc_RuntimeError, message); + } + return func; +} + +int get_tcl(HMODULE hMod) +{ + // Try to fill TCL global vars with function pointers. Return 0 for no + // functions found, 1 for all functions found, -1 for some but not all + // functions found. + TCL_CREATE_COMMAND = (Tcl_CreateCommand_t) + GetProcAddress(hMod, "Tcl_CreateCommand"); + if (TCL_CREATE_COMMAND == NULL) { // Maybe not TCL module + return 0; + } + TCL_APPEND_RESULT = (Tcl_AppendResult_t) _dfunc(hMod, + "Tcl_AppendResult"); + return (TCL_APPEND_RESULT == NULL) ? -1 : 1; +} + +int get_tk(HMODULE hMod) +{ + // Try to fill Tk global vars with function pointers. Return 0 for no + // functions found, 1 for all functions found, -1 for some but not all + // functions found. + TK_MAIN_WINDOW = (Tk_MainWindow_t) + GetProcAddress(hMod, "Tk_MainWindow"); + if (TK_MAIN_WINDOW == NULL) { // Maybe not Tk module + return 0; + } + return ( // -1 if any remaining symbols are NULL + ((TK_FIND_PHOTO = (Tk_FindPhoto_t) + _dfunc(hMod, "Tk_FindPhoto")) == NULL) || + ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t) + _dfunc(hMod, "Tk_PhotoPutBlock_NoComposite")) == NULL) || + ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t) + _dfunc(hMod, "Tk_PhotoBlank")) == NULL)) + ? -1 : 1; +} + +int load_tkinter_funcs(void) +{ + // Load TCL and Tk functions by searching all modules in current process. + // Return 0 for success, non-zero for failure. + + HMODULE hMods[1024]; + HANDLE hProcess; + DWORD cbNeeded; + unsigned int i; + int found_tcl = 0; + int found_tk = 0; + + // Returns pseudo-handle that does not need to be closed + hProcess = GetCurrentProcess(); + + // Iterate through modules in this process looking for TCL / Tk names + if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { + for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { + if (!found_tcl) { + found_tcl = get_tcl(hMods[i]); + if (found_tcl == -1) { + return 1; + } + } + if (!found_tk) { + found_tk = get_tk(hMods[i]); + if (found_tk == -1) { + return 1; + } + } + if (found_tcl && found_tk) { + return 0; + } + } + } + + if (found_tcl == 0) { + PyErr_SetString(PyExc_RuntimeError, "Could not find TCL routines"); + } else { + PyErr_SetString(PyExc_RuntimeError, "Could not find Tk routines"); + } + return 1; +} + +#else // not Windows + +/* + * On Unix, we can get the TCL and Tk synbols from the tkinter module, because + * tkinter uses these symbols, and the symbols are therefore visible in the + * tkinter dynamic library (module). + */ +#if PY3K +#define TKINTER_PKG "tkinter" +#define TKINTER_MOD "_tkinter" +// From module __file__ attribute to char *string for dlopen. +char *fname2char(PyObject *fname) +{ + PyObject *bytes = PyUnicode_EncodeFSDefault(fname); + if (bytes == NULL) { + return NULL; + } + return PyBytes_AsString(bytes); +} +#else +#define TKINTER_PKG "Tkinter" +#define TKINTER_MOD "tkinter" +// From module __file__ attribute to char *string for dlopen +#define fname2char(s) (PyString_AsString(s)) +#endif + +#include + +void *_dfunc(void *lib_handle, const char *func_name) +{ + // Load function `func_name` from `lib_handle`. + // Set Python exception if we can't find `func_name` in `lib_handle`. + // Returns function pointer or NULL if not present. + + // Reset errors. + dlerror(); + void *func = dlsym(lib_handle, func_name); + if (func == NULL) { + const char *error = dlerror(); + PyErr_SetString(PyExc_RuntimeError, error); + } + return func; +} + +int _func_loader(void *lib) +{ + // Fill global function pointers from dynamic lib. + // Return 1 if any pointer is NULL, 0 otherwise. + return ( + ((TCL_CREATE_COMMAND = (Tcl_CreateCommand_t) + _dfunc(lib, "Tcl_CreateCommand")) == NULL) || + ((TCL_APPEND_RESULT = (Tcl_AppendResult_t) + _dfunc(lib, "Tcl_AppendResult")) == NULL) || + ((TK_MAIN_WINDOW = (Tk_MainWindow_t) + _dfunc(lib, "Tk_MainWindow")) == NULL) || + ((TK_FIND_PHOTO = (Tk_FindPhoto_t) + _dfunc(lib, "Tk_FindPhoto")) == NULL) || + ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t) + _dfunc(lib, "Tk_PhotoPutBlock_NoComposite")) == NULL) || + ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t) + _dfunc(lib, "Tk_PhotoBlank")) == NULL)); +} + +int load_tkinter_funcs(void) +{ + // Load tkinter global funcs from tkinter compiled module. + // Return 0 for success, non-zero for failure. + int ret = -1; + void *tkinter_lib; + char *tkinter_libname; + PyObject *pModule = NULL, *pSubmodule = NULL, *pString = NULL; + + pModule = PyImport_ImportModule(TKINTER_PKG); + if (pModule == NULL) { + goto exit; + } + pSubmodule = PyObject_GetAttrString(pModule, TKINTER_MOD); + if (pSubmodule == NULL) { + goto exit; + } + pString = PyObject_GetAttrString(pSubmodule, "__file__"); + if (pString == NULL) { + goto exit; + } + tkinter_libname = fname2char(pString); + if (tkinter_libname == NULL) { + goto exit; + } + tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); + if (tkinter_lib == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "Cannot dlopen tkinter module file"); + goto exit; + } + ret = _func_loader(tkinter_lib); + // dlclose probably safe because tkinter has been imported. + dlclose(tkinter_lib); +exit: + Py_XDECREF(pModule); + Py_XDECREF(pSubmodule); + Py_XDECREF(pString); + return ret; +} +#endif // end not Windows + #if PY3K static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions, NULL, NULL, NULL, NULL }; @@ -244,7 +455,7 @@ PyMODINIT_FUNC PyInit__tkagg(void) import_array(); - return m; + return (load_tkinter_funcs() == 0) ? m : NULL; } #else PyMODINIT_FUNC init_tkagg(void) @@ -252,5 +463,7 @@ PyMODINIT_FUNC init_tkagg(void) import_array(); Py_InitModule("_tkagg", functions); + + load_tkinter_funcs(); } #endif diff --git a/src/_tkmini.h b/src/_tkmini.h new file mode 100644 index 000000000000..9b730b6c8c1f --- /dev/null +++ b/src/_tkmini.h @@ -0,0 +1,128 @@ +/* Small excerpts from the Tcl / Tk 8.6 headers + * + * License terms copied from: + * http://www.tcl.tk/software/tcltk/license.html + * as of 20 May 2016. + * + * Copyright (c) 1987-1994 The Regents of the University of California. + * Copyright (c) 1993-1996 Lucent Technologies. + * Copyright (c) 1994-1998 Sun Microsystems, Inc. + * Copyright (c) 1998-2000 by Scriptics Corporation. + * Copyright (c) 2002 by Kevin B. Kenny. All rights reserved. + * + * This software is copyrighted by the Regents of the University + * of California, Sun Microsystems, Inc., Scriptics Corporation, + * and other parties. The following terms apply to all files + * associated with the software unless explicitly disclaimed in + * individual files. + * + * The authors hereby grant permission to use, copy, modify, + * distribute, and license this software and its documentation + * for any purpose, provided that existing copyright notices are + * retained in all copies and that this notice is included + * verbatim in any distributions. No written agreement, license, + * or royalty fee is required for any of the authorized uses. + * Modifications to this software may be copyrighted by their + * authors and need not follow the licensing terms described + * here, provided that the new terms are clearly indicated on + * the first page of each file where they apply. + * + * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO + * ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS + * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN + * IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON + * AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO + * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + * + * GOVERNMENT USE: If you are acquiring this software on behalf + * of the U.S. government, the Government shall have only + * "Restricted Rights" in the software and related documentation + * as defined in the Federal Acquisition Regulations (FARs) in + * Clause 52.227.19 (c) (2). If you are acquiring the software + * on behalf of the Department of Defense, the software shall be + * classified as "Commercial Computer Software" and the + * Government shall have only "Restricted Rights" as defined in + * Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the + * foregoing, the authors grant the U.S. Government and others + * acting in its behalf permission to use and distribute the + * software in accordance with the terms specified in this + * license + */ + +/* + * Unless otherwise noted, these definitions are stable from Tcl / Tk 8.5 + * through Tck / Tk master as of 21 May 2016 + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Tcl header excerpts */ +#define TCL_OK 0 +#define TCL_ERROR 1 + +/* + * Users of versions of Tcl >= 8.6 encouraged to tread Tcl_Interp as an opaque + * pointer. The following definition results when TCL_NO_DEPRECATED defined. + */ +typedef struct Tcl_Interp Tcl_Interp; + +typedef struct Tcl_Command_ *Tcl_Command; +typedef void *ClientData; + +typedef int (Tcl_CmdProc) (ClientData clientData, Tcl_Interp + *interp, int argc, const char *argv[]); +typedef void (Tcl_CmdDeleteProc) (ClientData clientData); + +/* Typedefs derived from function signatures in Tcl header */ +/* Tcl_CreateCommand */ +typedef Tcl_Command (*Tcl_CreateCommand_t)(Tcl_Interp *interp, + const char *cmdName, Tcl_CmdProc *proc, + ClientData clientData, + Tcl_CmdDeleteProc *deleteProc); +/* Tcl_AppendResult */ +typedef void (*Tcl_AppendResult_t) (Tcl_Interp *interp, ...); + +/* Tk header excerpts */ +typedef struct Tk_Window_ *Tk_Window; + +typedef void *Tk_PhotoHandle; + +typedef struct Tk_PhotoImageBlock +{ + unsigned char *pixelPtr; + int width; + int height; + int pitch; + int pixelSize; + int offset[4]; +} Tk_PhotoImageBlock; + +/* Typedefs derived from function signatures in Tk header */ +/* Tk_MainWindow */ +typedef Tk_Window (*Tk_MainWindow_t) (Tcl_Interp *interp); +typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char + *imageName); +/* Tk_PhotoPutBLock_NoComposite typedef */ +typedef void (*Tk_PhotoPutBlock_NoComposite_t) (Tk_PhotoHandle handle, + Tk_PhotoImageBlock *blockPtr, int x, int y, + int width, int height); +/* Tk_PhotoBlank */ +typedef void (*Tk_PhotoBlank_t) (Tk_PhotoHandle handle); + +/* + * end block for C++ + */ + +#ifdef __cplusplus +} +#endif pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy