diff --git a/docs/content/main/_index.md b/docs/content/main/_index.md
index beb7258..c4e82fb 100644
--- a/docs/content/main/_index.md
+++ b/docs/content/main/_index.md
@@ -5,7 +5,10 @@ sort_by = "weight"
+++
-Eww (ElKowar's Wacky Widgets, pronounced with sufficient amounts of disgust) Is a widgeting system made in [rust](https://www.rust-lang.org/), which let's you create your own widgets similarly to how you can in AwesomeWM. The key difference: It is independent of your window manager!
+Eww (ElKowar's Wacky Widgets, pronounced with sufficient amounts of disgust)
+is a widgeting system made in [rust](https://www.rust-lang.org/),
+which let's you create your own widgets similarly to how you can in AwesomeWM.
+The key difference: It is independent of your window manager!
Configured in XML and themed using CSS, it is easy to customize and provides all the flexibility you need!
@@ -17,7 +20,9 @@ Configured in XML and themed using CSS, it is easy to customize and provides all
* rustc
* cargo (nightly toolchain)
-Rather than with your system package manager, I recommend installing it using [rustup](https://rustup.rs/), as this makes it easy to use the nightly toolchain, which is necessary to build eww.
+Rather than with your system package manager,
+I recommend installing it using [rustup](https://rustup.rs/),
+as this makes it easy to use the nightly toolchain necessary to build eww.
### Building
diff --git a/docs/content/main/configuration.md b/docs/content/main/configuration.md
index b809094..aa98a32 100644
--- a/docs/content/main/configuration.md
+++ b/docs/content/main/configuration.md
@@ -28,11 +28,11 @@ Your config structure should look like this:
-
+
-
+
@@ -75,7 +75,7 @@ Example:
```xml
- date +%H:%M
+ date +%H:%M
```
@@ -141,7 +141,7 @@ This part:
```xml
- The time is: {{my_time}} currently.
+ The time is: {{my_time}} currently.
```
@@ -156,7 +156,7 @@ So if we look at:
```xml
-
+
```
@@ -172,7 +172,7 @@ It doesn't have to be `{{my_time}}` either, it can be anything.
```xml
- The time is: {{very_long_list_of_animals}} currently.
+ The time is: {{very_long_list_of_animals}} currently.
```
@@ -182,83 +182,29 @@ To use that it would look like this:
```xml
-
+
```
### The `` block {#windows-block}
-This is the part the Eww reads and loads. The `` config should look something like this:
+All different windows you might want to use are defined in the `` block.
+The `` config should look something like this:
```xml
-
-
-
-
-
-
-
-```
-`` is the part that eww runs when you start it. In this example you would run eww by doing:
-```bash
-./eww open main_window
-```
-but if renamed the `` to be `` we would run eww by doing:
-```bash
-./eww open apple
-```
-
-The `stacking="fg"` says where the widget will be stacked. Possible values here are `foreground`, `fg`, `background` and `bg`.
-`foreground` or `fg` *always* stays above windows.
-`background` or `bg` *always* stays behind windows. So it will stay on your desktop.
-
-If you were to remove the `stacking="fg"` it would default it to `fg`.
-
-You can also have multiple windows in one document by doing:
-
-```xml
-
-
-
-
+
-
-
-
-
-
-
-
```
----
-- `` sets x-y size of the widget.
-- `` sets x-y position of the widget.
-- `` is the part which you say which `` eww should run. So if we take the example config from before:
-```xml
-
-
-
- The time is: {{my_time}} currently.
-
-
-
-
-
-
-
-
-```
-and then look at
-```xml
-
-
-
-```
-we will see that eww will run `` and not ``.
+The window block contains multiple elements to configure the window.
+- `` is used to specify the position and size of the window.
+- `` will contain the widget that is shown in the window.
+
+The `stacking="fg"` specifies if the window should appear on top of or behind other windows.
+Possible values here are `foreground`, `fg`, `background` and `bg`. It defaults to `fg`.
diff --git a/src/app.rs b/src/app.rs
index e040299..24007e4 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,6 +1,6 @@
use crate::{
config,
- config::{window_definition::WindowName, WindowStacking},
+ config::{window_definition::WindowName, AnchorPoint, WindowStacking},
eww_state,
script_var_handler::*,
util,
@@ -25,6 +25,7 @@ pub enum EwwCommand {
window_name: WindowName,
pos: Option,
size: Option,
+ anchor: Option,
},
CloseWindow {
window_name: WindowName,
@@ -75,8 +76,13 @@ impl App {
script_var_process::on_application_death();
std::process::exit(0);
}
- EwwCommand::OpenWindow { window_name, pos, size } => {
- self.open_window(&window_name, pos, size)?;
+ EwwCommand::OpenWindow {
+ window_name,
+ pos,
+ size,
+ anchor,
+ } => {
+ self.open_window(&window_name, pos, size, anchor)?;
}
EwwCommand::CloseWindow { window_name } => {
self.close_window(&window_name)?;
@@ -115,7 +121,13 @@ impl App {
Ok(())
}
- fn open_window(&mut self, window_name: &WindowName, pos: Option, size: Option) -> Result<()> {
+ fn open_window(
+ &mut self,
+ window_name: &WindowName,
+ pos: Option,
+ size: Option,
+ anchor: Option,
+ ) -> Result<()> {
// remove and close existing window with the same name
let _ = self.close_window(window_name);
@@ -135,10 +147,11 @@ impl App {
let monitor_geometry = display.get_default_screen().get_monitor_geometry(*screen_number);
- window_def.position = pos.unwrap_or(window_def.position);
- window_def.size = size.unwrap_or(window_def.size);
+ window_def.geometry.offset = pos.unwrap_or(window_def.geometry.offset);
+ window_def.geometry.size = size.unwrap_or(window_def.geometry.size);
+ window_def.geometry.anchor_point = anchor.unwrap_or(window_def.geometry.anchor_point);
- let actual_window_rect = get_window_rectangle_in_screen(monitor_geometry, window_def.position, window_def.size);
+ let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry);
let window = if window_def.focusable {
gtk::Window::new(gtk::WindowType::Toplevel)
@@ -172,6 +185,16 @@ impl App {
root_widget.get_style_context().add_class(&window_name.to_string());
window.add(root_widget);
+ // Handle the fact that the gtk window will have a different size than specified,
+ // as it is sized according to how much space it's contents require.
+ // This is necessary to handle different anchors correctly in case the size was wrong.
+ let (gtk_window_width, gtk_window_height) = window.get_size();
+ window_def.geometry.size = Coords {
+ x: NumWithUnit::Pixels(gtk_window_width),
+ y: NumWithUnit::Pixels(gtk_window_height),
+ };
+ let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry);
+
window.show_all();
let gdk_window = window.get_window().context("couldn't get gdk window from gtk window")?;
@@ -210,10 +233,8 @@ impl App {
let windows = self.windows.clone();
for (window_name, window) in windows {
- let old_pos = window.definition.position;
- let old_size = window.definition.size;
window.gtk_window.close();
- self.open_window(&window_name, Some(old_pos.into()), Some(old_size.into()))?;
+ self.open_window(&window_name, None, None, None)?;
}
Ok(())
}
@@ -233,25 +254,3 @@ fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) {
});
window.set_visual(visual.as_ref());
}
-
-/// Calculate the window rectangle given the configured window [`pos`] and [`size`], which might be relative to the screen size.
-fn get_window_rectangle_in_screen(screen_rect: gdk::Rectangle, pos: Coords, size: Coords) -> gdk::Rectangle {
- gdk::Rectangle {
- x: match pos.x {
- NumWithUnit::Percent(n) => ((screen_rect.width as f64 / 100.0) * n as f64) as i32,
- NumWithUnit::Pixels(n) => screen_rect.x + n,
- },
- y: match pos.y {
- NumWithUnit::Percent(n) => ((screen_rect.height as f64 / 100.0) * n as f64) as i32,
- NumWithUnit::Pixels(n) => screen_rect.y + n,
- },
- width: match size.x {
- NumWithUnit::Percent(n) => ((screen_rect.width as f64 / 100.0) * n as f64) as i32,
- NumWithUnit::Pixels(n) => n,
- },
- height: match size.y {
- NumWithUnit::Percent(n) => ((screen_rect.height as f64 / 100.0) * n as f64) as i32,
- NumWithUnit::Pixels(n) => n,
- },
- }
-}
diff --git a/src/config/element.rs b/src/config/element.rs
index c8e3614..db06204 100644
--- a/src/config/element.rs
+++ b/src/config/element.rs
@@ -28,12 +28,9 @@ impl WidgetDefinition {
);
}
- let size: Option<_> = Option::zip(xml.attr("width").ok(), xml.attr("height").ok());
- let size: Option> = size.map(|(x, y)| Ok((x.parse()?, y.parse()?)));
-
WidgetDefinition {
name: xml.attr("name")?.to_owned(),
- size: size.transpose()?,
+ size: Option::zip(xml.parse_optional_attr("width")?, xml.parse_optional_attr("height")?),
structure: WidgetUse::from_xml_node(xml.only_child()?)?,
}
}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 56829f2..757c9d2 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -11,10 +11,12 @@ pub mod element;
pub mod eww_config;
pub mod script_var;
pub mod window_definition;
+pub mod window_geometry;
pub mod xml_ext;
pub use eww_config::*;
pub use script_var::*;
pub use window_definition::*;
+pub use window_geometry::*;
#[macro_export]
macro_rules! ensure_xml_tag_is {
diff --git a/src/config/window_definition.rs b/src/config/window_definition.rs
index cf7dfa6..d692b66 100644
--- a/src/config/window_definition.rs
+++ b/src/config/window_definition.rs
@@ -1,16 +1,14 @@
-use crate::{ensure_xml_tag_is, value::Coords};
+use crate::ensure_xml_tag_is;
use anyhow::*;
use derive_more::*;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use super::*;
-use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct EwwWindowDefinition {
- pub position: Coords,
- pub size: Coords,
+ pub geometry: EwwWindowGeometry,
pub stacking: WindowStacking,
pub screen_number: Option,
pub widget: WidgetUse,
@@ -21,22 +19,18 @@ pub struct EwwWindowDefinition {
impl EwwWindowDefinition {
pub fn from_xml_element(xml: XmlElement) -> Result {
ensure_xml_tag_is!(xml, "window");
+ let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
+ let screen_number = xml.parse_optional_attr("screen")?;
+ let focusable = xml.parse_optional_attr("focusable")?;
- let size_node = xml.child("size")?;
- let size = Coords::from_strs(size_node.attr("x")?, size_node.attr("y")?)?;
- let pos_node = xml.child("pos")?;
- let position = Coords::from_strs(pos_node.attr("x")?, pos_node.attr("y")?)?;
-
- let stacking = xml.attr("stacking").ok().map(|x| x.parse()).transpose()?.unwrap_or_default();
- let screen_number = xml.attr("screen").ok().map(|x| x.parse()).transpose()?;
- let focusable = xml.attr("focusable").ok().map(|x| x.parse()).transpose()?;
let struts = xml.child("struts").ok().map(Struts::from_xml_element).transpose()?;
- let widget = WidgetUse::from_xml_node(xml.child("widget")?.only_child()?)?;
Ok(EwwWindowDefinition {
- position,
- size,
- widget,
+ geometry: match xml.child("geometry") {
+ Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
+ Err(_) => EwwWindowGeometry::default(),
+ },
+ widget: WidgetUse::from_xml_node(xml.child("widget")?.only_child()?)?,
stacking,
screen_number,
focusable: focusable.unwrap_or(false),
@@ -89,7 +83,8 @@ impl std::str::FromStr for WindowStacking {
}
#[repr(transparent)]
-#[derive(Clone, Hash, PartialEq, Eq, AsRef, FromStr, Display, Serialize, Deserialize, Default, From)]
+#[derive(Clone, Hash, PartialEq, Eq, AsRef, FromStr, Display, Serialize, Deserialize, Default, From, DebugCustom)]
+#[debug(fmt = "WindowName(\".0\")")]
pub struct WindowName(String);
impl std::borrow::Borrow for WindowName {
@@ -97,9 +92,3 @@ impl std::borrow::Borrow for WindowName {
&self.0
}
}
-
-impl fmt::Debug for WindowName {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "WindowName(\"{}\")", self.0)
- }
-}
diff --git a/src/config/window_geometry.rs b/src/config/window_geometry.rs
new file mode 100644
index 0000000..a19989e
--- /dev/null
+++ b/src/config/window_geometry.rs
@@ -0,0 +1,151 @@
+use crate::value::Coords;
+use anyhow::*;
+use serde::{Deserialize, Serialize};
+use smart_default::SmartDefault;
+
+use std::fmt;
+
+use super::xml_ext::XmlElement;
+
+#[derive(Debug, derive_more::Display, Clone, Copy, Eq, PartialEq, SmartDefault, Serialize, Deserialize)]
+pub enum AnchorAlignment {
+ #[display("start")]
+ #[default]
+ START,
+ #[display("center")]
+ CENTER,
+ #[display("end")]
+ END,
+}
+
+impl AnchorAlignment {
+ pub fn from_x_alignment(s: &str) -> Result {
+ match s {
+ "l" | "left" => Ok(AnchorAlignment::START),
+ "c" | "center" => Ok(AnchorAlignment::CENTER),
+ "r" | "right" => Ok(AnchorAlignment::END),
+ _ => bail!(
+ r#"couldn't parse '{}' as x-alignment. Must be one of "left", "center", "right""#,
+ s
+ ),
+ }
+ }
+
+ pub fn from_y_alignment(s: &str) -> Result {
+ match s {
+ "t" | "top" => Ok(AnchorAlignment::START),
+ "c" | "center" => Ok(AnchorAlignment::CENTER),
+ "b" | "bottom" => Ok(AnchorAlignment::END),
+ _ => bail!(
+ r#"couldn't parse '{}' as y-alignment. Must be one of "top", "center", "bottom""#,
+ s
+ ),
+ }
+ }
+
+ pub fn alignment_to_coordinate(&self, size_inner: i32, size_container: i32) -> i32 {
+ match self {
+ AnchorAlignment::START => 0,
+ AnchorAlignment::CENTER => (size_container / 2) - (size_inner / 2),
+ AnchorAlignment::END => size_container - size_inner,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
+pub struct AnchorPoint {
+ x: AnchorAlignment,
+ y: AnchorAlignment,
+}
+
+impl std::fmt::Display for AnchorPoint {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use AnchorAlignment::*;
+ match (self.x, self.y) {
+ (CENTER, CENTER) => write!(f, "center"),
+ (x, y) => write!(
+ f,
+ "{} {}",
+ match x {
+ START => "left",
+ CENTER => "center",
+ END => "right",
+ },
+ match y {
+ START => "top",
+ CENTER => "center",
+ END => "bottom",
+ }
+ ),
+ }
+ }
+}
+
+impl std::str::FromStr for AnchorPoint {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result {
+ if s == "center" {
+ Ok(AnchorPoint {
+ x: AnchorAlignment::CENTER,
+ y: AnchorAlignment::CENTER,
+ })
+ } else {
+ let (first, second) = s
+ .split_once(' ')
+ .context("Failed to parse anchor: Must either be \"center\" or be formatted like \"top left\"")?;
+ let x_y_result: Result<_> = try {
+ AnchorPoint {
+ x: AnchorAlignment::from_x_alignment(first)?,
+ y: AnchorAlignment::from_y_alignment(second)?,
+ }
+ };
+ x_y_result.or_else(|_| {
+ Ok(AnchorPoint {
+ x: AnchorAlignment::from_x_alignment(second)?,
+ y: AnchorAlignment::from_y_alignment(first)?,
+ })
+ })
+ }
+ }
+}
+
+#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
+pub struct EwwWindowGeometry {
+ pub anchor_point: AnchorPoint,
+ pub offset: Coords,
+ pub size: Coords,
+}
+
+impl EwwWindowGeometry {
+ pub fn from_xml_element(xml: XmlElement) -> Result {
+ Ok(EwwWindowGeometry {
+ anchor_point: xml.parse_optional_attr("anchor")?.unwrap_or_default(),
+ size: Coords {
+ x: xml.parse_optional_attr("width")?.unwrap_or_default(),
+ y: xml.parse_optional_attr("height")?.unwrap_or_default(),
+ },
+ offset: Coords {
+ x: xml.parse_optional_attr("x")?.unwrap_or_default(),
+ y: xml.parse_optional_attr("y")?.unwrap_or_default(),
+ },
+ })
+ }
+}
+
+impl std::fmt::Display for EwwWindowGeometry {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}-{} ({})", self.offset, self.size, self.anchor_point)
+ }
+}
+
+impl EwwWindowGeometry {
+ /// Calculate the window rectangle given the configured window geometry
+ pub fn get_window_rectangle(&self, screen_rect: gdk::Rectangle) -> gdk::Rectangle {
+ let (offset_x, offset_y) = self.offset.relative_to(screen_rect.width, screen_rect.height);
+ let (width, height) = self.size.relative_to(screen_rect.width, screen_rect.height);
+ let x = screen_rect.x + offset_x + self.anchor_point.x.alignment_to_coordinate(width, screen_rect.width);
+ let y = screen_rect.y + offset_y + self.anchor_point.y.alignment_to_coordinate(height, screen_rect.height);
+ gdk::Rectangle { x, y, width, height }
+ }
+}
diff --git a/src/config/xml_ext.rs b/src/config/xml_ext.rs
index 4908520..3635f4f 100644
--- a/src/config/xml_ext.rs
+++ b/src/config/xml_ext.rs
@@ -177,6 +177,20 @@ impl<'a, 'b> XmlElement<'a, 'b> {
}
}
+ pub fn optional_attr Result>(&self, key: &str, parse: F) -> Result