diff --git a/flake.lock b/flake.lock index 05f5201..eb47d80 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "hyprland-protocols": { "flake": false, "locked": { - "lastModified": 1670185345, - "narHash": "sha256-hxWGqlPecqEsE6nOHDV29KFBKePbY2Ipeac6lrChMKY=", + "lastModified": 1670703428, + "narHash": "sha256-4KUW5SKR0Y9uaYGcYwy53YJ3B/sgiprCL4fRGO+mpOA=", "owner": "hyprwm", "repo": "hyprland-protocols", - "rev": "4623a404c091e64743ba310199bb380ec52f1936", + "rev": "d0d6db8cb5bef6d93ca3ad8fb2124964173396da", "type": "github" }, "original": { diff --git a/hyprland-share-picker/main.cpp b/hyprland-share-picker/main.cpp index e69ad71..b32b9e5 100644 --- a/hyprland-share-picker/main.cpp +++ b/hyprland-share-picker/main.cpp @@ -16,6 +16,7 @@ #include #include #include +#include std::string execAndGet(const char* cmd) { std::array buffer; @@ -31,12 +32,58 @@ std::string execAndGet(const char* cmd) { } QApplication* pickerPtr = nullptr; +MainPicker* mainPickerPtr = nullptr; + +struct SWindowEntry { + std::string name; + std::string clazz; + int id = 0; +}; + +std::vector getWindows(const char* env) { + std::vector result; + + if (!env) + return result; + + std::string rolling = env; + + while (!rolling.empty()) { + // ID + const auto IDSEPPOS = rolling.find("[HC\011]"); + const auto IDSTR = rolling.substr(0, IDSEPPOS); + + // class + const auto CLASSSEPPOS = rolling.find("[HT\011]"); + const auto CLASSSTR = rolling.substr(IDSEPPOS + 5, CLASSSEPPOS - IDSEPPOS - 5); + + // title + const auto TITLESEPPOS = rolling.find("[HE\011]"); + const auto TITLESTR = rolling.substr(CLASSSEPPOS + 5, TITLESEPPOS - 5 - CLASSSEPPOS); + + try { + result.push_back({TITLESTR, CLASSSTR, std::stoi(IDSTR)}); + } catch (...) { + std::cout << "err\n"; // silent err + } + + rolling = rolling.substr(TITLESEPPOS + 5); + } + + return result; +} int main(int argc, char *argv[]) { qputenv("QT_LOGGING_RULES", "qml=false"); + + const char* WINDOWLISTSTR = getenv("XDPH_WINDOW_SHARING_LIST"); + + const auto WINDOWLIST = getWindows(WINDOWLISTSTR); + QApplication picker(argc, argv); pickerPtr = &picker; MainPicker w; + mainPickerPtr = &w; // get the tabwidget const auto TABWIDGET = (QTabWidget*)w.children()[1]->children()[0]; @@ -82,29 +129,18 @@ int main(int argc, char *argv[]) { // loop over them int windowIterator = 0; - while (windowsList.find("Window ") != std::string::npos) { - auto windowPropLen = windowsList.find("Window ", windowsList.find("\n\n") + 2); - if (windowPropLen == std::string::npos) - windowPropLen = windowsList.length(); - const std::string windowProp = windowsList.substr(0, windowPropLen); - windowsList = windowsList.substr(windowPropLen); + for (auto& window : WINDOWLIST) { - // get window name - auto windowName = windowProp.substr(windowProp.find(" -> ") + 4); - windowName = windowName.substr(0, windowName.find_first_of('\n') - 1); - - auto windowHandle = windowProp.substr(7, windowProp.find(" -> ") - 7); - - QString text = QString::fromStdString("Window " + windowHandle + ": " + windowName); + QString text = QString::fromStdString(window.clazz + ": " + window.name); QPushButton* button = new QPushButton(text, (QWidget*)WINDOWS_SCROLL_AREA_CONTENTS); button->move(9, 5 + (BUTTON_HEIGHT + BUTTON_PAD) * windowIterator); button->resize(BUTTON_WIDTH, BUTTON_HEIGHT); - QObject::connect(button, &QPushButton::clicked, [=] () { - std::string HANDLE = button->text().toStdString(); - HANDLE = HANDLE.substr(7, HANDLE.find_first_of(':') - 7); - std::cout << "window:" << HANDLE << "\n"; + mainPickerPtr->windowIDs[button] = window.id; + + QObject::connect(button, &QPushButton::clicked, [=] () { + std::cout << "window:" << mainPickerPtr->windowIDs[button] << "\n"; pickerPtr->quit(); return 0; }); diff --git a/hyprland-share-picker/mainpicker.h b/hyprland-share-picker/mainpicker.h index 1713646..4de1670 100644 --- a/hyprland-share-picker/mainpicker.h +++ b/hyprland-share-picker/mainpicker.h @@ -4,6 +4,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE namespace Ui { class MainPicker; } @@ -19,6 +20,8 @@ public: void onMonitorButtonClicked(QObject* target, QEvent* event); + std::unordered_map windowIDs; // button -> id + private: Ui::MainPicker *ui; }; diff --git a/include/screencast_common.h b/include/screencast_common.h index e437337..1f6b9eb 100644 --- a/include/screencast_common.h +++ b/include/screencast_common.h @@ -126,6 +126,8 @@ struct xdpw_screencast_context { // hyprland struct hyprland_toplevel_export_manager_v1 *hyprland_toplevel_manager; + struct zwlr_foreign_toplevel_manager_v1 *wlroots_toplevel_manager; + struct wl_list toplevel_resource_list; // gbm struct gbm_device *gbm; diff --git a/protocols/hyprland-protocols b/protocols/hyprland-protocols index 4623a40..301733a 160000 --- a/protocols/hyprland-protocols +++ b/protocols/hyprland-protocols @@ -1 +1 @@ -Subproject commit 4623a404c091e64743ba310199bb380ec52f1936 +Subproject commit 301733ae466b229066ba15a53e6d8b91c5dcef5b diff --git a/protocols/meson.build b/protocols/meson.build index 4682d52..5545a64 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -14,6 +14,7 @@ client_protocols = [ wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', + 'wlr-foreign-toplevel-management-unstable-v1.xml', 'hyprland-protocols/protocols/hyprland-toplevel-export-v1.xml' ] diff --git a/protocols/wlr-foreign-toplevel-management-unstable-v1.xml b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 0000000..44505bb --- /dev/null +++ b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + 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. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c index c249979..8e873d6 100644 --- a/src/screencast/wlr_screencast.c +++ b/src/screencast/wlr_screencast.c @@ -3,6 +3,7 @@ #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "wlr-screencopy-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" +#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" #include "hyprland-toplevel-export-v1-client-protocol.h" #include #include @@ -24,6 +25,117 @@ #include "xdpw.h" #include "logger.h" #include "fps_limit.h" +// + +struct SToplevelEntry { + struct zwlr_foreign_toplevel_handle_v1 *handle; + char name[256]; + char clazz[256]; + struct wl_list link; +}; + +void handleTitle(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title) { + struct xdpw_screencast_context *ctx = data; + + struct SToplevelEntry *current; + wl_list_for_each(current, &ctx->toplevel_resource_list, link) { + if (current->handle == handle) { + strncpy(current->name, title, 255); + current->name[255] = '\0'; + break; + } + } +} + +void handleAppID(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id) { + struct xdpw_screencast_context *ctx = data; + + struct SToplevelEntry *current; + wl_list_for_each(current, &ctx->toplevel_resource_list, link) { + if (current->handle == handle) { + strncpy(current->clazz, app_id, 255); + current->name[255] = '\0'; + break; + } + } +} + +void handleOutputEnter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { + ; // noop +} + +void handleOutputLeave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { + ; // noop +} + +void handleState(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *state) { + ; // noop +} + +void handleDone(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { + ; // noop +} + +void handleClosed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { + struct xdpw_screencast_context *ctx = data; + + struct SToplevelEntry *current; + wl_list_for_each(current, &ctx->toplevel_resource_list, link) { + if (current->handle == handle) { + break; + } + } + + wl_list_remove(¤t->link); +} + +void handleParent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent) { + ; // noop +} + +struct zwlr_foreign_toplevel_handle_v1_listener toplevelHandleListener = { + .title = handleTitle, + .app_id = handleAppID, + .output_enter = handleOutputEnter, + .output_leave = handleOutputLeave, + .state = handleState, + .done = handleDone, + .closed = handleClosed, + .parent = handleParent, +}; + +void handleToplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zwlr_foreign_toplevel_handle_v1 *toplevel) { + struct xdpw_screencast_context *ctx = data; + + struct SToplevelEntry* entry = malloc(sizeof(struct SToplevelEntry)); + + entry->handle = toplevel; + + wl_list_insert(&ctx->toplevel_resource_list, &entry->link); + + zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &toplevelHandleListener, ctx); +} + +void handleFinished(void *data, struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1) { + ; // noop +} + +struct zwlr_foreign_toplevel_manager_v1_listener toplevelListener = { + .toplevel = handleToplevel, + .finished = handleFinished, +}; + +struct SToplevelEntry* toplevelEntryFromID(struct xdpw_screencast_context *ctx, uint32_t id) { + struct SToplevelEntry *current; + wl_list_for_each(current, &ctx->toplevel_resource_list, link) { + if (((uint64_t)current->handle & 0xFFFFFFFF) == id) { + return current; + } + } + return NULL; +} + +/// void wlr_frame_free(struct xdpw_screencast_instance *cast) { if (!cast->wlr_frame) { @@ -423,8 +535,15 @@ void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast) { cast->ctx->screencopy_manager, cast->with_cursor, cast->target.output->output); } else { // share window - cast->frame_callback_hyprland = hyprland_toplevel_export_manager_v1_capture_toplevel( - cast->ctx->hyprland_toplevel_manager, cast->with_cursor, cast->target.window_handle); + struct SToplevelEntry* entry = toplevelEntryFromID(cast->ctx, cast->target.window_handle); + + if (!entry) { + logprint(DEBUG, "hyprland: error in getting entry"); + return; + } + + cast->frame_callback_hyprland = hyprland_toplevel_export_manager_v1_capture_toplevel_with_wlr_toplevel_handle( + cast->ctx->hyprland_toplevel_manager, cast->with_cursor, entry->handle); hyprland_toplevel_export_frame_v1_add_listener(cast->frame_callback_hyprland, &hyprland_frame_listener, cast); @@ -514,6 +633,65 @@ static void wlr_init_xdg_outputs(struct xdpw_screencast_context *ctx) { } } +// stolen from LLVM cuz it wouldnt include lol +static inline int vasprintf(char **strp, const char *fmt, va_list ap) { + const size_t buff_size = 256; + if ((*strp = (char *)malloc(buff_size)) == NULL) { + return -1; + } + + va_list ap_copy; + // va_copy may not be provided by the C library in C++ 03 mode. +#if defined(_LIBCPP_CXX03_LANG) && __has_builtin(__builtin_va_copy) + __builtin_va_copy(ap_copy, ap); +#else + va_copy(ap_copy, ap); +#endif + int str_size = vsnprintf(*strp, buff_size, fmt, ap_copy); + va_end(ap_copy); + + if ((size_t)str_size >= buff_size) { + if ((*strp = (char *)realloc(*strp, str_size + 1)) == NULL) { + return -1; + } + str_size = vsnprintf(*strp, str_size + 1, fmt, ap); + } + return str_size; +} + +char *getFormat(const char *fmt, ...) { + char *outputStr = NULL; + + va_list args; + va_start(args, fmt); + vasprintf(&outputStr, fmt, args); + va_end(args); + + return outputStr; +} + +char* buildWindowList(struct xdpw_screencast_context *ctx) { + + char* rolling = calloc(1, 1); + + struct SToplevelEntry* current; + wl_list_for_each(current, &ctx->toplevel_resource_list, link) { + + char* oldRolling = rolling; + + rolling = getFormat("%s%u[HC\011]%s[HT\011]%s[HE\011]", rolling, (uint32_t)(((uint64_t)current->handle) & 0xFFFFFFFF), current->clazz, current->name); + + free(oldRolling); + } + + for (size_t i = 0; i < strlen(rolling); ++i) { + if (rolling[i] == '\"') + rolling[i] = ' '; + } + + return rolling; +} + struct xdpw_share xdpw_wlr_chooser(struct xdpw_screencast_context *ctx) { char result[1024] = {0}; FILE *fp; @@ -523,13 +701,11 @@ struct xdpw_share xdpw_wlr_chooser(struct xdpw_screencast_context *ctx) { const char *XCURSOR_SIZE = getenv("XCURSOR_SIZE"); const char *HYPRLAND_INSTANCE_SIGNATURE = getenv("HYPRLAND_INSTANCE_SIGNATURE"); - char cmd[256] = "WAYLAND_DISPLAY="; - strcat(cmd, WAYLAND_DISPLAY); - strcat(cmd, " XCURSOR_SIZE="); - strcat(cmd, XCURSOR_SIZE ? XCURSOR_SIZE : "24"); - strcat(cmd, " HYPRLAND_INSTANCE_SIGNATURE="); - strcat(cmd, HYPRLAND_INSTANCE_SIGNATURE ? HYPRLAND_INSTANCE_SIGNATURE : "0"); - strcat(cmd, " QT_QPA_PLATFORM=wayland hyprland-share-picker"); + char* windowList = buildWindowList(ctx); + + char *cmd = getFormat("WAYLAND_DISPLAY=%s XCURSOR_SIZE=%s HYPRLAND_INSTANCE_SIGNATURE=%s XDPH_WINDOW_SHARING_LIST=\"%s\" hyprland-share-picker", WAYLAND_DISPLAY, XCURSOR_SIZE ? XCURSOR_SIZE : "24", HYPRLAND_INSTANCE_SIGNATURE ? HYPRLAND_INSTANCE_SIGNATURE : "0", windowList); + + free(windowList); logprint(DEBUG, "Screencast: Picker: Running command \"%s\"", cmd); @@ -545,6 +721,8 @@ struct xdpw_share xdpw_wlr_chooser(struct xdpw_screencast_context *ctx) { pclose(fp); + free(cmd); + // great, let's parse it. struct xdpw_share res = {NULL, -1, -1, -1, -1, -1}; @@ -630,7 +808,7 @@ struct xdpw_share xdpw_wlr_chooser(struct xdpw_screencast_context *ctx) { strncpy(display_name, result + 7, strlen(result) - 8); display_name[strlen(result) - 8] = 0; - res.window_handle = strtol(display_name, NULL, 16); + res.window_handle = strtol(display_name, NULL, 10); free(display_name); return res; @@ -882,6 +1060,17 @@ static void wlr_registry_handle_add(void *data, struct wl_registry *reg, ctx->hyprland_toplevel_manager = wl_registry_bind(reg, id, &hyprland_toplevel_export_manager_v1_interface, version); } + if (!strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name)) { + uint32_t version = ver; + + logprint(DEBUG, "hyprland: |-- registered to interface %s (Version %u)", interface, version); + + ctx->wlroots_toplevel_manager = wl_registry_bind(reg, id, &zwlr_foreign_toplevel_manager_v1_interface, version); + wl_list_init(&ctx->toplevel_resource_list); + + zwlr_foreign_toplevel_manager_v1_add_listener(ctx->wlroots_toplevel_manager, &toplevelListener, ctx); + } + if (strcmp(interface, wl_shm_interface.name) == 0) { logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, WL_SHM_VERSION); ctx->shm = wl_registry_bind(reg, id, &wl_shm_interface, WL_SHM_VERSION);