-
Notifications
You must be signed in to change notification settings - Fork 221
Open
Labels
Description
Wayfire version
0.11.0-44d058af (Jan 5 2026) branch master wlroots-0.19.2
GPU / Driver
deviceName = AMD Radeon RX 9060 XT (RADV GFX1200)
driverID = DRIVER_ID_MESA_RADV
driverName = radv
driverInfo = Mesa 26.0.0-devel (git-6d07a56c6a)
Describe the bug
Wayfire crash during config reload while reconfiguring outputs, triggered via IPC set_config_options.
To Reproduce
nvim wf-output-ctl-py
save the following:
#!/usr/bin/env python3
"""
Wayfire output toggle utility.
Supports toggling individual outputs, interactive selection,
disabling all outputs except the currently focused one,
and reloading outputs.
Output modes are detected via wlr-randr and persisted in
~/.config/wayfire_output_config.json for restoration.
"""
from wayfire import WayfireSocket
from subprocess import Popen, check_output
import json
import os
import sys
import re
import time
CONFIG_PATH = os.path.expanduser("~/.config/wayfire_output_config.json")
def load_config():
"""
Load stored output modes from disk.
"""
if not os.path.exists(CONFIG_PATH):
return {}
with open(CONFIG_PATH, "r") as f:
return json.load(f)
def save_config(data):
"""
Persist output mode configuration to disk.
"""
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
with open(CONFIG_PATH, "w") as f:
json.dump(data, f, indent=2)
def list_known_outputs(sock):
"""
Return all known outputs, including disabled ones.
"""
active = {o["name"] for o in sock.list_outputs()}
stored = set(load_config().keys())
return sorted(active | stored)
def ask_output(sock):
"""
Ask the user to select an output interactively.
"""
outputs = list_known_outputs(sock)
for i, name in enumerate(outputs):
print(f"{i}: {name}")
try:
idx = int(input("Select output: "))
except KeyboardInterrupt:
print("\nCancelled.")
sys.exit(0)
return outputs[idx]
def get_current_mode_from_wlr_randr(output_name):
"""
Detect the current mode of an output using wlr-randr.
Returns WIDTHxHEIGHT@HZ or None.
"""
lines = check_output(["wlr-randr"], text=True).splitlines()
inside = False
for line in lines:
if line.startswith(output_name + " "):
inside = True
continue
if inside and re.match(r"^[A-Z].*-\d+", line):
break
if inside and "(current)" in line:
m = re.search(r"(\d+x\d+)\s+px,\s+([\d.]+)\s+Hz", line)
if m:
res, hz = m.groups()
return f"{res}@{hz.replace('.', '')}"
return None
def turn_off_output(sock, output_name):
"""
Disable an output and store its current mode.
"""
config = load_config()
mode = get_current_mode_from_wlr_randr(output_name)
if mode:
config[output_name] = mode
save_config(config)
Popen(["wlopm", "--off", output_name])
sock.set_option_values({f"output:{output_name}": {"mode": "off"}})
print(f"Turned off {output_name}")
def turn_on_output(sock, output_name):
"""
Enable an output using the stored or detected mode.
"""
config = load_config()
mode = config.get(output_name)
if not mode:
mode = get_current_mode_from_wlr_randr(output_name)
if not mode:
print(f"No mode available for {output_name}, aborting.")
return
sock.set_option_values({f"output:{output_name}": {"mode": mode}})
print(f"Turned on {output_name} with mode {mode}")
def get_output_state(sock, output_name):
"""
Return whether an output is currently on or off.
"""
try:
mode = sock.get_option_value(f"output:{output_name}/mode")["value"]
except Exception:
return "off"
return "off" if mode == "off" or not mode else "on"
def toggle_output(sock, output_name):
"""
Toggle the state of an output.
"""
state = get_output_state(sock, output_name)
if state == "off":
turn_on_output(sock, output_name)
else:
turn_off_output(sock, output_name)
def disable_except_focused(sock):
"""
Disable all outputs except the currently focused one.
"""
focused = sock.get_focused_output()
focused_name = focused["name"]
outputs = list_known_outputs(sock)
for name in outputs:
if name != focused_name and get_output_state(sock, name) == "on":
turn_off_output(sock, name)
print(f"Focused output preserved: {focused_name}")
def reload_output(sock, output_name):
"""
Reload a single output by disabling and re-enabling it.
"""
was_on = get_output_state(sock, output_name) == "on"
if was_on:
turn_off_output(sock, output_name)
time.sleep(0.3)
turn_on_output(sock, output_name)
def reload_all_outputs(sock):
"""
Reload all known outputs.
"""
outputs = list_known_outputs(sock)
for name in outputs:
reload_output(sock, name)
if __name__ == "__main__":
sock = WayfireSocket()
if "--disable-except-focused" in sys.argv:
disable_except_focused(sock)
sys.exit(0)
if "--reload-all" in sys.argv:
reload_all_outputs(sock)
sys.exit(0)
if "--reload" in sys.argv:
idx = sys.argv.index("--reload")
try:
output = sys.argv[idx + 1]
except IndexError:
print("Missing output name for --reload")
sys.exit(1)
reload_output(sock, output)
sys.exit(0)
if len(sys.argv) > 1:
output = sys.argv[1]
else:
output = ask_output(sock)
toggle_output(sock, output)
nvim binding-test.py
save the following:
from wayfire import WayfireSocket
sock = WayfireSocket()
sock.register_binding(
"<super> KEY_U",
command="python wf-output-ctl.py --reload-all",
exec_always=True,
mode="normal",
)
Press the new keybind registered fast
Expected behavior
No crashes
Screenshots / Videos / Stacktrace
AddressSanitizer:DEADLYSIGNAL
=================================================================
==98458==ERROR: AddressSanitizer: SEGV on unknown address 0x000000002c52 (pc 0x7f22ccb784a6 bp 0x7ffce093a950 sp 0x7ffce093a0b8 T0)
==98458==The signal is caused by a READ memory access.
#0 0x7f22ccb784a6 in __sanitizer::internal_strlen(char const*) /usr/src/debug/gcc/gcc/libsanitizer/sanitizer_common/sanitizer_libc.cpp:176
#1 0x7f22ccb4ac1d in strdup /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:589
#2 0x7f22cc371e7b in wlr_xcursor_theme_load (/usr/lib/libwlroots-0.19.so+0xc9e7b) (BuildId: f756bc23f5d7e65dcba214a62afaab2914429b6a)
#3 0x7f22cc36956d in wlr_xcursor_manager_load (/usr/lib/libwlroots-0.19.so+0xc156d) (BuildId: f756bc23f5d7e65dcba214a62afaab2914429b6a)
#4 0x7f22cc3492db (/usr/lib/libwlroots-0.19.so+0xa12db) (BuildId: f756bc23f5d7e65dcba214a62afaab2914429b6a)
#5 0x7f22cd22e48d in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x848d) (BuildId: eaeed0645478c33a1a975d52ebd375751b26c19e)
#6 0x7f22cc35c238 in wlr_output_layout_add_auto (/usr/lib/libwlroots-0.19.so+0xb4238) (BuildId: f756bc23f5d7e65dcba214a62afaab2914429b6a)
#7 0x55fb6a4c827c in wf::output_layout_t::impl::ensure_noop_output() ../src/core/output-layout.cpp:1260
#8 0x55fb6a4d476c in wf::output_layout_t::impl::apply_configuration(std::map<wlr_output*, wf::output_state_t, std::less<wlr_output*>, std::allocator<std::pair<wlr_output* const, wf::output_state_t> > > const&) ../src/core/output-layout.cpp:1631
#9 0x55fb6a4cd432 in wf::output_layout_t::impl::reconfigure_from_config() ../src/core/output-layout.cpp:1386
#10 0x55fb6a4c13a7 in wf::output_layout_t::impl::on_config_reload::{lambda(wf::reload_config_signal*)#1}::operator()(wf::reload_config_signal*) const ../src/core/output-layout.cpp:1055
#11 0x55fb6a54e7c4 in void std::__invoke_impl<void, wf::output_layout_t::impl::on_config_reload::{lambda(wf::reload_config_signal*)#1}&, wf::reload_config_signal>(std::__invoke_other, wf::output_layout_t::impl::on_config_reload::{lambda(wf::reload_config_signal*)#1}&, wf::reload_config_signal&&) (/usr/bin/wayfire+0x20577c4) (BuildId: 4b216497133f4761bed65c14272bc3b4c30930f5)
#12 0x55fb6a53d162 in std::enable_if<is_invocable_r_v<void, wf::output_layout_t::impl::on_config_reload::{lambda(wf::reload_config_signal*)#1}&, wf::reload_config_signal*>, void>::type std::__invoke_r<void, wf::output_layout_t::impl::on_config_reload::{lambda(wf::reload_config_signal*)#1}&, wf::reload_config_signal*>(wf::output_layout_t::impl::on_config_reload::{lambda(wf::reload_config_signal*)#1}&, wf::reload_config_signal*&&) /usr/include/c++/15.2.1/bits/invoke.h:113
#13 0x55fb6a520fb2 in std::_Function_handler<void (wf::reload_config_signal*), wf::output_layout_t::impl::on_config_reload::{lambda(wf::reload_config_signal*)#1}>::_M_invoke(std::_Any_data const&, wf::reload_config_signal*&&) (/usr/bin/wayfire+0x2029fb2) (BuildId: 4b216497133f4761bed65c14272bc3b4c30930f5)
#14 0x7b22c62a1ecd in std::function<void (wf::reload_config_signal*)>::operator()(wf::reload_config_signal*) const /usr/include/c++/15.2.1/bits/std_function.h:593
#15 0x7b22c629df37 in wf::signal::connection_t<wf::reload_config_signal>::emit(wf::reload_config_signal*) ../src/api/wayfire/signal-provider.hpp:104
#16 0x7b22c62981a5 in wf::signal::provider_t::emit<wf::reload_config_signal>(wf::reload_config_signal*)::{lambda(wf::signal::connection_base_t*)#1}::operator()(wf::signal::connection_base_t*) const ../src/api/wayfire/signal-provider.hpp:151
#17 0x7b22c62a73ba in void std::__invoke_impl<void, wf::signal::provider_t::emit<wf::reload_config_signal>(wf::reload_config_signal*)::{lambda(wf::signal::connection_base_t*)#1}&, wf::signal::connection_base_t*>(std::__invoke_other, wf::signal::provider_t::emit<wf::reload_config_signal>(wf::reload_config_signal*)::{lambda(wf::signal::connection_base_t*)#1}&, wf::signal::connection_base_t*&&) /usr/include/c++/15.2.1/bits/invoke.h:63
#18 0x7b22c62a4e8a in std::enable_if<is_invocable_r_v<void, wf::signal::provider_t::emit<wf::reload_config_signal>(wf::reload_config_signal*)::{lambda(wf::signal::connection_base_t*)#1}&, wf::signal::connection_base_t*>, void>::type std::__invoke_r<void, wf::signal::provider_t::emit<wf::reload_config_signal>(wf::reload_config_signal*)::{lambda(wf::signal::connection_base_t*)#1}&, wf::signal::connection_base_t*>(wf::signal::provider_t::emit<wf::reload_config_signal>(wf::reload_config_signal*)::{lambda(wf::signal::connection_base_t*)#1}&, wf::signal::connection_base_t*&&) /usr/include/c++/15.2.1/bits/invoke.h:113
#19 0x7b22c62a205f in std::_Function_handler<void (wf::signal::connection_base_t*), wf::signal::provider_t::emit<wf::reload_config_signal>(wf::reload_config_signal*)::{lambda(wf::signal::connection_base_t*)#1}>::_M_invoke(std::_Any_data const&, wf::signal::connection_base_t*&&) /usr/include/c++/15.2.1/bits/std_function.h:292
#20 0x55fb6a5fed37 in std::function<void (wf::signal::connection_base_t*)>::operator()(wf::signal::connection_base_t*) const /usr/include/c++/15.2.1/bits/std_function.h:593
#21 0x55fb6a5f8498 in void std::__invoke_impl<void, std::function<void (wf::signal::connection_base_t*)>&, wf::signal::connection_base_t*&>(std::__invoke_other, std::function<void (wf::signal::connection_base_t*)>&, wf::signal::connection_base_t*&) /usr/include/c++/15.2.1/bits/invoke.h:63
#22 0x55fb6a5ef61d in std::enable_if<is_invocable_r_v<void, std::function<void (wf::signal::connection_base_t*)>&, wf::signal::connection_base_t*&>, void>::type std::__invoke_r<void, std::function<void (wf::signal::connection_base_t*)>&, wf::signal::connection_base_t*&>(std::function<void (wf::signal::connection_base_t*)>&, wf::signal::connection_base_t*&) /usr/include/c++/15.2.1/bits/invoke.h:113
#23 0x55fb6a5e7f14 in std::_Function_handler<void (wf::signal::connection_base_t*&), std::function<void (wf::signal::connection_base_t*)> >::_M_invoke(std::_Any_data const&, wf::signal::connection_base_t*&) /usr/include/c++/15.2.1/bits/std_function.h:292
#24 0x55fb6a5e589c in std::function<void (wf::signal::connection_base_t*&)>::operator()(wf::signal::connection_base_t*&) const /usr/include/c++/15.2.1/bits/std_function.h:593
#25 0x55fb6a5e0052 in wf::safe_list_t<wf::signal::connection_base_t*>::for_each(std::function<void (wf::signal::connection_base_t*&)>) /usr/include/wayfire/nonstd/safe-list.hpp:73
#26 0x55fb6a5d8ca1 in wf::signal::provider_t::for_each_connection(std::type_index, std::function<void (wf::signal::connection_base_t*)>) ../src/core/object.cpp:40
#27 0x7b22b97e0eec in wf::ipc_rules_utility_methods_t::set_config_options::{lambda(wf::json_t const&)#1}::operator()(wf::json_t const&) const (/home/neo/.local/lib/wayfire/libipc-rules.so+0x50eec) (BuildId: 4fcb6d39e567b0b69b6baeebe70c8b8e94e6e2fe)
#28 0x7b22b97e25fa in std::_Function_handler<wf::json_t (wf::json_t), wf::ipc_rules_utility_methods_t::set_config_options::{lambda(wf::json_t const&)#1}>::_M_invoke(std::_Any_data const&, wf::json_t&&) (/home/neo/.local/lib/wayfire/libipc-rules.so+0x525fa) (BuildId: 4fcb6d39e567b0b69b6baeebe70c8b8e94e6e2fe)
#29 0x7b22baca1b29 in std::_Function_handler<wf::json_t (wf::json_t, wf::ipc::client_interface_t*), wf::ipc::method_repository_t::register_method(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::function<wf::json_t (wf::json_t)>)::{lambda(wf::json_t const&, wf::ipc::client_interface_t*)#1}>::_M_invoke(std::_Any_data const&, wf::json_t&&, wf::ipc::client_interface_t*&&) (/home/neo/.local/lib/wayfire/libstipc.so+0xeb29) (BuildId: c7b476bdfe1dddcad4d0f3232efa5f65038d92a2)
#30 0x7b22bac7d737 in wf::ipc::method_repository_t::call_method(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, wf::json_t, wf::ipc::client_interface_t*) (/home/neo/.local/lib/wayfire/libcommand.so+0x1a737) (BuildId: 5fe0f7a698c53fd2d8efb04345f6f30bd29196c3)
#31 0x7b22bac4c884 in wf::ipc::server_t::handle_incoming_message(wf::ipc::client_t*, wf::json_t) (/home/neo/.local/lib/wayfire/libipc.so+0xa884) (BuildId: d5566ec1cee538497b0223a125762f1c514aa4db)
#32 0x7b22bac501a6 in wf::ipc::client_t::handle_fd_incoming(unsigned int) (/home/neo/.local/lib/wayfire/libipc.so+0xe1a6) (BuildId: d5566ec1cee538497b0223a125762f1c514aa4db)
#33 0x7b22bac4b4ad in wl_loop_handle_ipc_client_fd_event(int, unsigned int, void*) (/home/neo/.local/lib/wayfire/libipc.so+0x94ad) (BuildId: d5566ec1cee538497b0223a125762f1c514aa4db)
#34 0x7f22cd230641 in wl_event_loop_dispatch (/usr/lib/libwayland-server.so.0+0xa641) (BuildId: eaeed0645478c33a1a975d52ebd375751b26c19e)
#35 0x7f22cd232776 in wl_display_run (/usr/lib/libwayland-server.so.0+0xc776) (BuildId: eaeed0645478c33a1a975d52ebd375751b26c19e)
#36 0x55fb6a3faff0 in main ../src/main.cpp:514
#37 0x7f22cae27b8a (/usr/lib/libc.so.6+0x27b8a) (BuildId: 33141ef99aedd072bb910451a386a13eaf504222)
#38 0x7f22cae27c4a in __libc_start_main (/usr/lib/libc.so.6+0x27c4a) (BuildId: 33141ef99aedd072bb910451a386a13eaf504222)
#39 0x55fb6a3eec24 in _start (/usr/bin/wayfire+0x1ef7c24) (BuildId: 4b216497133f4761bed65c14272bc3b4c30930f5)
==98458==Register values:
rax = 0x0000000000000000 rbx = 0x0000000000002c52 rcx = 0x00007b52c8d05ab0 rdx = 0x0000000000000020
rdi = 0x0000000000002c52 rsi = 0x0000000000000000 rbp = 0x00007ffce093a950 rsp = 0x00007ffce093a0b8
r8 = 0x00007f22caf98ec0 r9 = 0x0000000000000000 r10 = 0x00007b52c8d05aa0 r11 = 0x00007f22ccee4240
r12 = 0x00007b52c8d05ab0 r13 = 0x00007ffce093a980 r14 = 0x000000003f800000 r15 = 0x00007c82c782a068
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/usr/lib/libwlroots-0.19.so+0xc9e7b) (BuildId: f756bc23f5d7e65dcba214a62afaab2914429b6a) in wlr_xcursor_theme_load
==98458==ABORTING
Additional context
You must press the keybind a few times to trigger this crash
Reactions are currently unavailable