This should not have any impact on existing configs since bincode claims compatibility of the wire format between 1.3.3 and 2.0.0.
280 lines
8.8 KiB
Rust
280 lines
8.8 KiB
Rust
mod handler;
|
|
|
|
#[cfg(feature = "it")]
|
|
use crate::it::test_config::TEST_CONFIG_ENTRY;
|
|
use {
|
|
crate::{
|
|
backend::{ConnectorId, DrmDeviceId, InputDeviceId},
|
|
config::handler::ConfigProxyHandler,
|
|
ifs::wl_seat::SeatId,
|
|
state::State,
|
|
utils::{
|
|
clonecell::CloneCell, numcell::NumCell, oserror::OsError, ptr_ext::PtrExt,
|
|
unlink_on_drop::UnlinkOnDrop, xrd::xrd,
|
|
},
|
|
},
|
|
bincode::Options,
|
|
jay_config::{
|
|
_private::{
|
|
bincode_ops,
|
|
ipc::{InitMessage, ServerMessage, V1InitMessage},
|
|
ConfigEntry, VERSION,
|
|
},
|
|
input::{InputDevice, Seat},
|
|
keyboard::ModifiedKeySym,
|
|
video::{Connector, DrmDevice},
|
|
},
|
|
libloading::Library,
|
|
std::{cell::Cell, mem, ptr, rc::Rc},
|
|
thiserror::Error,
|
|
};
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ConfigError {
|
|
#[error("Could not load the config library")]
|
|
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: 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.send(&ServerMessage::InvokeShortcut {
|
|
seat: Seat(seat.raw() as _),
|
|
mods: modsym.mods,
|
|
sym: modsym.sym,
|
|
});
|
|
}
|
|
|
|
pub fn new_drm_dev(&self, dev: DrmDeviceId) {
|
|
self.send(&ServerMessage::NewDrmDev {
|
|
device: DrmDevice(dev.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn del_drm_dev(&self, dev: DrmDeviceId) {
|
|
self.send(&ServerMessage::DelDrmDev {
|
|
device: DrmDevice(dev.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn new_connector(&self, connector: ConnectorId) {
|
|
self.send(&ServerMessage::NewConnector {
|
|
device: Connector(connector.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn del_connector(&self, connector: ConnectorId) {
|
|
self.send(&ServerMessage::DelConnector {
|
|
device: Connector(connector.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn connector_connected(&self, connector: ConnectorId) {
|
|
self.send(&ServerMessage::ConnectorConnect {
|
|
device: Connector(connector.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn connector_disconnected(&self, connector: ConnectorId) {
|
|
self.send(&ServerMessage::ConnectorDisconnect {
|
|
device: Connector(connector.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn new_input_device(&self, dev: InputDeviceId) {
|
|
self.send(&ServerMessage::NewInputDevice {
|
|
device: InputDevice(dev.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn del_input_device(&self, dev: InputDeviceId) {
|
|
self.send(&ServerMessage::DelInputDevice {
|
|
device: InputDevice(dev.raw() as _),
|
|
});
|
|
}
|
|
|
|
pub fn graphics_initialized(&self) {
|
|
self.send(&ServerMessage::GraphicsInitialized);
|
|
}
|
|
|
|
pub fn devices_enumerated(&self) {
|
|
self.send(&ServerMessage::DevicesEnumerated);
|
|
}
|
|
|
|
pub fn clear(&self) {
|
|
self.send(&ServerMessage::Clear);
|
|
}
|
|
|
|
pub fn idle(&self) {
|
|
self.send(&ServerMessage::Idle);
|
|
}
|
|
}
|
|
|
|
impl Drop for ConfigProxy {
|
|
fn drop(&mut self) {
|
|
self.destroy();
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn default_client_init(
|
|
srv_data: *const u8,
|
|
srv_unref: unsafe extern "C" fn(data: *const u8),
|
|
srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize),
|
|
msg: *const u8,
|
|
size: usize,
|
|
) -> *const u8 {
|
|
extern "C" fn configure() {
|
|
default_config::configure();
|
|
}
|
|
jay_config::_private::client::init(srv_data, srv_unref, srv_handler, msg, size, configure)
|
|
}
|
|
|
|
impl ConfigProxy {
|
|
fn new(lib: Option<Library>, entry: &ConfigEntry, state: &Rc<State>) -> Self {
|
|
let version = entry.version.min(VERSION);
|
|
let data = Rc::new(ConfigProxyHandler {
|
|
client_data: Cell::new(ptr::null()),
|
|
dropped: Cell::new(false),
|
|
_lib: lib,
|
|
_version: version,
|
|
unref: entry.unref,
|
|
handle_msg: entry.handle_msg,
|
|
state: state.clone(),
|
|
next_id: NumCell::new(1),
|
|
keymaps: Default::default(),
|
|
bufs: Default::default(),
|
|
workspace_ids: NumCell::new(1),
|
|
workspaces_by_name: Default::default(),
|
|
workspaces_by_id: Default::default(),
|
|
timer_ids: NumCell::new(1),
|
|
timers_by_name: Default::default(),
|
|
timers_by_id: Default::default(),
|
|
});
|
|
let init_msg = bincode_ops()
|
|
.serialize(&InitMessage::V1(V1InitMessage {}))
|
|
.unwrap();
|
|
unsafe {
|
|
let client_data = (entry.init)(
|
|
Rc::into_raw(data.clone()) as _,
|
|
unref,
|
|
handle_msg,
|
|
init_msg.as_ptr(),
|
|
init_msg.len(),
|
|
);
|
|
data.client_data.set(client_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 {
|
|
let entry = ConfigEntry {
|
|
version: VERSION,
|
|
init: default_client_init,
|
|
unref: jay_config::_private::client::unref,
|
|
handle_msg: jay_config::_private::client::handle_msg,
|
|
};
|
|
Self::new(None, &entry, state)
|
|
}
|
|
|
|
#[cfg(feature = "it")]
|
|
pub fn for_test(state: &Rc<State>) -> Self {
|
|
Self::new(None, &TEST_CONFIG_ENTRY, state)
|
|
}
|
|
|
|
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> {
|
|
// 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)),
|
|
};
|
|
let entry = lib.get::<&'static ConfigEntry>(b"JAY_CONFIG_ENTRY_V1\0");
|
|
let entry = match entry {
|
|
Ok(e) => *e,
|
|
Err(e) => return Err(ConfigError::LibraryDoesNotContainEntry(e)),
|
|
};
|
|
Ok(Self::new(Some(lib), entry, state))
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn unref(data: *const u8) {
|
|
let server = data as *const ConfigProxyHandler;
|
|
drop(Rc::from_raw(server));
|
|
}
|
|
|
|
unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) {
|
|
let server = (data as *const ConfigProxyHandler).deref();
|
|
if server.dropped.get() {
|
|
return;
|
|
}
|
|
let rc = Rc::from_raw(server);
|
|
let msg = std::slice::from_raw_parts(msg, size);
|
|
rc.handle_request(msg);
|
|
mem::forget(rc);
|
|
}
|