123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod commands;
mod config;
mod events;
mod hlwm;
mod icons;
mod media;
mod readers;
mod resume;
mod stats;
mod tray;
mod weather;
mod xicon;
#[cfg(target_os = "linux")]
mod linux;
use tauri::{Manager, PhysicalPosition, PhysicalSize, WebviewUrl, WebviewWindowBuilder};
/// Fallback bar height (logical px) when the config has none. The window
/// must be sized before the webview loads, so this mirrors the height
/// default in the frontend's bar schema (src/lib/bar-config.js) — keep
/// the two equal.
pub const DEFAULT_BAR_HEIGHT: f64 = 48.0;
/// Extra transparent space below the bar (logical px) where pop-down panels
/// (volume/brightness sliders) can render. Normally click-through via the
/// X11 input shape; made interactive only while a popup is open.
pub const POPUP_SPACE: f64 = 320.0;
/// Window geometry in physical pixels, shared (behind a Mutex) with
/// commands that toggle the interactive input region or resize the bar.
pub struct Geometry {
pub width_px: u32,
pub bar_px: u32,
pub total_px: u32,
/// Multiplier from frontend CSS px to physical px (zoom * monitor scale).
pub css_scale: f64,
/// Webview zoom factor, also applied to auxiliary windows (settings).
pub zoom: f64,
/// Whether a compositor is running; without one the window's bounding
/// shape must be clipped to visible content (transparency shows black).
pub composited: bool,
/// Monitor origin (for strut coordinates in root-window space).
pub origin_x: i32,
}
pub fn bar_height_from(config: &serde_json::Value) -> f64 {
config
.get("bar")
.and_then(|b| b.get("height"))
.and_then(serde_json::Value::as_f64)
.filter(|h| (16.0..=256.0).contains(h))
.unwrap_or(DEFAULT_BAR_HEIGHT)
}
/// Whether to force WebKitGTK's non-composited (software) rendering path.
///
/// Before WebKitGTK 2.54, accelerated compositing rendered black on
/// shaped/ARGB X11 windows (especially without a compositor), so the
/// software path was the only one that worked. On 2.54+ this legacy path
/// itself renders the bar black, so the override is limited to older
/// WebKit; the 2.54+ compositor-less case is handled in `setup` instead
/// (opaque window + shared-memory renderer). Setting
/// WEBKIT_DISABLE_COMPOSITING_MODE yourself (to "1" or "0") always wins.
#[cfg(target_os = "linux")]
fn force_software_compositing() -> bool {
// SAFETY: plain FFI getters with no preconditions or side effects.
let (major, minor) = unsafe {
(
webkit2gtk::ffi::webkit_get_major_version(),
webkit2gtk::ffi::webkit_get_minor_version(),
)
};
(major, minor) < (2, 54)
}
fn main() {
#[cfg(target_os = "linux")]
if std::env::var_os("WEBKIT_DISABLE_COMPOSITING_MODE").is_none()
&& force_software_compositing()
{
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
}
tauri::Builder::default()
.setup(|app| {
// Whether a compositing manager is running. Without one, the
// window's bounding shape clips everything outside the bar strip
// (and any open popup), so the window itself can be opaque. That
// matters since WebKitGTK 2.54: its renderers paint transparent
// surfaces as black or only partially on compositor-less X11,
// whereas an opaque window draws fine. The black backdrop looks
// identical to what the uncomposited X server showed through the
// alpha before (it ignores alpha and displays premultiplied RGB).
#[cfg(target_os = "linux")]
let composited = linux::screen_is_composited();
#[cfg(not(target_os = "linux"))]
let composited = true;
// Without a compositor the window carries an X11 *bounding*
// shape (see linux::set_region), and WebKitGTK 2.54's GPU
// (DMA-BUF) renderer paints nothing into a bounding-shaped
// window — a plain GtkWindow + WebKitWebView with a shape
// reproduces it. Its shared-memory renderer, the path WebKit
// uses when GL is unavailable, works, so select it before the
// first webview is created. Honor an explicit setting from the
// environment.
#[cfg(target_os = "linux")]
if !composited && std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() {
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
}
let builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
.title("divebar")
.decorations(false)
.always_on_top(true)
.skip_taskbar(true)
.visible(false);
let builder = if composited {
builder.transparent(true)
} else {
builder.background_color(tauri::window::Color(0, 0, 0, 255))
};
let window = builder.build()?;
// HiDPI: WebKitGTK only honors the integer GDK window scale, not
// font DPI, so allow zooming the whole webview. Priority:
// DIVEBAR_ZOOM env var > Xft.dpi autodetection > 1.0.
let zoom = std::env::var("DIVEBAR_ZOOM")
.ok()
.and_then(|v| v.parse::<f64>().ok())
.filter(|z| (0.25..=8.0).contains(z));
#[cfg(target_os = "linux")]
let zoom = zoom.or_else(|| linux::detect_zoom(&window));
let zoom = zoom.unwrap_or(1.0);
if zoom != 1.0 {
window.set_zoom(zoom)?;
}
// Size the bar to span the primary monitor, glued to its top edge.
let monitor = window
.primary_monitor()?
.or_else(|| window.current_monitor().ok().flatten())
.expect("no monitor found");
let scale = monitor.scale_factor();
let bar_height = bar_height_from(&config::load());
let bar_height_px = (bar_height * zoom * scale).round() as u32;
let total_height_px = ((bar_height + POPUP_SPACE) * zoom * scale).round() as u32;
let monitor_width_px = monitor.size().width;
let origin = *monitor.position();
// The window includes transparent popup space below the bar, but
// only the bar strip reserves screen space (strut) and accepts
// input by default.
window.set_size(PhysicalSize::new(monitor_width_px, total_height_px))?;
window.set_position(PhysicalPosition::new(origin.x, origin.y))?;
// On X11, mark the window as a dock and reserve screen space so
// tiled/maximized windows don't overlap the bar.
#[cfg(target_os = "linux")]
linux::setup_dock_window(&window, bar_height_px, monitor_width_px, origin.x);
app.manage(std::sync::Mutex::new(Geometry {
width_px: monitor_width_px,
bar_px: bar_height_px,
total_px: total_height_px,
css_scale: zoom * scale,
zoom,
composited,
origin_x: origin.x,
}));
window.show()?;
// Shape after mapping — some setups drop shapes applied pre-show.
#[cfg(target_os = "linux")]
{
let bar_strip = [(0, 0, monitor_width_px as i32, bar_height_px as i32)];
linux::set_region(&window, &bar_strip, !composited);
}
stats::spawn_stats_emitter(app.handle().clone());
hlwm::spawn_hlwm_watcher(app.handle().clone());
tray::spawn_tray_host(app.handle().clone());
weather::spawn_weather(app.handle().clone());
media::spawn_media(app.handle().clone());
resume::spawn_resume_watcher(app.handle().clone());
Ok(())
})
.invoke_handler(tauri::generate_handler![
commands::hlwm_use_tag,
commands::set_volume,
commands::toggle_mute,
commands::set_brightness,
commands::display_off,
commands::set_popup_open,
commands::get_env,
commands::get_latest_states,
commands::media_control,
commands::tray_activate,
commands::tray_menu_click,
commands::get_config,
commands::get_default_config,
commands::save_config,
commands::open_settings,
commands::close_settings,
commands::list_fonts,
commands::launch_wifi_gui,
commands::lock_screen,
commands::resolve_location,
])
.run(tauri::generate_context!())
.expect("error while running divebar");
}
|