diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index c460da374c8c..3f1de0ddcc2d 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -3,6 +3,7 @@ import pytest import matplotlib as mpl +from matplotlib import _c_internal_utils import matplotlib.pyplot as plt try: from matplotlib.backends import _macosx @@ -10,6 +11,12 @@ pytest.skip("These are mac only tests", allow_module_level=True) +pytestmark = [ + pytest.mark.skipif(not _c_internal_utils.display_is_valid(), + reason="Display is unavailable") +] + + @pytest.mark.backend('macosx') def test_cached_renderer(): # Make sure that figures have an associated renderer after diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index f4a7ef6755f2..c04ee926355f 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -305,9 +305,8 @@ def _get_testable_qt_backends(): ]: reason = None missing = [dep for dep in deps if not importlib.util.find_spec(dep)] - if (sys.platform == "linux" and - not _c_internal_utils.display_is_valid()): - reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" + if not _c_internal_utils.display_is_valid(): + reason = "Display is unavailable" elif missing: reason = "{} cannot be imported".format(", ".join(missing)) elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'): diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index ee20a94042f7..705666e30343 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -35,8 +35,8 @@ def _isolated_tk_test(success_count, func=None): reason="missing tkinter" ) @pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.display_is_valid(), - reason="$DISPLAY and $WAYLAND_DISPLAY are unset" + not _c_internal_utils.display_is_valid(), + reason="Display is unavailable" ) @pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649 ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 2464552d4b98..d8cd14cd223f 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -55,8 +55,7 @@ def wait_for(self, terminator): @functools.lru_cache def _get_available_interactive_backends(): - _is_linux_and_display_invalid = (sys.platform == "linux" and - not _c_internal_utils.display_is_valid()) + _is_display_invalid = not _c_internal_utils.display_is_valid() envs = [] for deps, env in [ *[([qt_api], @@ -74,8 +73,8 @@ def _get_available_interactive_backends(): ]: reason = None missing = [dep for dep in deps if not importlib.util.find_spec(dep)] - if _is_linux_and_display_invalid: - reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" + if _is_display_invalid: + reason = "Display is unavailable" elif missing: reason = "{} cannot be imported".format(", ".join(missing)) elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'): diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 782c390c9462..3175bf376abc 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -534,9 +534,7 @@ def test_backend_fallback_headless(tmp_path): env=env, check=True, stderr=subprocess.DEVNULL) -@pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.display_is_valid(), - reason="headless") +@pytest.mark.skipif(not _c_internal_utils.display_is_valid(), reason="headless") def test_backend_fallback_headful(tmp_path): pytest.importorskip("tkinter") env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmp_path)} diff --git a/meson.build b/meson.build index 41f080b3700e..1afc135bfb34 100644 --- a/meson.build +++ b/meson.build @@ -17,6 +17,10 @@ project( cc = meson.get_compiler('c') cpp = meson.get_compiler('cpp') +# Objective C is needed for the _c_internal_utils and macosx extension. +if host_machine.system() == 'darwin' + add_languages('objc', native: false) +endif # https://mesonbuild.com/Python-module.html py_mod = import('python') diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index 813aeb6f7d5a..f7e74f80af6e 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -18,6 +18,12 @@ #else #define UNUSED_ON_NON_WINDOWS Py_UNUSED #endif +#ifdef __APPLE__ +// Defined in _objc_internal_utils.m. +extern "C" { +int _macos_display_is_valid(void); +} +#endif namespace py = pybind11; using namespace pybind11::literals; @@ -69,6 +75,8 @@ mpl_display_is_valid(void) } } return false; +#elif defined(__APPLE__) + return _macos_display_is_valid() == 1; #else return true; #endif @@ -180,6 +188,8 @@ PYBIND11_MODULE(_c_internal_utils, m) succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL) succeeds. + On macOS, returns True if NSScreen::mainScreen is not nil. + On other platforms, always returns True.)"""); m.def( "Win32_GetCurrentProcessExplicitAppUserModelID", diff --git a/src/_objc_internal_utils.m b/src/_objc_internal_utils.m new file mode 100644 index 000000000000..a6271e63b69c --- /dev/null +++ b/src/_objc_internal_utils.m @@ -0,0 +1,13 @@ +#include + +int +_macos_display_is_valid(void) +{ + NSApplicationLoad(); + NSScreen *main = [NSScreen mainScreen]; + if (main != nil) { + return 1; + } else { + return 0; + } +} diff --git a/src/meson.build b/src/meson.build index db064a9c5ca1..b36601736ed9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -69,6 +69,12 @@ else user32 = [] endif +if host_machine.system() == 'darwin' + cocoa = dependency('appleframeworks', modules: 'Cocoa') +else + cocoa = [] +endif + extension_data = { '_backend_agg': { 'subdir': 'matplotlib/backends', @@ -81,10 +87,11 @@ extension_data = { }, '_c_internal_utils': { 'subdir': 'matplotlib', - 'sources': files( - '_c_internal_utils.cpp', - ), - 'dependencies': [pybind11_dep, dl, ole32, shell32, user32], + 'sources': [ + files('_c_internal_utils.cpp'), + (host_machine.system() == 'darwin') ? files('_objc_internal_utils.m') : [], + ], + 'dependencies': [pybind11_dep, dl, ole32, shell32, user32, cocoa], }, 'ft2font': { 'subdir': 'matplotlib', @@ -182,14 +189,13 @@ foreach ext, kwargs : extension_data endforeach if get_option('macosx') and host_machine.system() == 'darwin' - add_languages('objc', native: false) py3.extension_module( '_macosx', subdir: 'matplotlib/backends', sources: files( '_macosx.m', ), - dependencies: dependency('appleframeworks', modules: 'Cocoa'), + dependencies: [cocoa], override_options: ['werror=true'], install: true, )