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
Reloading config #2360
Reloading config #2360
Conversation
e8c7219
to
dd73f8f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Just had a popup not go away so converting to draft until it gets resolved. |
74d622b
to
0f61496
Compare
There was no bug, I was just being a bit dim and didn't notice the event loop warning printed to the log. Basically if the event loop isn't running yet then a timer cannot be set to hide the window, so in some instances you need to click it to hide it. Anyway I rebased and tidied a couple things, so I've un-drafted. |
Maybe worth a test or two? |
I added a test for |
I wasn't completely happy with the way I'd done this so I've re-written it and I think it's better. Two commits: one just adding By not having There is one unresolved issue. We need to check that |
This is ugly and untested but something like this? diff --git a/libqtile/utils.py b/libqtile/utils.py
index 2d6c7003..661583cd 100644
--- a/libqtile/utils.py
+++ b/libqtile/utils.py
@@ -23,6 +23,7 @@ import asyncio
import glob
import importlib
import os
+import time
import traceback
from collections import defaultdict
from collections.abc import Sequence
@@ -212,7 +213,11 @@ def send_notification(title, message, urgent=False, timeout=10000, id=None):
logger.warning("Eventloop has not started. Cannot send notification.")
return -1
else:
- loop.create_task(_notify(title, message, urgency, timeout, id))
+ task = loop.create_task(_notify(title, message, urgency, timeout, id))
+ while not task.done():
+ time.sleep(0.1)
+
+ return task.result()
return id
@@ -239,11 +244,14 @@ async def _notify(title, message, urgency, timeout, id):
if msg.message_type == MessageType.ERROR:
logger.warning("Unable to send notification. "
"Is a notification server running?")
+ id = -1
# a new bus connection is made each time a notification is sent so
# we disconnect when the notification is done
bus.disconnect()
+ return id
+ It's ugly as hell, though. |
e965909
to
d0c69d8
Compare
That is pretty ugly ;) With the old gi.require_version("Notify", "0.7")
from gi.repository import Notify
Notify.init("Qtile")
info = Notify.get_server_info() and |
You can still use dbus-next to get the server info, but it would be done asynchronously. Is that really a problem though? You can check the status/try to send a notification and attach a callback to send the popup if the server's not available. EDIT: I appreciate it's not as neat as just sending the popup if the return code is less than zero... |
Looks like I could use |
You've lost me. What can you do by using that function directly (which is still asynchronous) that you can't do by trying to send the notification and testing for success there? |
Not a big difference, just that |
OK - but you can only await from an async function so it still sounds like you need some extra steps. That said, you know what you're doing so I'll stop replying unless you actually need my help! |
ada3a90
to
cc38f19
Compare
I've resolved all of the groups problems, which ultimately were odd side-effects of the way groups were being persisted across reloads. I don't think it makes sense to persist groups across reloads the way they persist across restarts, as the implication of this PR is that cmd_reload_config is used for reloading the config and cmd_restart is used for updating the runtime libqtile following package/code update. Windows that are on groups that are removed are moved to the new current group so it is very clear for the user that they may need to place it to whatever group they want. If people agree with that approach (groups are refreshed completely from the config when you reload that config), then this PR is ready to go! |
As per our chat, here's a diff to solve the delay when waiting to see if there's an available notification server: diff --git a/libqtile/utils.py b/libqtile/utils.py
index 0c3203b4..34ff8020 100644
--- a/libqtile/utils.py
+++ b/libqtile/utils.py
@@ -264,6 +264,11 @@ def send_notification(title, message, urgent=False, timeout=10000, id=None, on_f
async def _notify(title, message, urgency, timeout, id) -> bool:
"""Returns True upon success, False upon failure."""
+ can_notify = await _has_notification_server()
+
+ if not can_notify:
+ return False
+
notification = ["qtile", # Application name
id, # id
"", # icon
@@ -292,6 +297,27 @@ async def _notify(title, message, urgency, timeout, id) -> bool:
return msg.message_type != MessageType.ERROR
+async def _has_notification_server():
+ """Checks for the existence of 'org.freedesktop.Notifications'."""
+ bus, msg = await _send_dbus_message(True,
+ MessageType.METHOD_CALL,
+ "org.freedesktop.DBus",
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "ListNames",
+ "",
+ [])
+
+ bus.disconnect()
+
+ if msg.message_type != MessageType.METHOD_RETURN:
+ return False
+
+ names = msg.body[0]
+
+ return "org.freedesktop.Notifications" in names
+
+
def guess_terminal(preference=None):
"""Try to guess terminal."""
test_terminals = [] |
Brilliant, thank you for that, I've folded it in. |
3cfc642
to
2f53abd
Compare
OK, I've stripped out the popup-related stuff, so if users want feedback about config errors then they'll need a notification server running. Seems like a reasonable expectation. |
Nice. I'll see if I can test at some point this weekend. Would be great to get this one merged at last. |
Only one comment from me: When I put a bug in my config and restart, the notification I get (via Notify) says "Configuration Error - None". This is because you're using |
Hmm. Still some bugs here. The log shows the |
A config reload with a config error would replace the currently running config which is perfectly usable. This doesn't yet change much, as during the initial load the default config is live, but when the reloading code is in place then it would unnecessary replace a good config with the default. This downside of this change is the lack of user notification that something has gone wrong. After lots of discussion on github and IRC we decided against using `Popup`s to notify the user as this may cause problems and new 'features' to make it less ugly. Instead we can use `send_notification`, so users using a dbus notification server will get notified. Others will not, and will need to check their configs/logs to identify the issue.
This makes the finalize methods of widgets cancel any scheduled tasks so that these tasks are not executed with finalised widgets, resulting in erroneous log output (at least). Fixes qtile#2509
This adds a cmd_ that when called will re-import the user's config file and reconfigure Qtile while running without restarting the process. This is much faster and visually cleaner than using cmd_restart to reload an updated config. It also allows for reloading a config using the Wayland backend which does not support process restarting. The logic of cmd_reload_config is as follows: - Dump state into a QtileState object so that we can keep a small amount of state constant across the reload - Finalize the configurable objects (i.e. everything except the core), these will be re-instantiated from the config - Clear various `Qtile` attributes that track configurables such as groups, widgets, chord stack, and also hooks. - Load the config. If this fails, continue with the existing config. - Restore the dumped state from the first step. - Ask the core to sort out how windows are assigned to groups with `core.distribute_windows`. This replaces the X11 core's `scan` method; the scan is done the first time this is called (with `initial=True`) during which the groups are taken from window hints, other the core matches the window's old group with the most appropriate group of the new groups.
I was finalising resources before reloading the config and only checking when reloading, so if it didn't reload (due to a bad config) then the resources were still finalised. Fixed this now by just validating the config before doing anything. Also rebased. |
ScratchPads are quite complicated so I ignored them for the main config-reloading commit, separating it out into this one. The idea here is that when Qtile is restarting, there is the expectation that windows will be scanned for and managed freshly, triggering a client_new and registered where appropriate to the scratchpad. For this, we keep the WIDs of living scratchpad windows across the restart in the QtileState. During config reloading however the window remains managed the whole time, so instead we keep track of the windows's WID and manually re-register it with the new scratchpad (as opposed to subscribing a client_new hook and waiting for the Qtile.manage() call).
This adds the `reload_config` command to the docs in some places, adds a mention to the changelog, and also replaces the `lazy.restart()` keybinding in the default config with `lazy.reload_config()` so that new users use that instead.
This adds a `supports_restarts` class attribute to the backend `Core`s that tells Qtile whether or not restarting is supported. Default True, the Wayland backend's is False. Calling Qtile.cmd_restart when using the Wayland backend will raise a CommandError.
When only reloading the config, the groups will be refreshed from the config.
Currently many resources are finalised before trying to reload, but if reloading fails then those resources are still finalized. Instead, the config should be validated before finalizing these resources so that they remain in place if the config is invalid.
Awesome work on this one. Thanks. |
EDIT: I appropriated this PR for general config-reloading changes. Previously it was limited to the popup commit but as these changes are a group (and would conflict when done separately) I've kept them together
Currently send_notification will try to send a notification but fail
quietly if there is no notification server running. To get the message
to the user's eyes, it can instead fall back to a transient Popup
window.
This also changes the config-reading code to use this mechanism to
inform the user of a config error, rather than creating a new Config
object just so we can stick a TextBox in it to notify them.