diff --git a/default-config/src/lib.rs b/default-config/src/lib.rs index a566f6ea..aa289e7a 100644 --- a/default-config/src/lib.rs +++ b/default-config/src/lib.rs @@ -4,17 +4,17 @@ use { config, drm::on_graphics_initialized, get_timer, get_workspace, - input::{create_seat, input_devices, on_new_input_device, InputDevice, Seat}, + input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat}, keyboard::{ mods::{Modifiers, ALT, CTRL, SHIFT}, syms::{ SYM_Super_L, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p, SYM_q, - SYM_t, SYM_u, SYM_v, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14, SYM_F15, - SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22, SYM_F23, - SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, + SYM_r, SYM_t, SYM_u, SYM_v, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14, + SYM_F15, SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22, + SYM_F23, SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, }, }, - quit, + quit, reload, status::set_status, switch_to_vt, Axis::{Horizontal, Vertical}, @@ -58,6 +58,8 @@ fn configure_seat(s: Seat) { s.bind(MOD | SYM_q, quit); + s.bind(MOD | SHIFT | SYM_r, reload); + let fnkeys = [ SYM_F1, SYM_F2, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, SYM_F10, SYM_F11, SYM_F12, @@ -78,7 +80,7 @@ fn configure_seat(s: Seat) { } pub fn configure() { - let seat = create_seat("default"); + let seat = get_seat("default"); configure_seat(seat); let handle_input_device = move |device: InputDevice| { device.set_seat(seat); diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 7b2708ef..0383bbd9 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -41,6 +41,7 @@ pub(crate) struct Client { on_graphics_initialized: Cell>>, on_new_connector: RefCell>>, bufs: RefCell>>, + reload: Cell, } impl Drop for Client { @@ -124,6 +125,7 @@ pub unsafe extern "C" fn init( on_graphics_initialized: Default::default(), on_new_connector: Default::default(), bufs: Default::default(), + reload: Cell::new(false), }); let init = slice::from_raw_parts(init, size); client.handle_init_msg(init); @@ -169,6 +171,14 @@ impl Client { self.with_response(|| self.send(msg)) } + pub fn reload(&self) { + self.send(&ClientMessage::Reload); + } + + pub fn is_reload(&self) -> bool { + self.reload.get() + } + pub fn spawn(&self, command: &Command) { let env = command .env @@ -358,9 +368,9 @@ impl Client { self.send(&ClientMessage::FocusParent { seat }); } - pub fn create_seat(&self, name: &str) -> Seat { - let res = self.send_with_response(&ClientMessage::CreateSeat { name }); - get_response!(res, Seat(0), CreateSeat, seat); + pub fn get_seat(&self, name: &str) -> Seat { + let res = self.send_with_response(&ClientMessage::GetSeat { name }); + get_response!(res, Seat(0), GetSeat, seat); seat } @@ -531,8 +541,10 @@ impl Client { } }; match msg { - ServerMessage::Configure => { + ServerMessage::Configure { reload } => { + self.reload.set(reload); (self.configure)(); + self.reload.set(false); } ServerMessage::Response { response } => { self.response.borrow_mut().push(response); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 71984041..c1cebbb9 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -12,7 +12,9 @@ use { #[derive(Encode, BorrowDecode, Debug)] pub enum ServerMessage { - Configure, + Configure { + reload: bool, + }, GraphicsInitialized, Response { response: Response, @@ -53,7 +55,7 @@ pub enum ClientMessage<'a> { file: Option<&'a str>, line: Option, }, - CreateSeat { + GetSeat { name: &'a str, }, Quit, @@ -237,6 +239,7 @@ pub enum ClientMessage<'a> { GetFullscreen { seat: Seat, }, + Reload, } #[derive(Encode, Decode, Debug)] @@ -258,7 +261,7 @@ pub enum Response { ParseKeymap { keymap: Keymap, }, - CreateSeat { + GetSeat { seat: Seat, }, GetInputDevices { diff --git a/jay-config/src/_private/logging.rs b/jay-config/src/_private/logging.rs index 04099810..5f313bcf 100644 --- a/jay-config/src/_private/logging.rs +++ b/jay-config/src/_private/logging.rs @@ -4,7 +4,7 @@ use { }; pub fn init() { - log::set_logger(&Logger).unwrap(); + let _ = log::set_logger(&Logger); log::set_max_level(LevelFilter::Trace); } diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 6ee3d091..4eba5e35 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -161,9 +161,9 @@ pub fn input_devices() -> Vec { pub fn remove_all_seats() {} -pub fn create_seat(name: &str) -> Seat { +pub fn get_seat(name: &str) -> Seat { let mut res = Seat(0); - (|| res = get!().create_seat(name))(); + (|| res = get!().get_seat(name))(); res } diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 6f0d1f85..2aadfcc1 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -121,3 +121,11 @@ impl Timer { get!().on_timer_tick(self, f); } } + +pub fn reload() { + get!().reload() +} + +pub fn is_reload() -> bool { + get!(false).is_reload() +} diff --git a/src/compositor.rs b/src/compositor.rs index 7547852f..7d2e35b4 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -30,8 +30,8 @@ use { }, user_session::import_environment, utils::{ - clonecell::CloneCell, errorfmt::ErrorFmt, fdcloser::FdCloser, oserror::OsError, - queue::AsyncQueue, run_toplevel::RunToplevel, tri::Try, + clonecell::CloneCell, errorfmt::ErrorFmt, fdcloser::FdCloser, numcell::NumCell, + oserror::OsError, queue::AsyncQueue, run_toplevel::RunToplevel, tri::Try, }, wheel::{Wheel, WheelError}, xkbcommon::XkbContext, @@ -174,6 +174,8 @@ fn start_compositor2( serial: Default::default(), idle_inhibitor_ids: Default::default(), run_toplevel, + config_dir: config_dir(), + config_file_id: NumCell::new(1), }); create_dummy_output(&state); let acceptor = Acceptor::install(&state)?; @@ -220,6 +222,7 @@ async fn start_compositor3(state: Rc, test_future: Option) { } let config = load_config(&state, is_test); + config.configure(false); state.config.set(Some(Rc::new(config))); let _geh = start_global_event_handlers(&state, &backend); @@ -237,19 +240,10 @@ fn load_config(state: &Rc, #[allow(unused_variables)] for_test: bool) -> if for_test { // todo } - let config_dir = if let Ok(xdg) = env::var("XDG_CONFIG_HOME") { - format!("{}/jay", xdg) - } else if let Ok(home) = env::var("HOME") { - format!("{}/.config/jay", home) - } else { - log::warn!("Neither XDG_CONFIG_HOME nor HOME are set. Using default config."); - return ConfigProxy::default(state); - }; - let config_path = format!("{}/config.so", config_dir); - match unsafe { ConfigProxy::from_file(&config_path, state) } { + match ConfigProxy::from_config_dir(state) { Ok(c) => c, Err(e) => { - log::warn!("Could not load {}: {}", config_path, ErrorFmt(e)); + log::warn!("Could not load config.so: {}", ErrorFmt(e)); log::warn!("Using default config"); ConfigProxy::default(state) } @@ -386,3 +380,14 @@ fn create_dummy_output(state: &Rc) { dummy_output.show_workspace(&dummy_workspace); state.dummy_output.set(Some(dummy_output)); } + +fn config_dir() -> Option { + if let Ok(xdg) = env::var("XDG_CONFIG_HOME") { + Some(format!("{}/jay", xdg)) + } else if let Ok(home) = env::var("HOME") { + Some(format!("{}/.config/jay", home)) + } else { + log::warn!("Neither XDG_CONFIG_HOME nor HOME are set. Using default config."); + None + } +} diff --git a/src/config.rs b/src/config.rs index 9a8d4f05..340f07db 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,10 @@ use { config::handler::ConfigProxyHandler, ifs::wl_seat::SeatId, state::State, - utils::{numcell::NumCell, ptr_ext::PtrExt}, + utils::{ + clonecell::CloneCell, numcell::NumCell, oserror::OsError, ptr_ext::PtrExt, + unlink_on_drop::UnlinkOnDrop, xrd::xrd, + }, }, jay_config::{ _private::{ @@ -29,15 +32,36 @@ pub enum ConfigError { CouldNotLoadLibrary(#[source] libloading::Error), #[error("Config library does not contain the entry symbol")] LibraryDoesNotContainEntry(#[source] libloading::Error), + #[error("Could not determine the config directory")] + ConfigDirNotSet, + #[error("Could not link the config file")] + LinkConfigFile(#[source] OsError), + #[error("XDG_RUNTIME_DIR is not set")] + XrdNotSet, } pub struct ConfigProxy { - handler: Rc, + handler: CloneCell>>, } impl ConfigProxy { + fn send(&self, msg: &ServerMessage) { + if let Some(handler) = self.handler.get() { + handler.send(msg); + } + } + + pub fn destroy(&self) { + if let Some(handler) = self.handler.take() { + unsafe { + handler.do_drop(); + (handler.unref)(handler.client_data.get()); + } + } + } + pub fn invoke_shortcut(&self, seat: SeatId, modsym: &ModifiedKeySym) { - self.handler.send(&ServerMessage::InvokeShortcut { + self.send(&ServerMessage::InvokeShortcut { seat: Seat(seat.raw() as _), mods: modsym.mods, sym: modsym.sym, @@ -45,52 +69,49 @@ impl ConfigProxy { } pub fn new_connector(&self, connector: ConnectorId) { - self.handler.send(&ServerMessage::NewConnector { + self.send(&ServerMessage::NewConnector { device: Connector(connector.raw() as _), }); } pub fn del_connector(&self, connector: ConnectorId) { - self.handler.send(&ServerMessage::DelConnector { + self.send(&ServerMessage::DelConnector { device: Connector(connector.raw() as _), }); } pub fn connector_connected(&self, connector: ConnectorId) { - self.handler.send(&ServerMessage::ConnectorConnect { + self.send(&ServerMessage::ConnectorConnect { device: Connector(connector.raw() as _), }); } pub fn connector_disconnected(&self, connector: ConnectorId) { - self.handler.send(&ServerMessage::ConnectorDisconnect { + self.send(&ServerMessage::ConnectorDisconnect { device: Connector(connector.raw() as _), }); } pub fn new_input_device(&self, dev: InputDeviceId) { - self.handler.send(&ServerMessage::NewInputDevice { + self.send(&ServerMessage::NewInputDevice { device: InputDevice(dev.raw() as _), }); } pub fn del_input_device(&self, dev: InputDeviceId) { - self.handler.send(&ServerMessage::DelInputDevice { + self.send(&ServerMessage::DelInputDevice { device: InputDevice(dev.raw() as _), }); } pub fn graphics_initialized(&self) { - self.handler.send(&ServerMessage::GraphicsInitialized); + self.send(&ServerMessage::GraphicsInitialized); } } impl Drop for ConfigProxy { fn drop(&mut self) { - unsafe { - self.handler.do_drop(); - (self.handler.unref)(self.handler.client_data.get()); - } + self.destroy(); } } @@ -140,8 +161,13 @@ impl ConfigProxy { ); data.client_data.set(client_data); } - data.send(&ServerMessage::Configure); - Self { handler: data } + Self { + handler: CloneCell::new(Some(data)), + } + } + + pub fn configure(&self, reload: bool) { + self.send(&ServerMessage::Configure { reload }); } pub fn default(state: &Rc) -> Self { @@ -154,9 +180,44 @@ impl ConfigProxy { Self::new(None, &entry, state) } - #[allow(dead_code)] + pub fn from_config_dir(state: &Rc) -> Result { + let dir = match state.config_dir.as_deref() { + Some(d) => d, + _ => return Err(ConfigError::ConfigDirNotSet), + }; + let file = format!("{}/config.so", dir); + unsafe { Self::from_file(&file, state) } + } + pub unsafe fn from_file(path: &str, state: &Rc) -> Result { - let lib = match Library::new(path) { + // Here we have to do a bit of a dance to support reloading. glibc will + // never load a library twice unless it has been unloaded in between. + // glibc identifies libraries by their file path and by their inode + // number. If either of those match, glibc considers the libraries + // identical. If the inode has not changed then this is not a problem + // for us since we don't want glibc to do any unnecessary work. + // However, if the user has created a new config with a new inode, then + // glibc will still not reload the library if we try to load it from + // the canonical location ~/.config/jay/config.so since it already has + // a library with that path loaded. To work around this, create a + // temporary symlink with an incrementing number and load the library + // from there. + let xrd = match xrd() { + Some(x) => x, + _ => return Err(ConfigError::XrdNotSet), + }; + let link = format!( + "{}/.jay_config.so.{}.{}", + xrd, + uapi::getpid(), + state.config_file_id.fetch_add(1) + ); + let _ = uapi::unlink(link.as_str()); + if let Err(e) = uapi::symlink(path, link.as_str()) { + return Err(ConfigError::LinkConfigFile(e.into())); + } + let _unlink = UnlinkOnDrop(&link); + let lib = match Library::new(&link) { Ok(l) => l, Err(e) => return Err(ConfigError::CouldNotLoadLibrary(e)), }; diff --git a/src/config/handler.rs b/src/config/handler.rs index de1c415b..ef1db086 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -5,6 +5,7 @@ use { self, ConnectorId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, }, compositor::MAX_EXTENTS, + config::ConfigProxy, ifs::wl_seat::{SeatId, WlSeatGlobal}, state::{ConnectorData, DeviceHandlerData, OutputData, State}, tree::{ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase}, @@ -121,11 +122,19 @@ impl ConfigProxyHandler { log::log!(level, "{:?}", debug); } - fn handle_create_seat(&self, name: &str) { + fn handle_get_seat(&self, name: &str) { + for seat in self.state.globals.seats.lock().values() { + if seat.seat_name() == name { + self.respond(Response::GetSeat { + seat: Seat(seat.id().raw() as _), + }); + return; + } + } let global_name = self.state.globals.name(); let seat = WlSeatGlobal::new(global_name, name, &self.state); self.state.globals.add_global(&self.state, &seat); - self.respond(Response::CreateSeat { + self.respond(Response::GetSeat { seat: Seat(seat.id().raw() as _), }); } @@ -143,6 +152,25 @@ impl ConfigProxyHandler { res } + fn handle_reload(&self) { + log::info!("Reloading config"); + let config = match ConfigProxy::from_config_dir(&self.state) { + Ok(c) => c, + Err(e) => { + log::error!("Cannot reload config: {}", ErrorFmt(e)); + return; + } + }; + if let Some(config) = self.state.config.take() { + config.destroy(); + for seat in self.state.globals.seats.lock().values() { + seat.clear_shortcuts(); + } + } + config.configure(true); + self.state.config.set(Some(Rc::new(config))); + } + fn handle_get_fullscreen(&self, seat: Seat) -> Result<(), CphError> { let seat = self.get_seat(seat)?; self.respond(Response::GetFullscreen { @@ -784,7 +812,7 @@ impl ConfigProxyHandler { file, line, } => self.handle_log_request(level, msg, file, line), - ClientMessage::CreateSeat { name } => self.handle_create_seat(name), + ClientMessage::GetSeat { name } => self.handle_get_seat(name), ClientMessage::ParseKeymap { keymap } => { self.handle_parse_keymap(keymap).wrn("parse_keymap")? } @@ -915,6 +943,7 @@ impl ConfigProxyHandler { ClientMessage::GetFullscreen { seat } => { self.handle_get_fullscreen(seat).wrn("get_fullscreen")? } + ClientMessage::Reload => self.handle_reload(), } Ok(()) } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 36682d0d..fab5e87c 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -619,6 +619,10 @@ impl WlSeatGlobal { self.id } + pub fn seat_name(&self) -> &str { + &self.seat_name + } + fn bind_( self: Rc, id: WlSeatId, diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index c7575d83..06a309cf 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -464,6 +464,10 @@ impl WlSeatGlobal { self.apply_changes(); } + pub fn clear_shortcuts(&self) { + self.shortcuts.clear(); + } + pub fn add_shortcut(&self, mods: Modifiers, keysym: KeySym) { self.shortcuts.set((mods.0, keysym.0), mods); } diff --git a/src/state.rs b/src/state.rs index 7fa49334..d8a7f439 100644 --- a/src/state.rs +++ b/src/state.rs @@ -97,6 +97,8 @@ pub struct State { pub acceptor: CloneCell>>, pub serial: NumCell>, pub run_toplevel: Rc, + pub config_dir: Option, + pub config_file_id: NumCell, } impl Debug for State { diff --git a/src/utils.rs b/src/utils.rs index 13345c86..95be0c4e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -24,6 +24,7 @@ pub mod stack; pub mod syncqueue; pub mod tri; pub mod trim; +pub mod unlink_on_drop; pub mod vasprintf; pub mod vec_ext; pub mod vecstorage; diff --git a/src/utils/unlink_on_drop.rs b/src/utils/unlink_on_drop.rs new file mode 100644 index 00000000..6061e514 --- /dev/null +++ b/src/utils/unlink_on_drop.rs @@ -0,0 +1,7 @@ +pub struct UnlinkOnDrop<'a>(pub &'a str); + +impl<'a> Drop for UnlinkOnDrop<'a> { + fn drop(&mut self) { + let _ = uapi::unlink(self.0); + } +}