Author SHA1 Message Date
Kenny Levinsen a5513802b0 Test 2019-11-21 22:48:27 +01:00
Kenny Levinsen 9233128b7c Use global poll for ipc_server 2019-10-05 22:43:09 +02:00
Kenny Levinsen b51fc8a1b0 Use global poll for UpowerBattery 2019-10-05 22:42:26 +02:00
Kenny Levinsen 0de3da162b widget: Add wait support
Widgets can now specify that they wish to wait on an fd or for a target
time to be reached.

This can be used to save a few worker threads.
2019-10-05 01:07:06 +02:00
Pedro Côrte-Real 510ad34183 Remove errant semicolon (#24) 2019-10-03 19:03:38 +02:00
15 changed files with 230 additions and 141 deletions
Generated
+10
View File
@@ -586,6 +586,14 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "timerfd"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typenum"
version = "1.11.2"
@@ -723,6 +731,7 @@ dependencies = [
"serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
"shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smithay-client-toolkit 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"timerfd 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wayland-client 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)",
"wayland-protocols 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -818,6 +827,7 @@ dependencies = [
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum timerfd 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6c9a7822e546fa39d0b5ae14a93a33903975b62af6597288aea77f0580a6abbe"
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
"checksum ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+1
View File
@@ -33,6 +33,7 @@ lazy_static = "1.4"
rcalc_lib = { version = "0.9" }
rust-ini = "0.13"
shlex = "0.1"
timerfd = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true }
+1 -1
View File
@@ -91,7 +91,7 @@ impl Widget {
.map(|x| x.unwrap())
.collect(),
)),
Widget::Clock { font_size } => Some(widgets::clock::Clock::new(font_size, tx.clone())),
Widget::Clock { font_size } => Some(widgets::clock::Clock::new(font_size)),
Widget::Date { font_size } => Some(widgets::date::Date::new(font_size)),
Widget::Calendar {
font_size,
+69 -43
View File
@@ -5,8 +5,10 @@ use std::os::unix::io::AsRawFd;
use std::os::unix::net::{UnixListener, UnixStream};
use std::sync::mpsc::channel;
use timerfd::{TimerFd, TimerState, SetTimeFlags};
use nix::poll::{poll, PollFd, PollFlags};
use os_pipe::pipe;
use chrono::{Local, Duration};
mod app;
mod buffer;
@@ -24,6 +26,7 @@ use app::{App, OutputMode};
use cmd::Cmd;
use config::Config;
use configfmt::ConfigFmt;
use widget::WaitContext;
enum Mode {
Start,
@@ -186,50 +189,20 @@ fn main() {
tx_pipe.write_all(&[0x1]).unwrap();
});
let ipc_queue = app.cmd_queue();
let _ = std::thread::Builder::new()
.name("ipc_server".to_string())
.spawn(move || loop {
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let client_queue = ipc_queue.clone();
let mut client_pipe = ipc_pipe.try_clone().unwrap();
let _ = std::thread::Builder::new()
.name("ipc_client".to_string())
.spawn(move || {
let r = BufReader::new(stream);
for line in r.lines() {
match line {
Ok(v) => match v.as_str() {
"kill" => {
client_queue.lock().unwrap().push_back(Cmd::Exit)
}
"toggle_visible" => client_queue
.lock()
.unwrap()
.push_back(Cmd::ToggleVisible),
v => eprintln!("unknown command: {}", v),
},
Err(_) => return,
}
client_pipe.write_all(&[0x1]).unwrap();
}
});
}
Err(_) => break,
}
}
});
let mut fds = [
PollFd::new(app.event_queue().get_connection_fd(), PollFlags::POLLIN),
PollFd::new(rx_pipe.as_raw_fd(), PollFlags::POLLIN),
];
let mut timer = TimerFd::new().unwrap();
let ev_fd = PollFd::new(app.event_queue().get_connection_fd(), PollFlags::POLLIN);
let rx_fd = PollFd::new(rx_pipe.as_raw_fd(), PollFlags::POLLIN);
let tm_fd = PollFd::new(timer.as_raw_fd(), PollFlags::POLLIN);
let ipc_fd = PollFd::new(listener.as_raw_fd(), PollFlags::POLLIN);
app.cmd_queue().lock().unwrap().push_back(Cmd::Draw);
let mut visible = !daemon;
let mut wait_ctx = WaitContext{
fds: Vec::new(),
target_time: None,
};
let q = app.cmd_queue();
loop {
let cmd = q.lock().unwrap().pop_front();
@@ -287,9 +260,28 @@ fn main() {
None => {
app.flush_display();
poll(&mut fds, -1).unwrap();
wait_ctx.fds.clear();
wait_ctx.fds.push(ev_fd);
wait_ctx.fds.push(rx_fd);
wait_ctx.fds.push(ipc_fd);
wait_ctx.target_time = None;
if fds[0].revents().unwrap().contains(PollFlags::POLLIN) {
app.get_widget().wait(&mut wait_ctx);
if let Some(target_time) = wait_ctx.target_time {
let n = Local::now();
let sleep = if target_time > n {
target_time - n
} else {
Duration::seconds(0)
};
timer.set_state(TimerState::Oneshot(sleep.to_std().unwrap()), SetTimeFlags::Default);
wait_ctx.fds.push(tm_fd);
}
poll(&mut wait_ctx.fds, -1).unwrap();
if wait_ctx.fds[0].revents().unwrap().contains(PollFlags::POLLIN) {
if let Some(guard) = app.event_queue().prepare_read() {
if let Err(e) = guard.read_events() {
if e.kind() != ::std::io::ErrorKind::WouldBlock {
@@ -306,10 +298,44 @@ fn main() {
.expect("Failed to dispatch all messages.");
}
if fds[1].revents().unwrap().contains(PollFlags::POLLIN) {
if wait_ctx.fds[1].revents().unwrap().contains(PollFlags::POLLIN) {
let mut v = [0x00];
rx_pipe.read_exact(&mut v).unwrap();
}
if wait_ctx.fds[2].revents().unwrap().contains(PollFlags::POLLIN) {
if let Ok((stream, _)) = listener.accept() {
let client_queue = q.clone();
let mut client_pipe = ipc_pipe.try_clone().unwrap();
let _ = std::thread::Builder::new()
.name("ipc_client".to_string())
.spawn(move || {
let r = BufReader::new(stream);
for line in r.lines() {
match line {
Ok(v) => match v.as_str() {
"kill" => {
client_queue.lock().unwrap().push_back(Cmd::Exit)
}
"toggle_visible" => client_queue
.lock()
.unwrap()
.push_back(Cmd::ToggleVisible),
v => eprintln!("unknown command: {}", v),
},
Err(_) => return,
}
client_pipe.write_all(&[0x1]).unwrap();
}
});
}
}
if wait_ctx.target_time.is_some() &&
wait_ctx.fds[wait_ctx.fds.len()-1].revents().unwrap().contains(PollFlags::POLLIN) {
timer.read();
q.lock().unwrap().push_back(Cmd::Draw);
}
}
}
}
+35
View File
@@ -1,3 +1,4 @@
use nix::poll::{PollFd};
use crate::buffer::Buffer;
use crate::color::Color;
use chrono::{DateTime, Local};
@@ -29,7 +30,25 @@ impl DrawReport {
}
}
pub struct WaitContext {
pub fds: Vec<PollFd>,
pub target_time: Option<DateTime<Local>>,
}
impl WaitContext {
pub fn set_time(&mut self, new_time: DateTime<Local>) {
if let Some(ot) = self.target_time {
if new_time < ot {
self.target_time = Some(new_time);
}
} else {
self.target_time = Some(new_time);
}
}
}
pub trait Widget {
fn wait(&mut self, ctx: &mut WaitContext);
fn enter(&mut self);
fn leave(&mut self);
fn size(&self) -> (u32, u32);
@@ -62,6 +81,11 @@ impl VerticalLayout {
}
impl Widget for VerticalLayout {
fn wait(&mut self, ctx: &mut WaitContext) {
for child in &mut self.children {
child.wait(ctx);
}
}
fn enter(&mut self) {
for child in &mut self.children {
child.enter();
@@ -168,6 +192,11 @@ impl HorizontalLayout {
}
impl Widget for HorizontalLayout {
fn wait(&mut self, ctx: &mut WaitContext) {
for child in &mut self.children {
child.wait(ctx);
}
}
fn enter(&mut self) {
for child in &mut self.children {
child.enter();
@@ -280,6 +309,9 @@ impl Margin {
}
impl Widget for Margin {
fn wait(&mut self, ctx: &mut WaitContext) {
self.child.wait(ctx)
}
fn enter(&mut self) {
self.child.enter()
}
@@ -354,6 +386,9 @@ impl Fixed {
}
impl Widget for Fixed {
fn wait(&mut self, ctx: &mut WaitContext) {
self.child.wait(ctx)
}
fn enter(&mut self) {
self.child.enter()
}
+2
View File
@@ -1,5 +1,6 @@
use crate::color::Color;
use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl};
use crate::widget::WaitContext;
use alsa::mixer::{Mixer, Selem, SelemChannelId, SelemId};
@@ -83,6 +84,7 @@ impl Alsa {
}
impl BarWidgetImpl for Alsa {
fn wait(&mut self, _: &mut WaitContext) {}
fn name(&self) -> &str {
"volume"
}
+2
View File
@@ -1,6 +1,7 @@
use crate::cmd::Cmd;
use crate::color::Color;
use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl};
use crate::widget::WaitContext;
use std::cell::RefCell;
use std::collections::HashMap;
@@ -502,6 +503,7 @@ impl PulseAudio {
}
impl BarWidgetImpl for PulseAudio {
fn wait(&mut self, _: &mut WaitContext) {}
fn name(&self) -> &str {
"volume"
}
+2
View File
@@ -1,5 +1,6 @@
use crate::color::Color;
use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl};
use crate::widget::WaitContext;
use std::fs::OpenOptions;
use std::io::{Error, ErrorKind};
@@ -93,6 +94,7 @@ impl Backlight {
}
impl BarWidgetImpl for Backlight {
fn wait(&mut self, _: &mut WaitContext) {}
fn name(&self) -> &str {
"backlight"
}
+5 -2
View File
@@ -1,10 +1,11 @@
use crate::color::Color;
use crate::draw::{draw_bar, draw_box, Font, ROBOTO_REGULAR};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext};
use std::sync::{Arc, Mutex};
pub trait BarWidgetImpl {
fn wait(&mut self, ctx: &mut WaitContext);
fn name(&self) -> &str;
fn value(&self) -> f32;
fn color(&self) -> Color;
@@ -61,12 +62,14 @@ impl BarWidget {
}
impl Widget for BarWidget {
fn wait(&mut self, ctx: &mut WaitContext) {
self.bar_impl.wait(ctx);
}
fn enter(&mut self) {}
fn leave(&mut self) {}
fn size(&self) -> (u32, u32) {
(self.length, self.font_size)
}
fn draw(
&mut self,
ctx: &mut DrawContext,
+70 -55
View File
@@ -1,10 +1,12 @@
use crate::cmd::Cmd;
use crate::color::Color;
use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl};
use crate::widget::WaitContext;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::thread;
use nix::poll::{PollFd, PollFlags};
use dbus;
@@ -28,21 +30,34 @@ fn get_upower_property(
.append2(
dbus::MessageItem::Str("org.freedesktop.UPower.Device".to_string()),
dbus::MessageItem::Str(property.to_string()),
);;
);
con.send_with_reply_and_block(msg, 1000).map_err(|_| {
::std::io::Error::new(::std::io::ErrorKind::Other, "could not send dbus message")
})
}
pub struct UpowerBattery {
device_path: String,
inner: Arc<Mutex<UpowerBatteryInner>>,
// The widget is created on a different thread than where it will be used, so
// we need it to be Send. However, dbus::Connection has a void*, so it's not
// auto-derived. So, we make a small wrapper where we make it Send.
struct DbusConnection(dbus::Connection);
impl std::convert::AsRef<dbus::Connection> for DbusConnection {
fn as_ref(&self) -> &dbus::Connection {
return &self.0
}
}
pub struct UpowerBatteryInner {
unsafe impl Send for DbusConnection {}
pub struct UpowerBattery {
device_path: String,
sender: Sender<Cmd>,
dirty: Arc<Mutex<bool>>,
state: UpowerBatteryState,
capacity: f64,
con: DbusConnection,
watch: dbus::Watch,
}
enum UpowerBatteryState {
@@ -55,12 +70,26 @@ enum UpowerBatteryState {
}
impl UpowerBattery {
pub fn from_device(device: &str) -> Result<Self, ::std::io::Error> {
pub fn from_device(dirty: Arc<Mutex<bool>>, sender: Sender<Cmd>, device: &str) -> Result<Self, ::std::io::Error> {
let con = dbus::Connection::get_private(dbus::BusType::System)
.map_err(|_| ::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to open dbus",
))?;
let device_path = format!("/org/freedesktop/UPower/devices/battery_{}", device);
let con = dbus::Connection::get_private(dbus::BusType::System).map_err(|_| {
::std::io::Error::new(::std::io::ErrorKind::Other, "could not get dbus connection")
})?;
let rule = format!(
"type='signal',\
path='{}',\
interface='org.freedesktop.DBus.Properties',\
member='PropertiesChanged'",
&device_path
);
con.add_match(&rule)
.map_err(|_| ::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to add match rule to dbus connection",
))?;
let upower_type: dbus::arg::Variant<u32> =
match get_upower_property(&con, &device_path, "Type")?.get1() {
Some(v) => v,
@@ -111,12 +140,22 @@ impl UpowerBattery {
}
};
let fds = con.watch_fds();
if fds.len() != 1 {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"expected 1 watch fd from dbus",
));
}
Ok(UpowerBattery {
device_path,
inner: Arc::new(Mutex::new(UpowerBatteryInner {
con: DbusConnection(con),
dirty,
sender,
capacity: capacity,
state: state,
})),
watch: fds[0],
})
}
@@ -126,36 +165,21 @@ impl UpowerBattery {
sender: Sender<Cmd>,
) -> Result<Box<BarWidget>, ::std::io::Error> {
BarWidget::new(font_size, length, move |dirty| {
let d = UpowerBattery::from_device("BAT0")?;
let path = d.device_path.clone();
let inner = d.inner.clone();
let _ = thread::Builder::new()
.name("battery_monitor".to_string())
.spawn(move || {
let con = dbus::Connection::get_private(dbus::BusType::System)
.expect("Failed to establish D-Bus connection.");
let rule = format!(
"type='signal',\
path='{}',\
interface='org.freedesktop.DBus.Properties',\
member='PropertiesChanged'",
path
);
let d = UpowerBattery::from_device(dirty, sender, "BAT0")?;
Ok(Box::new(d))
})
}
}
// First we're going to get an (irrelevant) NameAcquired event.
con.incoming(10_000).next();
con.add_match(&rule)
.expect("Failed to add D-Bus match rule.");
loop {
if con.incoming(10_000).next().is_some() {
let capacity = get_upower_property(&con, &path, "Percentage")
impl BarWidgetImpl for UpowerBattery {
fn wait(&mut self, ctx: &mut WaitContext) {
for _ in self.con.as_ref().watch_handle(self.watch.fd(), dbus::WatchEvent::Readable as u32) {
let capacity = get_upower_property(self.con.as_ref(), &self.device_path, "Percentage")
.unwrap()
.get1::<dbus::arg::Variant<f64>>()
.unwrap()
.0;
let state = match get_upower_property(&con, &path, "State")
let state = match get_upower_property(self.con.as_ref(), &self.device_path, "State")
.unwrap()
.get1::<dbus::arg::Variant<u32>>()
.unwrap()
@@ -169,33 +193,24 @@ impl UpowerBattery {
6 => UpowerBatteryState::Discharging,
_ => UpowerBatteryState::Unknown,
};
let mut inner = inner.lock().unwrap();
inner.state = state;
inner.capacity = capacity;
*dirty.lock().unwrap() = true;
sender.send(Cmd::Draw).unwrap();
}
}
});
Ok(Box::new(d))
})
}
self.state = state;
self.capacity = capacity;
*self.dirty.lock().unwrap() = true;
self.sender.send(Cmd::Draw).unwrap();
}
impl BarWidgetImpl for UpowerBattery {
ctx.fds.push(PollFd::new(self.watch.fd(), PollFlags::POLLIN));
}
fn name(&self) -> &str {
"battery"
}
fn value(&self) -> f32 {
let inner = self.inner.lock().unwrap();
(inner.capacity as f32) / 100.0
(self.capacity as f32) / 100.0
}
fn color(&self) -> Color {
let inner = self.inner.lock().unwrap();
match inner.state {
match self.state {
UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => {
if inner.capacity > 10.0 {
if self.capacity > 10.0 {
Color::new(1.0, 1.0, 1.0, 1.0)
} else {
Color::new(1.0, 0.5, 0.0, 1.0)
+2 -1
View File
@@ -1,7 +1,7 @@
use crate::buffer::Buffer;
use crate::color::Color;
use crate::draw::{Font, DEJAVUSANS_MONO, ROBOTO_REGULAR};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext};
use chrono::{Date, Datelike, Local};
@@ -177,6 +177,7 @@ impl Calendar {
}
impl Widget for Calendar {
fn wait(&mut self, _: &mut WaitContext) {}
fn enter(&mut self) {}
fn leave(&mut self) {}
+10 -21
View File
@@ -1,9 +1,6 @@
use crate::cmd::Cmd;
use crate::color::Color;
use crate::draw::{Font, ROBOTO_REGULAR};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget};
use std::sync::mpsc::Sender;
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext};
use chrono::{DateTime, Datelike, Duration, Local, Timelike};
@@ -14,23 +11,7 @@ pub struct Clock {
}
impl Clock {
pub fn new(size: f32, ch: Sender<Cmd>) -> Box<Clock> {
let _ = std::thread::Builder::new()
.name("clock_ticker".to_string())
.spawn(move || loop {
let n = Local::now();
let target = (n + Duration::seconds(60))
.with_second(0)
.unwrap()
.with_nanosecond(0)
.unwrap();
let d = target - n;
std::thread::sleep(d.to_std().unwrap());
ch.send(Cmd::Draw).unwrap();
});
pub fn new(size: f32) -> Box<Clock> {
let mut clock_cache = Font::new(&ROBOTO_REGULAR, size);
clock_cache.add_str_to_cache("0123456789:");
@@ -45,6 +26,14 @@ impl Clock {
}
impl Widget for Clock {
fn wait(&mut self, ctx: &mut WaitContext) {
let target = (self.cur_time + Duration::seconds(60))
.with_second(0)
.unwrap()
.with_nanosecond(0)
.unwrap();
ctx.set_time(target);
}
fn enter(&mut self) {}
fn leave(&mut self) {}
fn size(&self) -> (u32, u32) {
+2 -1
View File
@@ -1,6 +1,6 @@
use crate::color::Color;
use crate::draw::{Font, ROBOTO_REGULAR};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext};
use chrono::{DateTime, Datelike, Local};
@@ -27,6 +27,7 @@ impl Date {
}
impl Widget for Date {
fn wait(&mut self, _: &mut WaitContext) {}
fn enter(&mut self) {}
fn leave(&mut self) {}
fn size(&self) -> (u32, u32) {
+2 -1
View File
@@ -3,7 +3,7 @@ use crate::cmd::Cmd;
use crate::color::Color;
use crate::desktop::{load_desktop_files, Desktop};
use crate::draw::{Font, ROBOTO_REGULAR};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget};
use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget, WaitContext};
use std::cell::RefCell;
use std::cmp::Ordering;
@@ -192,6 +192,7 @@ fn wlcopy(s: &str) -> Result<(), String> {
}
impl Widget for Launcher {
fn wait(&mut self, _: &mut WaitContext) {}
fn enter(&mut self) {}
fn leave(&mut self) {
self.input = "".to_string();
+1
View File
@@ -0,0 +1 @@
asdfasdf