mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-09-11 12:22:06 +02:00
feat(bar): don't think i'll pursue this
This commit is contained in:
@@ -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",
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()]
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()]
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user