implement attribute type conversions

This commit is contained in:
elkowar 2020-09-27 12:29:39 +02:00
parent 2155ec0fd8
commit aefa9016f1
8 changed files with 146 additions and 52 deletions

36
Cargo.lock generated
View file

@ -156,6 +156,16 @@ dependencies = [
"system-deps", "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]] [[package]]
name = "cc" name = "cc"
version = "1.0.60" version = "1.0.60"
@ -312,6 +322,8 @@ name = "eww"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"calloop",
"crossbeam-channel",
"derive_more", "derive_more",
"extend", "extend",
"gdk", "gdk",
@ -320,9 +332,9 @@ dependencies = [
"grass", "grass",
"gtk", "gtk",
"hocon", "hocon",
"hotwatch",
"ipc-channel", "ipc-channel",
"maplit", "maplit",
"notify",
"pretty_assertions", "pretty_assertions",
"regex", "regex",
"serde", "serde",
@ -798,6 +810,16 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "hotwatch"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba0add391e9cd7d19c29024617a44df79c867ab003bce7f3224c1636595ec740"
dependencies = [
"log",
"notify",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.6.0" version = "1.6.0"
@ -1002,6 +1024,18 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "nom" name = "nom"
version = "4.2.3" version = "4.2.3"

View file

@ -23,7 +23,9 @@ ipc-channel="0.14.1"
serde = {version = "1.0", features = ["derive"]} serde = {version = "1.0", features = ["derive"]}
extend = "0.3.0" extend = "0.3.0"
grass = "0.10" grass = "0.10"
notify = "4.0" hotwatch = "0.4"
calloop = "0.6"
crossbeam-channel = "0.4"
#thiserror = "1.0" #thiserror = "1.0"

View file

@ -108,5 +108,6 @@ impl EwwWindowDefinition {
} }
pub fn parse_hocon(s: &str) -> Result<Hocon> { 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()?)
} }

View file

@ -1,12 +1,15 @@
use anyhow::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::convert::TryInto; use std::convert::TryInto;
use std::process::Command;
use crate::value::{AttrValue, PrimitiveValue}; use crate::value::{AttrValue, CommandPollingUse, PrimitiveValue};
#[derive(Default)] #[derive(Default)]
pub struct EwwState { pub struct EwwState {
on_change_handlers: HashMap<String, Vec<Box<dyn Fn(PrimitiveValue) + 'static>>>, on_change_handlers: HashMap<String, Vec<Box<dyn Fn(PrimitiveValue) + 'static>>>,
polling_commands: Vec<(CommandPollingUse, Box<dyn Fn(PrimitiveValue) + 'static>)>,
state: HashMap<String, PrimitiveValue>, state: HashMap<String, PrimitiveValue>,
} }
@ -54,6 +57,13 @@ impl EwwState {
false 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) => { AttrValue::Concrete(value) => {
set_value(value.clone()); set_value(value.clone());
true 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))
}

View file

@ -5,11 +5,9 @@ extern crate gtk;
use anyhow::*; use anyhow::*;
use gdk::*; use gdk::*;
use gio::prelude::*;
use grass; use grass;
use gtk::prelude::*; use gtk::prelude::*;
use ipc_channel::ipc; use ipc_channel::ipc;
use notify::{self, Watcher};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path; use std::path;
@ -21,6 +19,7 @@ pub mod value;
pub mod widgets; pub mod widgets;
use eww_state::*; use eww_state::*;
use hotwatch;
use value::PrimitiveValue; use value::PrimitiveValue;
#[macro_export] #[macro_export]
@ -98,11 +97,18 @@ fn initialize_server(opts: Opt) -> Result<()> {
.to_path_buf(); .to_path_buf();
let scss_file_path = config_dir.join("eww.scss"); 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))?; let mut hotwatch = hotwatch::Hotwatch::new()?;
file_watcher.watch(config_file_path.clone(), notify::RecursiveMode::NonRecursive)?; hotwatch.watch(
if let Err(e) = file_watcher.watch(scss_file_path.clone(), notify::RecursiveMode::NonRecursive) { 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) 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 = let eww_css =
grass::from_string(scss_content, &grass::Options::default()).map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?; grass::from_string(scss_content, &grass::Options::default()).map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?;
gtk::init()?;
let mut app = App { let mut app = App {
eww_state: EwwState::from_default_vars(eww_config.get_default_vars().clone()), eww_state: EwwState::from_default_vars(eww_config.get_default_vars().clone()),
eww_config, eww_config,
eww_css: eww_css.clone(), eww_css: eww_css.clone(),
windows: HashMap::new(), 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| { 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)?; app.handle_user_command(opts)?;
let (send, recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let (send, recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
@ -152,15 +159,13 @@ fn initialize_server(opts: Opt) -> Result<()> {
std::thread::spawn(move || { std::thread::spawn(move || {
while let Ok(event) = watcher_rx.recv() { while let Ok(event) = watcher_rx.recv() {
let result: Result<_> = try { let result: Result<_> = try {
dbg!(&event);
match event { match event {
notify::DebouncedEvent::Write(updated_path) | notify::DebouncedEvent::NoticeWrite(updated_path) hotwatch::Event::Write(path) | hotwatch::Event::NoticeWrite(path) if path == config_file_path => {
if updated_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)?)?; let new_eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(&config_content)?)?;
send.send(EwwEvent::ReloadConfig(new_eww_config))?; 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 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()) let eww_css = grass::from_string(scss_content, &grass::Options::default())
.map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?; .map_err(|err| anyhow!("SCSS parsing error: {:?}", err))?;
@ -170,8 +175,7 @@ fn initialize_server(opts: Opt) -> Result<()> {
} }
}; };
if let Err(err) = result { if let Err(err) = result {
eprintln!("error in server thread: {}", err); eprintln!("error in file watcher thread: {}", err);
std::process::exit(1);
} }
} }
}); });
@ -192,6 +196,7 @@ struct App {
eww_config: config::EwwConfig, eww_config: config::EwwConfig,
eww_css: String, eww_css: String,
windows: HashMap<String, gtk::Window>, windows: HashMap<String, gtk::Window>,
css_provider: gtk::CssProvider,
} }
impl App { impl App {
@ -231,7 +236,9 @@ impl App {
window.set_type_hint(gdk::WindowTypeHint::Dock); window.set_type_hint(gdk::WindowTypeHint::Dock);
window.set_position(gtk::WindowPosition::Center); window.set_position(gtk::WindowPosition::Center);
window.set_default_size(window_def.size.0, window_def.size.1); 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_decorated(false);
window.set_resizable(false);
let empty_local_state = HashMap::new(); let empty_local_state = HashMap::new();
let root_widget = &widgets::element_to_gtk_thing( let root_widget = &widgets::element_to_gtk_thing(
@ -259,18 +266,21 @@ impl App {
fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> { fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> {
self.eww_config = config; self.eww_config = config;
let windows = self.windows.clone(); let windows = self.windows.clone();
for (window_name, window) in windows { for (window_name, window) in windows {
dbg!(&window_name);
window.close(); window.close();
window.hide();
self.open_window(&window_name)?; self.open_window(&window_name)?;
} }
Ok(()) Ok(())
} }
fn reload_css(&mut self, css: String) -> Result<()> { fn load_css(&mut self, css: &str) -> Result<()> {
for window in self.windows.values() {} 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(()) Ok(())
} }
@ -279,7 +289,7 @@ impl App {
match event { match event {
EwwEvent::UserCommand(command) => self.handle_user_command(command)?, EwwEvent::UserCommand(command) => self.handle_user_command(command)?,
EwwEvent::ReloadConfig(config) => self.reload_all_windows(config)?, 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 { if let Err(err) = result {

View file

@ -12,6 +12,12 @@ pub enum PrimitiveValue {
Boolean(bool), Boolean(bool),
} }
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PollingCommandValue {
command: String,
interval: std::time::Duration,
}
impl std::str::FromStr for PrimitiveValue { impl std::str::FromStr for PrimitiveValue {
type Err = anyhow::Error; type Err = anyhow::Error;
@ -30,28 +36,21 @@ fn remove_surrounding(s: &str, surround: char) -> &str {
impl TryFrom<PrimitiveValue> for String { impl TryFrom<PrimitiveValue> for String {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> { fn try_from(x: PrimitiveValue) -> Result<Self> {
match x { x.as_string()
PrimitiveValue::String(x) => Ok(x),
_ => return Err(anyhow!("'{:?}' is not a string", x.clone())),
}
} }
} }
impl TryFrom<PrimitiveValue> for f64 { impl TryFrom<PrimitiveValue> for f64 {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> { fn try_from(x: PrimitiveValue) -> Result<Self> {
try_match!(PrimitiveValue::Number(x) = &x) x.as_f64()
.map_err(|_| anyhow!("'{:?}' is not a number", &x))
.map(|&x| x)
} }
} }
impl TryFrom<PrimitiveValue> for bool { impl TryFrom<PrimitiveValue> for bool {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> { fn try_from(x: PrimitiveValue) -> Result<Self> {
try_match!(PrimitiveValue::Boolean(x) = &x) x.as_bool()
.map_err(|_| anyhow!("'{:?}' is not a bool", &x))
.map(|&x| x)
} }
} }
@ -62,18 +61,30 @@ impl From<&str> for PrimitiveValue {
} }
impl PrimitiveValue { impl PrimitiveValue {
pub fn as_string(&self) -> Result<&String> { pub fn as_string(&self) -> Result<String> {
try_match!(PrimitiveValue::String(x) = self).map_err(|x| anyhow!("{:?} is not a string", x)) 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> { pub fn as_f64(&self) -> Result<f64> {
try_match!(PrimitiveValue::Number(x) = self) match self {
.map_err(|x| anyhow!("{:?} is not an f64", x)) PrimitiveValue::Number(x) => Ok(*x),
.map(|&x| 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> { pub fn as_bool(&self) -> Result<bool> {
try_match!(PrimitiveValue::Boolean(x) = self) match self {
.map_err(|x| anyhow!("{:?} is not a bool", x)) PrimitiveValue::Boolean(x) => Ok(*x),
.map(|&x| 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::Integer(n) => PrimitiveValue::Number(*n as f64),
Hocon::Real(n) => PrimitiveValue::Number(*n as f64), Hocon::Real(n) => PrimitiveValue::Number(*n as f64),
Hocon::Boolean(b) => PrimitiveValue::Boolean(*b), 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 { pub enum AttrValue {
Concrete(PrimitiveValue), Concrete(PrimitiveValue),
VarRef(String), VarRef(String),
CommandPolling(CommandPollingUse),
}
#[derive(Clone, Debug, PartialEq)]
pub struct CommandPollingUse {
command: String,
interval: std::time::Duration,
} }
impl AttrValue { impl AttrValue {
pub fn as_string(&self) -> Result<&String> { pub fn as_string(&self) -> Result<String> {
try_match!(AttrValue::Concrete(x) = self) try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not a string", e))? .map_err(|e| anyhow!("{:?} is not a string", e))?
.as_string() .as_string()

View file

@ -30,7 +30,7 @@ pub fn element_to_gtk_thing(
element: &element::ElementUse, element: &element::ElementUse,
) -> Result<gtk::Widget> { ) -> Result<gtk::Widget> {
match element { 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) => { element::ElementUse::Widget(widget) => {
let gtk_container = build_gtk_widget(widget_definitions, eww_state, local_env, widget)?; let gtk_container = build_gtk_widget(widget_definitions, eww_state, local_env, widget)?;

View file

@ -16,6 +16,8 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: &gtk::Wi
resolve!(bargs, gtk_widget, { resolve!(bargs, gtk_widget, {
resolve_str => "class" => |v| gtk_widget.get_style_context().add_class(&v), resolve_str => "class" => |v| gtk_widget.get_style_context().add_class(&v),
resolve_bool => "active" = true => |v| gtk_widget.set_sensitive(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: &gtk
pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<Option<gtk::Widget>> { pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<Option<gtk::Widget>> {
let gtk_widget = match bargs.widget.name.as_str() { let gtk_widget = match bargs.widget.name.as_str() {
"layout" => build_gtk_layout(bargs)?.upcast(),
"slider" => build_gtk_scale(bargs)?.upcast(), "slider" => build_gtk_scale(bargs)?.upcast(),
"image" => build_gtk_image(bargs)?.upcast(), "image" => build_gtk_image(bargs)?.upcast(),
"layout" => build_gtk_layout(bargs)?.upcast(),
"button" => build_gtk_button(bargs)?.upcast(), "button" => build_gtk_button(bargs)?.upcast(),
"label" => build_gtk_label(bargs)?.upcast(), "label" => build_gtk_label(bargs)?.upcast(),
_ => return Ok(None), _ => return Ok(None),
@ -69,7 +71,8 @@ fn build_gtk_scale(bargs: &mut BuilderArgs) -> Result<gtk::Scale> {
Some(&gtk::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)), Some(&gtk::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)),
); );
resolve!(bargs, gtk_widget, { 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) 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> { fn build_gtk_layout(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0); let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);
resolve!(bargs, gtk_widget, { 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_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)),
resolve_bool => "homogenous" = true => |v| gtk_widget.set_homogeneous(v),
}); });
Ok(gtk_widget) Ok(gtk_widget)
} }
@ -115,3 +118,14 @@ fn parse_orientation(o: &str) -> gtk::Orientation {
_ => gtk::Orientation::Horizontal, _ => 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,
}
}