2023-02-25 14:30:45 +00:00
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
pub mod clipboard;
|
2022-08-14 14:30:13 +01:00
|
|
|
/// Displays the current date and time.
|
|
|
|
///
|
|
|
|
/// A custom date/time format string can be set in the config.
|
|
|
|
///
|
|
|
|
/// Clicking the widget opens a popup containing the current time
|
|
|
|
/// with second-level precision and a calendar.
|
2023-02-01 20:42:05 +00:00
|
|
|
#[cfg(feature = "clock")]
|
2022-08-14 14:30:13 +01:00
|
|
|
pub mod clock;
|
2022-10-16 22:16:48 +01:00
|
|
|
pub mod custom;
|
2022-08-14 20:40:11 +01:00
|
|
|
pub mod focused;
|
2023-04-07 14:27:16 +01:00
|
|
|
pub mod label;
|
2022-08-14 14:30:13 +01:00
|
|
|
pub mod launcher;
|
2023-02-01 20:42:05 +00:00
|
|
|
#[cfg(feature = "music")]
|
2023-01-25 22:46:42 +00:00
|
|
|
pub mod music;
|
2022-08-14 14:30:13 +01:00
|
|
|
pub mod script;
|
2023-02-01 20:42:05 +00:00
|
|
|
#[cfg(feature = "sys_info")]
|
2022-08-14 14:30:13 +01:00
|
|
|
pub mod sysinfo;
|
2023-02-01 20:42:05 +00:00
|
|
|
#[cfg(feature = "tray")]
|
2022-08-14 14:30:13 +01:00
|
|
|
pub mod tray;
|
2023-03-19 02:16:49 +05:30
|
|
|
#[cfg(feature = "upower")]
|
|
|
|
pub mod upower;
|
2023-02-01 20:42:05 +00:00
|
|
|
#[cfg(feature = "workspaces")]
|
2022-08-14 14:30:13 +01:00
|
|
|
pub mod workspaces;
|
|
|
|
|
2023-04-21 23:51:54 +01:00
|
|
|
use crate::bridge_channel::BridgeChannel;
|
2023-04-22 14:49:15 +01:00
|
|
|
use crate::config::{BarPosition, CommonConfig, TransitionType};
|
2023-04-21 23:51:54 +01:00
|
|
|
use crate::popup::{Popup, WidgetGeometry};
|
|
|
|
use crate::{read_lock, send, write_lock};
|
2022-08-21 23:36:07 +01:00
|
|
|
use color_eyre::Result;
|
2022-08-14 14:30:13 +01:00
|
|
|
use glib::IsA;
|
2023-04-21 23:51:54 +01:00
|
|
|
use gtk::gdk::{EventMask, Monitor};
|
|
|
|
use gtk::prelude::*;
|
2023-04-22 14:49:15 +01:00
|
|
|
use gtk::{Application, EventBox, IconTheme, Orientation, Revealer, Widget};
|
2023-04-21 23:51:54 +01:00
|
|
|
use std::sync::{Arc, RwLock};
|
2022-09-25 22:49:00 +01:00
|
|
|
use tokio::sync::mpsc;
|
2023-04-21 23:51:54 +01:00
|
|
|
use tracing::debug;
|
2022-08-14 14:30:13 +01:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub enum ModuleLocation {
|
|
|
|
Left,
|
|
|
|
Center,
|
|
|
|
Right,
|
|
|
|
}
|
|
|
|
pub struct ModuleInfo<'a> {
|
|
|
|
pub app: &'a Application,
|
|
|
|
pub location: ModuleLocation,
|
2022-10-15 16:27:25 +01:00
|
|
|
pub bar_position: BarPosition,
|
2022-08-15 21:11:00 +01:00
|
|
|
pub monitor: &'a Monitor,
|
2022-08-14 20:40:11 +01:00
|
|
|
pub output_name: &'a str,
|
2023-01-29 18:38:57 +00:00
|
|
|
pub icon_theme: &'a IconTheme,
|
2022-09-25 22:49:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum ModuleUpdateEvent<T> {
|
|
|
|
/// Sends an update to the module UI
|
|
|
|
Update(T),
|
|
|
|
/// Toggles the open state of the popup.
|
2023-04-09 22:42:35 +01:00
|
|
|
TogglePopup(WidgetGeometry),
|
2022-09-25 22:49:00 +01:00
|
|
|
/// Force sets the popup open.
|
|
|
|
/// Takes the button X position and width.
|
2023-04-09 22:42:35 +01:00
|
|
|
OpenPopup(WidgetGeometry),
|
2022-09-25 22:49:00 +01:00
|
|
|
/// Force sets the popup closed.
|
|
|
|
ClosePopup,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct WidgetContext<TSend, TReceive> {
|
|
|
|
pub id: usize,
|
|
|
|
pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>,
|
|
|
|
pub controller_tx: mpsc::Sender<TReceive>,
|
|
|
|
pub widget_rx: glib::Receiver<TSend>,
|
|
|
|
pub popup_rx: glib::Receiver<TSend>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ModuleWidget<W: IsA<Widget>> {
|
|
|
|
pub widget: W,
|
|
|
|
pub popup: Option<gtk::Box>,
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait Module<W>
|
|
|
|
where
|
|
|
|
W: IsA<Widget>,
|
|
|
|
{
|
2022-09-25 22:49:00 +01:00
|
|
|
type SendMessage;
|
|
|
|
type ReceiveMessage;
|
|
|
|
|
2022-12-04 23:23:22 +00:00
|
|
|
fn name() -> &'static str;
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
fn spawn_controller(
|
|
|
|
&self,
|
|
|
|
info: &ModuleInfo,
|
|
|
|
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
|
|
|
rx: mpsc::Receiver<Self::ReceiveMessage>,
|
|
|
|
) -> Result<()>;
|
|
|
|
|
|
|
|
fn into_widget(
|
|
|
|
self,
|
|
|
|
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
|
|
|
info: &ModuleInfo,
|
|
|
|
) -> Result<ModuleWidget<W>>;
|
|
|
|
|
|
|
|
fn into_popup(
|
|
|
|
self,
|
|
|
|
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
|
|
|
_rx: glib::Receiver<Self::SendMessage>,
|
2023-01-29 18:38:57 +00:00
|
|
|
_info: &ModuleInfo,
|
2022-09-25 22:49:00 +01:00
|
|
|
) -> Option<gtk::Box>
|
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
|
|
|
None
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
2023-04-21 23:51:54 +01:00
|
|
|
|
|
|
|
/// Creates a module and sets it up.
|
|
|
|
/// This setup includes widget/popup content and event channels.
|
|
|
|
pub fn create_module<TModule, TWidget, TSend, TRec>(
|
|
|
|
module: TModule,
|
|
|
|
id: usize,
|
|
|
|
info: &ModuleInfo,
|
|
|
|
popup: &Arc<RwLock<Popup>>,
|
|
|
|
) -> Result<TWidget>
|
|
|
|
where
|
|
|
|
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
|
|
|
|
TWidget: IsA<Widget>,
|
|
|
|
TSend: Clone + Send + 'static,
|
|
|
|
{
|
|
|
|
let (w_tx, w_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
|
|
|
|
let (p_tx, p_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
|
|
|
|
|
|
|
|
let channel = BridgeChannel::<ModuleUpdateEvent<TSend>>::new();
|
|
|
|
let (ui_tx, ui_rx) = mpsc::channel::<TRec>(16);
|
|
|
|
|
|
|
|
module.spawn_controller(info, channel.create_sender(), ui_rx)?;
|
|
|
|
|
|
|
|
let context = WidgetContext {
|
|
|
|
id,
|
|
|
|
widget_rx: w_rx,
|
|
|
|
popup_rx: p_rx,
|
|
|
|
tx: channel.create_sender(),
|
|
|
|
controller_tx: ui_tx,
|
|
|
|
};
|
|
|
|
|
|
|
|
let name = TModule::name();
|
|
|
|
|
|
|
|
let module_parts = module.into_widget(context, info)?;
|
|
|
|
module_parts.widget.set_widget_name(name);
|
|
|
|
|
|
|
|
let mut has_popup = false;
|
|
|
|
if let Some(popup_content) = module_parts.popup {
|
|
|
|
register_popup_content(popup, id, popup_content);
|
|
|
|
has_popup = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup);
|
|
|
|
|
|
|
|
Ok(module_parts.widget)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Registers the popup content with the popup.
|
|
|
|
fn register_popup_content(popup: &Arc<RwLock<Popup>>, id: usize, popup_content: gtk::Box) {
|
|
|
|
write_lock!(popup).register_content(id, popup_content);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets up the bridge channel receiver
|
|
|
|
/// to pick up events from the controller, widget or popup.
|
|
|
|
///
|
|
|
|
/// Handles opening/closing popups
|
|
|
|
/// and communicating update messages between controllers and widgets/popups.
|
|
|
|
fn setup_receiver<TSend>(
|
|
|
|
channel: BridgeChannel<ModuleUpdateEvent<TSend>>,
|
|
|
|
w_tx: glib::Sender<TSend>,
|
|
|
|
p_tx: glib::Sender<TSend>,
|
|
|
|
popup: Arc<RwLock<Popup>>,
|
|
|
|
name: &'static str,
|
|
|
|
id: usize,
|
|
|
|
has_popup: bool,
|
|
|
|
) where
|
|
|
|
TSend: Clone + Send + 'static,
|
|
|
|
{
|
|
|
|
// some rare cases can cause the popup to incorrectly calculate its size on first open.
|
|
|
|
// we can fix that by just force re-rendering it on its first open.
|
|
|
|
let mut has_popup_opened = false;
|
|
|
|
|
|
|
|
channel.recv(move |ev| {
|
|
|
|
match ev {
|
|
|
|
ModuleUpdateEvent::Update(update) => {
|
|
|
|
if has_popup {
|
|
|
|
send!(p_tx, update.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
send!(w_tx, update);
|
|
|
|
}
|
|
|
|
ModuleUpdateEvent::TogglePopup(geometry) => {
|
|
|
|
debug!("Toggling popup for {} [#{}]", name, id);
|
|
|
|
let popup = read_lock!(popup);
|
|
|
|
if popup.is_visible() {
|
|
|
|
popup.hide();
|
|
|
|
} else {
|
|
|
|
popup.show_content(id);
|
|
|
|
popup.show(geometry);
|
|
|
|
|
|
|
|
if !has_popup_opened {
|
|
|
|
popup.show_content(id);
|
|
|
|
popup.show(geometry);
|
|
|
|
has_popup_opened = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ModuleUpdateEvent::OpenPopup(geometry) => {
|
|
|
|
debug!("Opening popup for {} [#{}]", name, id);
|
|
|
|
|
|
|
|
let popup = read_lock!(popup);
|
|
|
|
popup.hide();
|
|
|
|
popup.show_content(id);
|
|
|
|
popup.show(geometry);
|
|
|
|
|
|
|
|
if !has_popup_opened {
|
|
|
|
popup.show_content(id);
|
|
|
|
popup.show(geometry);
|
|
|
|
has_popup_opened = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ModuleUpdateEvent::ClosePopup => {
|
|
|
|
debug!("Closing popup for {} [#{}]", name, id);
|
|
|
|
|
|
|
|
let popup = read_lock!(popup);
|
|
|
|
popup.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Continue(true)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Takes a widget and adds it into a new `gtk::EventBox`.
|
|
|
|
/// The event box container is returned.
|
2023-04-22 14:49:15 +01:00
|
|
|
pub fn wrap_widget<W: IsA<Widget>>(
|
|
|
|
widget: &W,
|
|
|
|
common: CommonConfig,
|
|
|
|
orientation: Orientation,
|
|
|
|
) -> EventBox {
|
|
|
|
let revealer = Revealer::builder()
|
|
|
|
.transition_type(
|
|
|
|
common
|
|
|
|
.transition_type
|
|
|
|
.as_ref()
|
|
|
|
.unwrap_or(&TransitionType::SlideStart)
|
|
|
|
.to_revealer_transition_type(orientation),
|
|
|
|
)
|
|
|
|
.transition_duration(common.transition_duration.unwrap_or(250))
|
|
|
|
.build();
|
|
|
|
|
|
|
|
revealer.add(widget);
|
|
|
|
revealer.set_reveal_child(true);
|
|
|
|
|
2023-04-21 23:51:54 +01:00
|
|
|
let container = EventBox::new();
|
|
|
|
container.add_events(EventMask::SCROLL_MASK);
|
2023-04-22 14:49:15 +01:00
|
|
|
container.add(&revealer);
|
2023-04-21 23:51:54 +01:00
|
|
|
|
2023-04-22 14:49:15 +01:00
|
|
|
common.install(&container, &revealer);
|
2023-04-21 23:51:54 +01:00
|
|
|
|
|
|
|
container
|
|
|
|
}
|