From 85b90b73f81b1abc224c3a3e1fad686bede3ab1d Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Wed, 2 Sep 2026 07:11:51 +0530 Subject: [PATCH] feat: Implement full GTK Wayland overlay menu (GPU Screen Recorder style) --- src/bin/vrec-ui.rs | 16 ++++--- src/overlay.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/bin/vrec-ui.rs b/src/bin/vrec-ui.rs index 361b4f1..7935e60 100644 --- a/src/bin/vrec-ui.rs +++ b/src/bin/vrec-ui.rs @@ -19,10 +19,11 @@ fn main() -> Result<(), Box> { if args.len() > 1 { match args[1].as_str() { "--save" => { + vrec::overlay::show_notification_overlay(); return send_command(Command::SaveReplay).map_err(|e| e.into()); } "--menu" => { - vrec::overlay::show_saved_overlay(); + vrec::overlay::show_menu_overlay(); return Ok(()); } _ => {} @@ -39,13 +40,13 @@ fn main() -> Result<(), Box> { if session_type.to_lowercase() == "wayland" { if is_gnome || is_kde { - println!("WARNING: wlr-layer-shell unsupported on GNOME/KDE Wayland. Degrading to hotkey-only mode."); - println!("Note: Global hotkeys may also fail to register on some Wayland compositors."); + println!("WARNING: Wayland native layer shell used. Ensure your compositor allows it."); + println!("Note: Global hotkeys may fail to register on Wayland compositors."); } else { - println!("wlroots-based compositor detected. (wlr-layer-shell overlay would be spawned here)."); + println!("wlroots-based compositor detected."); } } else { - println!("X11 session detected. (X11 override-redirect overlay would be spawned here)."); + println!("X11 session detected."); } let manager = GlobalHotKeyManager::new().unwrap(); @@ -66,10 +67,11 @@ fn main() -> Result<(), Box> { if event.state == global_hotkey::HotKeyState::Pressed { if event.id == save_hotkey.id() { println!("Hotkey triggered! Sending SaveReplay command to daemon..."); + vrec::overlay::show_notification_overlay(); let _ = send_command(Command::SaveReplay); } else if event.id == menu_hotkey.id() { - println!("Alt+Z triggered! Opening GUI Overlay..."); - vrec::overlay::show_saved_overlay(); + println!("Alt+Z triggered! Opening GUI Menu Overlay..."); + vrec::overlay::show_menu_overlay(); } } } diff --git a/src/overlay.rs b/src/overlay.rs index e8d6360..5077cb1 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -3,9 +3,9 @@ use gtk::{Application, ApplicationWindow, Label, CssProvider, StyleContext}; use gtk_layer_shell::{LayerShell, Layer}; use std::time::Duration; -pub fn show_saved_overlay() { +pub fn show_notification_overlay() { let app = Application::builder() - .application_id("com.vrec.overlay") + .application_id("com.vrec.notification") .build(); app.connect_activate(|app| { @@ -56,3 +56,103 @@ pub fn show_saved_overlay() { app.run_with_args(&[] as &[&str]); } + +pub fn show_menu_overlay() { + let app = Application::builder() + .application_id("com.vrec.menu") + .build(); + + app.connect_activate(|app| { + let window = ApplicationWindow::builder() + .application(app) + .default_width(700) + .default_height(200) + .build(); + + window.init_layer_shell(); + window.set_layer(Layer::Overlay); + window.set_namespace("vrec-menu"); + window.set_keyboard_interactivity(true); + window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 100); + window.set_anchor(gtk_layer_shell::Edge::Top, true); + + // Main layout + let main_box = gtk::Box::new(gtk::Orientation::Horizontal, 20); + main_box.set_margin_start(20); + main_box.set_margin_end(20); + main_box.set_margin_top(20); + main_box.set_margin_bottom(20); + main_box.set_halign(gtk::Align::Center); + + // 1. Replay Box + let replay_box = gtk::Box::new(gtk::Orientation::Vertical, 10); + let replay_btn = gtk::Button::with_label("Instant Replay\n🟢 ON"); + replay_btn.set_size_request(200, 100); + let replay_settings_btn = gtk::Button::with_label("⚙️ Replay Settings"); + replay_box.pack_start(&replay_btn, true, true, 0); + replay_box.pack_start(&replay_settings_btn, false, false, 0); + + // 2. Record Box + let record_box = gtk::Box::new(gtk::Orientation::Vertical, 10); + let record_btn = gtk::Button::with_label("Recording\n⚪ OFF"); + record_btn.set_size_request(200, 100); + let record_settings_btn = gtk::Button::with_label("⚙️ Record Settings"); + record_box.pack_start(&record_btn, true, true, 0); + record_box.pack_start(&record_settings_btn, false, false, 0); + + // 3. Settings Box + let settings_box = gtk::Box::new(gtk::Orientation::Vertical, 10); + let gen_settings_btn = gtk::Button::with_label("General Settings"); + gen_settings_btn.set_size_request(200, 50); + let close_btn = gtk::Button::with_label("❌ Close Menu"); + close_btn.set_size_request(200, 50); + + let spacer = gtk::Box::new(gtk::Orientation::Vertical, 0); + settings_box.pack_start(&gen_settings_btn, false, false, 0); + settings_box.pack_start(&spacer, true, true, 0); + settings_box.pack_start(&close_btn, false, false, 0); + + main_box.pack_start(&replay_box, true, true, 0); + main_box.pack_start(&record_box, true, true, 0); + main_box.pack_start(&settings_box, true, true, 0); + + window.add(&main_box); + + let window_clone = window.clone(); + close_btn.connect_clicked(move |_| { + window_clone.close(); + }); + + // Add CSS styling + let css_provider = CssProvider::new(); + let css = r#" + window { + background-color: rgba(25, 25, 25, 0.95); + border-radius: 16px; + color: #ffffff; + } + button { + background-color: rgba(50, 50, 50, 0.8); + color: white; + border-radius: 12px; + padding: 10px; + font-weight: bold; + font-size: 16px; + border: 1px solid rgba(100, 100, 100, 0.5); + } + button:hover { + background-color: rgba(80, 80, 80, 0.9); + } + "#; + css_provider.load_from_data(css.as_bytes()).unwrap(); + StyleContext::add_provider_for_screen( + &gdk::Screen::default().unwrap(), + &css_provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); + + window.show_all(); + }); + + app.run_with_args(&[] as &[&str]); +}