-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
[MNT]: Pause should not raise window by default #22939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
An rcparam for controlling this behavior was added in mpl 3.3 to address this: https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.3.0.html#rcparams-for-controlling-default-raise-window-behavior Please remember that Matplotlib's user base is very wide and for ever user who definitely does not want the figure to come to the front, we have a user who definitely does! It is my understanding from all of those discussions is that any given user always wants one behavior or the other. This is a case where I think a global setting is the best option.
Fair, but in practice these things are very tightly coupled. If you want the UI to update and be "live", then you need to spin the event loop, if you want to spin the event loop you either have to run it for a fixed amount of time (which is what The connection to |
also xref #8246 |
@tacaswell Thank you for your reply.
Is there any evidence that the latter are as numerous as the former?
Still, its default value should match the most commonly desired behavior.
I don't recall using any other application that forces its window to the front every time something gets redrawn.
I don't think it's a red-herring. To quote #596 (comment):
|
I have a practical question on this. I did something like import numpy as np
import matplotlib.pyplot as plt
print(plt.rcParams["backend"])
rng = np.random.default_rng()
for _ in range(100):
val = rng.uniform()
plt.plot([0, val])
plt.pause(0.1) If I decide I don't want to watch to the end, how do I kill it? If I close the plot window, another one pops up with the next pass through the loop. I can't get back to the terminal because the plot window keeps stealing focus. I'm on RHEL7 using QtAgg backend. The behaviour seems to be the same whether I do this in interactive mode or run a script. |
I suppose there is no way to stop this due to the particular setup and interaction of a python loop and the gui event loop. Personally, I consider If you wanted to do something about it, you'll likely need pause to return a flag whether the user wants to exit so that you can do something
but it's non trivial how to determine the signal inside pause(): There may be 0 to arbitrary many open figures. Do you stop if one gets closed or if all get closed? Maybe you can catch a global key press! But that needs special implementation in the backend. |
If you make it slower: import numpy as np
import matplotlib.pyplot as plt
print(plt.rcParams["backend"])
rng = np.random.default_rng()
for _ in range(1000):
val = rng.uniform()
plt.plot([0, val])
try:
plt.pause(2)
except KeyboardInterrupt:
break you can capture a |
import threading
import numpy as np
import matplotlib.pyplot as plt
print(plt.rcParams["backend"])
rng = np.random.default_rng()
fig, ax = plt.subplots()
ev = threading.Event() # any sort of shared state will work
fig.canvas.mpl_connect('close_event', lambda *args: ev.set())
for _ in range(1000):
val = rng.uniform()
ax.plot([0, val])
plt.pause(.1)
if ev.is_set():
break |
Putting this together with https://matplotlib.org/stable/users/explain/interactive_guide.html#blocking-the-prompt import threading
import numpy as np
import matplotlib.pyplot as plt
print(plt.rcParams["backend"])
rng = np.random.default_rng()
fig, ax = plt.subplots()
ev = threading.Event()
fig.canvas.mpl_connect('close_event', lambda *args: ev.set())
plt.show(block=False)
for _ in range(1000):
val = rng.uniform()
ax.plot([0, val])
fig.canvas.draw_idle()
fig.canvas.start_event_loop(.1)
if ev.is_set():
break |
Wow, thanks for the detailed replies! These solutions do require the user to anticipate the problem ahead of time though (as does simply setting |
We go through a whole bunch of effort with the Qt backends to correctly handle signals: matplotlib/lib/matplotlib/backends/qt_compat.py Lines 200 to 268 in 0aac9f1
This does turn sigints into quitting the event loop and raising tk seems to not steal focus the same way qt does, but sigint does not quit the loop. However sigint + closing the window does escape wx also steals focus, but an exnternal sigint gets a gtk also steals focus, but an external sigint gets a Maybe the solution here is to by default hook up a key-binding that translates ctrl-C into sending sigint to our selves? That would atleast provide an out from the denial of service attack a hot-loop-around-pause (short of sshing in from another machine or a reboot). diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py
index 7100f2ed34..2fbd1888a1 100644
--- a/lib/matplotlib/backend_bases.py
+++ b/lib/matplotlib/backend_bases.py
@@ -2596,6 +2596,10 @@ def key_press_handler(event, canvas=None, toolbar=None):
toggle_yscale_keys = rcParams['keymap.yscale']
toggle_xscale_keys = rcParams['keymap.xscale']
+ if event.key == 'ctrl+c':
+ import signal
+ signal.raise_signal(signal.SIGINT)
+
# toggle fullscreen mode ('f', 'ctrl + f')
if event.key in fullscreen_keys:
try:
is enough, however
|
But none of these are Ctrl+c to the window; they are SIGINT to the process which is Ctrl+c only in the terminal. Ctrl+c in any of the windows would do nothing. |
Ctrl+C -> kill should only be wired up in the terminal. In GUIs this is a common shortcut and should not kill the figure. |
How to fix this high priority bug? UPD: def plt_pause(interval):
from matplotlib import _pylab_helpers
manager = _pylab_helpers.Gcf.get_active()
if manager is not None:
canvas = manager.canvas
if canvas.figure.stale:
canvas.draw_idle()
# show(block=False) # <- What causes the plot to steal focus
canvas.start_event_loop(interval)
else:
import time
time.sleep(interval)
plt.pause = plt_pause |
Why you discussing some Ctrl+C here? |
Summary
As pointed out in #596 (comment) and #596 (comment),
pause
should not raise the window by default. There are many users who have trouble with that behavior:Proposed fix
One way to remedy this would be to move away from the global rcParam approach and do something like the following:
raise_window
function.raise_window
argument forplot
(True
by default).raise_window
argument forpause
(False
by default).In general, it seems like a good idea to make semantically-distinct actions (redrawing, window raising, pausing for a specified time interval, etc.) orthogonal rather than mix them together and then have to untangle them via global parameters. This might also simplify the code for the various backends. Thoughts?
The text was updated successfully, but these errors were encountered: