Skip to content
Merged
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
11 changes: 9 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pkg_check_modules(SDBUSCPP REQUIRED sdbus-c++)
# pipewire
pkg_check_modules(PIPEWIRE REQUIRED libpipewire-0.3)

# GBM & DRM
pkg_check_modules(GBM REQUIRED IMPORTED_TARGET gbm)
pkg_check_modules(LIBDRM REQUIRED IMPORTED_TARGET libdrm)


# ImGui sources
set(IMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/imgui")
Expand All @@ -44,6 +48,7 @@ set(WAYLAND_PROTOCOLS
src/wayland/protocols/wlr-layer-shell-unstable-v1-client-protocol.c
src/wayland/protocols/xdg-shell-client-protocol.c
src/wayland/protocols/hyprland-toplevel-export-v1-client-protocol.c
src/wayland/protocols/linux-dmabuf-unstable-v1-client-protocol.c
)

# INIH
Expand Down Expand Up @@ -155,6 +160,8 @@ target_link_libraries(hyprwat PRIVATE
OpenGL::EGL
${Fontconfig_LIBRARIES}
PkgConfig::XKBCOMMON
PkgConfig::GBM
PkgConfig::LIBDRM
${PIPEWIRE_LIBRARIES}
${SDBUSCPP_LIBRARIES}
inih
Expand Down Expand Up @@ -183,9 +190,9 @@ set(CPACK_RPM_PACKAGE_RELEASE 1)

# deb and rpm dependencies
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"libwayland-client0, wayland-protocols, libegl1-mesa, libgl1-mesa-glx, libfontconfig1, libxkbcommon0, libpipewire-0.3-0, libsdbus-c++-1")
"libwayland-client0, wayland-protocols, libegl1-mesa, libgl1-mesa-glx, libfontconfig1, libxkbcommon0, libpipewire-0.3-0, libsdbus-c++-1, libgbm1, libdrm2")
set(CPACK_RPM_PACKAGE_REQUIRES
"wayland-libs, wayland-protocols, mesa-libEGL, mesa-libGL, fontconfig, libxkbcommon, pipewire, sdbus-c++")
"wayland-libs, wayland-protocols, mesa-libEGL, mesa-libGL, fontconfig, libxkbcommon, pipewire, sdbus-c++, mesa-libgbm, libdrm")


# remove the "-Linux" suffix
Expand Down
2 changes: 1 addition & 1 deletion PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pkgdesc="Hyprwat - A Wayland menu tool"
arch=('x86_64')
url="https://github.com/zackb/hyprwat"
license=('MIT')
depends=('wayland' 'mesa' 'fontconfig' 'libxkbcommon' 'sdbus-cpp' 'pipewire')
depends=('wayland' 'mesa' 'fontconfig' 'libxkbcommon' 'sdbus-cpp' 'pipewire' 'libdrm')
provides=('hyprwat')
conflicts=('hyprwat')
source=("https://github.com/zackb/hyprwat/releases/download/$pkgver/hyprwat-$pkgver.tar.gz")
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ wallpaper_width_ratio = 0.8
#### Arch Linux

```bash
sudo pacman -S cmake make gcc wayland wayland-protocols mesa fontconfig pkgconf libxkbcommon pipewire sdbus-c++
sudo pacman -S cmake make gcc wayland wayland-protocols mesa fontconfig pkgconf libxkbcommon pipewire sdbus-c++ libdrm
```

#### Debian/Ubuntu
Expand All @@ -146,7 +146,8 @@ sudo pacman -S cmake make gcc wayland wayland-protocols mesa fontconfig pkgconf
sudo apt update
sudo apt install cmake make g++ libwayland-dev wayland-protocols \
libegl1-mesa-dev libgl1-mesa-dev libfontconfig1-dev \
pkg-config libxkbcommon-dev libsdbus-c++-dev libpipewire-0.3-dev
pkg-config libxkbcommon-dev libsdbus-c++-dev libpipewire-0.3-dev \
libgbm-dev libdrm-dev
```

### Building
Expand Down
210 changes: 156 additions & 54 deletions src/frames/overview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <imgui.h>
#include <iostream>
#include <sstream>
#include <unistd.h>

extern "C" {
// mock for undefined reference in hyprland-toplevel-export protocol
Expand Down Expand Up @@ -36,6 +37,12 @@ OverviewFrame::~OverviewFrame() {
if (c->texture) {
glDeleteTextures(1, &c->texture);
}
if (c->buffer)
wl_buffer_destroy(c->buffer);
if (c->bo)
gbm_bo_destroy(c->bo);
if (c->fdToClose >= 0)
close(c->fdToClose);
}
}
}
Expand Down Expand Up @@ -63,38 +70,35 @@ void OverviewFrame::captureClients() {
capture->owner = this;
capture->client = c;
wv.clients.push_back(capture);
// Also add to global capture list
pendingCaptures.push_back(capture);
}
}
workspaces.push_back(wv);
}
}

if (!wlDisplay.exportManager()) {
debug::log(ERR, "Toplevel export manager not available");
void OverviewFrame::requestCapture(std::shared_ptr<CapturedClient> c) {
if (!wlDisplay.exportManager() || c->frame || c->failed || c->ready)
return;
}

for (auto& c : pendingCaptures) {
try {
unsigned long long addr;
std::stringstream ss;
ss << std::hex << c->client.address;
ss >> addr;
try {
unsigned long long addr;
std::stringstream ss;
ss << std::hex << c->client.address;
ss >> addr;

c->frame =
hyprland_toplevel_export_manager_v1_capture_toplevel(wlDisplay.exportManager(), 0, (uint32_t)addr);
c->frame = hyprland_toplevel_export_manager_v1_capture_toplevel(wlDisplay.exportManager(), 0, (uint32_t)addr);

if (c->frame) {
hyprland_toplevel_export_frame_v1_add_listener(c->frame, &export_frame_listener, c.get());
}
} catch (...) {
debug::log(ERR,
"Failed to capture client {} on workspace {}",
c->client.title.empty() ? std::to_string(c->client.workspaceId) : c->client.title,
c->client.workspaceId);
if (c->frame) {
hyprland_toplevel_export_frame_v1_add_listener(c->frame, &export_frame_listener, c.get());
} else {
c->failed = true;
}
} catch (...) {
debug::log(ERR,
"Failed to capture client {} on workspace {}",
c->client.title.empty() ? std::to_string(c->client.workspaceId) : c->client.title,
c->client.workspaceId);
c->failed = true;
}
}

Expand Down Expand Up @@ -137,60 +141,158 @@ void OverviewFrame::handle_linux_dmabuf(void* data,
struct hyprland_toplevel_export_frame_v1* export_frame,
uint32_t format,
uint32_t width,
uint32_t height) {}
uint32_t height) {
auto* c = static_cast<CapturedClient*>(data);
c->dmabufFormat = format;
c->dmabufWidth = width;
c->dmabufHeight = height;
}

void OverviewFrame::handle_buffer_done(void* data, struct hyprland_toplevel_export_frame_v1* export_frame) {
auto* c = static_cast<CapturedClient*>(data);
if (!c->owner)
return;

try {
c->shmBuffer = std::make_unique<wl::ShmBuffer>(
c->owner->wlDisplay.shm(), c->captureWidth, c->captureHeight, c->captureStride, c->captureFormat);
hyprland_toplevel_export_frame_v1_copy(export_frame, c->shmBuffer->getBuffer(), 1);
if (c->owner->wlDisplay.gbmDevice() && c->owner->wlDisplay.linuxDmabuf() && c->dmabufFormat != 0) {
c->bo = gbm_bo_create(c->owner->wlDisplay.gbmDevice(),
c->dmabufWidth,
c->dmabufHeight,
c->dmabufFormat,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);

if (!c->bo) {
c->failed = true;
return;
}

int fd = gbm_bo_get_fd(c->bo);
uint32_t stride = gbm_bo_get_stride(c->bo);
uint32_t offset = gbm_bo_get_offset(c->bo, 0);
uint64_t modifier = gbm_bo_get_modifier(c->bo);

zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params(c->owner->wlDisplay.linuxDmabuf());
zwp_linux_buffer_params_v1_add(params, fd, 0, offset, stride, modifier >> 32, modifier & 0xffffffff);

c->buffer =
zwp_linux_buffer_params_v1_create_immed(params, c->dmabufWidth, c->dmabufHeight, c->dmabufFormat, 0);
zwp_linux_buffer_params_v1_destroy(params);

c->fdToClose = fd;
hyprland_toplevel_export_frame_v1_copy(export_frame, c->buffer, 1);
} else {
c->shmBuffer = std::make_unique<wl::ShmBuffer>(
c->owner->wlDisplay.shm(), c->captureWidth, c->captureHeight, c->captureStride, c->captureFormat);
hyprland_toplevel_export_frame_v1_copy(export_frame, c->shmBuffer->getBuffer(), 1);
}
} catch (...) {
c->failed = true;
}
}

void OverviewFrame::createTexture(CapturedClient& c) {
if (c.texture != 0 || !c.ready || !c.shmBuffer)
if (c.texture != 0 || !c.ready)
return;

// ARGB to RGBA if necessary. SHM format is typically ARGB8888 or XRGB8888.
// OpenGL expects RGBA, so we might need to swap channels.
glGenTextures(1, &c.texture);
glBindTexture(GL_TEXTURE_2D, c.texture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// assuming format is WL_SHM_FORMAT_XRGB8888 or ARGB8888 which is BGRA in memory (little endian)
// upload using GL_BGRA_EXT or we manual swizzle.
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
c.captureWidth,
c.captureHeight,
0,
GL_BGRA_EXT,
GL_UNSIGNED_BYTE,
c.shmBuffer->getData());

glGenerateMipmap(GL_TEXTURE_2D);

// clean up shmBuffer as it's no longer needed in CPU RAM
c.shmBuffer.reset();
if (c.bo && c.buffer && c.fdToClose >= 0) {
typedef void (*PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)(GLenum target, void* image);
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR_ptr = nullptr;
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR_ptr = nullptr;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES_ptr = nullptr;

if (!eglCreateImageKHR_ptr) {
eglCreateImageKHR_ptr = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
eglDestroyImageKHR_ptr = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
glEGLImageTargetTexture2DOES_ptr =
(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
}

EGLDisplay display = eglGetDisplay((EGLNativeDisplayType)wlDisplay.display());
uint64_t modifier = gbm_bo_get_modifier(c.bo);
EGLint attribs[] = {EGL_WIDTH,
c.dmabufWidth,
EGL_HEIGHT,
c.dmabufHeight,
EGL_LINUX_DRM_FOURCC_EXT,
c.dmabufFormat,
EGL_DMA_BUF_PLANE0_FD_EXT,
c.fdToClose,
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
(EGLint)gbm_bo_get_offset(c.bo, 0),
EGL_DMA_BUF_PLANE0_PITCH_EXT,
(EGLint)gbm_bo_get_stride(c.bo),
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
(EGLint)(modifier & 0xFFFFFFFF),
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
(EGLint)(modifier >> 32),
EGL_NONE};

EGLImageKHR image =
eglCreateImageKHR_ptr(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs);

glGenTextures(1, &c.texture);
glBindTexture(GL_TEXTURE_2D, c.texture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glEGLImageTargetTexture2DOES_ptr(GL_TEXTURE_2D, image);

close(c.fdToClose);
c.fdToClose = -1;
eglDestroyImageKHR_ptr(display, image);

} else if (c.shmBuffer) {
glGenTextures(1, &c.texture);
glBindTexture(GL_TEXTURE_2D, c.texture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// assuming format is WL_SHM_FORMAT_XRGB8888 or ARGB8888 which is BGRA in memory (little endian)
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
c.captureWidth,
c.captureHeight,
0,
GL_BGRA_EXT,
GL_UNSIGNED_BYTE,
c.shmBuffer->getData());

c.shmBuffer.reset();
}
}

FrameResult OverviewFrame::render() {
// process generated textures
// request captures for visible workspaces (expanded buffer window)
int startIdx = std::max(0, selectedIndex - 4);
int endIdx = std::min((int)workspaces.size() - 1, selectedIndex + 4);
for (int i = startIdx; i <= endIdx; ++i) {
if (i >= 0 && i < workspaces.size()) {
for (auto& c : workspaces[i].clients) {
if (!c->frame && !c->failed && !c->ready) {
requestCapture(c);
}
}
}
}

// process generated textures time-sliced
int texturesCreatedThisFrame = 0;
const int MAX_TEXTURES_PER_FRAME = 2;

for (auto& w : workspaces) {
for (auto& c : w.clients) {
if (c->ready && c->texture == 0) {
createTexture(*c);
if (texturesCreatedThisFrame < MAX_TEXTURES_PER_FRAME) {
createTexture(*c);
texturesCreatedThisFrame++;
}
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion src/frames/overview.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
#include "../ui.hpp"
#include "../wayland/display.hpp"
#include "../wayland/protocols/hyprland-toplevel-export-v1-client-protocol.h"
#include "../wayland/protocols/linux-dmabuf-unstable-v1-client-protocol.h"
#include "../wayland/shm.hpp"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include <gbm.h>
#include <memory>
#include <mutex>
#include <vector>
Expand Down Expand Up @@ -34,6 +38,13 @@ class OverviewFrame : public Frame {
int captureWidth = 0;
int captureHeight = 0;
int captureStride = 0;

struct gbm_bo* bo = nullptr;
wl_buffer* buffer = nullptr;
int dmabufFormat = 0;
int dmabufWidth = 0;
int dmabufHeight = 0;
int fdToClose = -1;
};

struct WorkspaceView {
Expand All @@ -48,7 +59,6 @@ class OverviewFrame : public Frame {
wl::Display& wlDisplay;

std::vector<WorkspaceView> workspaces;
std::vector<std::shared_ptr<CapturedClient>> pendingCaptures;
std::mutex captureMutex;

float padding = 20.0f;
Expand All @@ -63,6 +73,7 @@ class OverviewFrame : public Frame {
void captureClients();
void navigate(int direction);
void createTexture(CapturedClient& c);
void requestCapture(std::shared_ptr<CapturedClient> c);

static void handle_buffer(void* data,
struct hyprland_toplevel_export_frame_v1* export_frame,
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/egl_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern "C" {
}

#include <EGL/egl.h>
#include <EGL/eglext.h>

namespace egl {
class Context {
Expand All @@ -19,6 +20,7 @@ namespace egl {
void swapBuffers();
Vec2 getBufferSize() const;
wl_egl_window* window() const { return egl_window; }
EGLDisplay getEGLDisplay() const { return egl_display; }

private:
wl_display* display;
Expand Down
Loading
Loading