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",
]
[[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"

View file

@ -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"

View file

@ -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()?)
}

View file

@ -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))
}

View file

@ -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 {

View file

@ -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()

View file

@ -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)?;

View file

@ -16,6 +16,8 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: &gtk::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: &gtk
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(&gtk::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_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)),
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,
}
}