Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3d89aadd5 | ||
|
|
c676c0223b | ||
|
|
29252cbbc6 | ||
|
|
c695288d1e | ||
|
|
c02937d8b6 | ||
|
|
b9ff6725cd | ||
|
|
3572d22ba8 | ||
|
|
50e2ab25a5 | ||
|
|
a1e36d9ac5 | ||
|
|
1ae11bab29 | ||
|
|
4e9f0e566f | ||
|
|
4ca4fa307a | ||
|
|
f2999fb53f | ||
|
|
b94e80c346 | ||
|
|
c5cc36b7cf | ||
|
|
14388085cb | ||
|
|
9908392af1 | ||
|
|
d10883169f |
Generated
+707
File diff suppressed because it is too large
Load Diff
+15
-2
@@ -8,24 +8,37 @@ edition = "2018"
|
||||
lto = "fat"
|
||||
|
||||
[features]
|
||||
default = ["alsa-widget", "pulseaudio-widget", "yaml-cfg", "json-cfg"]
|
||||
damage_debug = []
|
||||
|
||||
yaml-cfg = ["serde_yaml"]
|
||||
json-cfg = ["serde_json"]
|
||||
|
||||
alsa-widget = ["alsa"]
|
||||
pulseaudio-widget = ["libpulse-binding"]
|
||||
|
||||
[dependencies]
|
||||
smithay-client-toolkit = "0.6.4"
|
||||
byteorder = "1.3"
|
||||
rusttype = "0.7"
|
||||
failure = "0.1.5"
|
||||
chrono = "0.4"
|
||||
nix = "0.15"
|
||||
memmap = "0.7"
|
||||
os_pipe = "0.8"
|
||||
wayland-client = { version = "0.23", features = ["dlopen"] }
|
||||
wayland-protocols = { version = "0.23", features = ["client", "unstable_protocols"] }
|
||||
libpulse-binding = { version = "2.11"}
|
||||
wl-clipboard-rs = { version = "0.3" }
|
||||
dbus = "0.6"
|
||||
fuzzy-matcher = "0.2"
|
||||
lazy_static = "1.4"
|
||||
rcalc_lib = { version = "0.9" }
|
||||
rust-ini = "0.13"
|
||||
shlex = "0.1"
|
||||
serde_json = "1.0"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde_yaml = { version = "0.8", optional = true }
|
||||
|
||||
libpulse-binding = { version = "2.11", optional = true }
|
||||
alsa = { version = "0.3.0", optional = true }
|
||||
|
||||
@@ -11,7 +11,7 @@ Consider this alpha-quality: While it works, *everything* is subject to change w
|
||||
3. Put somewhere: `cp target/release/wldash /usr/local/bin/wldash`
|
||||
4. Run: `wldash`
|
||||
|
||||
To see the default configuration, run `wldash print-config`. To configure, place a file in `$XDG_CONFIG_HOME/wldash/config.json` (or if `XDG_CONFIG_HOME` is not set, `~/.config/wldash/config.json`).
|
||||
To see the default configuration, run `wldash print-config`. To configure, place a file in `$XDG_CONFIG_HOME/wldash/config.yaml` (or if `XDG_CONFIG_HOME` is not set, `~/.config/wldash/config.yaml`). JSON is also currently supported.
|
||||
|
||||
Notable settings: `outptuMode` can be `active` or `all`, `scale` can be set to `2` to half the buffer size, and the widgets (and their layout) can be configured.
|
||||
|
||||
@@ -62,4 +62,4 @@ The launcher also accepts prefix operators to change its mode:
|
||||
- Scaling of any kind - all sizes are currently hardcoded in pixels
|
||||
- Configurability
|
||||
- Cleanup and reorganization
|
||||
- Proper line editor for the launcher
|
||||
- Proper line editor for the launcher
|
||||
|
||||
+14
-2
@@ -34,10 +34,22 @@
|
||||
}
|
||||
]},
|
||||
{"calendar": {"font_size": 16.0,"sections": 3}},
|
||||
{"launcher": {"font_size": 32.0,"length": 1200}}
|
||||
{"launcher": {
|
||||
"font_size": 32.0,
|
||||
"length": 1200,
|
||||
"app_opener": "swaymsg exec ",
|
||||
"term_opener": "alacritty -e ",
|
||||
"url_opener": "xdg-open "
|
||||
}}
|
||||
]}
|
||||
}
|
||||
},
|
||||
"outputMode": "active",
|
||||
"scale": 1
|
||||
"scale": 1,
|
||||
"background": {
|
||||
"red": 0,
|
||||
"green": 0,
|
||||
"blue": 0,
|
||||
"opacity": 0.9
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
outputMode: active
|
||||
scale: 1
|
||||
background:
|
||||
red: 0.0
|
||||
green: 0.0
|
||||
blue: 0.0
|
||||
opacity: 0.9
|
||||
widget:
|
||||
margin:
|
||||
margins: [20, 20, 20, 20]
|
||||
widget:
|
||||
verticalLayout:
|
||||
- horizontalLayout:
|
||||
- margin:
|
||||
margins: [0, 88, 0, 32]
|
||||
widget:
|
||||
verticalLayout:
|
||||
- date:
|
||||
font_size: 64.0
|
||||
- clock:
|
||||
font_size: 256.0
|
||||
- verticalLayout:
|
||||
- margin:
|
||||
margins: [0, 0, 0, 8]
|
||||
widget:
|
||||
battery:
|
||||
font_size: 24.0
|
||||
length: 600
|
||||
- margin:
|
||||
margins: [0, 0, 0, 8]
|
||||
widget:
|
||||
backlight:
|
||||
font_size: 24.0
|
||||
length: 600
|
||||
- margin:
|
||||
margins: [0, 0, 0, 8]
|
||||
widget:
|
||||
pulseAudio:
|
||||
font_size: 24.0
|
||||
length: 600
|
||||
- calendar:
|
||||
font_size: 16.0
|
||||
sections: 3
|
||||
- launcher:
|
||||
font_size: 32.0
|
||||
length: 1200
|
||||
app_opener: "swaymsg exec "
|
||||
term_opener: "alacritty -e "
|
||||
url_opener: "xdg-open "
|
||||
+75
-56
@@ -21,6 +21,7 @@ use crate::widget::{DrawContext, Widget};
|
||||
use crate::cmd::Cmd;
|
||||
use crate::doublemempool::DoubleMemPool;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OutputMode {
|
||||
Active,
|
||||
All,
|
||||
@@ -123,47 +124,49 @@ impl AppInner {
|
||||
|
||||
self.configured_surfaces = Arc::new(Mutex::new(0));
|
||||
|
||||
if self.visible {
|
||||
match self.output_mode {
|
||||
OutputMode::Active => {
|
||||
if self.shell_surfaces.len() > 0 {
|
||||
return;
|
||||
}
|
||||
if !self.visible {
|
||||
self.surfaces = Vec::new();
|
||||
self.shell_surfaces = Vec::new();
|
||||
return;
|
||||
}
|
||||
|
||||
match self.output_mode {
|
||||
OutputMode::Active => {
|
||||
if self.shell_surfaces.len() > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let (surface, shell_surface) = AppInner::add_shell_surface(
|
||||
&compositor,
|
||||
&shell,
|
||||
self.scale,
|
||||
self.configured_surfaces.clone(),
|
||||
self.draw_tx.clone(),
|
||||
None,
|
||||
);
|
||||
self.surfaces = vec![surface];
|
||||
self.shell_surfaces = vec![shell_surface];
|
||||
}
|
||||
OutputMode::All => {
|
||||
let mut surfaces = Vec::new();
|
||||
let mut shell_surfaces = Vec::new();
|
||||
for output in self.outputs.iter() {
|
||||
let (surface, shell_surface) = AppInner::add_shell_surface(
|
||||
&compositor,
|
||||
&shell,
|
||||
self.scale,
|
||||
self.configured_surfaces.clone(),
|
||||
self.draw_tx.clone(),
|
||||
None,
|
||||
Some(&output.1),
|
||||
);
|
||||
self.surfaces = vec![surface];
|
||||
self.shell_surfaces = vec![shell_surface];
|
||||
}
|
||||
OutputMode::All => {
|
||||
let mut surfaces = Vec::new();
|
||||
let mut shell_surfaces = Vec::new();
|
||||
for output in self.outputs.iter() {
|
||||
let (surface, shell_surface) = AppInner::add_shell_surface(
|
||||
&compositor,
|
||||
&shell,
|
||||
self.scale,
|
||||
self.configured_surfaces.clone(),
|
||||
self.draw_tx.clone(),
|
||||
Some(&output.1),
|
||||
);
|
||||
surfaces.push(surface);
|
||||
shell_surfaces.push(shell_surface);
|
||||
}
|
||||
self.surfaces = surfaces;
|
||||
self.shell_surfaces = shell_surfaces;
|
||||
surfaces.push(surface);
|
||||
shell_surfaces.push(shell_surface);
|
||||
}
|
||||
self.surfaces = surfaces;
|
||||
self.shell_surfaces = shell_surfaces;
|
||||
}
|
||||
self.draw_tx.send(Cmd::ForceDraw).unwrap();
|
||||
} else {
|
||||
self.surfaces = Vec::new();
|
||||
self.shell_surfaces = Vec::new();
|
||||
}
|
||||
self.draw_tx.send(Cmd::ForceDraw).unwrap();
|
||||
}
|
||||
|
||||
fn add_output(&mut self, id: u32, output: wl_output::WlOutput) {
|
||||
@@ -203,13 +206,14 @@ pub struct App {
|
||||
event_queue: EventQueue,
|
||||
cmd_queue: Arc<Mutex<VecDeque<Cmd>>>,
|
||||
widget: Option<Box<dyn Widget + Send>>,
|
||||
bg: Color,
|
||||
inner: Arc<Mutex<AppInner>>,
|
||||
last_damage: Option<Vec<(i32, i32, i32, i32)>>,
|
||||
last_dim: (u32, u32),
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn redraw(&mut self, mut force: bool) -> Result<(), ::std::io::Error> {
|
||||
pub fn redraw(&mut self, force: bool) -> Result<(), ::std::io::Error> {
|
||||
let widget = match self.widget {
|
||||
Some(ref mut widget) => widget,
|
||||
None => return Ok(()),
|
||||
@@ -218,7 +222,10 @@ impl App {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
let time = Local::now();
|
||||
|
||||
if inner.shell_surfaces.len() != *inner.configured_surfaces.lock().unwrap() {
|
||||
if !inner.visible
|
||||
|| inner.shell_surfaces.len() != *inner.configured_surfaces.lock().unwrap()
|
||||
|| inner.surfaces.len() == 0
|
||||
{
|
||||
// Not ready yet
|
||||
return Ok(());
|
||||
}
|
||||
@@ -230,6 +237,7 @@ impl App {
|
||||
|
||||
let size = widget.size();
|
||||
let size_changed = self.last_dim != size;
|
||||
let force = force | size_changed;
|
||||
|
||||
// resize the pool if relevant
|
||||
pool.resize((4 * size.0 * size.1) as usize)
|
||||
@@ -238,8 +246,8 @@ impl App {
|
||||
let mut buf = Buffer::new(mmap, size);
|
||||
|
||||
// Copy old damage
|
||||
if let Some(d) = &self.last_damage {
|
||||
if !size_changed {
|
||||
let force = match (force, &self.last_damage) {
|
||||
(false, Some(d)) => {
|
||||
let lastmmap = last.mmap();
|
||||
let last = Buffer::new(lastmmap, size);
|
||||
|
||||
@@ -249,21 +257,18 @@ impl App {
|
||||
for d in d {
|
||||
last.copy_to(&mut buf, d.clone());
|
||||
}
|
||||
} else {
|
||||
force = true;
|
||||
false
|
||||
}
|
||||
} else {
|
||||
force = true;
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
|
||||
let bg = Color::new(0.0, 0.0, 0.0, 0.9);
|
||||
if force {
|
||||
buf.memset(&bg);
|
||||
buf.memset(&self.bg);
|
||||
}
|
||||
let report = widget.draw(
|
||||
&mut DrawContext {
|
||||
buf: &mut buf,
|
||||
bg: &bg,
|
||||
bg: &self.bg,
|
||||
time: &time,
|
||||
force,
|
||||
},
|
||||
@@ -272,8 +277,9 @@ impl App {
|
||||
|
||||
mmap.flush().unwrap();
|
||||
|
||||
if !size_changed && !report.full_damage && report.damage.len() == 0 {
|
||||
if !force && !report.full_damage && report.damage.len() == 0 {
|
||||
// Nothing to do
|
||||
self.pools.never_mind();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -310,9 +316,17 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn toggle_visible(&mut self) {
|
||||
pub fn hide(&mut self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.visible = !inner.visible;
|
||||
inner.visible = false;
|
||||
self.last_dim = (0, 0);
|
||||
inner.outputs_changed();
|
||||
}
|
||||
|
||||
pub fn show(&mut self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.visible = true;
|
||||
self.last_dim = (0, 0);
|
||||
inner.outputs_changed();
|
||||
}
|
||||
|
||||
@@ -337,7 +351,7 @@ impl App {
|
||||
self.redraw(true)
|
||||
}
|
||||
|
||||
pub fn new(tx: Sender<Cmd>, output_mode: OutputMode, scale: u32) -> App {
|
||||
pub fn new(tx: Sender<Cmd>, output_mode: OutputMode, bg: Color, scale: u32) -> App {
|
||||
let inner = Arc::new(Mutex::new(AppInner::new(tx.clone(), output_mode, scale)));
|
||||
|
||||
//
|
||||
@@ -422,6 +436,14 @@ impl App {
|
||||
// Keyboard processing
|
||||
//
|
||||
let kbd_clone = cmd_queue.clone();
|
||||
let modifiers_state = Arc::new(Mutex::new(ModifiersState {
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
caps_lock: false,
|
||||
logo: false,
|
||||
num_lock: false,
|
||||
}));
|
||||
map_keyboard_auto(&seat, move |event: KbEvent, _| match event {
|
||||
KbEvent::Key {
|
||||
keysym,
|
||||
@@ -431,22 +453,19 @@ impl App {
|
||||
} => match state {
|
||||
KeyState::Pressed => match keysym {
|
||||
keysyms::XKB_KEY_Escape => kbd_clone.lock().unwrap().push_back(Cmd::Exit),
|
||||
keysyms::XKB_KEY_c if modifiers_state.lock().unwrap().ctrl => {
|
||||
kbd_clone.lock().unwrap().push_back(Cmd::Exit)
|
||||
}
|
||||
v => kbd_clone.lock().unwrap().push_back(Cmd::Keyboard {
|
||||
key: v,
|
||||
key_state: state,
|
||||
modifiers_state: ModifiersState {
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
caps_lock: false,
|
||||
logo: false,
|
||||
num_lock: false,
|
||||
},
|
||||
modifiers_state: modifiers_state.lock().unwrap().clone(),
|
||||
interpreted: utf8,
|
||||
}),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
KbEvent::Modifiers { modifiers } => *modifiers_state.lock().unwrap() = modifiers,
|
||||
_ => (),
|
||||
})
|
||||
.expect("Failed to map keyboard");
|
||||
@@ -467,7 +486,6 @@ impl App {
|
||||
},
|
||||
));
|
||||
|
||||
inner.lock().unwrap().outputs_changed();
|
||||
event_queue.sync_roundtrip().unwrap();
|
||||
|
||||
//
|
||||
@@ -543,6 +561,7 @@ impl App {
|
||||
cmd_queue: cmd_queue,
|
||||
pools: pools,
|
||||
widget: None,
|
||||
bg: bg,
|
||||
inner: inner,
|
||||
last_damage: None,
|
||||
last_dim: (0, 0),
|
||||
|
||||
+45
-40
@@ -1,4 +1,6 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
|
||||
pub struct Color {
|
||||
red: f32,
|
||||
green: f32,
|
||||
@@ -8,55 +10,27 @@ pub struct Color {
|
||||
|
||||
impl Color {
|
||||
pub fn new(red: f32, green: f32, blue: f32, opacity: f32) -> Color {
|
||||
let (red, green, blue, opacity) = clamp(red, green, blue, opacity);
|
||||
Color {
|
||||
red: if red > 1.0 {
|
||||
1.0
|
||||
} else if red < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
red
|
||||
},
|
||||
green: if green > 1.0 {
|
||||
1.0
|
||||
} else if green < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
green
|
||||
},
|
||||
blue: if blue > 1.0 {
|
||||
1.0
|
||||
} else if blue < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
blue
|
||||
},
|
||||
opacity: if opacity > 1.0 {
|
||||
1.0
|
||||
} else if opacity < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
opacity
|
||||
},
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
opacity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend(&self, other: &Color, ratio: f32) -> Color {
|
||||
let ratio = if ratio > 1.0 {
|
||||
1.0
|
||||
} else if ratio < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
ratio
|
||||
};
|
||||
let ratio = clamp_f32(ratio, 0.0, 1.0);
|
||||
|
||||
Color {
|
||||
red: self.red + ((other.red - self.red) * ratio),
|
||||
green: self.green + ((other.green - self.green) * ratio),
|
||||
blue: self.blue + ((other.blue - self.blue) * ratio),
|
||||
opacity: self.opacity + ((other.opacity - self.opacity) * ratio),
|
||||
red: blend_f32(self.red, other.red, ratio),
|
||||
green: blend_f32(self.green, other.green, ratio),
|
||||
blue: blend_f32(self.blue, other.blue, ratio),
|
||||
opacity: blend_f32(self.opacity, other.opacity, ratio),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_argb8888(&self) -> u32 {
|
||||
((255.0 * self.opacity) as u32 & 0xFF) << 24
|
||||
| ((255.0 * self.red) as u32 & 0xFF) << 16
|
||||
@@ -64,3 +38,34 @@ impl Color {
|
||||
| ((255.0 * self.blue) as u32 & 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) {
|
||||
clamp_naive(r, g, b, o)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp_naive(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) {
|
||||
(
|
||||
clamp_f32(r, 0.0, 1.0),
|
||||
clamp_f32(g, 0.0, 1.0),
|
||||
clamp_f32(b, 0.0, 1.0),
|
||||
clamp_f32(o, 0.0, 1.0),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp_f32(v: f32, a: f32, b: f32) -> f32 {
|
||||
if v > b {
|
||||
b
|
||||
} else if v < a {
|
||||
a
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn blend_f32(a: f32, b: f32, r: f32) -> f32 {
|
||||
a + ((b - a) * r)
|
||||
}
|
||||
|
||||
+20
-2
@@ -1,4 +1,5 @@
|
||||
use crate::cmd::Cmd;
|
||||
use crate::color::Color;
|
||||
use crate::widget;
|
||||
use crate::widgets;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -47,10 +48,16 @@ pub enum Widget {
|
||||
font_size: f32,
|
||||
length: u32,
|
||||
},
|
||||
#[cfg(feature = "pulseaudio-widget")]
|
||||
PulseAudio {
|
||||
font_size: f32,
|
||||
length: u32,
|
||||
},
|
||||
#[cfg(feature = "alsa-widget")]
|
||||
AlsaSound {
|
||||
font_size: f32,
|
||||
length: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Widget {
|
||||
@@ -120,8 +127,16 @@ impl Widget {
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "pulseaudio-widget")]
|
||||
Widget::PulseAudio { font_size, length } => {
|
||||
match widgets::sound::PulseAudio::new(font_size, length, tx.clone()) {
|
||||
match widgets::audio::PulseAudio::new(font_size, length, tx.clone()) {
|
||||
Ok(w) => Some(w),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alsa-widget")]
|
||||
Widget::AlsaSound { font_size, length } => {
|
||||
match widgets::audio::Alsa::new(font_size, length) {
|
||||
Ok(w) => Some(w),
|
||||
Err(_) => None,
|
||||
}
|
||||
@@ -146,9 +161,10 @@ impl Default for OutputMode {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Config {
|
||||
pub widget: Widget,
|
||||
pub output_mode: OutputMode,
|
||||
pub scale: u32,
|
||||
pub background: Color,
|
||||
pub widget: Widget,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -180,6 +196,7 @@ impl Default for Config {
|
||||
length: 600,
|
||||
}),
|
||||
}),
|
||||
#[cfg(feature = "pulseaudio-widget")]
|
||||
Box::new(Widget::Margin {
|
||||
margins: (0, 0, 0, 8),
|
||||
widget: Box::new(Widget::PulseAudio {
|
||||
@@ -204,6 +221,7 @@ impl Default for Config {
|
||||
},
|
||||
output_mode: Default::default(),
|
||||
scale: 1,
|
||||
background: Color::new(0.0, 0.0, 0.0, 0.9),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::io::BufRead;
|
||||
|
||||
pub enum ConfigFmt {
|
||||
#[cfg(feature = "yaml-cfg")]
|
||||
Yaml,
|
||||
#[cfg(feature = "json-cfg")]
|
||||
Json,
|
||||
}
|
||||
|
||||
pub const CONFIG_NAMES: &'static [&'static str] = &[
|
||||
#[cfg(feature = "yaml-cfg")]
|
||||
"config.yaml",
|
||||
#[cfg(feature = "json-cfg")]
|
||||
"config.json",
|
||||
];
|
||||
|
||||
impl ConfigFmt {
|
||||
#[inline]
|
||||
pub fn new(ext: &str) -> Option<Self> {
|
||||
match ext {
|
||||
#[cfg(feature = "yaml-cfg")]
|
||||
"yaml" => Some(ConfigFmt::Yaml),
|
||||
#[cfg(feature = "json-cfg")]
|
||||
"json" => Some(ConfigFmt::Json),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_reader<B: BufRead, T: DeserializeOwned>(&self, r: B) -> T {
|
||||
match self {
|
||||
#[cfg(feature = "yaml-cfg")]
|
||||
ConfigFmt::Yaml => serde_yaml::from_reader(r).unwrap(),
|
||||
#[cfg(feature = "json-cfg")]
|
||||
ConfigFmt::Json => serde_json::from_reader(r).unwrap(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn to_string<T: Serialize + ?Sized>(&self, src: &T) -> String {
|
||||
match self {
|
||||
#[cfg(feature = "yaml-cfg")]
|
||||
ConfigFmt::Yaml => serde_yaml::to_string(&src).unwrap(),
|
||||
#[cfg(feature = "json-cfg")]
|
||||
ConfigFmt::Json => serde_json::to_string_pretty(&src).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigFmt {
|
||||
fn default() -> Self {
|
||||
// attributes require curly braces to work
|
||||
#[cfg(feature = "yaml-cfg")]
|
||||
{
|
||||
ConfigFmt::Yaml
|
||||
}
|
||||
#[cfg(all(feature = "json-cfg", not(feature = "yaml-cfg")))]
|
||||
{
|
||||
ConfigFmt::Json
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -27,7 +27,7 @@ impl Desktop {
|
||||
enabled_escape: false,
|
||||
},
|
||||
)?;
|
||||
match file.section(Some("Desktop Entry").to_owned()) {
|
||||
match file.section(Some("Desktop Entry")) {
|
||||
Some(desktop) => Ok(Desktop {
|
||||
entry_type: desktop.get("Type").unwrap_or(&"".to_string()).to_string(),
|
||||
name: desktop.get("Name").unwrap_or(&"".to_string()).to_string(),
|
||||
@@ -45,7 +45,7 @@ impl Desktop {
|
||||
}
|
||||
|
||||
fn parse_dir(d: &str) -> Result<Vec<Desktop>, Box<dyn Error>> {
|
||||
let mut files: Vec<Desktop> = Vec::new();
|
||||
let mut files: Vec<Desktop> = Vec::with_capacity(16);
|
||||
for entry in fs::read_dir(d)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
@@ -19,7 +19,7 @@ impl DoubleMemPool {
|
||||
pub fn pool(&mut self) -> Option<(&mut MemPool, &mut MemPool)> {
|
||||
let switch = self.switch;
|
||||
self.switch = !self.switch;
|
||||
let (last, cur) = if switch {
|
||||
let (last, cur) = if !switch {
|
||||
(&mut self.pool2, &mut self.pool1)
|
||||
} else {
|
||||
(&mut self.pool1, &mut self.pool2)
|
||||
@@ -31,4 +31,8 @@ impl DoubleMemPool {
|
||||
Some((last, cur))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn never_mind(&mut self) {
|
||||
self.switch = !self.switch;
|
||||
}
|
||||
}
|
||||
|
||||
+95
-27
@@ -1,4 +1,3 @@
|
||||
use std::default::Default;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
@@ -14,6 +13,7 @@ mod buffer;
|
||||
mod cmd;
|
||||
mod color;
|
||||
mod config;
|
||||
mod configfmt;
|
||||
mod desktop;
|
||||
mod doublemempool;
|
||||
mod draw;
|
||||
@@ -22,12 +22,15 @@ mod widgets;
|
||||
|
||||
use app::{App, OutputMode};
|
||||
use cmd::Cmd;
|
||||
use config::Config;
|
||||
use configfmt::ConfigFmt;
|
||||
|
||||
enum Mode {
|
||||
Start,
|
||||
Daemonize,
|
||||
StartOrKill,
|
||||
ToggleVisible,
|
||||
PrintConfig,
|
||||
PrintConfig(ConfigFmt),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -43,34 +46,69 @@ fn main() {
|
||||
},
|
||||
};
|
||||
|
||||
let config: config::Config = match File::open(config_home + "/config.json") {
|
||||
Ok(f) => {
|
||||
let reader = BufReader::new(f);
|
||||
serde_json::from_reader(reader).unwrap()
|
||||
}
|
||||
Err(_) => Default::default(),
|
||||
};
|
||||
// From all existing files take the first readable one and write it's extension to `ext`
|
||||
let mut ext = [0x0; 8];
|
||||
let file = configfmt::CONFIG_NAMES
|
||||
.iter()
|
||||
.map(|name| std::path::Path::new(&config_home).join(name))
|
||||
.filter_map(|path| {
|
||||
match File::open(&path) {
|
||||
Ok(file) => {
|
||||
let e = path.extension().and_then(|e| e.to_str())?;
|
||||
let len = e.len();
|
||||
let from = len.saturating_sub(8); // the longest possible extension
|
||||
ext[0..len - from].copy_from_slice(&e.as_bytes()[from..]);
|
||||
Some(file)
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
.next();
|
||||
|
||||
let fmt = std::str::from_utf8(&ext)
|
||||
.ok()
|
||||
.and_then(ConfigFmt::new)
|
||||
.unwrap_or_default();
|
||||
|
||||
let config: Config = file
|
||||
.map(|f| fmt.from_reader(BufReader::new(f)))
|
||||
.unwrap_or_default();
|
||||
|
||||
let scale = config.scale;
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let mode = match args.len() {
|
||||
1 => Mode::Start,
|
||||
2 => match args[1].as_str() {
|
||||
"start" => Mode::Start,
|
||||
let mut args = env::args();
|
||||
let _ = args.next();
|
||||
let mode = match args.next() {
|
||||
Some(arg) => match arg.as_str() {
|
||||
"start" => Mode::Daemonize,
|
||||
"start-or-kill" => Mode::StartOrKill,
|
||||
"toggle-visible" => Mode::ToggleVisible,
|
||||
"print-config" => Mode::PrintConfig,
|
||||
"print-config" => Mode::PrintConfig(fmt),
|
||||
s => {
|
||||
eprintln!("unsupported sub-command {}", s);
|
||||
std::process::exit(1);
|
||||
let p = "print-config-";
|
||||
let l = p.len();
|
||||
let fmt = if s.starts_with(p) {
|
||||
ConfigFmt::new(&s[l..])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(fmt) = fmt {
|
||||
Mode::PrintConfig(fmt)
|
||||
} else {
|
||||
eprintln!("unsupported sub-command {}", s);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
v => {
|
||||
eprintln!("expected 0 or 1 arguments, got {}", v);
|
||||
std::process::exit(1);
|
||||
}
|
||||
None => Mode::Start,
|
||||
};
|
||||
if let Some(_) = args.next() {
|
||||
// total = args.count + 2 (the two we skipped + the rest)
|
||||
eprintln!("expected 0 or 1 arguments, got {}", args.count() + 2);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut daemon = false;
|
||||
|
||||
match mode {
|
||||
Mode::ToggleVisible => {
|
||||
@@ -93,8 +131,15 @@ fn main() {
|
||||
std::process::exit(1);
|
||||
};
|
||||
}
|
||||
Mode::PrintConfig => {
|
||||
println!("{}", serde_json::to_string_pretty(&config).unwrap());
|
||||
Mode::Daemonize => {
|
||||
if let Ok(_) = UnixStream::connect(socket_path.clone()) {
|
||||
eprintln!("wldash is already running");
|
||||
std::process::exit(1);
|
||||
};
|
||||
daemon = true;
|
||||
}
|
||||
Mode::PrintConfig(fmt) => {
|
||||
println!("{}", fmt.to_string(&config));
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
@@ -107,6 +152,8 @@ fn main() {
|
||||
config::OutputMode::Active => OutputMode::Active,
|
||||
};
|
||||
|
||||
let background = config.background;
|
||||
|
||||
let (tx_draw, rx_draw) = channel();
|
||||
let tx_draw_mod = tx_draw.clone();
|
||||
let (mod_tx, mod_rx) = channel();
|
||||
@@ -118,7 +165,12 @@ fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
let mut app = App::new(tx_draw, output_mode, scale);
|
||||
let mut app = App::new(tx_draw, output_mode, background, scale);
|
||||
if daemon {
|
||||
app.hide();
|
||||
} else {
|
||||
app.show();
|
||||
}
|
||||
let widget = mod_rx.recv().unwrap();
|
||||
app.set_widget(widget).unwrap();
|
||||
|
||||
@@ -177,6 +229,7 @@ fn main() {
|
||||
|
||||
app.cmd_queue().lock().unwrap().push_back(Cmd::Draw);
|
||||
|
||||
let mut visible = !daemon;
|
||||
let q = app.cmd_queue();
|
||||
loop {
|
||||
let cmd = q.lock().unwrap().pop_front();
|
||||
@@ -209,11 +262,26 @@ fn main() {
|
||||
q.lock().unwrap().push_back(Cmd::Draw);
|
||||
}
|
||||
Cmd::ToggleVisible => {
|
||||
app.toggle_visible();
|
||||
visible = !visible;
|
||||
if visible {
|
||||
app.get_widget().enter();
|
||||
app.show();
|
||||
} else {
|
||||
app.hide();
|
||||
app.get_widget().leave();
|
||||
}
|
||||
app.flush_display();
|
||||
}
|
||||
Cmd::Exit => {
|
||||
let _ = std::fs::remove_file(socket_path);
|
||||
return;
|
||||
if daemon {
|
||||
visible = false;
|
||||
app.hide();
|
||||
app.get_widget().leave();
|
||||
app.flush_display();
|
||||
} else {
|
||||
let _ = std::fs::remove_file(socket_path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
||||
@@ -30,6 +30,8 @@ impl DrawReport {
|
||||
}
|
||||
|
||||
pub trait Widget {
|
||||
fn enter(&mut self);
|
||||
fn leave(&mut self);
|
||||
fn size(&self) -> (u32, u32);
|
||||
fn draw(
|
||||
&mut self,
|
||||
@@ -60,6 +62,16 @@ impl VerticalLayout {
|
||||
}
|
||||
|
||||
impl Widget for VerticalLayout {
|
||||
fn enter(&mut self) {
|
||||
for child in &mut self.children {
|
||||
child.enter();
|
||||
}
|
||||
}
|
||||
fn leave(&mut self) {
|
||||
for child in &mut self.children {
|
||||
child.leave();
|
||||
}
|
||||
}
|
||||
fn size(&self) -> (u32, u32) {
|
||||
let mut width = 0;
|
||||
let mut height = 0;
|
||||
@@ -156,6 +168,16 @@ impl HorizontalLayout {
|
||||
}
|
||||
|
||||
impl Widget for HorizontalLayout {
|
||||
fn enter(&mut self) {
|
||||
for child in &mut self.children {
|
||||
child.enter();
|
||||
}
|
||||
}
|
||||
fn leave(&mut self) {
|
||||
for child in &mut self.children {
|
||||
child.leave();
|
||||
}
|
||||
}
|
||||
fn size(&self) -> (u32, u32) {
|
||||
let mut width = 0;
|
||||
let mut height = 0;
|
||||
@@ -258,6 +280,12 @@ impl Margin {
|
||||
}
|
||||
|
||||
impl Widget for Margin {
|
||||
fn enter(&mut self) {
|
||||
self.child.enter()
|
||||
}
|
||||
fn leave(&mut self) {
|
||||
self.child.leave()
|
||||
}
|
||||
fn size(&self) -> (u32, u32) {
|
||||
let size = self.child.size();
|
||||
(
|
||||
@@ -326,6 +354,12 @@ impl Fixed {
|
||||
}
|
||||
|
||||
impl Widget for Fixed {
|
||||
fn enter(&mut self) {
|
||||
self.child.enter()
|
||||
}
|
||||
fn leave(&mut self) {
|
||||
self.child.leave()
|
||||
}
|
||||
fn size(&self) -> (u32, u32) {
|
||||
self.size
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
use crate::color::Color;
|
||||
use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl};
|
||||
|
||||
use alsa::mixer::{Mixer, Selem, SelemChannelId, SelemId};
|
||||
|
||||
const CARD_NAME: &'static str = "default";
|
||||
const SELEM_NAME: &'static str = "Master";
|
||||
const SELEM_ID: u32 = 0u32;
|
||||
|
||||
#[inline]
|
||||
fn alsa_error_to_io_error(fail: &'static str, err: &alsa::Error) -> ::std::io::Error {
|
||||
let kind = ::std::io::ErrorKind::Other;
|
||||
let func = err.func();
|
||||
let errno = err
|
||||
.errno()
|
||||
.map(|errno| format!("; errno = {}", errno))
|
||||
.unwrap_or(String::new());
|
||||
return ::std::io::Error::new(kind, format!("{}: {}{}", fail, func, errno));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn alsa_volume_to_f32(volume: i64, min: i64, max: i64) -> f32 {
|
||||
(volume - min) as f32 / (max - min) as f32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn f32_to_alsa_volume(volume: f32, min: i64, max: i64) -> i64 {
|
||||
min + ((max - min) as f32 * volume) as i64
|
||||
}
|
||||
|
||||
pub struct Alsa {
|
||||
pub mixer: Mixer,
|
||||
}
|
||||
|
||||
impl Alsa {
|
||||
pub fn new(font_size: f32, length: u32) -> ::std::io::Result<Box<BarWidget>> {
|
||||
let mixer = Mixer::new(CARD_NAME, true)
|
||||
.map_err(|err| alsa_error_to_io_error("Failed to create ALSA mixer", &err))?;
|
||||
let mixer = Self { mixer };
|
||||
mixer.get_master_volume()?; // probing
|
||||
Ok(BarWidget::new_simple(font_size, length, Box::new(mixer)))
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_master<'a>(&'a self) -> ::std::io::Result<Selem<'a>> {
|
||||
let master_id = SelemId::new(SELEM_NAME, SELEM_ID);
|
||||
match self.mixer.find_selem(&master_id) {
|
||||
Some(master) => Ok(master),
|
||||
None => {
|
||||
let kind = ::std::io::ErrorKind::NotFound;
|
||||
let desc = "`Master` not found";
|
||||
Err(::std::io::Error::new(kind, desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_master_volume(&self) -> ::std::io::Result<f32> {
|
||||
let master = self.get_master()?;
|
||||
let (min, max) = master.get_playback_volume_range();
|
||||
master
|
||||
.get_playback_volume(SelemChannelId::mono())
|
||||
.map(|volume| alsa_volume_to_f32(volume, min, max))
|
||||
.map_err(|e| alsa_error_to_io_error("Failed to get `Master` volume", &e))
|
||||
}
|
||||
pub fn set_master_volume(&self, volume: f32) -> ::std::io::Result<()> {
|
||||
let master = self.get_master()?;
|
||||
let (min, max) = master.get_playback_volume_range();
|
||||
let volume = f32_to_alsa_volume(volume, min, max);
|
||||
master
|
||||
.set_playback_volume_all(volume)
|
||||
.map_err(|e| alsa_error_to_io_error("Failed to set `Master` volume", &e))
|
||||
}
|
||||
pub fn inc_master_volume(&self, diff: f32) -> ::std::io::Result<()> {
|
||||
let master = self.get_master()?;
|
||||
let (min, max) = master.get_playback_volume_range();
|
||||
master
|
||||
.get_playback_volume(SelemChannelId::mono())
|
||||
.and_then(|volume| {
|
||||
let volume = alsa_volume_to_f32(volume, min, max);
|
||||
let volume = f32_to_alsa_volume(volume + diff, min, max);
|
||||
master.set_playback_volume_all(volume)
|
||||
})
|
||||
.map_err(|e| alsa_error_to_io_error("Failed to inc/dec `Master` volume", &e))
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidgetImpl for Alsa {
|
||||
fn name(&self) -> &str {
|
||||
"volume"
|
||||
}
|
||||
fn value(&self) -> f32 {
|
||||
match self.get_master_volume() {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!(e),
|
||||
}
|
||||
}
|
||||
fn color(&self) -> Color {
|
||||
Color::new(1.0, 1.0, 1.0, 1.0)
|
||||
}
|
||||
fn inc(&mut self, diff: f32) {
|
||||
if let Err(e) = self.inc_master_volume(diff) {
|
||||
panic!(e)
|
||||
}
|
||||
}
|
||||
fn set(&mut self, abs: f32) {
|
||||
if let Err(e) = self.set_master_volume(abs) {
|
||||
panic!(e)
|
||||
}
|
||||
}
|
||||
fn toggle(&mut self) {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#[cfg(feature = "alsa-widget")]
|
||||
mod alsa_sound;
|
||||
#[cfg(feature = "alsa-widget")]
|
||||
pub use alsa_sound::Alsa;
|
||||
|
||||
#[cfg(feature = "pulseaudio-widget")]
|
||||
mod pulse_sound;
|
||||
#[cfg(feature = "pulseaudio-widget")]
|
||||
pub use pulse_sound::PulseAudio;
|
||||
@@ -245,7 +245,10 @@ impl PulseAudioClient {
|
||||
conn.mainloop.borrow_mut().run().unwrap();
|
||||
});
|
||||
|
||||
if !rx1.recv().unwrap() || !rx2.recv().unwrap() {
|
||||
let pa_writer_res = rx1.recv().unwrap();
|
||||
let pa_reader_res = rx2.recv().unwrap();
|
||||
|
||||
if !pa_writer_res || !pa_reader_res {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
"unable to start pulseaudio thread",
|
||||
@@ -61,6 +61,8 @@ impl BarWidget {
|
||||
}
|
||||
|
||||
impl Widget for BarWidget {
|
||||
fn enter(&mut self) {}
|
||||
fn leave(&mut self) {}
|
||||
fn size(&self) -> (u32, u32) {
|
||||
(self.length, self.font_size)
|
||||
}
|
||||
|
||||
@@ -177,6 +177,9 @@ impl Calendar {
|
||||
}
|
||||
|
||||
impl Widget for Calendar {
|
||||
fn enter(&mut self) {}
|
||||
fn leave(&mut self) {}
|
||||
|
||||
fn size(&self) -> (u32, u32) {
|
||||
let cal_width = 7 * self.font_size * 3 + self.font_size * 2;
|
||||
let cal_pad = self.font_size * 3;
|
||||
|
||||
@@ -45,8 +45,12 @@ impl Clock {
|
||||
}
|
||||
|
||||
impl Widget for Clock {
|
||||
fn enter(&mut self) {}
|
||||
fn leave(&mut self) {}
|
||||
fn size(&self) -> (u32, u32) {
|
||||
((2.0 * self.size).ceil() as u32, self.size.ceil() as u32)
|
||||
let digit = (self.size * 0.45).ceil() as u32;
|
||||
let colon = (self.size * 0.20).ceil() as u32;
|
||||
(digit * 4 + colon, self.size.ceil() as u32)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
@@ -55,7 +59,6 @@ impl Widget for Clock {
|
||||
pos: (u32, u32),
|
||||
) -> Result<DrawReport, ::std::io::Error> {
|
||||
let (width, height) = self.size();
|
||||
|
||||
if !(ctx.time.date() != self.cur_time.date()
|
||||
|| ctx.time.hour() != self.cur_time.hour()
|
||||
|| ctx.time.minute() != self.cur_time.minute()
|
||||
|
||||
@@ -27,6 +27,8 @@ impl Date {
|
||||
}
|
||||
|
||||
impl Widget for Date {
|
||||
fn enter(&mut self) {}
|
||||
fn leave(&mut self) {}
|
||||
fn size(&self) -> (u32, u32) {
|
||||
((6.5 * self.size).ceil() as u32, self.size.ceil() as u32)
|
||||
}
|
||||
|
||||
+16
-14
@@ -101,8 +101,8 @@ impl Launcher {
|
||||
)?
|
||||
};
|
||||
|
||||
x_off += size.0 + self.font_size / 4;
|
||||
width_remaining -= (size.0 + self.font_size / 4) as i32;
|
||||
x_off += size.0 + self.font_size / 2;
|
||||
width_remaining -= (size.0 + self.font_size / 2) as i32;
|
||||
|
||||
if width_remaining < 0 {
|
||||
break;
|
||||
@@ -178,20 +178,21 @@ fn calc(s: &str) -> Result<String, String> {
|
||||
}
|
||||
|
||||
fn wlcopy(s: &str) -> Result<(), String> {
|
||||
let mut child = std::process::Command::new("wl-copy")
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.arg(s)
|
||||
.spawn()
|
||||
.map_err(|_| "wl-copy not available".to_string())?;
|
||||
child
|
||||
.wait()
|
||||
.map_err(|_| "unable to run wl-copy".to_string())?;
|
||||
Ok(())
|
||||
use wl_clipboard_rs::copy::{MimeType, Options, Source};
|
||||
use failure::Fail;
|
||||
let opts = Options::new();
|
||||
opts.copy(Source::Bytes(s.as_bytes()), MimeType::Text).map_err(|e|e.name().unwrap_or("unknown error").to_string())
|
||||
}
|
||||
|
||||
impl Widget for Launcher {
|
||||
fn enter(&mut self) {}
|
||||
fn leave(&mut self) {
|
||||
self.input = "".to_string();
|
||||
self.offset = 0;
|
||||
self.result = None;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
fn size(&self) -> (u32, u32) {
|
||||
(self.length, self.font_size)
|
||||
}
|
||||
@@ -269,11 +270,12 @@ impl Widget for Launcher {
|
||||
fn keyboard_input(
|
||||
&mut self,
|
||||
key: u32,
|
||||
_: ModifiersState,
|
||||
modifiers: ModifiersState,
|
||||
_: KeyState,
|
||||
interpreted: Option<String>,
|
||||
) {
|
||||
match key {
|
||||
keysyms::XKB_KEY_u if modifiers.ctrl => self.leave(),
|
||||
keysyms::XKB_KEY_BackSpace => {
|
||||
if self.input.len() > 0 {
|
||||
self.input = self.input[..self.input.len() - 1].to_string();
|
||||
|
||||
+3
-1
@@ -5,4 +5,6 @@ pub mod calendar;
|
||||
pub mod clock;
|
||||
pub mod date;
|
||||
pub mod launcher;
|
||||
pub mod sound;
|
||||
|
||||
#[cfg(any(feature = "alsa-widget", feature = "pulseaudio-widget"))]
|
||||
pub mod audio;
|
||||
|
||||
Reference in New Issue
Block a user