Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- Keybinds: `SUPER + T/E/B/C` (terminal, file manager, browser, editor) now launch the app configured in `config.toml` instead of always falling back to the `variables.lua` default (#2087). `key_binds.lua` loads before `dynamic.lua` merges the user's `~/.config/hyde/config.toml`, so binding `hl.dsp.exec_cmd(hyde.config.app.terminal)` read and froze the pre-merge default at bind time. The four binds now use `hyde.config.exec("app.terminal")` (and the matching paths for the others), which resolves the config value at keypress time instead.
- Waybar: the sensors module recovers from an empty or invalid saved page instead of crashing on every refresh.
- Waybar: the bar's systemd unit now survives a prior crash loop (#2160). `run_waybar()` and `--watch` launch it via `systemd-run --unit=hyde-<session>-bar.service`, but without `--collect` a unit that lands in `failed` (e.g. `start-limit-hit`) keeps that name until it is manually `reset-failed` or garbage-collected later. The next `systemd-run` with the same name was then rejected ("Unit already exists"), so `hyde-shell reload` and `waybar --update` redeployed the CSS but the bar stayed down. `--collect` makes systemd free the name as soon as the unit fails, so the next launch recreates it cleanly
- Waybar: changing the layout, theme or wallpaper no longer crashes, freezes or hides the bar (#2184). `restart_waybar()` hot-reloaded Waybar with `systemctl --user kill -s SIGUSR2`, which signals every process in the bar's unit, not only Waybar, so module children (image loaders, scripts, even an AUR helper started from the updates module) were killed. Waybar's own reload also crashed now and then and leaked memory on every reload, and a bar started a moment earlier by an overlapping switch was killed by the signal (`status=12/USR2`). One or two fast layout switches were enough to trigger it, and a few crashes in a row hit systemd's start limit, leaving no bar. With systemd the unit is now restarted (after `reset-failed`, so fast switching can't hit the start limit), falling back to a fresh launch if the unit is gone. Without systemd the SIGUSR2 reload is unchanged. `--set`, `--next`, `--prev`, `--select-layout` and a theme's layout preset also refreshed and reloaded the bar twice per switch: the second round of file writes landed while Waybar was still reading the first, so it read an empty `config.jsonc`/`includes.json` (`Error parsing JSON: Line 1, Column 1`) and exited. They now refresh and restart it once. Waybar's `config.jsonc`, `style.css`, `includes.json` and generated CSS files are now written to a temp file and renamed into place, so Waybar never reads a half-written file
- Submodules: `.gitmodules` tracked `language-packs` on a `master` branch that `HyDE-Project/language-packs` does not have (its only branch is `main`), so `git submodule update --remote language-packs` found nothing to follow. It now tracks `main`
- SDDM: the bundled Candy and Corners greeter themes show date and time in the system locale. Both hardcoded an English 12h format in `theme.conf` (`hh:mm A`), so a 24h locale still read `05:05 PM` on the login screen. The formats are blank now (fixed in HyDE-Project/sddm-themes#4, the archives in `Source/arcs/` are refreshed to match), and Corners' `DateTimePanel.qml` falls back to Qt's locale-aware short time and long date, since Qt renders a blank format as an empty string. `install_pst.sh` extracts the selected theme when SDDM is installed and `/etc/sddm.conf.d/backup_the_hyde_project.conf` does not exist yet, or when `HYDE_INSTALL_SDDM=true` is set; an already configured system keeps its old theme config until then
- Notifications: swaync loads `~/.config/swaync/user-style.css` again. The `@import` for it in the shipped `style.css` was missing its closing `;`, so GTK dropped the rule as an unterminated block and custom swaync styles were silently ignored
Expand Down
138 changes: 104 additions & 34 deletions Configs/.local/lib/hyde/waybar.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def get_current_layout_from_config():
set_state_value("WAYBAR_LAYOUT_PATH", layout)
set_state_value("WAYBAR_LAYOUT_NAME", layout_name)

shutil.copyfile(layout, CONFIG_JSONC)
_atomic_copy(layout, CONFIG_JSONC)
logger.debug(f"Created config.jsonc with first layout: {layout}")
return layout

Expand Down Expand Up @@ -340,7 +340,7 @@ def get_current_layout_from_config():
set_state_value("WAYBAR_LAYOUT_PATH", layout)
set_state_value("WAYBAR_LAYOUT_NAME", layout_name)

shutil.copyfile(layout, CONFIG_JSONC)
_atomic_copy(layout, CONFIG_JSONC)
logger.debug(f"Updated config.jsonc with layout: {layout}")

return layout
Expand Down Expand Up @@ -427,22 +427,30 @@ def resolve_style_path(layout_path):
return os.path.join(STYLE_DIRS[0], "defaults.css")


# Set once _apply_layout() has regenerated the files and restarted Waybar, so
# main() doesn't do both again for the same run.
_layout_applied = False


def _apply_layout(layout_path, style_path, notify_label):
"""Shared: copy layout, update state, regenerate CSS/includes, notify, restart."""
global _layout_applied
layout_name = os.path.basename(layout_path).replace(".jsonc", "")
set_state_value("WAYBAR_LAYOUT_PATH", layout_path)
set_state_value("WAYBAR_LAYOUT_NAME", layout_name)
set_state_value("WAYBAR_STYLE_PATH", style_path)

style_filepath = os.path.join(str(xdg_config_home()), "waybar", "style.css")
shutil.copyfile(layout_path, CONFIG_JSONC)
_atomic_copy(layout_path, CONFIG_JSONC)
ensure_user_style()
write_style_file(style_filepath, style_path)
update_icon_size()
update_border_radius()
generate_includes()
update_global_css()
notify.send("Waybar", f"Layout changed to {notify_label}", replace_id=9)
restart_waybar()
_layout_applied = True


def set_layout(layout):
Expand Down Expand Up @@ -592,6 +600,43 @@ def modify_json_key(data, key, value):
return data


def _atomic_write(path, data):
"""Replace path with data in one step: write a temp file next to it, then rename.

Waybar reads its config and CSS files while HyDE rewrites them; a plain
open(..., "w") lets it see an empty or half-written file (HyDE-Project/HyDE#2184).
"""
# Write through a symlinked config instead of replacing the link.
path = os.path.realpath(path)
fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(path), prefix=f".{os.path.basename(path)}.")
try:
if isinstance(data, bytes):
with os.fdopen(fd, "wb") as file:
file.write(data)
else:
with os.fdopen(fd, "w", encoding="utf-8") as file:
file.write(data)
# mkstemp creates the file as 0600; keep the mode the old file had, or
# give a new file the mode open(..., "w") would (0666 minus the umask).
if os.path.exists(path):
shutil.copymode(path, tmp_path)
else:
umask = os.umask(0)
os.umask(umask)
os.chmod(tmp_path, 0o666 & ~umask)
os.replace(tmp_path, path)
except BaseException:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
raise


def _atomic_copy(src, dst):
"""shutil.copyfile() through _atomic_write()."""
with open(src, "rb") as file:
_atomic_write(dst, file.read())


def write_style_file(style_filepath, source_filepath):
"""Override the style file with the given source style."""
wallbash_gtk_css_file = os.path.join(str(xdg_cache_home()), "hyde", "wallbash", "gtk.css")
Expand Down Expand Up @@ -626,8 +671,7 @@ def write_style_file(style_filepath, source_filepath):
/* Users who want to override the current style add/edit 'user-style.css' */
@import "user-style.css";
"""
with open(style_filepath, "w") as file:
file.write(style_css)
_atomic_write(style_filepath, style_css)
logger.debug(f"Successfully wrote style to '{style_filepath}'")


Expand Down Expand Up @@ -712,7 +756,25 @@ def kill_waybar():


def restart_waybar():
"""Reload Waybar if running to prevent the hiding bug, otherwise start it."""
"""Restart Waybar to load the new config, or start it if it isn't running.

With systemd the unit is restarted instead of hot-reloaded with SIGUSR2
(HyDE-Project/HyDE#2184): systemctl kill signals every process in the unit's
cgroup, not only Waybar; Waybar's reload handler can crash and leaks memory
on each reload; and a Waybar started a moment earlier by a concurrent call
is killed by the signal before it installs its handler. systemd serializes
concurrent restarts. reset-failed clears the start limit (5 starts in 10 s),
which fast layout switching would otherwise hit, leaving no bar.
"""
if HAS_SYSTEMD:
logger.debug(f"Restarting {UNIT_NAME}...")
subprocess.run(["systemctl", "--user", "reset-failed", UNIT_NAME], capture_output=True)
result = subprocess.run(["systemctl", "--user", "restart", UNIT_NAME], capture_output=True)
# The unit is transient and --collect'ed, so it may not exist anymore.
if result.returncode != 0:
run_waybar()
return

if is_waybar_running_for_current_user():
logger.debug("Hot-reloading Waybar config via SIGUSR2...")
_signal_waybar("SIGUSR2")
Expand Down Expand Up @@ -961,8 +1023,7 @@ def update_icon_size():

includes_data.update(updated_entries)

with open(includes_file, "w") as file:
json.dump(includes_data, file, indent=4)
_atomic_write(includes_file, json.dumps(includes_data, indent=4))
logger.debug(
f"Successfully updated icon sizes and appended to '{includes_file}' with {len(updated_entries)} entries."
)
Expand Down Expand Up @@ -1000,8 +1061,7 @@ def update_global_css():
}}
"""

with open(global_css_path, "w") as file:
file.write(global_css_content)
_atomic_write(global_css_path, global_css_content)
logger.debug(f"Successfully generated global CSS at '{global_css_path}'")


Expand Down Expand Up @@ -1154,15 +1214,14 @@ def update_border_radius():
continue
if os.path.exists(template_path):
logger.debug(f"Found template at {template_path}, copying to {css_filepath}")
shutil.copyfile(template_path, css_filepath)
_atomic_copy(template_path, css_filepath)
break
else:
logger.error("Template for border-radius.css not found in INCLUDES_DIRS")
# defaults.css @imports this file and Waybar exits on a missing
# import, so leave an empty stylesheet (square corners) rather than
# nothing (HyDE-Project/HyDE#2160).
with open(css_filepath, "w", encoding="utf-8") as file:
file.write("/* border-radius template not found; HyDE writes this file */\n")
_atomic_write(css_filepath, "/* border-radius template not found; HyDE writes this file */\n")
return

border_radius = os.getenv("WAYBAR_BORDER_RADIUS")
Expand Down Expand Up @@ -1215,8 +1274,7 @@ def update_border_radius():
updated_content = re.sub(r"\d+pt", f"{border_radius}pt", content)
logger.debug("Applied border radius value to CSS content")

with open(css_filepath, "w", encoding="utf-8") as file:
file.write(updated_content)
_atomic_write(css_filepath, updated_content)
logger.debug(f"Successfully updated border radius in {css_filepath}")


Expand Down Expand Up @@ -1251,31 +1309,34 @@ def generate_includes():
position = position.strip().strip('"').strip("'") if position else "top"
includes_data["position"] = position

with open(includes_file, "w") as file:
json.dump(includes_data, file, indent=4)
_atomic_write(includes_file, json.dumps(includes_data, indent=4))
logger.debug(
f"Successfully updated '{includes_file}' with {len(includes)} entries and position '{position}'."
)


def update_config(config_path):
config_jsonc = os.path.join(str(xdg_config_home()), "waybar", "config.jsonc")
shutil.copyfile(config_path, config_jsonc)
_atomic_copy(config_path, config_jsonc)
logger.debug(f"Successfully copied config from '{config_path}' to '{config_jsonc}'")


def update_style(style_path):
style_filepath = os.path.join(str(xdg_config_home()), "waybar", "style.css")
def ensure_user_style():
"""Create the user-style.css that style.css imports, if it is missing."""
user_style_filepath = os.path.join(str(xdg_config_home()), "waybar", "user-style.css")
theme_style_filepath = os.path.join(str(xdg_config_home()), "waybar", "theme.css")

Path(user_style_filepath).parent.mkdir(parents=True, exist_ok=True)

if not os.path.exists(user_style_filepath):
with open(user_style_filepath, "w") as file:
file.write("/* User custom styles */\n")
_atomic_write(user_style_filepath, "/* User custom styles */\n")
logger.debug(f"Created '{user_style_filepath}'")


def update_style(style_path):
style_filepath = os.path.join(str(xdg_config_home()), "waybar", "style.css")
theme_style_filepath = os.path.join(str(xdg_config_home()), "waybar", "theme.css")

ensure_user_style()

if not os.path.exists(theme_style_filepath):
logger.error(
f"Missing '{theme_style_filepath}', Please run 'hyde-shell reload' to generate it."
Expand Down Expand Up @@ -1356,7 +1417,7 @@ def main():
if not CONFIG_JSONC.exists():
logger.debug("Config file missing, creating from layout path")
CONFIG_JSONC.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(layout_path, CONFIG_JSONC)
_atomic_copy(layout_path, CONFIG_JSONC)
logger.debug("Created config.jsonc from state file layout")
else:
config_hash = get_file_hash(CONFIG_JSONC)
Expand All @@ -1368,7 +1429,7 @@ def main():
backup_layout(layout_name)

try:
shutil.copyfile(layout_path, CONFIG_JSONC)
_atomic_copy(layout_path, CONFIG_JSONC)
logger.debug("Updated config.jsonc with layout from state file")
except Exception as e:
logger.error(f"Failed to update config.jsonc: {e}")
Expand All @@ -1392,7 +1453,7 @@ def main():
if get_file_hash(CONFIG_JSONC) != get_file_hash(found_layout):
backup_layout(layout_name)

shutil.copyfile(found_layout, CONFIG_JSONC)
_atomic_copy(found_layout, CONFIG_JSONC)
logger.debug("Updated config.jsonc with layout by name")
else:
logger.error(f"Could not find layout by name: {layout_name}")
Expand All @@ -1404,15 +1465,15 @@ def main():
set_state_value("WAYBAR_LAYOUT_PATH", first_layout)
set_state_value("WAYBAR_LAYOUT_NAME", first_layout_name)
CONFIG_JSONC.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(first_layout, CONFIG_JSONC)
_atomic_copy(first_layout, CONFIG_JSONC)
logger.debug(f"Used first available layout: {first_layout}")
else:
# No layout path in state file or layout path is empty
logger.debug("No valid layout path in state file, determining current layout")
current_layout = get_current_layout_from_config()
if current_layout:
CONFIG_JSONC.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(current_layout, CONFIG_JSONC)
_atomic_copy(current_layout, CONFIG_JSONC)
logger.debug(f"Created config.jsonc from determined layout: {current_layout}")
else:
logger.debug("State file not found, creating it")
Expand Down Expand Up @@ -1493,11 +1554,13 @@ def main():
# The color/theme hook uses --update; a theme may opt into a paired
# layout and stylesheet (Mac OS: HyDE #2089 / hyde-gallery #127).
apply_theme_layout()
update_icon_size()
update_border_radius()
generate_includes()
update_global_css()
logger.debug("Updating config and style...")
if not _layout_applied:
# A preset already refreshed the files and restarted Waybar.
update_icon_size()
update_border_radius()
generate_includes()
update_global_css()
logger.debug("Updating config and style...")
if args.update_global_css:
update_global_css()
if args.update_icon_size:
Expand Down Expand Up @@ -1529,6 +1592,13 @@ def main():

if args.watch:
watch_waybar()
elif _layout_applied and not args.style:
# --set/--next/--prev, --select-layout and a theme preset (--update)
# already did this in _apply_layout(), user-style.css included. Doing
# it again restarted Waybar twice per switch, and with the old SIGUSR2
# reload the second round of writes hit the files while Waybar was
# still reading them (#2184).
return
Comment thread
coderabbitai[bot] marked this conversation as resolved.
else:
update_icon_size()
update_border_radius()
Expand Down
Loading