Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 258 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ schema = ["dep:schemars"]

[dependencies]
# core
gtk = { package = "gtk4", version = "0.10.0", features = ["v4_10"] }
gtk = { package = "gtk4", version = "0.10.0", features = ["v4_14"] }
gtk-layer-shell = { package = "gtk4-layer-shell", version = "0.6.3" }
glib = "0.21.1"
glib = "0.21.3"
tokio = { version = "1.47.1", features = [
"macros",
"rt-multi-thread",
Expand All @@ -145,9 +145,17 @@ walkdir = "2.5.0"
notify = { version = "8.2.0", default-features = false }
wayland-client = "0.31.1"
wayland-protocols-wlr = { version = "0.3.9", features = ["client"] }
wayland-protocols-hyprland = { version = "1.1.0", features = ["client"] }
smithay-client-toolkit = { version = "0.20.0", default-features = false, features = [
"calloop",
] }

# -- testing --
gbm = "0.18.0"
drm = "0.14.1"
udev = "0.9.3"
# -- end testing --

universal-config = { version = "0.5.1", default-features = false }
ctrlc = "3.5.0"
cfg-if = "1.0.3"
Expand Down Expand Up @@ -198,9 +206,10 @@ serde_json = { version = "1.0.145", optional = true } # ipc, niri

# schema
schemars = { version = "1.0.4", optional = true, features = ["indexmap2"] }
log = "0.4.27"

[build-dependencies]
clap = { version = "4.5.48", features = ["derive"] }
clap_complete = "4.5.58"
serde = { version = "1.0.226", features = ["derive"] }
serde_json = "1.0.145"
serde_json = "1.0.145"
6 changes: 5 additions & 1 deletion docs/modules/Launcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Optionally displays a launchable set of favourites.
| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
| `launch_command` | `string` | `gtk-launch {app_name}` | Command used to launch applications. |
| `show_previews` | `boolean` | `true` | Whether to show window previews when hovering icons. |
| `preview_size` | `integer` | `256` | Target width for a window preview. Aspect ratio is preserved. |
| `preview_fps` | `integer` | `60` | Target framerate for window preview updates. 0-999. |
| `launch_command` | `string` | `gtk-launch {app_name}` | Command used to launch applications. |
| `reversed` | `boolean` | `false` | Whether to reverse the order of favorites/items |
| `minimize_focused` | `boolean` | `true` | Whether to minimize a focused window when its icon is clicked. Only minimizes single windows. |
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `end` | Location of the ellipses and where to truncate text from. Applies to application names when `show_names` is enabled. |
Expand All @@ -31,6 +34,7 @@ Optionally displays a launchable set of favourites.
| `page_size` | `integer` | `1000` | Number of items to show on a page. When the number of items is reached, controls appear which can be used to move forward/back through the list of items. |
| `icons.page_back` | `string` or [image](images) | `󰅁` | Icon to show for page back button. |
| `icons.page_forward` | `string` or [image](images) | `󰅂` | Icon to show for page forward button. |

<details>
<summary>JSON</summary>

Expand Down
111 changes: 111 additions & 0 deletions src/clients/wayland/dmabuf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::collections::HashMap;
use std::fs::File;
use std::os::fd::{AsFd, BorrowedFd};
use drm::Device;
use drm::control::Device as ControlDevice;
use smithay_client_toolkit::dmabuf::{DmabufFeedback, DmabufHandler, DmabufState};
use smithay_client_toolkit::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1;
use smithay_client_toolkit::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1;
use tracing::{error, trace};
use udev::DeviceType;
use wayland_client::{Connection, QueueHandle};
use wayland_client::protocol::wl_buffer::WlBuffer;
use crate::clients::wayland::{Environment};
use color_eyre::eyre::OptionExt;
use color_eyre::Result;

struct Card(File);

#[allow(non_camel_case_types)]
type dev_t = u64;

impl Card {
fn open(device: dev_t) -> Result<Self> {
let device = udev::Device::from_devnum(DeviceType::Character, device)?;

let mut options = std::fs::OpenOptions::new();
options.read(true);
options.write(true);

let file = device
.devnode()
.map(|node| options.open(node))
.ok_or_eyre("Device has no node")??;

Ok(Self(file))
}
}

impl AsFd for Card {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}

impl Device for Card {}
impl ControlDevice for Card {}

impl DmabufHandler for Environment {
fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.dmabuf_state
}

fn dmabuf_feedback(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_proxy: &ZwpLinuxDmabufFeedbackV1,
feedback: DmabufFeedback,
) {
trace!("Got feedback: {feedback:?}");

let format_table = feedback.format_table();
let tranches = feedback.tranches();

let mut formats = HashMap::new();

for tranch in tranches {
for index in &tranch.formats {
let format = format_table[*index as usize];
formats
.entry(format.format)
.or_insert_with(Vec::new)
.push(format.modifier);
}
}

self.dmabuf_formats.extend(formats);

// FIXME: This will probably break on multi-gpu setups
// may need to try each tranche in order
let card = Card::open(feedback.main_device()).unwrap();

let gbm_dev = gbm::Device::new(card.0).unwrap();
self.gbm_device = Some(gbm_dev);
}

fn created(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
params: &ZwpLinuxBufferParamsV1,
_buffer: WlBuffer,
) {
trace!("created (async)");
params.destroy();
}

fn failed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
params: &ZwpLinuxBufferParamsV1,
) {
error!("Failed to create DMA-BUF buffer");
params.destroy();
}

fn released(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _buffer: &WlBuffer) {
trace!("DMA-BUF handle released");
}
}
1 change: 1 addition & 0 deletions src/clients/wayland/ext_image_copy_capture/frame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions src/clients/wayland/ext_image_copy_capture/manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

3 changes: 3 additions & 0 deletions src/clients/wayland/ext_image_copy_capture/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod frame;
mod manager;
mod session;
1 change: 1 addition & 0 deletions src/clients/wayland/ext_image_copy_capture/session.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

128 changes: 128 additions & 0 deletions src/clients/wayland/hyprland_toplevel_export/frame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use super::manager::ToplevelManagerState;
use super::{Buffer, BufferRequest};
use crate::clients::wayland::ToplevelHandle;
use crate::lock;
use color_eyre::Result;
use std::sync::{Arc, Mutex};
use tracing::{error, warn};
use wayland_client::{Connection, Dispatch, QueueHandle, WEnum};
use wayland_protocols_hyprland::toplevel_export::v1::client::hyprland_toplevel_export_frame_v1::{
Event, Flags, HyprlandToplevelExportFrameV1,
};

pub trait ToplevelFrameDataExt: Send + Sync {
fn buffer_request(&self) -> BufferRequest;
fn set_buffer_request(&self, buffer: BufferRequest);

fn handle(&self) -> Option<&ToplevelHandle>;

fn copied_first_frame(&self) -> bool;
fn set_copied_first_frame(&self);
}

impl ToplevelFrameDataExt for ToplevelFrameData {
fn buffer_request(&self) -> BufferRequest {
let inner = lock!(self.inner);
inner.buffer_request
}

fn set_buffer_request(&self, request: BufferRequest) {
lock!(self.inner).buffer_request = request;
}

fn handle(&self) -> Option<&ToplevelHandle> {
self.handle.as_ref()
}

fn copied_first_frame(&self) -> bool {
lock!(self.inner).copied_first_frame
}

fn set_copied_first_frame(&self) {
lock!(self.inner).copied_first_frame = true;
}
}

#[derive(Default, Debug)]
pub struct ToplevelFrameData {
pub handle: Option<ToplevelHandle>,
pub inner: Arc<Mutex<ToplevelFrameDataInner>>,
}

impl ToplevelFrameData {}

#[derive(Debug, Default)]
pub struct ToplevelFrameDataInner {
buffer_request: BufferRequest,
copied_first_frame: bool,
}

pub trait ToplevelFrameHandler: Sized {
/// Requests a new DMA-BUF is created for the provided parameters.
fn dma_buffer(&mut self, request: BufferRequest, handle_id: usize) -> Result<Buffer>;

/// Provides the buffer once ready.
/// This includes the copied contents.
fn buffer_ready(&mut self, handle: &ToplevelHandle);
}

impl<D, U> Dispatch<HyprlandToplevelExportFrameV1, U, D> for ToplevelManagerState
where
D: Dispatch<HyprlandToplevelExportFrameV1, U> + ToplevelFrameHandler,
U: ToplevelFrameDataExt,
{
fn event(
state: &mut D,
proxy: &HyprlandToplevelExportFrameV1,
event: Event,
data: &U,
_conn: &Connection,
_qhandle: &QueueHandle<D>,
) {
match event {
Event::LinuxDmabuf {
format,
width,
height,
} => {
data.set_buffer_request(BufferRequest { format, width, height })
},
Event::BufferDone => {
let Some(handle_id) = data.handle().and_then(|h| h.info()).map(|i| i.id) else {
error!("Missing handle");
return;
};

match state.dma_buffer(data.buffer_request(), handle_id) {
Ok(buffer) => {
proxy.copy(&buffer.wl_buffer, !data.copied_first_frame() as i32);
},
Err(err) => { error!("failed to fetch buffer: {err:?}"); proxy.destroy() },
}
}
Event::Flags { flags } => match flags {
WEnum::Value(flags) => {
if flags.contains(Flags::YInvert) {
error!("Received unhandled YInvert transform flag");
}
}
WEnum::Unknown(_) => {
error!("Received unknown flags for toplevel frame");
}
},
Event::Ready { .. } => {
let handle = data.handle().unwrap();
state.buffer_ready(handle);
data.set_copied_first_frame();

proxy.destroy();
}
Event::Failed => {
error!("Failed to capture frame");
proxy.destroy();
}
Event::Buffer { .. /* shm ignored in favour of dmabuf */ } | Event::Damage { .. } => {}
_ => warn!("Received unhandled toplevel frame event: {:?}", event),
}
}
}
Loading
Loading