mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-24 10:04:19 -05:00
235 lines
7.4 KiB
C++
235 lines
7.4 KiB
C++
#include <cstring>
|
|
#include <sys/poll.h>
|
|
#include <sys/mman.h>
|
|
#include <fcntl.h>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
#include <print>
|
|
#include <format>
|
|
#include <string>
|
|
|
|
#include <wayland-client.h>
|
|
#include <wayland.hpp>
|
|
#include <xdg-shell.hpp>
|
|
|
|
#include <hyprutils/memory/SharedPtr.hpp>
|
|
#include <hyprutils/math/Vector2D.hpp>
|
|
|
|
using Hyprutils::Math::Vector2D;
|
|
using namespace Hyprutils::Memory;
|
|
|
|
struct SState {
|
|
wl_display* display;
|
|
CSharedPointer<CCWlRegistry> registry;
|
|
CSharedPointer<CCWlCompositor> compositor;
|
|
CSharedPointer<CCWlShm> shm;
|
|
CSharedPointer<CCXdgWmBase> xdgWm;
|
|
CSharedPointer<CCWlOutput> output;
|
|
|
|
CSharedPointer<CCWlShmPool> shmPool;
|
|
CSharedPointer<CCWlBuffer> shmBuf;
|
|
int shmFd = -1;
|
|
size_t shmSize = 0;
|
|
bool hasXrgb = false;
|
|
|
|
CSharedPointer<CCWlSurface> surf;
|
|
CSharedPointer<CCXdgSurface> xdgSurf;
|
|
CSharedPointer<CCXdgToplevel> toplevel;
|
|
Vector2D geom = {1280, 720};
|
|
bool fullscreen = false;
|
|
};
|
|
|
|
static bool started = false;
|
|
static bool shouldExit = false;
|
|
|
|
template <typename... Args>
|
|
static void clientLog(std::format_string<Args...> fmt, Args&&... args) {
|
|
std::string s = std::format(fmt, std::forward<Args>(args)...);
|
|
std::println("{}", s);
|
|
std::fflush(stdout);
|
|
}
|
|
|
|
static bool bindGlobals(SState& s) {
|
|
s.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(s.display));
|
|
|
|
s.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* iface, uint32_t) {
|
|
const std::string n = iface;
|
|
if (n == "wl_compositor")
|
|
s.compositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), id, &wl_compositor_interface, 6));
|
|
else if (n == "wl_shm")
|
|
s.shm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), id, &wl_shm_interface, 1));
|
|
else if (n == "xdg_wm_base")
|
|
s.xdgWm = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), id, &xdg_wm_base_interface, 1));
|
|
else if (n == "wl_output" && !s.output)
|
|
// only bind the first output; test env has one virtual monitor
|
|
s.output = makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), id, &wl_output_interface, 4));
|
|
});
|
|
|
|
s.registry->setGlobalRemove([](CCWlRegistry*, uint32_t) {});
|
|
wl_display_roundtrip(s.display);
|
|
|
|
return s.compositor && s.shm && s.xdgWm && s.output;
|
|
}
|
|
|
|
static bool makeShm(SState& s, Vector2D geom) {
|
|
if (!s.hasXrgb)
|
|
return false;
|
|
|
|
size_t stride = (size_t)geom.x * 4;
|
|
size_t size = (size_t)geom.y * stride;
|
|
|
|
if (!s.shmPool) {
|
|
const char* name = "/wl-shm-fs-mon";
|
|
s.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
if (s.shmFd < 0)
|
|
return false;
|
|
if (shm_unlink(name) < 0 || ftruncate(s.shmFd, size) < 0) {
|
|
close(s.shmFd);
|
|
return false;
|
|
}
|
|
s.shmPool = makeShared<CCWlShmPool>(s.shm->sendCreatePool(s.shmFd, size));
|
|
s.shmSize = size;
|
|
} else if (size > s.shmSize) {
|
|
if (ftruncate(s.shmFd, size) < 0)
|
|
return false;
|
|
s.shmPool->sendResize(size);
|
|
s.shmSize = size;
|
|
}
|
|
|
|
if (s.shmBuf) {
|
|
s.shmBuf->sendDestroy();
|
|
s.shmBuf.reset();
|
|
}
|
|
s.shmBuf = makeShared<CCWlBuffer>(s.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));
|
|
return s.shmBuf && s.shmBuf->resource();
|
|
}
|
|
|
|
static bool setupSurface(SState& s) {
|
|
s.shm->setFormat([&](CCWlShm*, uint32_t fmt) {
|
|
if (fmt == WL_SHM_FORMAT_XRGB8888)
|
|
s.hasXrgb = true;
|
|
});
|
|
|
|
s.xdgWm->setPing([&](CCXdgWmBase* p, uint32_t serial) { p->sendPong(serial); });
|
|
|
|
s.surf = makeShared<CCWlSurface>(s.compositor->sendCreateSurface());
|
|
s.xdgSurf = makeShared<CCXdgSurface>(s.xdgWm->sendGetXdgSurface(s.surf->resource()));
|
|
s.toplevel = makeShared<CCXdgToplevel>(s.xdgSurf->sendGetToplevel());
|
|
|
|
if (!s.surf->resource() || !s.xdgSurf->resource() || !s.toplevel->resource())
|
|
return false;
|
|
|
|
s.toplevel->setClose([](CCXdgToplevel*) { exit(0); });
|
|
|
|
s.toplevel->setConfigure([&](CCXdgToplevel*, int32_t w, int32_t h, wl_array* states) {
|
|
if (w > 0 && h > 0)
|
|
s.geom = {(double)w, (double)h};
|
|
|
|
// parse the state array to detect fullscreen
|
|
s.fullscreen = false;
|
|
auto stateSpan = std::span<const uint32_t>(static_cast<const uint32_t*>(states->data), states->size / sizeof(uint32_t));
|
|
for (uint32_t st : stateSpan) {
|
|
|
|
if (st == XDG_TOPLEVEL_STATE_FULLSCREEN) {
|
|
s.fullscreen = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!makeShm(s, s.geom))
|
|
exit(-1);
|
|
});
|
|
|
|
s.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {
|
|
if (!s.shmBuf)
|
|
return;
|
|
|
|
p->sendSetWindowGeometry(0, 0, s.geom.x, s.geom.y);
|
|
s.surf->sendAttach(s.shmBuf.get(), 0, 0);
|
|
s.surf->sendCommit();
|
|
p->sendAckConfigure(serial);
|
|
|
|
if (!started) {
|
|
started = true;
|
|
clientLog("started");
|
|
}
|
|
});
|
|
|
|
s.toplevel->sendSetTitle("fullscreen-with-monitor client");
|
|
s.toplevel->sendSetAppId("fullscreen-with-monitor");
|
|
|
|
s.surf->sendAttach(nullptr, 0, 0);
|
|
s.surf->sendCommit();
|
|
|
|
return true;
|
|
}
|
|
|
|
static void handleCmd(SState& s, std::string_view cmd) {
|
|
if (cmd.starts_with("unfullscreen")) {
|
|
s.toplevel->sendUnsetFullscreen();
|
|
wl_display_flush(s.display);
|
|
} else if (cmd.starts_with("fullscreen")) {
|
|
s.toplevel->sendSetFullscreen(s.output->resource());
|
|
wl_display_flush(s.display);
|
|
} else if (cmd.starts_with("get")) {
|
|
clientLog("{}", s.fullscreen ? "1" : "0");
|
|
} else if (cmd.starts_with("exit")) {
|
|
shouldExit = true;
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
SState s;
|
|
|
|
s.display = wl_display_connect(nullptr);
|
|
if (!s.display) {
|
|
clientLog("connect failed");
|
|
return -1;
|
|
}
|
|
|
|
if (!bindGlobals(s)) {
|
|
clientLog("failed to bind globals (no wl_output?)");
|
|
return -1;
|
|
}
|
|
|
|
if (!setupSurface(s)) {
|
|
clientLog("surface setup failed");
|
|
return -1;
|
|
}
|
|
|
|
std::array<char, 1024> buf;
|
|
buf.fill(0);
|
|
wl_display_flush(s.display);
|
|
|
|
struct pollfd fds[2] = {{.fd = wl_display_get_fd(s.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};
|
|
|
|
while (!shouldExit && poll(fds, 2, 0 - 1) != -1) {
|
|
if (fds[0].revents & POLLIN) {
|
|
wl_display_flush(s.display);
|
|
if (wl_display_prepare_read(s.display) == 0) {
|
|
wl_display_read_events(s.display);
|
|
wl_display_dispatch_pending(s.display);
|
|
} else
|
|
wl_display_dispatch(s.display);
|
|
|
|
int ret;
|
|
do {
|
|
ret = wl_display_dispatch_pending(s.display);
|
|
wl_display_flush(s.display);
|
|
} while (ret > 0);
|
|
}
|
|
|
|
if (fds[1].revents & POLLIN) {
|
|
ssize_t n = read(fds[1].fd, buf.data(), buf.size() - 1);
|
|
if (n > 0) {
|
|
buf[n] = 0;
|
|
handleCmd(s, buf.data());
|
|
}
|
|
}
|
|
}
|
|
|
|
wl_display* display = s.display;
|
|
s = {};
|
|
wl_display_disconnect(display);
|
|
return 0;
|
|
} |