Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4c35d32

Browse files
authored
Merge pull request #17396 from anntzer/guillotine
Improve headlessness detection for backend selection.
2 parents 319f35b + 043c578 commit 4c35d32

File tree

5 files changed

+93
-27
lines changed

5 files changed

+93
-27
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2748,10 +2748,15 @@ def show(self):
27482748
warning in `.Figure.show`.
27492749
"""
27502750
# This should be overridden in GUI backends.
2751-
if cbook._get_running_interactive_framework() != "headless":
2752-
raise NonGuiException(
2753-
f"Matplotlib is currently using {get_backend()}, which is "
2754-
f"a non-GUI backend, so cannot show the figure.")
2751+
if sys.platform == "linux" and not os.environ.get("DISPLAY"):
2752+
# We cannot check _get_running_interactive_framework() ==
2753+
# "headless" because that would also suppress the warning when
2754+
# $DISPLAY exists but is invalid, which is more likely an error and
2755+
# thus warrants a warning.
2756+
return
2757+
raise NonGuiException(
2758+
f"Matplotlib is currently using {get_backend()}, which is a "
2759+
f"non-GUI backend, so cannot show the figure.")
27552760

27562761
def destroy(self):
27572762
pass

lib/matplotlib/backends/backend_qt5.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import sys
77
import traceback
88

9-
import matplotlib
10-
9+
import matplotlib as mpl
1110
from matplotlib import backend_tools, cbook
1211
from matplotlib._pylab_helpers import Gcf
1312
from matplotlib.backend_bases import (
@@ -113,11 +112,8 @@ def _create_qApp():
113112
is_x11_build = False
114113
else:
115114
is_x11_build = hasattr(QtGui, "QX11Info")
116-
if is_x11_build:
117-
display = os.environ.get('DISPLAY')
118-
if display is None or not re.search(r':\d', display):
119-
raise RuntimeError('Invalid DISPLAY variable')
120-
115+
if is_x11_build and not mpl._c_internal_utils.display_is_valid():
116+
raise RuntimeError('Invalid DISPLAY variable')
121117
try:
122118
QtWidgets.QApplication.setAttribute(
123119
QtCore.Qt.AA_EnableHighDpiScaling)
@@ -567,7 +563,7 @@ def __init__(self, canvas, num):
567563

568564
self.window.setCentralWidget(self.canvas)
569565

570-
if matplotlib.is_interactive():
566+
if mpl.is_interactive():
571567
self.window.show()
572568
self.canvas.draw_idle()
573569

@@ -601,9 +597,9 @@ def _widgetclosed(self):
601597
def _get_toolbar(self, canvas, parent):
602598
# must be inited after the window, drawingArea and figure
603599
# attrs are set
604-
if matplotlib.rcParams['toolbar'] == 'toolbar2':
600+
if mpl.rcParams['toolbar'] == 'toolbar2':
605601
toolbar = NavigationToolbar2QT(canvas, parent, True)
606-
elif matplotlib.rcParams['toolbar'] == 'toolmanager':
602+
elif mpl.rcParams['toolbar'] == 'toolmanager':
607603
toolbar = ToolbarQt(self.toolmanager, self.window)
608604
else:
609605
toolbar = None
@@ -619,7 +615,7 @@ def resize(self, width, height):
619615

620616
def show(self):
621617
self.window.show()
622-
if matplotlib.rcParams['figure.raise_window']:
618+
if mpl.rcParams['figure.raise_window']:
623619
self.window.activateWindow()
624620
self.window.raise_()
625621

@@ -792,8 +788,7 @@ def save_figure(self, *args):
792788
sorted_filetypes = sorted(filetypes.items())
793789
default_filetype = self.canvas.get_default_filetype()
794790

795-
startpath = os.path.expanduser(
796-
matplotlib.rcParams['savefig.directory'])
791+
startpath = os.path.expanduser(mpl.rcParams['savefig.directory'])
797792
start = os.path.join(startpath, self.canvas.get_default_filename())
798793
filters = []
799794
selectedFilter = None
@@ -811,8 +806,7 @@ def save_figure(self, *args):
811806
if fname:
812807
# Save dir for next time, unless empty str (i.e., use cwd).
813808
if startpath != "":
814-
matplotlib.rcParams['savefig.directory'] = (
815-
os.path.dirname(fname))
809+
mpl.rcParams['savefig.directory'] = os.path.dirname(fname)
816810
try:
817811
self.canvas.figure.savefig(fname)
818812
except Exception as e:

lib/matplotlib/cbook/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def _get_running_interactive_framework():
7373
if 'matplotlib.backends._macosx' in sys.modules:
7474
if sys.modules["matplotlib.backends._macosx"].event_loop_is_running():
7575
return "macosx"
76-
if sys.platform.startswith("linux") and not os.environ.get("DISPLAY"):
76+
if not _c_internal_utils.display_is_valid():
7777
return "headless"
7878
return None
7979

setupext.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,10 @@ def get_extensions(self):
349349
# c_internal_utils
350350
ext = Extension(
351351
"matplotlib._c_internal_utils", ["src/_c_internal_utils.c"],
352-
libraries=({"win32": ["ole32", "shell32", "user32"]}
353-
.get(sys.platform, [])))
352+
libraries=({
353+
"linux": ["dl"],
354+
"win32": ["ole32", "shell32", "user32"],
355+
}.get(sys.platform, [])))
354356
yield ext
355357
# contour
356358
ext = Extension(

src/_c_internal_utils.c

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,68 @@
11
#define PY_SSIZE_T_CLEAN
22
#include <Python.h>
3+
#ifdef __linux__
4+
#include <dlfcn.h>
5+
#endif
36
#ifdef _WIN32
47
#include <Objbase.h>
58
#include <Shobjidl.h>
69
#include <Windows.h>
710
#endif
811

9-
static PyObject* mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module)
12+
static PyObject*
13+
mpl_display_is_valid(PyObject* module)
14+
{
15+
#ifdef __linux__
16+
void* libX11;
17+
// The getenv check is redundant but helps performance as it is much faster
18+
// than dlopen().
19+
if (getenv("DISPLAY")
20+
&& (libX11 = dlopen("libX11.so.6", RTLD_LAZY))) {
21+
struct Display* display = NULL;
22+
struct Display* (* XOpenDisplay)(char const*) =
23+
dlsym(libX11, "XOpenDisplay");
24+
int (* XCloseDisplay)(struct Display*) =
25+
dlsym(libX11, "XCloseDisplay");
26+
if (XOpenDisplay && XCloseDisplay
27+
&& (display = XOpenDisplay(NULL))) {
28+
XCloseDisplay(display);
29+
}
30+
if (dlclose(libX11)) {
31+
PyErr_SetString(PyExc_RuntimeError, dlerror());
32+
return NULL;
33+
}
34+
if (display) {
35+
Py_RETURN_TRUE;
36+
}
37+
}
38+
void* libwayland_client;
39+
if (getenv("WAYLAND_DISPLAY")
40+
&& (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) {
41+
struct wl_display* display = NULL;
42+
struct wl_display* (* wl_display_connect)(char const*) =
43+
dlsym(libwayland_client, "wl_display_connect");
44+
void (* wl_display_disconnect)(struct wl_display*) =
45+
dlsym(libwayland_client, "wl_display_disconnect");
46+
if (wl_display_connect && wl_display_disconnect
47+
&& (display = wl_display_connect(NULL))) {
48+
wl_display_disconnect(display);
49+
}
50+
if (dlclose(libwayland_client)) {
51+
PyErr_SetString(PyExc_RuntimeError, dlerror());
52+
return NULL;
53+
}
54+
if (display) {
55+
Py_RETURN_TRUE;
56+
}
57+
}
58+
Py_RETURN_FALSE;
59+
#else
60+
Py_RETURN_TRUE;
61+
#endif
62+
}
63+
64+
static PyObject*
65+
mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module)
1066
{
1167
#ifdef _WIN32
1268
wchar_t* appid = NULL;
@@ -22,7 +78,8 @@ static PyObject* mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module)
2278
#endif
2379
}
2480

25-
static PyObject* mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg)
81+
static PyObject*
82+
mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg)
2683
{
2784
#ifdef _WIN32
2885
wchar_t* appid = PyUnicode_AsWideCharString(arg, NULL);
@@ -40,7 +97,8 @@ static PyObject* mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, P
4097
#endif
4198
}
4299

43-
static PyObject* mpl_GetForegroundWindow(PyObject* module)
100+
static PyObject*
101+
mpl_GetForegroundWindow(PyObject* module)
44102
{
45103
#ifdef _WIN32
46104
return PyLong_FromVoidPtr(GetForegroundWindow());
@@ -49,7 +107,8 @@ static PyObject* mpl_GetForegroundWindow(PyObject* module)
49107
#endif
50108
}
51109

52-
static PyObject* mpl_SetForegroundWindow(PyObject* module, PyObject *arg)
110+
static PyObject*
111+
mpl_SetForegroundWindow(PyObject* module, PyObject *arg)
53112
{
54113
#ifdef _WIN32
55114
HWND handle = PyLong_AsVoidPtr(arg);
@@ -66,6 +125,12 @@ static PyObject* mpl_SetForegroundWindow(PyObject* module, PyObject *arg)
66125
}
67126

68127
static PyMethodDef functions[] = {
128+
{"display_is_valid", (PyCFunction)mpl_display_is_valid, METH_NOARGS,
129+
"display_is_valid()\n--\n\n"
130+
"Check whether the current X11 or Wayland display is valid.\n\n"
131+
"On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL)\n"
132+
"succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL)\n"
133+
"succeeds. On other platforms, always returns True."},
69134
{"Win32_GetCurrentProcessExplicitAppUserModelID",
70135
(PyCFunction)mpl_GetCurrentProcessExplicitAppUserModelID, METH_NOARGS,
71136
"Win32_GetCurrentProcessExplicitAppUserModelID()\n--\n\n"
@@ -83,7 +148,7 @@ static PyMethodDef functions[] = {
83148
"always returns None."},
84149
{"Win32_SetForegroundWindow",
85150
(PyCFunction)mpl_SetForegroundWindow, METH_O,
86-
"Win32_SetForegroundWindow(hwnd)\n--\n\n"
151+
"Win32_SetForegroundWindow(hwnd, /)\n--\n\n"
87152
"Wrapper for Windows' SetForegroundWindow. On non-Windows platforms, \n"
88153
"a no-op."},
89154
{NULL, NULL}}; // sentinel.

0 commit comments

Comments
 (0)