feat: Use gtk4-layer-shell for true Wayland native overlays

This commit is contained in:
katR 2026-09-01 22:44:47 +05:30
parent 8a6a576193
commit 884fe3227a
8 changed files with 749 additions and 419 deletions

945
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,10 +8,13 @@ ashpd = { version = "0.13.13", features = ["screencast"] }
crossbeam-channel = "0.5.16"
ffmpeg-next = "9.0.0"
ffmpeg-sys-next = "9.0.0"
gdk = "0.18.2"
glib = "0.22.9"
global-hotkey = "0.8.0"
gtk-layer-shell = "0.8.2"
gtk = { version = "0.18.2", package = "gtk" }
libc = "0.2.189"
memmap2 = "0.9.11"
minifb = "0.28.0"
mp4 = "0.14.0"
pipewire = "0.10.1"
ringbuf = "0.5.1"

19
scratch/gtk_fix.patch Normal file
View file

@ -0,0 +1,19 @@
--- src/overlay.rs
+++ src/overlay.rs
@@ -16,11 +16,11 @@
.default_height(80)
.build();
- window.init_layer_shell();
- window.set_layer(Layer::Overlay);
- window.set_namespace("vrec-overlay");
- window.set_margin(gtk_layer_shell::Edge::Top, 40);
- window.set_anchor(gtk_layer_shell::Edge::Top, true);
+ gtk_layer_shell::init_for_window(&window);
+ gtk_layer_shell::set_layer(&window, Layer::Overlay);
+ gtk_layer_shell::set_namespace(&window, "vrec-overlay");
+ gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, 40);
+ gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Top, true);
// Make it transparent and borderless
let css_provider = CssProvider::new();

19
scratch/gtk_fix2.patch Normal file
View file

@ -0,0 +1,19 @@
--- src/overlay.rs
+++ src/overlay.rs
@@ -16,11 +16,11 @@
.default_height(80)
.build();
- gtk_layer_shell::init_for_window(&window);
- gtk_layer_shell::set_layer(&window, Layer::Overlay);
- gtk_layer_shell::set_namespace(&window, "vrec-overlay");
- gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, 40);
- gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Top, true);
+ window.init_layer_shell();
+ window.set_layer(Layer::Overlay);
+ window.set_layer_shell_namespace("vrec-overlay");
+ window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 40);
+ window.set_anchor(gtk_layer_shell::Edge::Top, true);
// Make it transparent and borderless
let css_provider = CssProvider::new();

View file

@ -1,26 +0,0 @@
use minifb::{Window, WindowOptions};
pub struct DebugWindow {
window: Window,
}
impl DebugWindow {
pub fn new(width: usize, height: usize) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let window = Window::new(
"vrec debug capture",
width,
height,
WindowOptions::default(),
)?;
Ok(Self { window })
}
pub fn update(&mut self, buffer: &[u32], width: usize, height: usize) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.window.update_with_buffer(buffer, width, height)?;
Ok(())
}
pub fn is_open(&self) -> bool {
self.window.is_open() && !self.window.is_key_down(minifb::Key::Escape)
}
}

View file

@ -3,6 +3,5 @@ pub mod encoder;
pub mod ring;
pub mod muxer;
pub mod hotkey;
pub mod debug;
pub mod ipc;
pub mod overlay;

View file

@ -1,45 +1,58 @@
use minifb::{Window, WindowOptions};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Label, CssProvider, StyleContext};
use gtk_layer_shell::{LayerShell, Layer};
use std::time::Duration;
pub fn show_saved_overlay() {
let width = 300;
let height = 100;
let mut window = match Window::new(
"VRec Overlay",
width,
height,
WindowOptions {
borderless: true,
title: false,
resize: false,
scale: minifb::Scale::X1,
scale_mode: minifb::ScaleMode::Stretch,
topmost: true,
transparency: true,
none: false,
}
) {
Ok(win) => win,
Err(e) => {
eprintln!("Failed to create overlay window: {}", e);
return;
}
};
// Create a semi-transparent green box or just dark gray
let buffer: Vec<u32> = vec![0xFF222222; width * height];
window.set_target_fps(60);
let start_time = std::time::Instant::now();
let display_duration = Duration::from_secs(2);
while window.is_open() && start_time.elapsed() < display_duration {
// We could render text here, but for now we just show a flash/banner.
if let Err(e) = window.update_with_buffer(&buffer, width, height) {
eprintln!("Overlay update error: {}", e);
break;
}
}
let app = Application::builder()
.application_id("com.vrec.overlay")
.build();
app.connect_activate(|app| {
let window = ApplicationWindow::builder()
.application(app)
.default_width(300)
.default_height(80)
.build();
window.init_layer_shell();
window.set_layer(Layer::Overlay);
window.set_namespace("vrec-overlay");
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 40);
window.set_anchor(gtk_layer_shell::Edge::Top, true);
// Make it transparent and borderless
let css_provider = CssProvider::new();
let css = r#"
window {
background-color: rgba(30, 30, 30, 0.9);
border-radius: 12px;
color: #ffffff;
font-weight: bold;
font-size: 20px;
padding: 15px;
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.5);
}
"#;
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,
);
let label = Label::new(Some("✅ Replay Saved!"));
window.add(&label);
window.show_all();
// Close after 2 seconds
let window_clone = window.clone();
glib::timeout_add_local(Duration::from_secs(2), move || {
window_clone.close();
glib::ControlFlow::Break
});
});
app.run_with_args(&[] as &[&str]);
}

58
src/overlay.rs.orig Normal file
View file

@ -0,0 +1,58 @@
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Label, CssProvider, StyleContext};
use gtk_layer_shell::{LayerShell, Layer};
use std::time::Duration;
pub fn show_saved_overlay() {
let app = Application::builder()
.application_id("com.vrec.overlay")
.build();
app.connect_activate(|app| {
let window = ApplicationWindow::builder()
.application(app)
.default_width(300)
.default_height(80)
.build();
gtk_layer_shell::init_for_window(&window);
gtk_layer_shell::set_layer(&window, Layer::Overlay);
gtk_layer_shell::set_namespace(&window, "vrec-overlay");
gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, 40);
gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Top, true);
// Make it transparent and borderless
let css_provider = CssProvider::new();
let css = r#"
window {
background-color: rgba(30, 30, 30, 0.9);
border-radius: 12px;
color: #ffffff;
font-weight: bold;
font-size: 20px;
padding: 15px;
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.5);
}
"#;
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,
);
let label = Label::new(Some("✅ Replay Saved!"));
window.add(&label);
window.show_all();
// Close after 2 seconds
let window_clone = window.clone();
glib::timeout_add_local(Duration::from_secs(2), move || {
window_clone.close();
glib::ControlFlow::Break
});
});
app.run_with_args(&[] as &[&str]);
}