diff --git a/CMakeLists.txt b/CMakeLists.txt
index 239b142..81a559b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,7 +57,6 @@ pkg_check_modules(
IMPORTED_TARGET
hyprlang>=0.6.0
hyprutils>=0.2.4
- wayland-client
hyprtoolkit>=0.4.1
hyprwire
pixman-1
@@ -67,51 +66,11 @@ file(GLOB_RECURSE SRCFILES "src/*.cpp")
add_executable(hyprpaper ${SRCFILES})
-# Wayland
-
-pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
-message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
-pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
-message(
- STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}")
-
-function(protocolnew protoPath protoName external)
- if(external)
- set(path ${CMAKE_SOURCE_DIR}/${protoPath})
- else()
- set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
- endif()
- add_custom_command(
- OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp
- ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp
- COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml
- ${CMAKE_SOURCE_DIR}/protocols/
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- target_sources(hyprpaper PRIVATE protocols/${protoName}.cpp
- protocols/${protoName}.hpp)
-endfunction()
-function(protocolWayland)
- add_custom_command(
- OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp
- ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp
- COMMAND hyprwayland-scanner --wayland-enums --client
- ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- target_sources(hyprpaper PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
-endfunction()
-
-protocolwayland()
-
-protocolnew("protocols" "wlr-layer-shell-unstable-v1" true)
-protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
-protocolnew("staging/fractional-scale" "fractional-scale-v1" false)
-protocolnew("stable/viewporter" "viewporter" false)
-protocolnew("stable/xdg-shell" "xdg-shell" false)
-protocolnew("staging/cursor-shape" "cursor-shape-v1" false)
-protocolnew("stable/tablet" "tablet-v2" false)
-
# Hyprwire
+pkg_get_variable(HYPRWIRE_PROTOCOLS_DIR hyprwire-protocols pkgdatadir)
+message(STATUS "Found hyprwire-protocols at ${HYPRWIRE_PROTOCOLS_DIR}")
+
function(hyprprotocolServer protoPath protoName)
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
add_custom_command(
@@ -125,8 +84,27 @@ function(hyprprotocolServer protoPath protoName)
)
endfunction()
+function(hyprprotocol protoPath protoName external)
+ if(external)
+ set(path ${protoPath})
+ else()
+ set(path ${HYPRWIRE_PROTOCOLS_DIR}/${protoPath})
+ endif()
+ add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.hpp
+ COMMAND hyprwire-scanner --client ${path}/${protoName}.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+ target_sources(hyprpaper PRIVATE hw-protocols/${protoName}-client.cpp
+ hw-protocols/${protoName}-client.hpp
+ )
+endfunction()
+
hyprprotocolServer(hw-protocols hyprpaper_core)
+hyprprotocol(hyprtavern hp_hyprtavern_core_v1 false)
+
#
string(REPLACE "\"" " " GIT_COMMIT_MESSAGE_ESCAPED "${GIT_COMMIT_MESSAGE}")
diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml
deleted file mode 100644
index f29eb87..0000000
--- a/protocols/wlr-layer-shell-unstable-v1.xml
+++ /dev/null
@@ -1,285 +0,0 @@
-
-
-
- Copyright © 2017 Drew DeVault
-
- Permission to use, copy, modify, distribute, and sell this
- software and its documentation for any purpose is hereby granted
- without fee, provided that the above copyright notice appear in
- all copies and that both that copyright notice and this permission
- notice appear in supporting documentation, and that the name of
- the copyright holders not be used in advertising or publicity
- pertaining to distribution of the software without specific,
- written prior permission. The copyright holders make no
- representations about the suitability of this software for any
- purpose. It is provided "as is" without express or implied
- warranty.
-
- THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
- SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
- SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
- AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
- ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
- THIS SOFTWARE.
-
-
-
-
- Clients can use this interface to assign the surface_layer role to
- wl_surfaces. Such surfaces are assigned to a "layer" of the output and
- rendered with a defined z-depth respective to each other. They may also be
- anchored to the edges and corners of a screen and specify input handling
- semantics. This interface should be suitable for the implementation of
- many desktop shell components, and a broad number of other applications
- that interact with the desktop.
-
-
-
-
- Create a layer surface for an existing surface. This assigns the role of
- layer_surface, or raises a protocol error if another role is already
- assigned.
-
- Creating a layer surface from a wl_surface which has a buffer attached
- or committed is a client error, and any attempts by a client to attach
- or manipulate a buffer prior to the first layer_surface.configure call
- must also be treated as errors.
-
- You may pass NULL for output to allow the compositor to decide which
- output to use. Generally this will be the one that the user most
- recently interacted with.
-
- Clients can specify a namespace that defines the purpose of the layer
- surface.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- These values indicate which layers a surface can be rendered in. They
- are ordered by z depth, bottom-most first. Traditional shell surfaces
- will typically be rendered between the bottom and top layers.
- Fullscreen shell surfaces are typically rendered at the top layer.
- Multiple surfaces can share a single layer, and ordering within a
- single layer is undefined.
-
-
-
-
-
-
-
-
-
-
-
- An interface that may be implemented by a wl_surface, for surfaces that
- are designed to be rendered as a layer of a stacked desktop-like
- environment.
-
- Layer surface state (size, anchor, exclusive zone, margin, interactivity)
- is double-buffered, and will be applied at the time wl_surface.commit of
- the corresponding wl_surface is called.
-
-
-
-
- Sets the size of the surface in surface-local coordinates. The
- compositor will display the surface centered with respect to its
- anchors.
-
- If you pass 0 for either value, the compositor will assign it and
- inform you of the assignment in the configure event. You must set your
- anchor to opposite edges in the dimensions you omit; not doing so is a
- protocol error. Both values are 0 by default.
-
- Size is double-buffered, see wl_surface.commit.
-
-
-
-
-
-
-
- Requests that the compositor anchor the surface to the specified edges
- and corners. If two orthoginal edges are specified (e.g. 'top' and
- 'left'), then the anchor point will be the intersection of the edges
- (e.g. the top left corner of the output); otherwise the anchor point
- will be centered on that edge, or in the center if none is specified.
-
- Anchor is double-buffered, see wl_surface.commit.
-
-
-
-
-
-
- Requests that the compositor avoids occluding an area of the surface
- with other surfaces. The compositor's use of this information is
- implementation-dependent - do not assume that this region will not
- actually be occluded.
-
- A positive value is only meaningful if the surface is anchored to an
- edge, rather than a corner. The zone is the number of surface-local
- coordinates from the edge that are considered exclusive.
-
- Surfaces that do not wish to have an exclusive zone may instead specify
- how they should interact with surfaces that do. If set to zero, the
- surface indicates that it would like to be moved to avoid occluding
- surfaces with a positive excluzive zone. If set to -1, the surface
- indicates that it would not like to be moved to accommodate for other
- surfaces, and the compositor should extend it all the way to the edges
- it is anchored to.
-
- For example, a panel might set its exclusive zone to 10, so that
- maximized shell surfaces are not shown on top of it. A notification
- might set its exclusive zone to 0, so that it is moved to avoid
- occluding the panel, but shell surfaces are shown underneath it. A
- wallpaper or lock screen might set their exclusive zone to -1, so that
- they stretch below or over the panel.
-
- The default value is 0.
-
- Exclusive zone is double-buffered, see wl_surface.commit.
-
-
-
-
-
-
- Requests that the surface be placed some distance away from the anchor
- point on the output, in surface-local coordinates. Setting this value
- for edges you are not anchored to has no effect.
-
- The exclusive zone includes the margin.
-
- Margin is double-buffered, see wl_surface.commit.
-
-
-
-
-
-
-
-
-
- Set to 1 to request that the seat send keyboard events to this layer
- surface. For layers below the shell surface layer, the seat will use
- normal focus semantics. For layers above the shell surface layers, the
- seat will always give exclusive keyboard focus to the top-most layer
- which has keyboard interactivity set to true.
-
- Layer surfaces receive pointer, touch, and tablet events normally. If
- you do not want to receive them, set the input region on your surface
- to an empty region.
-
- Events is double-buffered, see wl_surface.commit.
-
-
-
-
-
-
- This assigns an xdg_popup's parent to this layer_surface. This popup
- should have been created via xdg_surface::get_popup with the parent set
- to NULL, and this request must be invoked before committing the popup's
- initial state.
-
- See the documentation of xdg_popup for more details about what an
- xdg_popup is and how it is used.
-
-
-
-
-
-
- When a configure event is received, if a client commits the
- surface in response to the configure event, then the client
- must make an ack_configure request sometime before the commit
- request, passing along the serial of the configure event.
-
- If the client receives multiple configure events before it
- can respond to one, it only has to ack the last configure event.
-
- A client is not required to commit immediately after sending
- an ack_configure request - it may even ack_configure several times
- before its next surface commit.
-
- A client may send multiple ack_configure requests before committing, but
- only the last request sent before a commit indicates which configure
- event the client really is responding to.
-
-
-
-
-
-
- This request destroys the layer surface.
-
-
-
-
-
- The configure event asks the client to resize its surface.
-
- Clients should arrange their surface for the new states, and then send
- an ack_configure request with the serial sent in this configure event at
- some point before committing the new surface.
-
- The client is free to dismiss all but the last configure event it
- received.
-
- The width and height arguments specify the size of the window in
- surface-local coordinates.
-
- The size is a hint, in the sense that the client is free to ignore it if
- it doesn't resize, pick a smaller size (to satisfy aspect ratio or
- resize in steps of NxM pixels). If the client picks a smaller size and
- is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
- surface will be centered on this axis.
-
- If the width or height arguments are zero, it means the client should
- decide its own window dimension.
-
-
-
-
-
-
-
-
- The closed event is sent by the compositor when the surface will no
- longer be shown. The output may have been destroyed or the user may
- have asked for it to be removed. Further changes to the surface will be
- ignored. The client should destroy the resource after receiving this
- event, and create a new surface if they so choose.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/ipc/Tavern.cpp b/src/ipc/Tavern.cpp
new file mode 100644
index 0000000..8316986
--- /dev/null
+++ b/src/ipc/Tavern.cpp
@@ -0,0 +1,153 @@
+#include "Tavern.hpp"
+
+#include "../helpers/Logger.hpp"
+#include "IPC.hpp"
+#include "../ui/UI.hpp"
+
+using namespace IPC;
+
+constexpr const uint32_t PROTOCOL_VERSION = 1;
+
+static SP impl = makeShared(PROTOCOL_VERSION);
+
+CTavernPeer::CTavernPeer(SP sock, WP conn) : m_socket(sock), m_connection(conn) {
+ m_impl = makeShared(1, [this](SP obj) {
+ auto manager = m_managers.emplace_back(makeShared(std::move(obj)));
+
+ // We disconnect on destroying the manager. TODO: disconnect when the peer disconnects?
+ manager->setDestroy([this, weak = WP{manager}] {
+ m_connection->dropPeer(this);
+ std::erase(m_managers, weak);
+ });
+ manager->setOnDestroy([this, weak = WP{manager}] {
+ m_connection->dropPeer(this);
+ std::erase(m_managers, weak);
+ });
+
+ manager->setGetWallpaperObject([this, weak = WP{manager}](uint32_t id) {
+ if (!weak)
+ return;
+
+ m_wallpaperObjects.emplace_back(makeShared(
+ makeShared(m_socket->createObject(weak->getObject()->client(), weak->getObject(), "hyprpaper_wallpaper", id))));
+ });
+ });
+
+ m_socket->addImplementation(m_impl);
+}
+
+CTavernPeer::~CTavernPeer() {
+ if (!g_ui || m_fd < 0)
+ return;
+
+ g_ui->backend()->removeFd(m_fd);
+}
+
+void CTavernPeer::onNewDisplay(const std::string& sv) {
+ for (const auto& m : m_managers) {
+ m->sendAddMonitor(sv.c_str());
+ }
+}
+
+void CTavernPeer::onRemovedDisplay(const std::string& sv) {
+ for (const auto& m : m_managers) {
+ m->sendRemoveMonitor(sv.c_str());
+ }
+}
+
+int CTavernPeer::extractFD() {
+ if (m_fd >= 0)
+ return m_fd;
+
+ m_fd = m_socket->extractLoopFD();
+ return m_fd;
+}
+
+void CTavernPeer::dispatch() {
+ if (!m_socket->dispatchEvents())
+ m_connection->dropPeer(this);
+}
+
+CTavernConnection::CTavernConnection() = default;
+
+void CTavernConnection::init() {
+ const auto XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");
+
+ if (!XDG_RUNTIME_DIR) {
+ g_logger->log(LOG_ERR, "CTavernConnection: no runtime dir");
+ return;
+ }
+
+ const auto WL_DISPLAY = getenv("WAYLAND_DISPLAY");
+
+ m_socket = Hyprwire::IClientSocket::open(XDG_RUNTIME_DIR + std::string{"/hyprtavern/ht.sock"});
+
+ if (!m_socket) {
+ g_logger->log(LOG_ERR, "CTavernConnection: tavern is not serving beer, ignoring tavern");
+ return;
+ }
+
+ m_socket->addImplementation(impl);
+
+ if (!m_socket->waitForHandshake()) {
+ g_logger->log(LOG_ERR, "CTavernConnection: failed a handshake to tavern");
+ return;
+ }
+
+ const auto SPEC = m_socket->getSpec(impl->protocol()->specName());
+
+ if (!SPEC) {
+ g_logger->log(LOG_ERR, "CTavernConnection: tavern is bad (no protocol)");
+ return;
+ }
+
+ m_manager = makeShared(m_socket->bindProtocol(impl->protocol(), PROTOCOL_VERSION));
+
+ m_busObject = makeShared(m_manager->sendGetBusObject("hyprpaper"));
+
+ if (WL_DISPLAY)
+ m_busObject->sendExposeProperty("GLOBAL:WAYLAND_DISPLAY", WL_DISPLAY);
+ m_busObject->sendExposeProtocol("hyprpaper_core", 1);
+
+ m_busObject->setNewFd([this](int fd) {
+ auto sock = Hyprwire::IServerSocket::open(fd);
+
+ if (!sock) {
+ g_logger->log(LOG_ERR, "CTavernConnection: received a fd {} but it's dead", fd);
+ return;
+ }
+
+ auto x = m_peers.emplace_back(makeShared(sock, m_self));
+
+ g_ui->backend()->addFd(x->extractFD(), [w = WP{x}] {
+ if (!w)
+ return;
+
+ w->dispatch();
+ });
+ });
+
+ g_ui->backend()->addFd(m_socket->extractLoopFD(), [this] { //
+ m_socket->dispatchEvents();
+ });
+}
+
+bool CTavernConnection::connected() {
+ return m_busObject;
+}
+
+void CTavernConnection::dropPeer(CTavernPeer* peer) {
+ std::erase_if(m_peers, [peer](const auto& e) { return e.get() == peer; });
+}
+
+void CTavernConnection::onNewDisplay(const std::string& sv) {
+ for (const auto& p : m_peers) {
+ p->onNewDisplay(sv);
+ }
+}
+
+void CTavernConnection::onRemovedDisplay(const std::string& sv) {
+ for (const auto& p : m_peers) {
+ p->onRemovedDisplay(sv);
+ }
+}
diff --git a/src/ipc/Tavern.hpp b/src/ipc/Tavern.hpp
new file mode 100644
index 0000000..d68b488
--- /dev/null
+++ b/src/ipc/Tavern.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "../helpers/Memory.hpp"
+
+#include
+#include
+#include
+
+namespace IPC {
+ class CWallpaperObject;
+ class CTavernConnection;
+
+ class CTavernPeer {
+ public:
+ CTavernPeer(SP sock, WP conn);
+ ~CTavernPeer();
+
+ int extractFD();
+
+ void onNewDisplay(const std::string& sv);
+ void onRemovedDisplay(const std::string& sv);
+
+ void dispatch();
+
+ private:
+ SP m_socket;
+ int m_fd = -1;
+ SP m_impl;
+ WP m_connection;
+
+ std::vector> m_managers;
+ std::vector> m_wallpaperObjects;
+ };
+
+ class CTavernConnection {
+ public:
+ CTavernConnection();
+ ~CTavernConnection() = default;
+
+ void init();
+
+ WP m_self;
+
+ bool connected();
+ void dropPeer(CTavernPeer* peer);
+
+ void onNewDisplay(const std::string& sv);
+ void onRemovedDisplay(const std::string& sv);
+
+ private:
+ SP m_socket;
+ SP m_manager;
+ SP m_busObject;
+
+ std::vector> m_peers;
+ };
+
+ inline UP g_tavernConnection;
+};
\ No newline at end of file
diff --git a/src/ui/UI.cpp b/src/ui/UI.cpp
index 072a0eb..171279b 100644
--- a/src/ui/UI.cpp
+++ b/src/ui/UI.cpp
@@ -2,6 +2,7 @@
#include "../helpers/Logger.hpp"
#include "../ipc/HyprlandSocket.hpp"
#include "../ipc/IPC.hpp"
+#include "../ipc/Tavern.hpp"
#include "../config/WallpaperMatcher.hpp"
#include
@@ -70,12 +71,20 @@ CWallpaperTarget::CWallpaperTarget(SP output, const std::s
void CUI::registerOutput(const SP& mon) {
g_matcher->registerOutput(mon->port());
+
if (IPC::g_IPCSocket)
IPC::g_IPCSocket->onNewDisplay(mon->port());
+ if (IPC::g_tavernConnection)
+ IPC::g_tavernConnection->onNewDisplay(mon->port());
+
mon->m_events.removed.listenStatic([this, m = WP{mon}] {
g_matcher->unregisterOutput(m->port());
+
if (IPC::g_IPCSocket)
IPC::g_IPCSocket->onRemovedDisplay(m->port());
+ if (IPC::g_tavernConnection)
+ IPC::g_tavernConnection->onRemovedDisplay(m->port());
+
std::erase_if(m_targets, [&m](const auto& e) { return e->m_monitorName == m->port(); });
});
}
@@ -88,8 +97,12 @@ bool CUI::run() {
if (!m_backend)
return false;
- if (*PENABLEIPC)
- IPC::g_IPCSocket = makeUnique();
+ if (*PENABLEIPC) {
+ IPC::g_IPCSocket = makeUnique();
+ IPC::g_tavernConnection = makeUnique();
+ IPC::g_tavernConnection->m_self = IPC::g_tavernConnection;
+ IPC::g_tavernConnection->init();
+ }
const auto MONITORS = m_backend->getOutputs();