From 6d53770d655daebf5f0370b7f3bd868a2f9e80d8 Mon Sep 17 00:00:00 2001 From: itycodes Date: Tue, 1 Oct 2024 07:34:54 +0200 Subject: [PATCH] Initial commit --- Makefile | 49 ++++ README.md | 11 + ext-foreign-toplevel-list-v1.xml | 219 +++++++++++++++ ext-image-capture-source-v1.xml | 109 ++++++++ ext-image-copy-capture-v1.xml | 456 +++++++++++++++++++++++++++++++ main.c | 252 +++++++++++++++++ rif.h | 26 ++ 7 files changed, 1122 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 ext-foreign-toplevel-list-v1.xml create mode 100644 ext-image-capture-source-v1.xml create mode 100644 ext-image-copy-capture-v1.xml create mode 100644 main.c create mode 100644 rif.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b575c85 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +.PHONY: all clean + +CC = gcc +CLIBS = -lwayland-client +CFLAGS = $(CLIBS) + +EXT_SOURCE_PROTOCOL_IMPL = ext-image-capture-source-v1-protocol.c +EXT_SOURCE_PROTOCOL_HEADER = ext-image-capture-source-v1-protocol.h + +EXT_CAPTURE_PROTOCOL_IMPL = ext-image-copy-capture-v1-protocol.c +EXT_CAPTURE_PROTOCOL_HEADER = ext-image-copy-capture-v1-protocol.h + +EXT_TOPLEVEL_PROTOCOL_IMPL = ext-foreign-toplevel-list-v1-protocol.c +EXT_TOPLEVEL_PROTOCOL_HEADER = ext-foreign-toplevel-list-v1-protocol.h + +SRC = main.c +OUT = screencap + +PROTOCOL_IMPLS = $(EXT_TOPLEVEL_PROTOCOL_IMPL) $(EXT_SOURCE_PROTOCOL_IMPL) $(EXT_CAPTURE_PROTOCOL_IMPL) +PROTOCOL_HEADERS = $(EXT_TOPLEVEL_PROTOCOL_HEADER) $(EXT_SOURCE_PROTOCOL_HEADER) $(EXT_CAPTURE_PROTOCOL_HEADER) +PROTOCOLS = $(PROTOCOL_IMPLS) $(PROTOCOL_HEADERS) + +all: $(OUT) + +clean: + rm -f $(OUT) + rm -f $(PROTOCOLS) + +$(OUT): $(PROTOCOLS) $(SRC) + $(CC) $(PROTOCOL_IMPLS) $(SRC) $(CFLAGS) -o $(OUT) + +$(EXT_SOURCE_PROTOCOL_IMPL): + wayland-scanner private-code ./ext-image-capture-source-v1.xml $(EXT_SOURCE_PROTOCOL_IMPL) + +$(EXT_SOURCE_PROTOCOL_HEADER): + wayland-scanner client-header ./ext-image-capture-source-v1.xml $(EXT_SOURCE_PROTOCOL_HEADER) + +$(EXT_CAPTURE_PROTOCOL_IMPL): + wayland-scanner private-code ./ext-image-copy-capture-v1.xml $(EXT_CAPTURE_PROTOCOL_IMPL) + +$(EXT_CAPTURE_PROTOCOL_HEADER): + wayland-scanner client-header ./ext-image-copy-capture-v1.xml $(EXT_CAPTURE_PROTOCOL_HEADER) + +$(EXT_TOPLEVEL_PROTOCOL_IMPL): + wayland-scanner private-code ./ext-foreign-toplevel-list-v1.xml $(EXT_TOPLEVEL_PROTOCOL_IMPL) + +$(EXT_TOPLEVEL_PROTOCOL_HEADER): + wayland-scanner client-header ./ext-foreign-toplevel-list-v1.xml $(EXT_TOPLEVEL_PROTOCOL_HEADER) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4950777 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +A simple Wayland screenshotting utility that utilizes the new `ext-image-capture-source-v1` & `ext-image-copy-capture-v1`. + +Runtime Dependencies: + +- `libwayland-client` + +Make Dependencies: + +- `wayland-scanner` +- `make` +- `gcc` diff --git a/ext-foreign-toplevel-list-v1.xml b/ext-foreign-toplevel-list-v1.xml new file mode 100644 index 0000000..11b0113 --- /dev/null +++ b/ext-foreign-toplevel-list-v1.xml @@ -0,0 +1,219 @@ + + + + Copyright © 2018 Ilia Bozhinov + Copyright © 2020 Isaac Freund + Copyright © 2022 wb9688 + Copyright © 2023 i509VCB + + 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 provide protocol object handles for + toplevels, possibly originating from another client. + + This protocol is intentionally minimalistic and expects additional + functionality (e.g. creating a screencopy source from a toplevel handle, + getting information about the state of the toplevel) to be implemented + in extension protocols. + + The compositor may choose to restrict this protocol to a special client + launched by the compositor itself or expose it to all clients, + this is compositor policy. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + A toplevel is defined as a surface with a role similar to xdg_toplevel. + XWayland surfaces may be treated like toplevels in this protocol. + + After a client binds the ext_foreign_toplevel_list_v1, each mapped + toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel + event. + + Clients which only care about the current state can perform a roundtrip after + binding this global. + + For each instance of ext_foreign_toplevel_list_v1, the compositor must + create a new ext_foreign_toplevel_handle_v1 object for each mapped toplevel. + + If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished + event after the global is bound, the compositor must not send any + ext_foreign_toplevel_list_v1.toplevel events. + + + + + 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 properties of the toplevel (identifier, title, app_id) will be sent + immediately after this event using the corresponding events for + ext_foreign_toplevel_handle_v1. The compositor will use the + ext_foreign_toplevel_handle_v1.done event to indicate when all data has + been sent. + + + + + + + This event indicates that the compositor is done sending events + to this object. The client should destroy the object. + See ext_foreign_toplevel_list_v1.destroy for more information. + + The compositor must not send any more toplevel events after this event. + + + + + + This request indicates that the client no longer wishes to receive + events for new toplevels. + + The Wayland protocol is asynchronous, meaning the compositor may send + further toplevel events until the stop request is processed. + The client should wait for a ext_foreign_toplevel_list_v1.finished + event before destroying this object. + + + + + + This request should be called either when the client will no longer + use the ext_foreign_toplevel_list_v1 or after the finished event + has been received to allow destruction of the object. + + If a client wishes to destroy this object it should send a + ext_foreign_toplevel_list_v1.stop request and wait for a ext_foreign_toplevel_list_v1.finished + event, then destroy the handles and then this object. + + + + + + + A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel + window. A single app may have multiple mapped toplevels. + + + + + This request should be used when the client will no longer use the handle + or after the closed event has been received to allow destruction of the + object. + + When a handle is destroyed, a new handle may not be created by the server + until the toplevel is unmapped and then remapped. Destroying a toplevel handle + is not recommended unless the client is cleaning up child objects + before destroying the ext_foreign_toplevel_list_v1 object, the toplevel + was closed or the toplevel handle will not be used in the future. + + Other protocols which extend the ext_foreign_toplevel_handle_v1 + interface should require destructors for extension interfaces be + called before allowing the toplevel handle to be destroyed. + + + + + + The server will emit no further events on the ext_foreign_toplevel_handle_v1 + after this event. Any requests received aside from the destroy request must + be ignored. Upon receiving this event, the client should destroy the handle. + + Other protocols which extend the ext_foreign_toplevel_handle_v1 + interface must also ignore requests other than destructors. + + + + + + This event is sent after all changes in the toplevel state have + been sent. + + This allows changes to the ext_foreign_toplevel_handle_v1 properties + to be atomically applied. Other protocols which extend the + ext_foreign_toplevel_handle_v1 interface may use this event to also + atomically apply any pending state. + + This event must not be sent after the ext_foreign_toplevel_handle_v1.closed + event. + + + + + + The title of the toplevel has changed. + + The configured state must not be applied immediately. See + ext_foreign_toplevel_handle_v1.done for details. + + + + + + + The app id of the toplevel has changed. + + The configured state must not be applied immediately. See + ext_foreign_toplevel_handle_v1.done for details. + + + + + + + This identifier is used to check if two or more toplevel handles belong + to the same toplevel. + + The identifier is useful for command line tools or privileged clients + which may need to reference an exact toplevel across processes or + instances of the ext_foreign_toplevel_list_v1 global. + + The compositor must only send this event when the handle is created. + + The identifier must be unique per toplevel and it's handles. Two different + toplevels must not have the same identifier. The identifier is only valid + as long as the toplevel is mapped. If the toplevel is unmapped the identifier + must not be reused. An identifier must not be reused by the compositor to + ensure there are no races when sharing identifiers between processes. + + An identifier is a string that contains up to 32 printable ASCII bytes. + An identifier must not be an empty string. It is recommended that a + compositor includes an opaque generation value in identifiers. How the + generation value is used when generating the identifier is implementation + dependent. + + + + + diff --git a/ext-image-capture-source-v1.xml b/ext-image-capture-source-v1.xml new file mode 100644 index 0000000..b31a5da --- /dev/null +++ b/ext-image-capture-source-v1.xml @@ -0,0 +1,109 @@ + + + + Copyright © 2022 Andri Yngvason + Copyright © 2024 Simon Ser + + 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 protocol serves as an intermediary between capturing protocols and + potential image capture sources such as outputs and toplevels. + + This protocol may be extended to support more image capture sources in the + future, thereby adding those image capture sources to other protocols that + use the image capture source object without having to modify those + protocols. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + The image capture source object is an opaque descriptor for a capturable + resource. This resource may be any sort of entity from which an image + may be derived. + + Note, because ext_image_capture_source_v1 objects are created from multiple + independent factory interfaces, the ext_image_capture_source_v1 interface is + frozen at version 1. + + + + + Destroys the image capture source. This request may be sent at any time + by the client. + + + + + + + A manager for creating image capture source objects for wl_output objects. + + + + + Creates a source object for an output. Images captured from this source + will show the same content as the output. Some elements may be omitted, + such as cursors and overlays that have been marked as transparent to + capturing. + + + + + + + + Destroys the manager. This request may be sent at any time by the client + and objects created by the manager will remain valid after its + destruction. + + + + + + + A manager for creating image capture source objects for + ext_foreign_toplevel_handle_v1 objects. + + + + + Creates a source object for a foreign toplevel handle. Images captured + from this source will show the same content as the toplevel. + + + + + + + + Destroys the manager. This request may be sent at any time by the client + and objects created by the manager will remain valid after its + destruction. + + + + diff --git a/ext-image-copy-capture-v1.xml b/ext-image-copy-capture-v1.xml new file mode 100644 index 0000000..dfb25a6 --- /dev/null +++ b/ext-image-copy-capture-v1.xml @@ -0,0 +1,456 @@ + + + + Copyright © 2021-2023 Andri Yngvason + Copyright © 2024 Simon Ser + + 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 protocol allows clients to ask the compositor to capture image sources + such as outputs and toplevels into user submitted buffers. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + + + + + + + + + Create a capturing session for an image capture source. + + If the paint_cursors option is set, cursors shall be composited onto + the captured frame. The cursor must not be composited onto the frame + if this flag is not set. + + If the options bitfield is invalid, the invalid_option protocol error + is sent. + + + + + + + + + Create a cursor capturing session for the pointer of an image capture + source. + + + + + + + + + Destroy the manager object. + + Other objects created via this interface are unaffected. + + + + + + + This object represents an active image copy capture session. + + After a capture session is created, buffer constraint events will be + emitted from the compositor to tell the client which buffer types and + formats are supported for reading from the session. The compositor may + re-send buffer constraint events whenever they change. + + The advertise buffer constraints, the compositor must send in no + particular order: zero or more shm_format and dmabuf_format events, zero + or one dmabuf_device event, and exactly one buffer_size event. Then the + compositor must send a done event. + + When the client has received all the buffer constraints, it can create a + buffer accordingly, attach it to the capture session using the + attach_buffer request, set the buffer damage using the damage_buffer + request and then send the capture request. + + + + + + + + + Provides the dimensions of the source image in buffer pixel coordinates. + + The client must attach buffers that match this size. + + + + + + + + Provides the format that must be used for shared-memory buffers. + + This event may be emitted multiple times, in which case the client may + choose any given format. + + + + + + + This event advertises the device buffers must be allocated on for + dma-buf buffers. + + In general the device is a DRM node. The DRM node type (primary vs. + render) is unspecified. Clients must not rely on the compositor sending + a particular node type. Clients cannot check two devices for equality + by comparing the dev_t value. + + + + + + + Provides the format that must be used for dma-buf buffers. + + The client may choose any of the modifiers advertised in the array of + 64-bit unsigned integers. + + This event may be emitted multiple times, in which case the client may + choose any given format. + + + + + + + + This event is sent once when all buffer constraint events have been + sent. + + The compositor must always end a batch of buffer constraint events with + this event, regardless of whether it sends the initial constraints or + an update. + + + + + + This event indicates that the capture session has stopped and is no + longer available. This can happen in a number of cases, e.g. when the + underlying source is destroyed, if the user decides to end the image + capture, or if an unrecoverable runtime error has occurred. + + The client should destroy the session after receiving this event. + + + + + + Create a capture frame for this session. + + At most one frame object can exist for a given session at any time. If + a client sends a create_frame request before a previous frame object + has been destroyed, the duplicate_frame protocol error is raised. + + + + + + + Destroys the session. This request can be sent at any time by the + client. + + This request doesn't affect ext_image_copy_capture_frame_v1 objects created by + this object. + + + + + + + This object represents an image capture frame. + + The client should attach a buffer, damage the buffer, and then send a + capture request. + + If the capture is successful, the compositor must send the frame metadata + (transform, damage, presentation_time in any order) followed by the ready + event. + + If the capture fails, the compositor must send the failed event. + + + + + + + + + + + Destroys the session. This request can be sent at any time by the + client. + + + + + + Attach a buffer to the session. + + The wl_buffer.release request is unused. + + The new buffer replaces any previously attached buffer. + + This request must not be sent after capture, or else the + already_captured protocol error is raised. + + + + + + + Apply damage to the buffer which is to be captured next. This request + may be sent multiple times to describe a region. + + The client indicates the accumulated damage since this wl_buffer was + last captured. During capture, the compositor will update the buffer + with at least the union of the region passed by the client and the + region advertised by ext_image_copy_capture_frame_v1.damage. + + When a wl_buffer is captured for the first time, or when the client + doesn't track damage, the client must damage the whole buffer. + + This is for optimisation purposes. The compositor may use this + information to reduce copying. + + These coordinates originate from the upper left corner of the buffer. + + If x or y are strictly negative, or if width or height are negative or + zero, the invalid_buffer_damage protocol error is raised. + + This request must not be sent after capture, or else the + already_captured protocol error is raised. + + + + + + + + + + Capture a frame. + + Unless this is the first successful captured frame performed in this + session, the compositor may wait an indefinite amount of time for the + source content to change before performing the copy. + + This request may only be sent once, or else the already_captured + protocol error is raised. A buffer must be attached before this request + is sent, or else the no_buffer protocol error is raised. + + + + + + This event is sent before the ready event and holds the transform that + the compositor has applied to the buffer contents. + + + + + + + This event is sent before the ready event. It may be generated multiple + times to describe a region. + + The first captured frame in a session will always carry full damage. + Subsequent frames' damaged regions describe which parts of the buffer + have changed since the last ready event. + + These coordinates originate in the upper left corner of the buffer. + + + + + + + + + + This event indicates the time at which the frame is presented to the + output in system monotonic time. This event is sent before the ready + event. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. + + + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. + + The buffer may be re-used by the client after this event. + + After receiving this event, the client must destroy the object. + + + + + + + An unspecified runtime error has occurred. The client may retry. + + + + + The buffer submitted by the client doesn't match the latest session + constraints. The client should re-allocate its buffers and retry. + + + + + The session has stopped. See ext_image_copy_capture_session_v1.stopped. + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client must destroy the object. + + + + + + + + This object represents a cursor capture session. It extends the base + capture session with cursor-specific metadata. + + + + + + + + + Destroys the session. This request can be sent at any time by the + client. + + This request doesn't affect ext_image_copy_capture_frame_v1 objects created by + this object. + + + + + + Gets the image copy capture session for this cursor session. + + The session will produce frames of the cursor image. The compositor may + pause the session when the cursor leaves the captured area. + + This request must not be sent more than once, or else the + duplicate_session protocol error is raised. + + + + + + + Sent when a cursor enters the captured area. It shall be generated + before the "position" and "hotspot" events when and only when a cursor + enters the area. + + The cursor enters the captured area when the cursor image intersects + with the captured area. Note, this is different from e.g. + wl_pointer.enter. + + + + + + Sent when a cursor leaves the captured area. No "position" or "hotspot" + event is generated for the cursor until the cursor enters the captured + area again. + + + + + + Cursors outside the image capture source do not get captured and no + event will be generated for them. + + The given position is the position of the cursor's hotspot and it is + relative to the main buffer's top left corner in transformed buffer + pixel coordinates. The coordinates may be negative or greater than the + main buffer size. + + + + + + + + The hotspot describes the offset between the cursor image and the + position of the input device. + + The given coordinates are the hotspot's offset from the origin in + buffer coordinates. + + Clients should not apply the hotspot immediately: the hotspot becomes + effective when the next ext_image_copy_capture_frame_v1.ready event is received. + + Compositors may delay this event until the client captures a new frame. + + + + + + diff --git a/main.c b/main.c new file mode 100644 index 0000000..85debe7 --- /dev/null +++ b/main.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include + +#include + +#include + +#include "ext-image-capture-source-v1-protocol.h" +#include "ext-image-copy-capture-v1-protocol.h" + +#include "rif.h" + +#define ASSERT(cond, msg)\ + if(!(cond)) {\ + fprintf(stderr, "Runtime assertion %s at %s:%d failed: %s\n", #cond, __FILE__, __LINE__, msg);\ + exit(-1);\ + } + +// Not included in any standard header +// Thus, declaring manually. +// See memfd_create(2). +int memfd_create(const char* name, unsigned int flags); + +struct image_data { + uint32_t width; + uint32_t height; + uint32_t shm_format; +}; + +struct wl_output* stream_output = NULL; +struct wl_shm* wl_shm = NULL; +struct wl_buffer* frame_buffer = NULL; +struct ext_output_image_capture_source_manager_v1* output_source_manager = NULL; +struct ext_image_copy_capture_manager_v1* capture_manager = NULL; + +void* shm_data = NULL; + +struct image_data img_data; + +uint8_t is_frame_ready = 0; +uint8_t is_capture_ready = 0; + +uint32_t buf_size; + +void handle_global( + void* data, + struct wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t version) { + if(strcmp(interface, wl_output_interface.name) == 0) { + stream_output = wl_registry_bind(registry, name, &wl_output_interface, version); + } + if(strcmp(interface, wl_shm_interface.name) == 0) { + wl_shm = wl_registry_bind(registry, name, &wl_shm_interface, version); + } + if(strcmp(interface, ext_output_image_capture_source_manager_v1_interface.name) == 0) { + output_source_manager = wl_registry_bind(registry, name, &ext_output_image_capture_source_manager_v1_interface, version); + } + if(strcmp(interface, ext_image_copy_capture_manager_v1_interface.name) == 0) { + capture_manager = wl_registry_bind(registry, name, &ext_image_copy_capture_manager_v1_interface, version); + } +} + +void handle_global_remove( + void* data, + struct wl_registry* registry, + uint32_t name) { + // Who cares +} + +const struct wl_registry_listener reg_callbacks = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +void buffer_size(void *data, + struct ext_image_copy_capture_session_v1 *ext_image_copy_capture_session_v1, + uint32_t width, + uint32_t height) { + printf("x: %d, y: %d\n", width, height); + img_data.width = width; + img_data.height = height; +} + +void shm_format(void *data, + struct ext_image_copy_capture_session_v1 *ext_image_copy_capture_session_v1, + uint32_t format) { + img_data.shm_format = format; + ASSERT(format == WL_SHM_FORMAT_XBGR8888 || format == WL_SHM_FORMAT_ABGR8888, "Unsupported buffer format."); +} + +void dmabuf_device(void *data, + struct ext_image_copy_capture_session_v1 *ext_image_copy_capture_session_v1, + struct wl_array *device) { + +} + + +void dmabuf_format(void *data, + struct ext_image_copy_capture_session_v1 *ext_image_copy_capture_session_v1, + uint32_t format, + struct wl_array *modifiers) { + +} + +void done(void *data, + struct ext_image_copy_capture_session_v1 *ext_image_copy_capture_session_v1) { + const int shm_fd = memfd_create("wl_shm data", 0); + ASSERT(shm_fd != -1, "Failed to create memfd."); + + // Format is hardcoded to chan width 4 + const uint32_t chan_width = 4; + const uint32_t stride = img_data.width * chan_width; + buf_size = img_data.height * stride; + + ASSERT(ftruncate(shm_fd, buf_size) != -1, "Failed to ftruncate the memfd."); + + shm_data = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + ASSERT(shm_data != MAP_FAILED, "Failed to mmap the memfd."); + + struct wl_shm_pool* pool = wl_shm_create_pool(wl_shm, shm_fd, buf_size); + ASSERT(pool != NULL, "Failed to create the wl_shm_pool."); + + frame_buffer = wl_shm_pool_create_buffer(pool, 0, img_data.width, img_data.height, stride, img_data.shm_format); + ASSERT(frame_buffer != NULL, "Failed to create the wl_buffer."); + + is_frame_ready = 1; +} + + +void stopped(void *data, + struct ext_image_copy_capture_session_v1 *ext_image_copy_capture_session_v1) { + +} + +const struct ext_image_copy_capture_session_v1_listener capture_listener = { + .buffer_size = buffer_size, + .shm_format = shm_format, + .dmabuf_device = dmabuf_device, + .dmabuf_format = dmabuf_format, + .done = done, + .stopped = stopped +}; + + +void transform(void *data, + struct ext_image_copy_capture_frame_v1 *ext_image_copy_capture_frame_v1, + uint32_t transform) { + +} +void damage(void *data, + struct ext_image_copy_capture_frame_v1 *ext_image_copy_capture_frame_v1, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + +} +void presentation_time(void *data, + struct ext_image_copy_capture_frame_v1 *ext_image_copy_capture_frame_v1, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec) { + +} +void ready(void *data, + struct ext_image_copy_capture_frame_v1 *ext_image_copy_capture_frame_v1) { + is_capture_ready = 1; +} +void failed(void *data, + struct ext_image_copy_capture_frame_v1 *ext_image_copy_capture_frame_v1, + uint32_t reason) { + switch(reason) { + case 0: + printf("Capture failed. Reason: Unknown Runtime Error."); + break; + case 1: + printf("Capture failed. Reason: Buffer Constraints Mismatch."); + break; + case 2: + printf("Capture failed. Reason: Session Is No Longer Available."); + break; + } +} + +const struct ext_image_copy_capture_frame_v1_listener frame_listener = { + .transform = transform, + .damage = damage, + .presentation_time = presentation_time, + .ready = ready, + .failed = failed +}; + + +int main() { + struct wl_display* dpy = wl_display_connect(NULL); + ASSERT(dpy != NULL, "Unable to connect to Wayland"); + + struct wl_registry* registry = wl_display_get_registry(dpy); + wl_registry_add_listener(registry, ®_callbacks, NULL); + wl_display_roundtrip(dpy); + + ASSERT(wl_shm != NULL, "core.wl_shm not supported."); + + ASSERT(stream_output != NULL, "No outputs found!"); + + ASSERT(output_source_manager != NULL, "ext-image-capture-source-v1.ext_output_image_capture_source_manager_v1 not supported."); + ASSERT(capture_manager != NULL, "ext-image-copy-capture-v1.ext_image_copy_capture_manager_v1 not supported."); + + struct ext_image_capture_source_v1* capture_source = + ext_output_image_capture_source_manager_v1_create_source(output_source_manager, stream_output); + ASSERT(capture_source != NULL, "Failed creating a source for the default output!") + + struct ext_image_copy_capture_session_v1* capture_session = + ext_image_copy_capture_manager_v1_create_session(capture_manager, capture_source, EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS); + ASSERT(capture_session != NULL, "Failed creating a session for the capture!"); + + ext_image_copy_capture_session_v1_add_listener(capture_session, &capture_listener, NULL); + wl_display_roundtrip(dpy); + + while(is_frame_ready == 0 && wl_display_dispatch(dpy) != -1) { + // Empty + } + + struct ext_image_copy_capture_frame_v1* frame = ext_image_copy_capture_session_v1_create_frame(capture_session); + ASSERT(frame != NULL, "Failed to create frame!"); + + ext_image_copy_capture_frame_v1_add_listener(frame, &frame_listener, NULL); + + ext_image_copy_capture_frame_v1_attach_buffer(frame, frame_buffer); + ext_image_copy_capture_frame_v1_damage_buffer(frame, 0, 0, img_data.width, img_data.height); + ext_image_copy_capture_frame_v1_capture(frame); + + wl_display_roundtrip(dpy); + + while(is_capture_ready == 0 && wl_display_dispatch(dpy) != -1) { + // Empty + } + + printf("Saving screenshot.\n"); + + // void write_rif_little(char* path, uint32_t width, uint32_t size, uint8_t format, void* data) { + // Thanks to endianess shenanigans, ABGR/XBGR is RGBA. + write_rif_little("out.rif", img_data.width, buf_size, RIF_FORMAT_R8G8B8A8, shm_data); + + ext_image_copy_capture_frame_v1_destroy(frame); + + return 0; +} diff --git a/rif.h b/rif.h new file mode 100644 index 0000000..8999986 --- /dev/null +++ b/rif.h @@ -0,0 +1,26 @@ +typedef struct rif { + // "lifV0001" + // "rifV0001" + char magic_num[8]; // Big Endian. UTF-8 Encoded. 6C 69 66 56 30 30 30 31 + uint64_t width; // Big Endian + uint8_t format; + uint8_t data[]; +} rif_t; + +#define RIF_MAGIC_LITTLE {'l', 'i', 'f', 'V', '0', '0', '0', '1'} +#define RIF_MAGIC_BIG {'r', 'i', 'f', 'V', '0', '0', '0', '1'} + +#define RIF_FORMAT_R8G8B8 0 +#define RIF_FORMAT_R8G8B8A8 1 + +void write_rif_little(char* path, uint32_t width, uint32_t size, uint8_t format, void* data) { +#define IMG_SIZE 17 + size // 17 is the size of raw_img excluding the data field + rif_t* img = malloc(IMG_SIZE); + char magic[] = RIF_MAGIC_LITTLE; + memcpy(((char*)img)+0, magic, 8); + img->width = width; + img->format = format; + memcpy(((char*)img)+17, data, size); + FILE* output_file = fopen(path, "w"); + fwrite(img, 1, IMG_SIZE, output_file); +}