1
0
Fork 0
forked from wry/wry

config: implement config reloading

This commit is contained in:
Julian Orth 2022-05-02 16:20:25 +02:00
parent aa19aab915
commit 7b40b42990
14 changed files with 188 additions and 50 deletions

View file

@ -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<State>, test_future: Option<TestFuture>) {
}
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<State>, #[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<State>) {
dummy_output.show_workspace(&dummy_workspace);
state.dummy_output.set(Some(dummy_output));
}
fn config_dir() -> Option<String> {
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
}
}

View file

@ -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<ConfigProxyHandler>,
handler: CloneCell<Option<Rc<ConfigProxyHandler>>>,
}
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<State>) -> Self {
@ -154,9 +180,44 @@ impl ConfigProxy {
Self::new(None, &entry, state)
}
#[allow(dead_code)]
pub fn from_config_dir(state: &Rc<State>) -> Result<Self, ConfigError> {
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<State>) -> Result<Self, ConfigError> {
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)),
};

View file

@ -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(())
}

View file

@ -619,6 +619,10 @@ impl WlSeatGlobal {
self.id
}
pub fn seat_name(&self) -> &str {
&self.seat_name
}
fn bind_(
self: Rc<Self>,
id: WlSeatId,

View file

@ -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);
}

View file

@ -97,6 +97,8 @@ pub struct State {
pub acceptor: CloneCell<Option<Rc<Acceptor>>>,
pub serial: NumCell<Wrapping<u32>>,
pub run_toplevel: Rc<RunToplevel>,
pub config_dir: Option<String>,
pub config_file_id: NumCell<u64>,
}
impl Debug for State {

View file

@ -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;

View file

@ -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);
}
}