feat(bar): don't think i'll pursue this

This commit is contained in:
LGUG2Z
2022-04-25 08:04:18 -07:00
parent a10b13c799
commit 506600d689
27 changed files with 2952 additions and 274 deletions
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "komorebi-bar"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
komorebi = { path = "../komorebi" }
komorebi-core = { path = "../komorebi-core" }
as-any = "0.3"
chrono = "0.4"
color-eyre = "0.6"
eframe = "0.17"
egui = "0.17"
lazy_static = "1.4"
miow = "0.4"
schemafy = "0.6"
serde = "1"
serde_json = "1"
parking_lot = "0.12"
local-ip-address = "0.4"
clipboard-win = "4.4"
sysinfo = "0.23"
[dependencies.windows]
version = "0.35"
features = [
"Win32_Graphics_Gdi",
]
+125
View File
@@ -0,0 +1,125 @@
use crate::date::Date;
use crate::ram::Ram;
use crate::time::Time;
use crate::widget::BarWidget;
use crate::widget::Output;
use crate::widget::Widget;
use crate::IpAddress;
use crate::Storage;
use crate::Workspaces;
use clipboard_win::set_clipboard_string;
use color_eyre::owo_colors::OwoColorize;
use eframe::epi::App;
use eframe::epi::Frame;
use egui::style::Margin;
use egui::CentralPanel;
use egui::Color32;
use egui::Context;
use egui::Direction;
use egui::Layout;
use egui::Rounding;
use std::process::Command;
use std::sync::atomic::Ordering;
pub struct Bar {
pub background_rgb: Color32,
pub text_rgb: Color32,
pub workspaces: Workspaces,
pub time: Time,
pub date: Date,
pub ip_address: IpAddress,
pub memory: Ram,
pub storage: Storage,
}
impl App for Bar {
fn update(&mut self, ctx: &Context, frame: &Frame) {
let custom_frame = egui::Frame {
margin: Margin::symmetric(8.0, 8.0),
rounding: Rounding::none(),
fill: self.background_rgb,
..Default::default()
};
CentralPanel::default().frame(custom_frame).show(ctx, |ui| {
ui.horizontal(|horizontal| {
horizontal.style_mut().visuals.override_text_color = Option::from(self.text_rgb);
horizontal.with_layout(Layout::left_to_right(), |ltr| {
for (i, workspace) in self.workspaces.output().iter().enumerate() {
if workspace == "komorebi offline" {
ltr.label(workspace);
} else {
ctx.request_repaint();
if ltr
.selectable_label(*self.workspaces.selected.lock() == i, workspace)
.clicked()
{
let mut selected = self.workspaces.selected.lock();
*selected = i;
if let Err(error) = Workspaces::focus(i) {
eprintln!("{}", error)
};
}
}
}
});
horizontal.with_layout(Layout::right_to_left(), |rtl| {
for time in self.time.output() {
ctx.request_repaint();
if rtl.button(format!("🕐 {}", time)).clicked() {
self.time.format.toggle()
};
}
for date in self.date.output() {
if rtl.button(format!("📅 {}", date)).clicked() {
self.date.format.next()
};
}
for memory in self.memory.output() {
if rtl.button(format!("🐏 {}", memory)).clicked() {
if let Err(error) =
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).output()
{
eprintln!("{}", error)
}
};
}
for disk in self.storage.output() {
if rtl.button(format!("🖴 {}", disk)).clicked() {
if let Err(error) = Command::new("cmd.exe")
.args([
"/C",
"explorer.exe",
disk.split(' ').collect::<Vec<&str>>()[0],
])
.output()
{
eprintln!("{}", error)
}
};
}
for ip in self.ip_address.output() {
if rtl.button(format!("🌐 {}", ip)).clicked() {
if let Err(error) =
Command::new("cmd.exe").args(["/C", "ncpa.cpl"]).output()
{
eprintln!("{}", error)
}
};
}
});
})
});
}
fn name(&self) -> &str {
"komorebi-bar"
}
}
+46
View File
@@ -0,0 +1,46 @@
use crate::widget::BarWidget;
pub enum DateFormat {
MonthDateYear,
YearMonthDate,
DateMonthYear,
DayDateMonthYear,
}
impl DateFormat {
pub fn next(&mut self) {
match self {
DateFormat::MonthDateYear => *self = Self::YearMonthDate,
DateFormat::YearMonthDate => *self = Self::DateMonthYear,
DateFormat::DateMonthYear => *self = Self::DayDateMonthYear,
DateFormat::DayDateMonthYear => *self = Self::MonthDateYear,
};
}
fn fmt_string(&self) -> String {
match self {
DateFormat::MonthDateYear => String::from("%D"),
DateFormat::YearMonthDate => String::from("%F"),
DateFormat::DateMonthYear => String::from("%v"),
DateFormat::DayDateMonthYear => String::from("%A %e %B %Y"),
}
}
}
pub struct Date {
pub format: DateFormat,
}
impl Date {
pub fn init(format: DateFormat) -> Self {
Self { format }
}
}
impl BarWidget for Date {
fn output(&mut self) -> Vec<String> {
vec![chrono::Local::now()
.format(&self.format.fmt_string())
.to_string()]
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::widget::BarWidget;
use local_ip_address::find_ifa;
use local_ip_address::local_ip;
pub struct IpAddress {
pub interface: String,
}
impl IpAddress {
pub fn init(interface: String) -> Self {
IpAddress { interface }
}
}
impl BarWidget for IpAddress {
fn output(&mut self) -> Vec<String> {
if let Ok(interfaces) = local_ip_address::list_afinet_netifas() {
if let Some((interface, ip_address)) =
local_ip_address::find_ifa(interfaces, &self.interface)
{
return vec![format!("{}: {}", interface, ip_address)];
}
}
vec![format!("{}: disconnected", self.interface)]
}
}
+80
View File
@@ -0,0 +1,80 @@
mod bar;
mod date;
mod ip_address;
mod ram;
mod storage;
mod time;
mod widget;
mod workspaces;
use crate::ip_address::IpAddress;
use crate::ram::Ram;
use crate::storage::Storage;
use bar::Bar;
use color_eyre::Result;
use date::Date;
use date::DateFormat;
use eframe::run_native;
use eframe::NativeOptions;
use egui::Color32;
use egui::Pos2;
use egui::Vec2;
use komorebi::WindowsApi;
use time::Time;
use time::TimeFormat;
use windows::Win32::Graphics::Gdi::HMONITOR;
use workspaces::Workspaces;
fn main() -> Result<()> {
let workspaces = Workspaces::init(0)?;
let time = Time::init(TimeFormat::TwentyFourHour);
let date = Date::init(DateFormat::DayDateMonthYear);
let ip_address = IpAddress::init(String::from("Ethernet"));
let app = Bar {
background_rgb: Color32::from_rgb(255, 0, 0),
text_rgb: Color32::from_rgb(255, 255, 255),
workspaces,
time,
date,
ip_address,
memory: Ram,
storage: Storage,
};
let mut win_option = NativeOptions {
decorated: false,
..Default::default()
};
// let hmonitors = WindowsApi::valid_hmonitors()?;
// for hmonitor in hmonitors {
// let info = WindowsApi::monitor_info(hmonitor)?;
// }
let info = WindowsApi::monitor_info_w(HMONITOR(65537))?;
let offset = Offsets {
vertical: 10.0,
horizontal: 200.0,
};
win_option.initial_window_pos = Option::from(Pos2::new(
info.rcWork.left as f32 + offset.horizontal,
info.rcWork.top as f32 + offset.vertical * 2.0,
));
win_option.initial_window_size = Option::from(Vec2::new(
info.rcWork.right as f32 - (offset.horizontal * 2.0),
info.rcWork.top as f32 - offset.vertical,
));
win_option.always_on_top = true;
run_native(Box::new(app), win_option);
}
struct Offsets {
vertical: f32,
horizontal: f32,
}
+15
View File
@@ -0,0 +1,15 @@
use crate::widget::BarWidget;
use sysinfo::RefreshKind;
use sysinfo::System;
use sysinfo::SystemExt;
pub struct Ram;
impl BarWidget for Ram {
fn output(&mut self) -> Vec<String> {
let sys = System::new_with_specifics(RefreshKind::new().with_memory());
let used = sys.used_memory();
let total = sys.total_memory();
vec![format!("RAM: {}%", (used * 100) / total)]
}
}
+35
View File
@@ -0,0 +1,35 @@
use crate::widget::BarWidget;
use crate::widget::Output;
use crate::widget::Widget;
use color_eyre::Result;
use sysinfo::DiskExt;
use sysinfo::RefreshKind;
use sysinfo::System;
use sysinfo::SystemExt;
pub struct Storage;
impl BarWidget for Storage {
fn output(&mut self) -> Vec<String> {
let sys = System::new_with_specifics(RefreshKind::new().with_disks_list());
let mut disks = vec![];
for disk in sys.disks() {
let mount = disk.mount_point();
let total = disk.total_space();
let available = disk.available_space();
let used = total - available;
disks.push(format!(
"{} {}%",
mount.to_string_lossy(),
(used * 100) / total
))
}
disks.reverse();
disks
}
}
+40
View File
@@ -0,0 +1,40 @@
use crate::widget::BarWidget;
pub enum TimeFormat {
TwelveHour,
TwentyFourHour,
}
impl TimeFormat {
pub fn toggle(&mut self) {
match self {
TimeFormat::TwelveHour => *self = TimeFormat::TwentyFourHour,
TimeFormat::TwentyFourHour => *self = TimeFormat::TwelveHour,
};
}
fn fmt_string(&self) -> String {
match self {
TimeFormat::TwelveHour => String::from("%l:%M:%S %p"),
TimeFormat::TwentyFourHour => String::from("%T"),
}
}
}
pub struct Time {
pub format: TimeFormat,
}
impl Time {
pub fn init(format: TimeFormat) -> Self {
Self { format }
}
}
impl BarWidget for Time {
fn output(&mut self) -> Vec<String> {
vec![chrono::Local::now()
.format(&self.format.fmt_string())
.to_string()]
}
}
+25
View File
@@ -0,0 +1,25 @@
use as_any::AsAny;
use color_eyre::Result;
#[derive(Debug, Clone)]
pub enum Output {
SingleBox(String),
MultiBox(Vec<String>),
}
#[derive(Debug, Copy, Clone)]
pub enum RepaintStrategy {
Default,
Constant,
}
pub trait Widget: AsAny {
fn output(&mut self) -> Result<Output>;
fn repaint_strategy(&self) -> RepaintStrategy {
RepaintStrategy::Default
}
}
pub trait BarWidget {
fn output(&mut self) -> Vec<String>;
}
+179
View File
@@ -0,0 +1,179 @@
use crate::widget::BarWidget;
use color_eyre::Report;
use color_eyre::Result;
use komorebi::Notification;
use komorebi::State;
use miow::pipe::NamedPipe;
use parking_lot::Mutex;
use std::io::Read;
use std::process::Command;
use std::sync::Arc;
use std::thread;
use std::thread::sleep;
use std::time::Duration;
pub struct Workspaces {
pub enabled: bool,
pub monitor_idx: usize,
pub connected: Arc<Mutex<bool>>,
pub pipe: Arc<Mutex<NamedPipe>>,
pub state: Arc<Mutex<State>>,
pub selected: Arc<Mutex<usize>>,
}
impl BarWidget for Workspaces {
fn output(&mut self) -> Vec<String> {
let state = self.state.lock();
let mut workspaces = vec![];
if let Some(primary_monitor) = state.monitors.elements().get(self.monitor_idx) {
for (i, workspace) in primary_monitor.workspaces().iter().enumerate() {
workspaces.push(if let Some(name) = workspace.name() {
name.clone()
} else {
format!("{}", i + 1)
});
}
}
if workspaces.is_empty() || !*self.connected.lock() {
vec!["komorebi offline".to_string()]
} else {
workspaces
}
}
}
const PIPE: &str = r#"\\.\pipe\"#;
impl Workspaces {
pub fn focus(index: usize) -> Result<()> {
Ok(Command::new("cmd.exe")
.args([
"/C",
"komorebic.exe",
"focus-workspace",
&format!("{}", index),
])
.output()
.map(|_| ())?)
}
pub fn init(monitor_idx: usize) -> Result<Self> {
let name = format!("bar-{}", monitor_idx);
let pipe = format!("{}\\{}", PIPE, name);
let mut named_pipe = NamedPipe::new(pipe)?;
let mut output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", &name])
.output()?;
while !output.status.success() {
println!(
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
output.status.code()
);
sleep(Duration::from_secs(5));
output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", &name])
.output()?;
}
named_pipe.connect()?;
let mut buf = vec![0; 4096];
let mut bytes_read = named_pipe.read(&mut buf)?;
let mut data = String::from_utf8(buf[0..bytes_read].to_vec())?;
while data == "\n" {
bytes_read = named_pipe.read(&mut buf)?;
data = String::from_utf8(buf[0..bytes_read].to_vec())?;
}
let notification: Notification = serde_json::from_str(&data)?;
let mut workspaces = Self {
enabled: true,
monitor_idx,
connected: Arc::new(Mutex::new(true)),
pipe: Arc::new(Mutex::new(named_pipe)),
state: Arc::new(Mutex::new(notification.state)),
selected: Arc::new(Mutex::new(0)),
};
workspaces.listen()?;
Ok(workspaces)
}
pub fn listen(&mut self) -> Result<()> {
let state = self.state.clone();
let pipe = self.pipe.clone();
let connected = self.connected.clone();
let selected = self.selected.clone();
thread::spawn(move || -> Result<()> {
let mut buf = vec![0; 4096];
loop {
let mut named_pipe = pipe.lock();
match (*named_pipe).read(&mut buf) {
Ok(bytes_read) => {
let data = String::from_utf8(buf[0..bytes_read].to_vec())?;
if data == "\n" {
continue;
}
let notification: Notification = serde_json::from_str(&data)?;
let mut sl = selected.lock();
*sl = notification.state.monitors.elements()[0].focused_workspace_idx();
let mut st = state.lock();
*st = notification.state;
}
Err(error) => {
// Broken pipe
if error.raw_os_error().unwrap() == 109 {
{
let mut cn = connected.lock();
*cn = false;
}
named_pipe.disconnect()?;
let mut output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", "bar"])
.output()?;
while !output.status.success() {
println!(
"komorebic.exe failed with error code {:?}, retrying in 5 seconds...",
output.status.code()
);
sleep(Duration::from_secs(5));
output = Command::new("cmd.exe")
.args(["/C", "komorebic.exe", "subscribe", "bar"])
.output()?;
}
named_pipe.connect()?;
{
let mut cn = connected.lock();
*cn = true;
}
} else {
return Err(Report::from(error));
}
}
}
}
});
Ok(())
}
}