From 685fd7b8250acfed07d325414798fc665ebcdeb7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 12 Jun 2026 01:33:38 -0400 Subject: [PATCH] macOS: Check for display availability when looking for backends --- lib/matplotlib/tests/test_backend_macosx.py | 7 +++++++ lib/matplotlib/tests/test_backend_qt.py | 5 ++--- lib/matplotlib/tests/test_backend_tk.py | 4 ++-- lib/matplotlib/tests/test_backends_interactive.py | 9 ++++++--- lib/matplotlib/tests/test_rcparams.py | 4 +--- src/_c_internal_utils.cpp | 13 +++++++++++++ src/meson.build | 6 +++--- 7 files changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index 0648e43cde94..9e0519fe663a 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -6,10 +6,17 @@ from unittest import mock import matplotlib as mpl +from matplotlib import _c_internal_utils import matplotlib.pyplot as plt from matplotlib.testing import subprocess_run_helper +pytestmark = [ + pytest.mark.skipif(not _c_internal_utils.display_is_valid(), + reason="Display is unavailable") +] + + _test_timeout = 60 diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index ae24effe505f..ce694e8059ff 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -310,9 +310,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 839d299f3d48..aac817f3264b 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -36,8 +36,8 @@ def _isolated_tk_test(success_count, func=None): reason="missing tkinter" ) @pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), - reason="$DISPLAY is unset" + not _c_internal_utils.display_is_valid(), + reason="Display is unavailable" ) @functools.wraps(func) def test_func(): diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 2e2713f15ee1..d146f9e55c3a 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -59,6 +59,8 @@ def _get_available_interactive_backends(): not _c_internal_utils.display_is_valid()) _is_linux_and_xdisplay_invalid = (sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid()) + _is_macos_and_display_invalid = (sys.platform == "darwin" and + not _c_internal_utils.display_is_valid()) envs = [] for deps, env in [ *[([qt_api], @@ -85,6 +87,8 @@ def _get_available_interactive_backends(): reason = "$DISPLAY is unset" elif _is_linux_and_display_invalid: reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" + elif _is_macos_and_display_invalid: + reason = "Display is unavailable" elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'): reason = "macosx backend fails on Azure" elif env["MPLBACKEND"].startswith('gtk'): @@ -451,9 +455,8 @@ def qt5_and_qt6_pairs(): yield from ([qt5, qt6], [qt6, qt5]) -@pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.display_is_valid(), - reason="$DISPLAY and $WAYLAND_DISPLAY are unset") +@pytest.mark.skipif(not _c_internal_utils.display_is_valid(), + reason='Display is unavailable') @pytest.mark.parametrize('host, mpl', [*qt5_and_qt6_pairs()]) def test_cross_Qt_imports(host, mpl): try: diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index fb1b2730e93e..73ace5c88069 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -573,9 +573,7 @@ def test_backend_fallback_headless_auto_backend(tmp_path): assert backend.strip().lower() == "agg" -@pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), - reason="headless") +@pytest.mark.skipif(not _c_internal_utils.display_is_valid(), reason="headless") def test_backend_fallback_headful(tmp_path): if parse_version(pytest.__version__) >= parse_version('8.2.0'): pytest_kwargs = dict(exc_type=ImportError) diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index ff9dc4fcc52b..b19b7419d079 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -31,6 +31,7 @@ #ifdef __APPLE__ #include #include +#include #endif namespace py = pybind11; @@ -94,6 +95,16 @@ mpl_display_is_valid(void) } } return false; +#elif defined(__APPLE__) + CFDictionaryRef session_info; + + session_info = CGSessionCopyCurrentDictionary(); + if (session_info == NULL) { + return false; + } + + CFRelease(session_info); + return true; #else return true; #endif @@ -258,6 +269,8 @@ PYBIND11_MODULE(_c_internal_utils, m, py::mod_gil_not_used()) succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL) succeeds. + On macOS, returns True if CGSessionCopyCurrentDictionary is not NULL. + On other platforms, always returns True.)"""); m.def( "xdisplay_is_valid", &mpl_xdisplay_is_valid, diff --git a/src/meson.build b/src/meson.build index 077b7f0353b6..d44036c479d0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -31,9 +31,9 @@ else endif if host_machine.system() == 'darwin' - coretext = dependency('appleframeworks', modules: 'CoreText') + appleframeworks = dependency('appleframeworks', modules: ['CoreGraphics', 'CoreText']) else - coretext = [] + appleframeworks = [] endif extension_data = { @@ -50,7 +50,7 @@ extension_data = { 'sources': files( '_c_internal_utils.cpp', ), - 'dependencies': [pybind11_dep, dl, ole32, shell32, user32, coretext], + 'dependencies': [pybind11_dep, dl, ole32, shell32, user32, appleframeworks], }, 'ft2font': { 'subdir': 'matplotlib',