diff --git a/lib/matplotlib/_c_internal_utils.pyi b/lib/matplotlib/_c_internal_utils.pyi index 3efc81bc8332..ccc172cde27a 100644 --- a/lib/matplotlib/_c_internal_utils.pyi +++ b/lib/matplotlib/_c_internal_utils.pyi @@ -1,4 +1,5 @@ def display_is_valid() -> bool: ... +def xdisplay_is_valid() -> bool: ... def Win32_GetForegroundWindow() -> int | None: ... def Win32_SetForegroundWindow(hwnd: int) -> None: ... diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index cff8f02fd349..7cf32c4d5f6a 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -72,7 +72,7 @@ def _get_running_interactive_framework(): if frame.f_code in codes: return "tk" frame = frame.f_back - # premetively break reference cycle between locals and the frame + # Preemptively break reference cycle between locals and the frame. del frame macosx = sys.modules.get("matplotlib.backends._macosx") if macosx and macosx.event_loop_is_running(): diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index ee20a94042f7..89782e8a66f3 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" + sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), + reason="$DISPLAY is unset" ) @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 2c6b61a48438..ca702bc1d99c 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -57,6 +57,8 @@ def wait_for(self, terminator): def _get_available_interactive_backends(): _is_linux_and_display_invalid = (sys.platform == "linux" and not _c_internal_utils.display_is_valid()) + _is_linux_and_xdisplay_invalid = (sys.platform == "linux" and + not _c_internal_utils.xdisplay_is_valid()) envs = [] for deps, env in [ *[([qt_api], @@ -74,10 +76,12 @@ 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" - elif missing: + if missing: reason = "{} cannot be imported".format(", ".join(missing)) + elif env["MPLBACKEND"] == "tkagg" and _is_linux_and_xdisplay_invalid: + reason = "$DISPLAY is unset" + elif _is_linux_and_display_invalid: + reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" elif env["MPLBACKEND"] == 'macosx' and os.environ.get('TF_BUILD'): reason = "macosx backend fails on Azure" elif env["MPLBACKEND"].startswith('gtk'): diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 4823df0ce250..25ae258ffcbb 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -536,7 +536,7 @@ def test_backend_fallback_headless(tmp_path): @pytest.mark.skipif( - sys.platform == "linux" and not _c_internal_utils.display_is_valid(), + sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), reason="headless") def test_backend_fallback_headful(tmp_path): pytest.importorskip("tkinter") diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index 74bb97904f89..561cb303639c 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -33,7 +33,7 @@ namespace py = pybind11; using namespace pybind11::literals; static bool -mpl_display_is_valid(void) +mpl_xdisplay_is_valid(void) { #ifdef __linux__ void* libX11; @@ -57,6 +57,19 @@ mpl_display_is_valid(void) return true; } } + return false; +#else + return true; +#endif +} + +static bool +mpl_display_is_valid(void) +{ +#ifdef __linux__ + if (mpl_xdisplay_is_valid()) { + return true; + } void* libwayland_client; if (getenv("WAYLAND_DISPLAY") && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) { @@ -194,6 +207,16 @@ PYBIND11_MODULE(_c_internal_utils, m) succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL) succeeds. + On other platforms, always returns True.)"""); + m.def( + "xdisplay_is_valid", &mpl_xdisplay_is_valid, + R"""( -- + Check whether the current X11 display is valid. + + On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL) + succeeds. Use this function if you need to specifically check for X11 + only (e.g., for Tkinter). + On other platforms, always returns True.)"""); m.def( "Win32_GetCurrentProcessExplicitAppUserModelID",