use crate::layer_properties::popup::{PopupAnchor, PopupGravity};
use smithay_client_toolkit::{
shell::{
wlr_layer::{Anchor, KeyboardInteractivity, Layer},
xdg::popup::Popup,
},
shm::slot::{Buffer, SlotPool},
};
use std::{
cell::{Cell, RefCell},
fs,
io::Write,
os::unix::net::UnixDatagram,
path::Path,
rc::Rc,
sync::Mutex,
};
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_subscriber::{
EnvFilter, Layer as TracingTraitLayer,
filter::Filtered,
fmt::{self, format::DefaultFields},
layer::{Layered, SubscriberExt},
registry::Registry,
reload::Layer as LoadLayer,
};
pub struct PopupCore {
pub(crate) pool: Rc<RefCell<SlotPool>>,
pub(crate) popup: Popup,
pub(crate) popup_conf: PopupConf,
pub(crate) buffer: Buffer,
}
pub struct PopupConf {
pub width: u32,
pub height: u32,
pub anchor: PopupAnchor,
pub gravity: PopupGravity,
pub anchor_rect: (i32, i32, i32, i32),
}
impl From<u32> for Dimension {
fn from(value: u32) -> Self {
Dimension::Pixel(value)
}
}
#[derive(Debug, Clone, Default)]
pub enum Dimension {
#[default]
Full,
Percentage(u32),
Pixel(u32),
}
#[derive(Debug, Clone)]
pub struct WindowConf {
pub(super) width: Dimension,
pub(super) height: Dimension,
pub(super) evaluated_width: u32,
pub(super) evaluated_height: u32,
pub(super) anchor: [Option<Anchor>; 4],
pub(super) margin: (i32, i32, i32, i32),
pub(super) layer_type: Layer,
pub(super) board_interactivity: Cell<KeyboardInteractivity>,
pub(super) exclusive_zone: Option<i32>,
pub(super) monitor_name: Option<String>,
pub(super) natural_scroll: bool,
}
impl WindowConf {
pub fn builder() -> WindowConfBuilder {
WindowConfBuilder::default()
}
}
#[derive(Default)]
pub struct WindowConfBuilder {
max_width: Dimension,
max_height: Dimension,
anchor: [Option<Anchor>; 4],
margin: (i32, i32, i32, i32),
layer_type: Option<Layer>,
board_interactivity: KeyboardInteractivity,
exclusive_zone: Option<i32>,
monitor_name: Option<String>,
natural_scroll: bool,
}
impl WindowConfBuilder {
pub fn width<I: Into<Dimension>>(&mut self, width: I) -> &mut Self {
let new = self;
new.max_width = width.into();
new
}
pub fn height<I: Into<Dimension>>(&mut self, height: I) -> &mut Self {
let x = self;
x.max_height = height.into();
x
}
pub fn anchor_1(&mut self, anchor: Anchor) -> &mut Self {
let x = self;
x.anchor[0] = Some(anchor);
x
}
pub fn anchor_2(&mut self, anchor: Anchor) -> &mut Self {
let x = self;
x.anchor[1] = Some(anchor);
x
}
pub fn anchor_3(&mut self, anchor: Anchor) -> &mut Self {
let x = self;
x.anchor[2] = Some(anchor);
x
}
pub fn anchor_4(&mut self, anchor: Anchor) -> &mut Self {
let x = self;
x.anchor[3] = Some(anchor);
x
}
pub fn margins(&mut self, top: i32, right: i32, bottom: i32, left: i32) -> &mut Self {
let x = self;
x.margin = (top, right, bottom, left);
x
}
pub fn layer_type(&mut self, layer: Layer) -> &mut Self {
let x = self;
x.layer_type = Some(layer);
x
}
pub fn board_interactivity(&mut self, board: KeyboardInteractivity) -> &mut Self {
let x = self;
x.board_interactivity = board;
x
}
pub fn exclusive_zone(&mut self, dimension: i32) -> &mut Self {
let x = self;
x.exclusive_zone = Some(dimension);
x
}
pub fn monitor(&mut self, name: String) -> &mut Self {
let x = self;
x.monitor_name = Some(name);
x
}
pub fn natural_scroll(&mut self, scroll: bool) -> &mut Self {
let x = self;
x.natural_scroll = scroll;
x
}
pub fn build(&self) -> Result<WindowConf, Box<dyn std::error::Error>> {
Ok(WindowConf {
width: if let Dimension::Percentage(x) = self.max_width
&& x == 0
{
return Err("width is zero in percentage".into());
} else if let Dimension::Pixel(y) = self.max_width
&& y == 0
{
return Err("width is zero in pixel".into());
} else {
self.max_width.clone()
},
height: if let Dimension::Percentage(x) = self.max_height
&& x == 0
{
return Err("height is zero in percentage".into());
} else if let Dimension::Pixel(y) = self.max_height
&& y == 0
{
return Err("height is zero in pixel".into());
} else {
self.max_height.clone()
},
evaluated_width: 0,
evaluated_height: 0,
anchor: self.anchor,
margin: self.margin,
layer_type: match self.layer_type {
None => Layer::Top,
Some(val) => val,
},
board_interactivity: Cell::new(self.board_interactivity),
exclusive_zone: self.exclusive_zone,
monitor_name: {
let needs_monitor =
matches!(self.max_width, Dimension::Full | Dimension::Percentage(_))
|| matches!(self.max_height, Dimension::Full | Dimension::Percentage(_));
if needs_monitor && self.monitor_name.is_none() {
return Err(
"Provide explicit monitor name if using Full or Percentage dimensions"
.into(),
);
} else {
self.monitor_name.clone()
}
},
natural_scroll: self.natural_scroll,
})
}
}
pub(crate) type HomeHandle = tracing_subscriber::reload::Handle<
Filtered<
tracing_subscriber::fmt::Layer<
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
RollingFileAppender,
>,
EnvFilter,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
>,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
>,
DefaultFields,
tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Full, ()>,
std::sync::Mutex<SocketWriter>,
>,
EnvFilter,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
RollingFileAppender,
>,
EnvFilter,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
>,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
>,
>,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
DefaultFields,
tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Full, ()>,
RollingFileAppender,
>,
EnvFilter,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
>,
Layered<
Filtered<
tracing_subscriber::fmt::Layer<
Registry,
DefaultFields,
tracing_subscriber::fmt::format::Format<
tracing_subscriber::fmt::format::Full,
(),
>,
>,
EnvFilter,
Registry,
>,
Registry,
>,
>,
>;
pub(crate) fn set_up_tracing(widget_name: &str) -> HomeHandle {
let runtime_dir = std::env::var("XDG_RUNTIME_DIR").expect("runtime dir is not set");
let logging_dir = runtime_dir + "/spell/";
let socket_dir = logging_dir.clone() + "/spell.sock";
let _ = fs::create_dir(Path::new(&logging_dir));
let _ = fs::remove_file(&socket_dir);
let stream = UnixDatagram::unbound().unwrap();
stream
.set_nonblocking(true)
.expect("Non blocking couldn't be set");
let writer = RollingFileAppender::builder()
.rotation(Rotation::HOURLY) .filename_prefix(widget_name) .filename_suffix("log") .build(&logging_dir) .expect("initializing rolling file appender failed");
let layer_writer = fmt::layer()
.without_time()
.with_target(false)
.with_writer(writer)
.with_ansi(false)
.with_filter(EnvFilter::new("spell_framework=trace,info"));
let layer_socket = fmt::Layer::default()
.without_time()
.with_target(false)
.with_writer(Mutex::new(SocketWriter::new(stream)))
.with_filter(EnvFilter::new("spell_framework=info, warn"));
let (layer_env, handle) = LoadLayer::new(layer_socket);
let subs = tracing_subscriber::registry()
.with(
fmt::layer()
.without_time()
.with_target(false)
.with_filter(EnvFilter::new("spell_framework=info, warn")),
)
.with(layer_writer)
.with(layer_env);
let _ = tracing::subscriber::set_global_default(subs);
handle
}
pub(crate) struct SocketWriter {
socket: UnixDatagram,
}
impl SocketWriter {
fn new(socket: UnixDatagram) -> Self {
SocketWriter { socket }
}
}
impl Write for SocketWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let runtime_dir = std::env::var("XDG_RUNTIME_DIR").expect("runtime dir is not set");
let logging_dir = runtime_dir + "/spell/";
let socket_dir = logging_dir.clone() + "/spell.sock";
self.socket.send_to(buf, Path::new(&socket_dir))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[allow(dead_code)]
pub enum LayerConf {
Window(WindowConf),
Windows(Vec<WindowConf>),
Lock(u32, u32),
}