Merge pull request #79 from pinnacle-comp/xdg

Xdg Base Directories support
This commit is contained in:
Ottatop 2023-09-11 04:02:56 -05:00 committed by GitHub
commit aa3d69794e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 151 additions and 52 deletions

View file

@ -6,11 +6,9 @@ edition = "2021"
license = "GPL-3.0+" license = "GPL-3.0+"
description = "A WIP Smithay-based Wayland compositor, inspired by AwesomeWM" description = "A WIP Smithay-based Wayland compositor, inspired by AwesomeWM"
readme = "README.md" readme = "README.md"
repository = "https://github.com/Ottatop/pinnacle/" repository = "https://github.com/pinnacle-comp/pinnacle/"
keywords = ["wayland", "compositor", "smithay", "lua"] keywords = ["wayland", "compositor", "smithay", "lua"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
@ -25,13 +23,18 @@ rmp-serde = { version = "1.1.2" }
calloop = { version = "0.10.1", features = ["executor", "futures-io"] } calloop = { version = "0.10.1", features = ["executor", "futures-io"] }
futures-lite = { version = "1.13.0" } futures-lite = { version = "1.13.0" }
async-process = { version = "1.7.0" } async-process = { version = "1.7.0" }
itertools = { version = "0.11.0" }
x11rb = { version = "0.12.0", default-features = false, features = ["composite"], optional = true } x11rb = { version = "0.12.0", default-features = false, features = ["composite"], optional = true }
shellexpand = "3.1.0" shellexpand = "3.1.0"
toml = "0.7.7" toml = "0.7.7"
anyhow = { version = "1.0.75", features = ["backtrace"] } anyhow = { version = "1.0.75", features = ["backtrace"] }
clap = { version = "4.4.2", features = ["derive"] } clap = { version = "4.4.2", features = ["derive"] }
xkbcommon = "0.6.0" xkbcommon = "0.6.0"
xdg = "2.5.2"
lazy_static = "1.4.0"
tracing-appender = "0.2.2"
walkdir = "2.4.0"
sysinfo = "0.29.10"
[features] [features]
default = ["egl", "winit", "udev", "xwayland"] default = ["egl", "winit", "udev", "xwayland"]

View file

@ -46,10 +46,13 @@ use anyhow::Context;
use smithay::reexports::calloop::{ use smithay::reexports::calloop::{
self, channel::Sender, generic::Generic, EventSource, Interest, Mode, PostAction, self, channel::Sender, generic::Generic, EventSource, Interest, Mode, PostAction,
}; };
use sysinfo::{ProcessRefreshKind, RefreshKind, SystemExt};
use walkdir::WalkDir;
use self::msg::{Msg, OutgoingMsg}; use self::msg::{Msg, OutgoingMsg};
pub const DEFAULT_SOCKET_DIR: &str = "/tmp"; pub const DEFAULT_SOCKET_DIR: &str = "/tmp";
pub const SOCKET_NAME: &str = "pinnacle_socket";
fn handle_client( fn handle_client(
mut stream: UnixStream, mut stream: UnixStream,
@ -90,11 +93,51 @@ impl PinnacleSocketSource {
/// Create a loop source that listens for connections to the provided socket_dir. /// Create a loop source that listens for connections to the provided socket_dir.
/// This will also set PINNACLE_SOCKET for use in API implementations. /// This will also set PINNACLE_SOCKET for use in API implementations.
pub fn new(sender: Sender<Msg>, socket_dir: &Path) -> anyhow::Result<Self> { pub fn new(sender: Sender<Msg>, socket_dir: &Path) -> anyhow::Result<Self> {
let socket_path = socket_dir.join("pinnacle_socket"); let system = sysinfo::System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::new()),
);
if let Ok(exists) = socket_path.try_exists() { // Test if you are running multiple instances of Pinnacle
if exists { let multiple_instances = system.processes_by_exact_name("pinnacle").count() > 1;
std::fs::remove_file(&socket_path).context("Failed to remove old socket")?;
// If you are, append a suffix to the socket name
let socket_name = if multiple_instances {
let mut suffix: u8 = 1;
while let Ok(true) = socket_dir
.join(format!("{SOCKET_NAME}_{suffix}"))
.try_exists()
{
suffix += 1;
}
format!("{SOCKET_NAME}_{suffix}")
} else {
SOCKET_NAME.to_string()
};
let socket_path = socket_dir.join(socket_name);
// If there are multiple instances, don't touch other sockets
if multiple_instances {
if let Ok(exists) = socket_path.try_exists() {
if exists {
std::fs::remove_file(&socket_path).context(format!(
"Failed to remove old socket at {}",
socket_path.to_string_lossy()
))?;
}
}
} else {
// If there are, remove them all
for entry in WalkDir::new(socket_dir)
.contents_first(true)
.into_iter()
.filter_entry(|entry| entry.file_name().to_string_lossy().starts_with(SOCKET_NAME))
.filter_map(|e| e.ok())
{
std::fs::remove_file(entry.path()).context(format!(
"Failed to remove old socket at {}",
entry.path().to_string_lossy()
))?;
} }
} }

View file

@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use itertools::{Either, Itertools};
use smithay::{ use smithay::{
desktop::layer_map_for_output, desktop::layer_map_for_output,
output::Output, output::Output,
@ -401,14 +400,17 @@ fn corner(layout: &Layout, windows: Vec<WindowElement>, rect: Rectangle<i32, Log
_ => { _ => {
let mut windows = windows.into_iter(); let mut windows = windows.into_iter();
let Some(corner) = windows.next() else { unreachable!() }; let Some(corner) = windows.next() else { unreachable!() };
let (horiz_stack, vert_stack): (Vec<WindowElement>, Vec<WindowElement>) =
windows.enumerate().partition_map(|(i, win)| { let mut horiz_stack = Vec::<WindowElement>::new();
if i % 2 == 0 { let mut vert_stack = Vec::<WindowElement>::new();
Either::Left(win)
} else { for (i, win) in windows.enumerate() {
Either::Right(win) if i % 2 == 0 {
} horiz_stack.push(win);
}); } else {
vert_stack.push(win);
}
}
let div_factor = 2; let div_factor = 2;

View file

@ -11,7 +11,12 @@
// #![deny(unused_imports)] // gonna force myself to keep stuff clean // #![deny(unused_imports)] // gonna force myself to keep stuff clean
#![warn(clippy::unwrap_used)] #![warn(clippy::unwrap_used)]
use std::path::Path;
use clap::Parser; use clap::Parser;
use tracing_subscriber::{fmt::writer::MakeWriterExt, EnvFilter};
use walkdir::WalkDir;
use xdg::BaseDirectories;
mod api; mod api;
mod backend; mod backend;
@ -29,6 +34,11 @@ mod state;
mod tag; mod tag;
mod window; mod window;
lazy_static::lazy_static! {
pub static ref XDG_BASE_DIRS: BaseDirectories =
BaseDirectories::with_prefix("pinnacle").expect("couldn't create xdg BaseDirectories");
}
#[derive(clap::Args, Debug)] #[derive(clap::Args, Debug)]
#[group(id = "backend", required = false, multiple = false)] #[group(id = "backend", required = false, multiple = false)]
struct Backends { struct Backends {
@ -53,18 +63,24 @@ struct Args {
force: bool, force: bool,
} }
const PINNACLE_LOG_PREFIX: &str = "pinnacle.log";
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
match tracing_subscriber::EnvFilter::try_from_default_env() { let xdg_state_dir = XDG_BASE_DIRS.get_state_home();
Ok(env_filter) => {
tracing_subscriber::fmt() trim_logs(&xdg_state_dir);
.compact()
.with_env_filter(env_filter) let appender = tracing_appender::rolling::hourly(&xdg_state_dir, PINNACLE_LOG_PREFIX);
.init(); let (appender, _guard) = tracing_appender::non_blocking(appender);
} let writer = appender.and(std::io::stdout);
Err(_) => {
tracing_subscriber::fmt().compact().init(); let env_filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("debug"));
}
} tracing_subscriber::fmt()
.compact()
.with_env_filter(env_filter)
.with_writer(writer)
.init();
let args = Args::parse(); let args = Args::parse();
@ -124,3 +140,41 @@ fn main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn trim_logs(log_path: impl AsRef<Path>) {
let logs = WalkDir::new(log_path)
.sort_by(|a, b| {
let a_creation_time = a
.metadata()
.expect("failed to get log metadata")
.created()
.expect("failed to get log creation time");
let b_creation_time = b
.metadata()
.expect("failed to get log metadata")
.created()
.expect("failed to get log creation time");
a_creation_time.cmp(&b_creation_time)
})
.contents_first(true)
.into_iter()
.filter_entry(|entry| {
entry.file_type().is_file()
&& entry
.file_name()
.to_string_lossy()
.starts_with(PINNACLE_LOG_PREFIX)
})
.filter_map(|dir| dir.ok())
.collect::<Vec<_>>();
// If there are more than 4 logs, delete all but 3
if logs.len() > 4 {
let num_to_delete = logs.len().saturating_sub(3);
for entry in logs.into_iter().take(num_to_delete) {
tracing::info!("Deleting {:?}", entry.path());
std::fs::remove_file(entry.path()).expect("failed to remove oldest log file");
}
}
}

View file

@ -16,7 +16,7 @@ use crate::{
window_rules::{WindowRule, WindowRuleCondition}, window_rules::{WindowRule, WindowRuleCondition},
CallbackId, ModifierMask, Msg, CallbackId, ModifierMask, Msg,
}, },
PinnacleSocketSource, DEFAULT_SOCKET_DIR, PinnacleSocketSource,
}, },
backend::{udev::Udev, winit::Winit, BackendData}, backend::{udev::Udev, winit::Winit, BackendData},
cursor::Cursor, cursor::Cursor,
@ -204,27 +204,26 @@ impl State {
let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>(); let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>();
let config_dir = get_config_dir(); let config_dir = get_config_dir();
tracing::debug!("config dir is {:?}", config_dir);
let metaconfig = crate::metaconfig::parse(&config_dir)?; let metaconfig = crate::metaconfig::parse(&config_dir)?;
let socket_dir = { // If a socket is provided in the metaconfig, use it.
let dir_string = shellexpand::full( let socket_dir = if let Some(socket_dir) = &metaconfig.socket_dir {
metaconfig
.socket_dir
.as_deref()
.unwrap_or(DEFAULT_SOCKET_DIR),
)?
.to_string();
// cd into the metaconfig dir and canonicalize to preserve relative paths // cd into the metaconfig dir and canonicalize to preserve relative paths
// like ./dir/here // like ./dir/here
let current_dir = std::env::current_dir()?; let current_dir = std::env::current_dir()?;
std::env::set_current_dir(&config_dir)?; std::env::set_current_dir(&config_dir)?;
let pathbuf = PathBuf::from(&dir_string).canonicalize()?; let socket_dir = PathBuf::from(socket_dir).canonicalize()?;
std::env::set_current_dir(current_dir)?; std::env::set_current_dir(current_dir)?;
socket_dir
pathbuf } else {
// Otherwise, use $XDG_RUNTIME_DIR. If that doesn't exist, use /tmp.
crate::XDG_BASE_DIRS
.get_runtime_directory()
.cloned()
.unwrap_or(PathBuf::from(crate::api::DEFAULT_SOCKET_DIR))
}; };
let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir) let socket_source = PinnacleSocketSource::new(tx_channel, &socket_dir)
@ -401,17 +400,11 @@ impl State {
} }
fn get_config_dir() -> PathBuf { fn get_config_dir() -> PathBuf {
let config_dir = std::env::var("PINNACLE_CONFIG_DIR").unwrap_or_else(|_| { let config_dir = std::env::var("PINNACLE_CONFIG_DIR")
let default_config_dir = .ok()
std::env::var("XDG_CONFIG_HOME").unwrap_or("~/.config".to_string()); .and_then(|s| Some(PathBuf::from(shellexpand::full(&s).ok()?.to_string())));
PathBuf::from(default_config_dir) config_dir.unwrap_or(crate::XDG_BASE_DIRS.get_config_home())
.join("pinnacle")
.to_string_lossy()
.to_string()
});
PathBuf::from(shellexpand::tilde(&config_dir).to_string())
} }
/// This should be called *after* you have created the [`PinnacleSocketSource`] to ensure /// This should be called *after* you have created the [`PinnacleSocketSource`] to ensure
@ -422,7 +415,9 @@ fn start_config(metaconfig: Metaconfig, config_dir: &Path) -> anyhow::Result<Con
let mut command = metaconfig.command.split(' '); let mut command = metaconfig.command.split(' ');
let arg1 = command.next().expect("empty command"); let arg1 = command
.next()
.context("command in metaconfig.toml was empty")?;
std::env::set_var("PINNACLE_DIR", std::env::current_dir()?); std::env::set_var("PINNACLE_DIR", std::env::current_dir()?);
@ -457,6 +452,8 @@ fn start_config(metaconfig: Metaconfig, config_dir: &Path) -> anyhow::Result<Con
.args(command) .args(command)
.envs(envs) .envs(envs)
.current_dir(config_dir) .current_dir(config_dir)
.stdout(async_process::Stdio::inherit())
.stderr(async_process::Stdio::inherit())
.spawn() .spawn()
.expect("failed to spawn config"); .expect("failed to spawn config");