Replaces somebar's channel to dwl from stdin to a wayland extension. Normally, somebar reads dwl's state by reading from stdin. This requires that somebar is started from dwl, and does not allow sending commands back to dwl. This patch replaces this channel with a wayland protocol extension. somebar can use this protocol extension to observe and update the dwl window management state. Important! dwl must have the wayland-ipc patch applied for this to work! https://git.sr.ht/~raphi/dwl/blob/master/patches/wayland-ipc.patch diff --git a/protocols/meson.build b/protocols/meson.build index 7bd222b..5752608 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -15,6 +15,7 @@ wayland_xmls = [ wl_protocol_dir + '/stable/xdg-shell/xdg-shell.xml', wl_protocol_dir + '/unstable/xdg-output/xdg-output-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', + 'net-tapesoftware-dwl-wm-unstable-v1.xml', ] wayland_sources = [ wayland_scanner_code.process(wayland_xmls), diff --git a/protocols/net-tapesoftware-dwl-wm-unstable-v1.xml b/protocols/net-tapesoftware-dwl-wm-unstable-v1.xml new file mode 100644 index 0000000..390f5a1 --- /dev/null +++ b/protocols/net-tapesoftware-dwl-wm-unstable-v1.xml @@ -0,0 +1,164 @@ + + + + Copyright (c) 2021 Raphael Robatsch + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice (including the + next paragraph) shall be included in all copies or substantial portions + of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + + This interface is exposed as a global in the wl_registry. + + Clients can use this protocol to receive updates of the window manager + state (active tags, active layout, and focused window). + Clients can also control this state. + + After binding, the client will receive the available tags and layouts + with the 'tag' and 'layout' events. These can be used in subsequent + dwl_wm_monitor_v1.set_tags/set_layout requests, and to interpret the + dwl_wm_monitor_v1.layout/tag events. + + + + + This request indicates that the client will not use the dwl_wm + object any more. Objects that have been created through this instance + are not affected. + + + + + + Gets a dwl monitor for the specified output. The window manager + state on the output can be controlled using the monitor. + + + + + + + + This event is sent immediately after binding. + A roundtrip after binding guarantees that the client has received all tags. + + + + + + + This event is sent immediately after binding. + A roundtrip after binding guarantees that the client has received all layouts. + + + + + + + + Observes and controls one monitor. + + Events are double-buffered: Clients should cache all events and only + redraw themselves once the 'frame' event is sent. + + Requests are not double-buffered: The compositor will update itself + immediately. + + + + + + + + + + + This request indicates that the client is done with this dwl_monitor. + All further requests are ignored. + + + + + + If 'selected' is nonzero, this monitor is the currently selected one. + + + + + + + Announces the update of a tag. num_clients and focused_client can be + used to draw client indicators. + + + + + + + + + + Announces the update of the selected layout. + + + + + + + Announces the update of the selected client. + + + + + + + Sent after all other events belonging to the status update has been sent. + Clients should redraw themselves now. + + + + + + Changes are applied immediately. + + + + + + + + tags are updated as follows: + new_tags = (current_tags AND and_tags) XOR xor_tags + + Changes are applied immediately. + + + + + + + + Changes are applied immediately. + + + + + diff --git a/src/common.hpp b/src/common.hpp index aed4480..12a3e2e 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -10,6 +10,7 @@ #include #include #include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "net-tapesoftware-dwl-wm-unstable-v1-client-protocol.h" struct Color { Color() {} @@ -38,7 +39,14 @@ extern wl_display* display; extern wl_compositor* compositor; extern wl_shm* shm; extern zwlr_layer_shell_v1* wlrLayerShell; +extern std::vector tagNames; +extern std::vector layoutNames; +void view(Monitor& m, const Arg& arg); +void toggleview(Monitor& m, const Arg& arg); +void setlayout(Monitor& m, const Arg& arg); +void tag(Monitor& m, const Arg& arg); +void toggletag(Monitor& m, const Arg& arg); void spawn(Monitor&, const Arg& arg); void setCloexec(int fd); [[noreturn]] void die(const char* why); @@ -59,6 +67,7 @@ WL_DELETER(wl_output, wl_output_release); WL_DELETER(wl_pointer, wl_pointer_release); WL_DELETER(wl_seat, wl_seat_release); WL_DELETER(wl_surface, wl_surface_destroy); +WL_DELETER(znet_tapesoftware_dwl_wm_monitor_v1, znet_tapesoftware_dwl_wm_monitor_v1_release); WL_DELETER(zwlr_layer_surface_v1, zwlr_layer_surface_v1_destroy); WL_DELETER(cairo_t, cairo_destroy); diff --git a/src/config.def.hpp b/src/config.def.hpp index 40a8c95..a9560cb 100644 --- a/src/config.def.hpp +++ b/src/config.def.hpp @@ -16,12 +16,11 @@ constexpr ColorScheme colorInactive = {Color(0xbb, 0xbb, 0xbb), Color(0x22, 0x22 constexpr ColorScheme colorActive = {Color(0xee, 0xee, 0xee), Color(0x00, 0x55, 0x77)}; constexpr const char* termcmd[] = {"foot", nullptr}; -static std::vector tagNames = { - "1", "2", "3", - "4", "5", "6", - "7", "8", "9", -}; - constexpr Button buttons[] = { + { ClkTagBar, BTN_LEFT, view, {0} }, + { ClkTagBar, BTN_RIGHT, tag, {0} }, + { ClkTagBar, BTN_MIDDLE, toggletag, {0} }, + { ClkLayoutSymbol, BTN_LEFT, setlayout, {.ui = 0} }, + { ClkLayoutSymbol, BTN_RIGHT, setlayout, {.ui = 2} }, { ClkStatusText, BTN_RIGHT, spawn, {.v = termcmd} }, }; diff --git a/src/main.cpp b/src/main.cpp index 6198d8b..9e7549a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -21,8 +20,8 @@ #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" +#include "net-tapesoftware-dwl-wm-unstable-v1-client-protocol.h" #include "common.hpp" -#include "config.hpp" #include "bar.hpp" #include "line_buffer.hpp" @@ -34,6 +33,7 @@ struct Monitor { bool desiredVisibility {true}; bool hasData; uint32_t tags; + wl_unique_ptr dwlMonitor; }; struct SeatPointer { @@ -54,8 +54,6 @@ static void updatemon(Monitor &mon); static void onReady(); static void setupStatusFifo(); static void onStatus(); -static void onStdin(); -static void handleStdin(const std::string& line); static void updateVisibility(const std::string& name, bool(*updater)(bool)); static void onGlobalAdd(void*, wl_registry* registry, uint32_t name, const char* interface, uint32_t version); static void onGlobalRemove(void*, wl_registry* registry, uint32_t name); @@ -67,6 +65,9 @@ wl_display* display; wl_compositor* compositor; wl_shm* shm; zwlr_layer_shell_v1* wlrLayerShell; +znet_tapesoftware_dwl_wm_v1* dwlWm; +std::vector tagNames; +std::vector layoutNames; static xdg_wm_base* xdgWmBase; static zxdg_output_manager_v1* xdgOutputManager; static wl_surface* cursorSurface; @@ -85,6 +86,26 @@ static int statusFifoFd {-1}; static int statusFifoWriter {-1}; static bool quitting {false}; +void view(Monitor& m, const Arg& arg) +{ + znet_tapesoftware_dwl_wm_monitor_v1_set_tags(m.dwlMonitor.get(), arg.ui, 1); +} +void toggleview(Monitor& m, const Arg& arg) +{ + znet_tapesoftware_dwl_wm_monitor_v1_set_tags(m.dwlMonitor.get(), m.tags ^ arg.ui, 0); +} +void setlayout(Monitor& m, const Arg& arg) +{ + znet_tapesoftware_dwl_wm_monitor_v1_set_layout(m.dwlMonitor.get(), arg.ui); +} +void tag(Monitor& m, const Arg& arg) +{ + znet_tapesoftware_dwl_wm_monitor_v1_set_client_tags(m.dwlMonitor.get(), 0, arg.ui); +} +void toggletag(Monitor& m, const Arg& arg) +{ + znet_tapesoftware_dwl_wm_monitor_v1_set_client_tags(m.dwlMonitor.get(), ~0, arg.ui); +} void spawn(Monitor&, const Arg& arg) { if (fork() == 0) { @@ -189,11 +210,62 @@ static const struct wl_seat_listener seatListener = { .name = [](void*, wl_seat*, const char* name) { } }; +static const struct znet_tapesoftware_dwl_wm_v1_listener dwlWmListener = { + .tag = [](void*, znet_tapesoftware_dwl_wm_v1*, const char* name) { + tagNames.push_back(name); + }, + .layout = [](void*, znet_tapesoftware_dwl_wm_v1*, const char* name) { + layoutNames.push_back(name); + }, +}; + +static const struct znet_tapesoftware_dwl_wm_monitor_v1_listener dwlWmMonitorListener { + .selected = [](void* mv, znet_tapesoftware_dwl_wm_monitor_v1*, uint32_t selected) { + auto mon = static_cast(mv); + if (selected) { + selmon = mon; + } else if (selmon == mon) { + selmon = nullptr; + } + mon->bar.setSelected(selected); + }, + .tag = [](void* mv, znet_tapesoftware_dwl_wm_monitor_v1*, uint32_t tag, uint32_t state, uint32_t numClients, int32_t focusedClient) { + auto mon = static_cast(mv); + int tagState = TagState::None; + if (state & ZNET_TAPESOFTWARE_DWL_WM_MONITOR_V1_TAG_STATE_ACTIVE) + tagState |= TagState::Active; + if (state & ZNET_TAPESOFTWARE_DWL_WM_MONITOR_V1_TAG_STATE_URGENT) + tagState |= TagState::Urgent; + mon->bar.setTag(tag, tagState, numClients, focusedClient); + uint32_t mask = 1 << tag; + if (tagState & TagState::Active) { + mon->tags |= mask; + } else { + mon->tags &= ~mask; + } + }, + .layout = [](void* mv, znet_tapesoftware_dwl_wm_monitor_v1*, uint32_t layout) { + auto mon = static_cast(mv); + mon->bar.setLayout(layoutNames[layout]); + }, + .title = [](void* mv, znet_tapesoftware_dwl_wm_monitor_v1*, const char* title) { + auto mon = static_cast(mv); + mon->bar.setTitle(title); + }, + .frame = [](void* mv, znet_tapesoftware_dwl_wm_monitor_v1*) { + auto mon = static_cast(mv); + mon->hasData = true; + updatemon(*mon); + } +}; + void setupMonitor(uint32_t name, wl_output* output) { auto& monitor = monitors.emplace_back(Monitor {name, {}, wl_unique_ptr {output}}); monitor.bar.setStatus(lastStatus); auto xdgOutput = zxdg_output_manager_v1_get_xdg_output(xdgOutputManager, monitor.wlOutput.get()); zxdg_output_v1_add_listener(xdgOutput, &xdgOutputListener, &monitor); + monitor.dwlMonitor.reset(znet_tapesoftware_dwl_wm_v1_get_monitor(dwlWm, monitor.wlOutput.get())); + znet_tapesoftware_dwl_wm_monitor_v1_add_listener(monitor.dwlMonitor.get(), &dwlWmMonitorListener, &monitor); } void updatemon(Monitor& mon) @@ -219,6 +291,7 @@ void onReady() requireGlobal(shm, "wl_shm"); requireGlobal(wlrLayerShell, "zwlr_layer_shell_v1"); requireGlobal(xdgOutputManager, "zxdg_output_manager_v1"); + requireGlobal(dwlWm, "znet_tapesoftware_dwl_wm_v1"); setupStatusFifo(); wl_display_roundtrip(display); // roundtrip so we receive all dwl tags etc. @@ -226,7 +299,6 @@ void onReady() for (auto output : uninitializedOutputs) { setupMonitor(output.first, output.second); } - wl_display_roundtrip(display); // wait for xdg_output names before we read stdin } void setupStatusFifo() @@ -259,66 +331,6 @@ void setupStatusFifo() } } -static LineBuffer<512> stdinBuffer; -static void onStdin() -{ - auto res = stdinBuffer.readLines( - [](void* p, size_t size) { return read(0, p, size); }, - [](char* p, size_t size) { handleStdin({p, size}); }); - if (res == 0) { - quitting = true; - } -} - -static void handleStdin(const std::string& line) -{ - // this parses the lines that dwl sends in printstatus() - std::string monName, command; - auto stream = std::istringstream {line}; - stream >> monName >> command; - if (!stream.good()) { - return; - } - auto mon = std::find_if(begin(monitors), end(monitors), [&](const Monitor& mon) { - return mon.xdgName == monName; - }); - if (mon == end(monitors)) - return; - if (command == "title") { - auto title = std::string {}; - std::getline(stream, title); - mon->bar.setTitle(title); - } else if (command == "selmon") { - uint32_t selected; - stream >> selected; - mon->bar.setSelected(selected); - if (selected) { - selmon = &*mon; - } else if (selmon == &*mon) { - selmon = nullptr; - } - } else if (command == "tags") { - uint32_t occupied, tags, clientTags, urgent; - stream >> occupied >> tags >> clientTags >> urgent; - for (auto i=0u; ibar.setTag(i, state, occupied & tagMask ? 1 : 0, clientTags & tagMask ? 0 : -1); - } - mon->tags = tags; - } else if (command == "layout") { - auto layout = std::string {}; - std::getline(stream, layout); - mon->bar.setLayout(layout); - } - mon->hasData = true; - updatemon(*mon); -} - const std::string prefixStatus = "status "; const std::string prefixShow = "show "; const std::string prefixHide = "hide "; @@ -393,6 +405,10 @@ void onGlobalAdd(void*, wl_registry* registry, uint32_t name, const char* interf xdg_wm_base_add_listener(xdgWmBase, &xdgWmBaseListener, nullptr); return; } + if (reg.handle(dwlWm, znet_tapesoftware_dwl_wm_v1_interface, 1)) { + znet_tapesoftware_dwl_wm_v1_add_listener(dwlWm, &dwlWmListener, nullptr); + return; + } if (wl_seat* wlSeat; reg.handle(wlSeat, wl_seat_interface, 7)) { auto& seat = seats.emplace_back(Seat {name, wl_unique_ptr {wlSeat}}); wl_seat_add_listener(wlSeat, &seatListener, &seat); @@ -494,10 +510,6 @@ int main(int argc, char* argv[]) .fd = displayFd, .events = POLLIN, }); - pollfds.push_back({ - .fd = STDIN_FILENO, - .events = POLLIN, - }); if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) < 0) { diesys("fcntl F_SETFL"); } @@ -522,8 +534,6 @@ int main(int argc, char* argv[]) ev.events = POLLIN; waylandFlush(); } - } else if (ev.fd == STDIN_FILENO && (ev.revents & POLLIN)) { - onStdin(); } else if (ev.fd == statusFifoFd && (ev.revents & POLLIN)) { onStatus(); } else if (ev.fd == signalSelfPipe[0] && (ev.revents & POLLIN)) {