implement attribute type conversions
This commit is contained in:
parent
2155ec0fd8
commit
aefa9016f1
8 changed files with 146 additions and 52 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -156,6 +156,16 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59561a8b3968ba4bda0c46f42e0568507c5d26e94c3b6f2a0c730cbecd83ff3a"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.60"
|
||||
|
@ -312,6 +322,8 @@ name = "eww"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"calloop",
|
||||
"crossbeam-channel",
|
||||
"derive_more",
|
||||
"extend",
|
||||
"gdk",
|
||||
|
@ -320,9 +332,9 @@ dependencies = [
|
|||
"grass",
|
||||
"gtk",
|
||||
"hocon",
|
||||
"hotwatch",
|
||||
"ipc-channel",
|
||||
"maplit",
|
||||
"notify",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"serde",
|
||||
|
@ -798,6 +810,16 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hotwatch"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba0add391e9cd7d19c29024617a44df79c867ab003bce7f3224c1636595ec740"
|
||||
dependencies = [
|
||||
"log",
|
||||
"notify",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.0"
|
||||
|
@ -1002,6 +1024,18 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "4.2.3"
|
||||
|
|
|
@ -23,7 +23,9 @@ ipc-channel="0.14.1"
|
|||
serde = {version = "1.0", features = ["derive"]}
|
||||
extend = "0.3.0"
|
||||
grass = "0.10"
|
||||
notify = "4.0"
|
||||
hotwatch = "0.4"
|
||||
calloop = "0.6"
|
||||
crossbeam-channel = "0.4"
|
||||
|
||||
#thiserror = "1.0"
|
||||
|
||||
|
|
|
@ -108,5 +108,6 @@ impl EwwWindowDefinition {
|
|||
}
|
||||
|
||||
pub fn parse_hocon(s: &str) -> Result<Hocon> {
|
||||
Ok(HoconLoader::new().load_str(s)?.hocon()?)
|
||||
let s = s.trim();
|
||||
Ok(HoconLoader::new().strict().load_str(s)?.hocon()?)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use anyhow::*;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::value::{AttrValue, PrimitiveValue};
|
||||
use crate::value::{AttrValue, CommandPollingUse, PrimitiveValue};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EwwState {
|
||||
on_change_handlers: HashMap<String, Vec<Box<dyn Fn(PrimitiveValue) + 'static>>>,
|
||||
polling_commands: Vec<(CommandPollingUse, Box<dyn Fn(PrimitiveValue) + 'static>)>,
|
||||
state: HashMap<String, PrimitiveValue>,
|
||||
}
|
||||
|
||||
|
@ -54,6 +57,13 @@ impl EwwState {
|
|||
false
|
||||
}
|
||||
}
|
||||
AttrValue::CommandPolling(command_polling_use) => {
|
||||
self.polling_commands
|
||||
.push((command_polling_use.clone(), Box::new(set_value.clone())));
|
||||
// TODO how do i handle commands needing to be run on the first resolve? this is an issue,....
|
||||
//self.resolve(local_env, &value.into(), set_value);
|
||||
true
|
||||
}
|
||||
AttrValue::Concrete(value) => {
|
||||
set_value(value.clone());
|
||||
true
|
||||
|
@ -112,3 +122,8 @@ impl EwwState {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_command(cmd: &str) -> Result<PrimitiveValue> {
|
||||
let output = String::from_utf8(Command::new("/bin/bash").arg("-c").arg(cmd).output()?.stdout)?;
|
||||
Ok(PrimitiveValue::from(output))
|
||||
}
|
||||
|
|
54
src/main.rs
54
src/main.rs
|
@ -5,11 +5,9 @@ extern crate gtk;
|
|||
|
||||
use anyhow::*;
|
||||
use gdk::*;
|
||||
use gio::prelude::*;
|
||||
use grass;
|
||||
use gtk::prelude::*;
|
||||
use ipc_channel::ipc;
|
||||
use notify::{self, Watcher};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path;
|
||||
|
@ -21,6 +19,7 @@ pub mod value;
|
|||
pub mod widgets;
|
||||
|
||||
use eww_state::*;
|
||||
use hotwatch;
|
||||
use value::PrimitiveValue;
|
||||
|
||||
#[macro_export]
|
||||
|
@ -98,11 +97,18 @@ fn initialize_server(opts: Opt) -> Result<()> {
|
|||
.to_path_buf();
|
||||
let scss_file_path = config_dir.join("eww.scss");
|
||||
|
||||
let (watcher_tx, watcher_rx) = std::sync::mpsc::channel();
|
||||
let (watcher_tx, watcher_rx) = crossbeam_channel::unbounded();
|
||||
|
||||
let mut file_watcher = notify::watcher(watcher_tx, std::time::Duration::from_millis(100))?;
|
||||
file_watcher.watch(config_file_path.clone(), notify::RecursiveMode::NonRecursive)?;
|
||||
if let Err(e) = file_watcher.watch(scss_file_path.clone(), notify::RecursiveMode::NonRecursive) {
|
||||
let mut hotwatch = hotwatch::Hotwatch::new()?;
|
||||
hotwatch.watch(
|
||||
config_file_path.clone(),
|
||||
glib::clone!(@strong watcher_tx => move |evt| watcher_tx.send(evt).unwrap()),
|
||||
)?;
|
||||
|
||||
if let Err(e) = hotwatch.watch(
|
||||
scss_file_path.clone(),
|
||||
glib::clone!(@strong watcher_tx => move |evt| watcher_tx.send(evt).unwrap()),
|
||||
) {
|
||||
eprintln!("WARN: error while loading CSS file for hot-reloading: \n{}", e)
|
||||
}
|
||||
|
||||
|
@ -113,20 +119,21 @@ fn initialize_server(opts: Opt) -> Result<()> {
|
|||
let eww_css =
|
||||
grass::from_string(scss_content, &grass::Options::default()).map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?;
|
||||
|
||||
gtk::init()?;
|
||||
|
||||
let mut app = App {
|
||||
eww_state: EwwState::from_default_vars(eww_config.get_default_vars().clone()),
|
||||
eww_config,
|
||||
eww_css: eww_css.clone(),
|
||||
windows: HashMap::new(),
|
||||
css_provider: gtk::CssProvider::new(),
|
||||
};
|
||||
gtk::init()?;
|
||||
|
||||
let css_provider = gtk::CssProvider::new();
|
||||
css_provider.load_from_data(eww_css.as_bytes())?;
|
||||
gdk::Screen::get_default().map(|screen| {
|
||||
gtk::StyleContext::add_provider_for_screen(&screen, &css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
});
|
||||
|
||||
app.load_css(&eww_css)?;
|
||||
app.handle_user_command(opts)?;
|
||||
|
||||
let (send, recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||
|
@ -152,15 +159,13 @@ fn initialize_server(opts: Opt) -> Result<()> {
|
|||
std::thread::spawn(move || {
|
||||
while let Ok(event) = watcher_rx.recv() {
|
||||
let result: Result<_> = try {
|
||||
dbg!(&event);
|
||||
match event {
|
||||
notify::DebouncedEvent::Write(updated_path) | notify::DebouncedEvent::NoticeWrite(updated_path)
|
||||
if updated_path == config_file_path =>
|
||||
{
|
||||
hotwatch::Event::Write(path) | hotwatch::Event::NoticeWrite(path) if path == config_file_path => {
|
||||
let config_content = std::fs::read_to_string(path).unwrap_or_default();
|
||||
let new_eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(&config_content)?)?;
|
||||
send.send(EwwEvent::ReloadConfig(new_eww_config))?;
|
||||
}
|
||||
notify::DebouncedEvent::Write(updated_path) if updated_path == scss_file_path => {
|
||||
hotwatch::Event::Write(path) if path == scss_file_path => {
|
||||
let scss_content = std::fs::read_to_string(scss_file_path.clone()).unwrap_or_default();
|
||||
let eww_css = grass::from_string(scss_content, &grass::Options::default())
|
||||
.map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?;
|
||||
|
@ -170,8 +175,7 @@ fn initialize_server(opts: Opt) -> Result<()> {
|
|||
}
|
||||
};
|
||||
if let Err(err) = result {
|
||||
eprintln!("error in server thread: {}", err);
|
||||
std::process::exit(1);
|
||||
eprintln!("error in file watcher thread: {}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -192,6 +196,7 @@ struct App {
|
|||
eww_config: config::EwwConfig,
|
||||
eww_css: String,
|
||||
windows: HashMap<String, gtk::Window>,
|
||||
css_provider: gtk::CssProvider,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
@ -231,7 +236,9 @@ impl App {
|
|||
window.set_type_hint(gdk::WindowTypeHint::Dock);
|
||||
window.set_position(gtk::WindowPosition::Center);
|
||||
window.set_default_size(window_def.size.0, window_def.size.1);
|
||||
window.set_size_request(window_def.size.0, window_def.size.1);
|
||||
window.set_decorated(false);
|
||||
window.set_resizable(false);
|
||||
|
||||
let empty_local_state = HashMap::new();
|
||||
let root_widget = &widgets::element_to_gtk_thing(
|
||||
|
@ -259,18 +266,21 @@ impl App {
|
|||
|
||||
fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> {
|
||||
self.eww_config = config;
|
||||
|
||||
let windows = self.windows.clone();
|
||||
for (window_name, window) in windows {
|
||||
dbg!(&window_name);
|
||||
window.close();
|
||||
window.hide();
|
||||
self.open_window(&window_name)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reload_css(&mut self, css: String) -> Result<()> {
|
||||
for window in self.windows.values() {}
|
||||
fn load_css(&mut self, css: &str) -> Result<()> {
|
||||
self.css_provider.load_from_data(css.as_bytes())?;
|
||||
//self.css_provider.load_from_data(eww_css.as_bytes())?;
|
||||
//gdk::Screen::get_default().map(|screen| {
|
||||
//gtk::StyleContext::add_provider_for_screen(&screen, &self.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
//});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -279,7 +289,7 @@ impl App {
|
|||
match event {
|
||||
EwwEvent::UserCommand(command) => self.handle_user_command(command)?,
|
||||
EwwEvent::ReloadConfig(config) => self.reload_all_windows(config)?,
|
||||
EwwEvent::ReloadCss(css) => self.reload_css(css)?,
|
||||
EwwEvent::ReloadCss(css) => self.load_css(&css)?,
|
||||
}
|
||||
};
|
||||
if let Err(err) = result {
|
||||
|
|
58
src/value.rs
58
src/value.rs
|
@ -12,6 +12,12 @@ pub enum PrimitiveValue {
|
|||
Boolean(bool),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct PollingCommandValue {
|
||||
command: String,
|
||||
interval: std::time::Duration,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for PrimitiveValue {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
@ -30,28 +36,21 @@ fn remove_surrounding(s: &str, surround: char) -> &str {
|
|||
impl TryFrom<PrimitiveValue> for String {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(x: PrimitiveValue) -> Result<Self> {
|
||||
match x {
|
||||
PrimitiveValue::String(x) => Ok(x),
|
||||
_ => return Err(anyhow!("'{:?}' is not a string", x.clone())),
|
||||
}
|
||||
x.as_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PrimitiveValue> for f64 {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(x: PrimitiveValue) -> Result<Self> {
|
||||
try_match!(PrimitiveValue::Number(x) = &x)
|
||||
.map_err(|_| anyhow!("'{:?}' is not a number", &x))
|
||||
.map(|&x| x)
|
||||
x.as_f64()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PrimitiveValue> for bool {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(x: PrimitiveValue) -> Result<Self> {
|
||||
try_match!(PrimitiveValue::Boolean(x) = &x)
|
||||
.map_err(|_| anyhow!("'{:?}' is not a bool", &x))
|
||||
.map(|&x| x)
|
||||
x.as_bool()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,18 +61,30 @@ impl From<&str> for PrimitiveValue {
|
|||
}
|
||||
|
||||
impl PrimitiveValue {
|
||||
pub fn as_string(&self) -> Result<&String> {
|
||||
try_match!(PrimitiveValue::String(x) = self).map_err(|x| anyhow!("{:?} is not a string", x))
|
||||
pub fn as_string(&self) -> Result<String> {
|
||||
match self {
|
||||
PrimitiveValue::String(x) => Ok(x.clone()),
|
||||
PrimitiveValue::Number(x) => Ok(format!("{}", x)),
|
||||
PrimitiveValue::Boolean(x) => Ok(format!("{}", x)),
|
||||
}
|
||||
}
|
||||
pub fn as_f64(&self) -> Result<f64> {
|
||||
try_match!(PrimitiveValue::Number(x) = self)
|
||||
.map_err(|x| anyhow!("{:?} is not an f64", x))
|
||||
.map(|&x| x)
|
||||
match self {
|
||||
PrimitiveValue::Number(x) => Ok(*x),
|
||||
PrimitiveValue::String(x) => x
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert string {:?} to f64: {}", &self, e)),
|
||||
_ => Err(anyhow!("{:?} is not an f64", &self)),
|
||||
}
|
||||
}
|
||||
pub fn as_bool(&self) -> Result<bool> {
|
||||
try_match!(PrimitiveValue::Boolean(x) = self)
|
||||
.map_err(|x| anyhow!("{:?} is not a bool", x))
|
||||
.map(|&x| x)
|
||||
match self {
|
||||
PrimitiveValue::Boolean(x) => Ok(*x),
|
||||
PrimitiveValue::String(x) => x
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert string {:?} to bool: {}", &self, e)),
|
||||
_ => Err(anyhow!("{:?} is not a string", &self)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +99,7 @@ impl std::convert::TryFrom<&Hocon> for PrimitiveValue {
|
|||
Hocon::Integer(n) => PrimitiveValue::Number(*n as f64),
|
||||
Hocon::Real(n) => PrimitiveValue::Number(*n as f64),
|
||||
Hocon::Boolean(b) => PrimitiveValue::Boolean(*b),
|
||||
_ => return Err(anyhow!("cannot convert {} to config::PrimitiveValue")),
|
||||
_ => return Err(anyhow!("cannot convert {} to config::ConcreteValue")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -97,10 +108,17 @@ impl std::convert::TryFrom<&Hocon> for PrimitiveValue {
|
|||
pub enum AttrValue {
|
||||
Concrete(PrimitiveValue),
|
||||
VarRef(String),
|
||||
CommandPolling(CommandPollingUse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CommandPollingUse {
|
||||
command: String,
|
||||
interval: std::time::Duration,
|
||||
}
|
||||
|
||||
impl AttrValue {
|
||||
pub fn as_string(&self) -> Result<&String> {
|
||||
pub fn as_string(&self) -> Result<String> {
|
||||
try_match!(AttrValue::Concrete(x) = self)
|
||||
.map_err(|e| anyhow!("{:?} is not a string", e))?
|
||||
.as_string()
|
||||
|
|
|
@ -30,7 +30,7 @@ pub fn element_to_gtk_thing(
|
|||
element: &element::ElementUse,
|
||||
) -> Result<gtk::Widget> {
|
||||
match element {
|
||||
element::ElementUse::Text(text) => Ok(gtk::Label::new(Some(text.as_string()?)).upcast()), // TODO this should use resolve
|
||||
element::ElementUse::Text(text) => Ok(gtk::Label::new(Some(&text.as_string()?)).upcast()), // TODO this should use resolve
|
||||
element::ElementUse::Widget(widget) => {
|
||||
let gtk_container = build_gtk_widget(widget_definitions, eww_state, local_env, widget)?;
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Wi
|
|||
resolve!(bargs, gtk_widget, {
|
||||
resolve_str => "class" => |v| gtk_widget.get_style_context().add_class(&v),
|
||||
resolve_bool => "active" = true => |v| gtk_widget.set_sensitive(v),
|
||||
resolve_str => "valign" => |v| gtk_widget.set_valign(parse_align(&v)),
|
||||
resolve_str => "halign" => |v| gtk_widget.set_halign(parse_align(&v)),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -51,9 +53,9 @@ pub(super) fn resolve_orientable_attrs(bargs: &mut BuilderArgs, gtk_widget: >k
|
|||
|
||||
pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<Option<gtk::Widget>> {
|
||||
let gtk_widget = match bargs.widget.name.as_str() {
|
||||
"layout" => build_gtk_layout(bargs)?.upcast(),
|
||||
"slider" => build_gtk_scale(bargs)?.upcast(),
|
||||
"image" => build_gtk_image(bargs)?.upcast(),
|
||||
"layout" => build_gtk_layout(bargs)?.upcast(),
|
||||
"button" => build_gtk_button(bargs)?.upcast(),
|
||||
"label" => build_gtk_label(bargs)?.upcast(),
|
||||
_ => return Ok(None),
|
||||
|
@ -69,7 +71,8 @@ fn build_gtk_scale(bargs: &mut BuilderArgs) -> Result<gtk::Scale> {
|
|||
Some(>k::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)),
|
||||
);
|
||||
resolve!(bargs, gtk_widget, {
|
||||
resolve_bool => "flipped" => |v| gtk_widget.set_inverted(v)
|
||||
resolve_bool => "flipped" => |v| gtk_widget.set_inverted(v),
|
||||
resolve_bool => "draw-value" = false => |v| gtk_widget.set_draw_value(v),
|
||||
});
|
||||
Ok(gtk_widget)
|
||||
}
|
||||
|
@ -94,9 +97,9 @@ fn build_gtk_image(bargs: &mut BuilderArgs) -> Result<gtk::Image> {
|
|||
fn build_gtk_layout(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
|
||||
let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
resolve!(bargs, gtk_widget, {
|
||||
resolve_f64 => "spacing" = 10.0 => |v| gtk_widget.set_spacing(v as i32),
|
||||
resolve_f64 => "spacing" = 0.0 => |v| gtk_widget.set_spacing(v as i32),
|
||||
resolve_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)),
|
||||
|
||||
resolve_bool => "homogenous" = true => |v| gtk_widget.set_homogeneous(v),
|
||||
});
|
||||
Ok(gtk_widget)
|
||||
}
|
||||
|
@ -115,3 +118,14 @@ fn parse_orientation(o: &str) -> gtk::Orientation {
|
|||
_ => gtk::Orientation::Horizontal,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_align(o: &str) -> gtk::Align {
|
||||
match o {
|
||||
"fill" => gtk::Align::Fill,
|
||||
"baseline" => gtk::Align::Baseline,
|
||||
"center" => gtk::Align::Center,
|
||||
"start" => gtk::Align::Start,
|
||||
"end" => gtk::Align::End,
|
||||
_ => gtk::Align::Start,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue