1
0
Fork 0
forked from wry/wry

Merge pull request #450 from mahkoh/jorth/named-actions-loop

toml-config: clear named-action reference cycles
This commit is contained in:
mahkoh 2025-05-01 19:18:32 +02:00 committed by GitHub
commit a235b0fee3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 34 additions and 7 deletions

1
Cargo.lock generated
View file

@ -624,6 +624,7 @@ dependencies = [
"error_reporter", "error_reporter",
"futures-util", "futures-util",
"log", "log",
"run-on-drop",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",

View file

@ -17,3 +17,4 @@ backtrace = "0.3.69"
error_reporter = "1.0.0" error_reporter = "1.0.0"
serde_json = "1.0.114" serde_json = "1.0.114"
bstr = { version = "1.9.0", default-features = false, features = ["std"] } bstr = { version = "1.9.0", default-features = false, features = ["std"] }
run-on-drop = "1.0.0"

View file

@ -33,6 +33,7 @@ use {
}, },
bincode::Options, bincode::Options,
futures_util::task::ArcWake, futures_util::task::ArcWake,
run_on_drop::{OnDrop, on_drop},
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::{HashMap, VecDeque, hash_map::Entry}, collections::{HashMap, VecDeque, hash_map::Entry},
@ -100,6 +101,7 @@ pub(crate) struct Client {
on_del_drm_device: RefCell<Option<Callback<DrmDevice>>>, on_del_drm_device: RefCell<Option<Callback<DrmDevice>>>,
on_idle: RefCell<Option<Callback>>, on_idle: RefCell<Option<Callback>>,
on_switch_event: RefCell<HashMap<InputDevice, Callback<SwitchEvent>>>, on_switch_event: RefCell<HashMap<InputDevice, Callback<SwitchEvent>>>,
on_unload: Cell<Option<OnDrop<Box<dyn FnOnce()>>>>,
bufs: RefCell<Vec<Vec<u8>>>, bufs: RefCell<Vec<Vec<u8>>>,
reload: Cell<bool>, reload: Cell<bool>,
read_interests: RefCell<HashMap<PollableId, Interest>>, read_interests: RefCell<HashMap<PollableId, Interest>>,
@ -232,6 +234,7 @@ pub unsafe extern "C" fn init(
on_del_drm_device: Default::default(), on_del_drm_device: Default::default(),
on_idle: Default::default(), on_idle: Default::default(),
on_switch_event: Default::default(), on_switch_event: Default::default(),
on_unload: Default::default(),
bufs: Default::default(), bufs: Default::default(),
reload: Cell::new(false), reload: Cell::new(false),
read_interests: Default::default(), read_interests: Default::default(),
@ -1250,6 +1253,12 @@ impl Client {
} }
} }
pub fn on_unload(&self, f: impl FnOnce() + 'static) {
if let Some(prev) = self.on_unload.replace(Some(on_drop(Box::new(f)))) {
prev.forget();
}
}
fn handle_msg(&self, msg: &[u8]) { fn handle_msg(&self, msg: &[u8]) {
self.handle_msg2(msg); self.handle_msg2(msg);
self.dispatch_futures(); self.dispatch_futures();

View file

@ -304,3 +304,13 @@ pub fn toggle_float_above_fullscreen() {
pub fn set_show_float_pin_icon(show: bool) { pub fn set_show_float_pin_icon(show: bool) {
get!().set_show_float_pin_icon(show); get!().set_show_float_pin_icon(show);
} }
/// Sets a callback to run when this config is unloaded.
///
/// Only one callback can be set at a time. If another callback is already set, it will be
/// dropped without being run.
///
/// This function can be used to terminate threads and clear reference cycles.
pub fn on_unload(f: impl FnOnce() + 'static) {
get!().on_unload(f);
}

View file

@ -23,7 +23,7 @@ use {
is_reload, is_reload,
keyboard::{Keymap, ModifiedKeySym}, keyboard::{Keymap, ModifiedKeySym},
logging::set_log_level, logging::set_log_level,
on_devices_enumerated, on_idle, quit, reload, set_color_management_enabled, on_devices_enumerated, on_idle, on_unload, quit, reload, set_color_management_enabled,
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen, set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
set_idle, set_idle_grace_period, set_show_float_pin_icon, set_ui_drag_enabled, set_idle, set_idle_grace_period, set_show_float_pin_icon, set_ui_drag_enabled,
set_ui_drag_threshold, set_ui_drag_threshold,
@ -238,6 +238,7 @@ impl Action {
let name = Rc::new(name); let name = Rc::new(name);
B::new(move || { B::new(move || {
state state
.persistent
.actions .actions
.borrow_mut() .borrow_mut()
.insert(name.clone(), action.clone()); .insert(name.clone(), action.clone());
@ -246,7 +247,7 @@ impl Action {
Action::UndefineAction { name } => { Action::UndefineAction { name } => {
let state = state.clone(); let state = state.clone();
B::new(move || { B::new(move || {
state.actions.borrow_mut().remove(&name); state.persistent.actions.borrow_mut().remove(&name);
}) })
} }
Action::NamedAction { name } => { Action::NamedAction { name } => {
@ -259,7 +260,7 @@ impl Action {
} }
state.action_depth.set(depth + 1); state.action_depth.set(depth + 1);
let _reset = on_drop(|| state.action_depth.set(depth)); let _reset = on_drop(|| state.action_depth.set(depth));
let Some(action) = state.actions.borrow().get(&name).cloned() else { let Some(action) = state.persistent.actions.borrow().get(&name).cloned() else {
log::error!("There is no action named {name}"); log::error!("There is no action named {name}");
return; return;
}; };
@ -648,7 +649,6 @@ impl Output {
} }
} }
#[expect(clippy::type_complexity)]
struct State { struct State {
outputs: AHashMap<String, OutputMatch>, outputs: AHashMap<String, OutputMatch>,
drm_devices: AHashMap<String, DrmDeviceMatch>, drm_devices: AHashMap<String, DrmDeviceMatch>,
@ -662,7 +662,6 @@ struct State {
action_depth_max: u64, action_depth_max: u64,
action_depth: Cell<u64>, action_depth: Cell<u64>,
actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
} }
impl Drop for State { impl Drop for State {
@ -882,6 +881,8 @@ struct PersistentState {
default: Config, default: Config,
seat: Seat, seat: Seat,
binds: RefCell<AHashSet<ModifiedKeySym>>, binds: RefCell<AHashSet<ModifiedKeySym>>,
#[expect(clippy::type_complexity)]
actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
} }
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) { fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
@ -962,12 +963,12 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
io_outputs: Default::default(), io_outputs: Default::default(),
action_depth_max: config.max_action_depth, action_depth_max: config.max_action_depth,
action_depth: Cell::new(0), action_depth: Cell::new(0),
actions: Default::default(),
}); });
state.set_status(&config.status); state.set_status(&config.status);
persistent.actions.borrow_mut().clear();
for a in config.named_actions { for a in config.named_actions {
let action = a.action.into_rc_fn(&state); let action = a.action.into_rc_fn(&state);
state.actions.borrow_mut().insert(a.name, action); persistent.actions.borrow_mut().insert(a.name, action);
} }
let mut switch_actions = vec![]; let mut switch_actions = vec![];
for input in &mut config.inputs { for input in &mut config.inputs {
@ -1184,7 +1185,12 @@ pub fn configure() {
default: default.unwrap(), default: default.unwrap(),
seat: default_seat(), seat: default_seat(),
binds: Default::default(), binds: Default::default(),
actions: Default::default(),
}); });
{
let p = persistent.clone();
on_unload(move || p.actions.borrow_mut().clear());
}
load_config(true, &persistent); load_config(true, &persistent);
} }