mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-12-22 09:49:49 +01:00
Add thread for wlr screensharing
This commit is contained in:
parent
3058987873
commit
2a31d2d922
17 changed files with 1345 additions and 8 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -50,3 +50,6 @@ modules.order
|
|||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# build folder
|
||||
build/
|
||||
|
|
18
include/pipewire_screencast.h
Normal file
18
include/pipewire_screencast.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#ifndef PIPEWIRE_SCREENCAST_H
|
||||
#define PIPEWIRE_SCREENCAST_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/param/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <spa/support/type-map.h>
|
||||
#include "wlr_screencast.h"
|
||||
#include "screencast_common.h"
|
||||
|
||||
#define BUFFERS 1
|
||||
#define ALIGN 16
|
||||
|
||||
void *pwr_start(void *data);
|
||||
|
||||
#endif
|
18
include/screencast.h
Normal file
18
include/screencast.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#ifndef SCREENCAST_H
|
||||
#define SCREENCAST_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/mman.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "pipewire_screencast.h"
|
||||
#include "screencast_common.h"
|
||||
#include "wlr_screencast.h"
|
||||
#include "xdpw.h"
|
||||
|
||||
#endif
|
107
include/screencast_common.h
Normal file
107
include/screencast_common.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
#ifndef SCREENCAST_COMMON_H
|
||||
#define SCREENCAST_COMMON_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
// Disableable logger
|
||||
//#define logger(...) printf(__VA_ARGS__)
|
||||
#define logger(...)
|
||||
|
||||
struct damage {
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct simple_frame {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t size;
|
||||
uint32_t stride;
|
||||
bool y_invert;
|
||||
uint64_t tv_sec;
|
||||
uint32_t tv_nsec;
|
||||
enum wl_shm_format format;
|
||||
struct damage *damage;
|
||||
|
||||
struct wl_buffer *buffer;
|
||||
void *data;
|
||||
};
|
||||
|
||||
struct pwr_type {
|
||||
struct spa_type_media_type media_type;
|
||||
struct spa_type_media_subtype media_subtype;
|
||||
struct spa_type_format_video format_video;
|
||||
struct spa_type_video_format video_format;
|
||||
uint32_t meta_cursor;
|
||||
};
|
||||
|
||||
struct screencast_context {
|
||||
|
||||
// pipewire
|
||||
struct pwr_type type;
|
||||
|
||||
struct pw_main_loop *loop;
|
||||
struct spa_source *event;
|
||||
struct pw_core *core;
|
||||
struct pw_type *t;
|
||||
struct pw_remote *remote;
|
||||
struct spa_hook remote_listener;
|
||||
struct pw_stream *stream;
|
||||
struct spa_hook stream_listener;
|
||||
struct spa_video_info_raw pwr_format;
|
||||
uint32_t seq;
|
||||
uint32_t node_id;
|
||||
|
||||
bool stream_state;
|
||||
|
||||
pthread_t pwr_thread;
|
||||
|
||||
// wlroots
|
||||
|
||||
struct wl_display *display;
|
||||
struct wl_list output_list;
|
||||
struct wl_registry *registry;
|
||||
struct zwlr_screencopy_manager_v1 *screencopy_manager;
|
||||
struct wl_shm *shm;
|
||||
|
||||
// main frame callback
|
||||
struct zwlr_screencopy_frame_v1 *frame_callback;
|
||||
|
||||
// target output
|
||||
struct wayland_output *target_output;
|
||||
uint32_t framerate;
|
||||
bool with_cursor;
|
||||
|
||||
// frame
|
||||
struct zwlr_screencopy_frame_v1 *wlr_frame;
|
||||
struct simple_frame simple_frame;
|
||||
|
||||
// frame mutex
|
||||
pthread_mutex_t lock;
|
||||
|
||||
// if something happens during capture
|
||||
int err;
|
||||
bool quit;
|
||||
};
|
||||
|
||||
struct wayland_output {
|
||||
struct wl_list link;
|
||||
uint32_t id;
|
||||
struct wl_output *output;
|
||||
char *make;
|
||||
char *model;
|
||||
int width;
|
||||
int height;
|
||||
float framerate;
|
||||
};
|
||||
|
||||
uint32_t pipewire_from_wl_shm(void *data);
|
||||
char *strdup(const char *src);
|
||||
|
||||
#endif /* SCREENCAST_COMMON_H */
|
29
include/wlr_screencast.h
Normal file
29
include/wlr_screencast.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#ifndef WLR_SCREENCAST_H
|
||||
#define WLR_SCREENCAST_H
|
||||
|
||||
#include "wlr-screencopy-unstable-v1-client-protocol.h"
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <png.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
#include "screencast_common.h"
|
||||
|
||||
#define SC_MANAGER_VERSION 2
|
||||
|
||||
void wlr_frame_free(struct screencast_context *ctx);
|
||||
int wlr_screencopy_init(struct screencast_context *ctx);
|
||||
void wlr_screencopy_uninit(struct screencast_context *ctx);
|
||||
struct wayland_output *wlr_find_output(struct screencast_context *ctx,
|
||||
struct wl_output *out, uint32_t id);
|
||||
void wlr_register_cb(struct screencast_context *ctx);
|
||||
|
||||
#endif
|
|
@ -19,6 +19,7 @@ enum {
|
|||
};
|
||||
|
||||
int init_screenshot(sd_bus *bus);
|
||||
int init_screencast(sd_bus *bus);
|
||||
|
||||
struct xdpw_request *request_create(sd_bus *bus, const char *object_path);
|
||||
void request_destroy(struct xdpw_request *req);
|
||||
|
|
30
meson.build
30
meson.build
|
@ -3,11 +3,12 @@ project(
|
|||
'c',
|
||||
version: '0.0.0',
|
||||
license: 'MIT',
|
||||
meson_version: '>=0.43.0',
|
||||
meson_version: '>=0.46.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=2',
|
||||
'werror=true',
|
||||
'debug=true',
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -19,27 +20,40 @@ add_project_arguments(cc.get_supported_arguments([
|
|||
'-Wno-unused-parameter',
|
||||
]), language: 'c')
|
||||
|
||||
xdpw_inc = include_directories('include')
|
||||
inc = include_directories('include')
|
||||
|
||||
threads = dependency('threads')
|
||||
rt = cc.find_library('rt')
|
||||
pipewire = dependency('libpipewire-0.2')
|
||||
spa = dependency('libspa-0.1')
|
||||
wayland_client = dependency('wayland-client')
|
||||
wayland_protos = dependency('wayland-protocols', version: '>=1.14')
|
||||
systemd = dependency('libsystemd')
|
||||
|
||||
# subdir('protocol')
|
||||
subdir('protocols')
|
||||
|
||||
executable(
|
||||
'xdg-desktop-portal-wlr',
|
||||
files([
|
||||
'main.c',
|
||||
'request.c',
|
||||
'session.c',
|
||||
'screenshot.c',
|
||||
'src/core/main.c',
|
||||
'src/core/request.c',
|
||||
'src/core/session.c',
|
||||
'src/screenshot/screenshot.c',
|
||||
'src/screencast/screencast.c',
|
||||
'src/screencast/screencast_common.c',
|
||||
'src/screencast/wlr_screencast.c',
|
||||
'src/screencast/pipewire_screencast.c',
|
||||
]),
|
||||
dependencies: [
|
||||
wayland_client,
|
||||
wlr_protos,
|
||||
systemd,
|
||||
pipewire,
|
||||
spa,
|
||||
threads,
|
||||
rt
|
||||
],
|
||||
include_directories: [xdpw_inc],
|
||||
include_directories: [inc],
|
||||
install: true,
|
||||
)
|
||||
|
||||
|
|
44
protocols/meson.build
Normal file
44
protocols/meson.build
Normal file
|
@ -0,0 +1,44 @@
|
|||
wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true)
|
||||
if wayland_scanner_dep.found()
|
||||
wayland_scanner = find_program(
|
||||
wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'),
|
||||
native: true,
|
||||
)
|
||||
else
|
||||
wayland_scanner = find_program('wayland-scanner', native: true)
|
||||
endif
|
||||
|
||||
xml='wlr-screencopy-unstable-v1.xml'
|
||||
wl_protos_src = []
|
||||
wl_protos_headers = []
|
||||
|
||||
wl_protos_src += custom_target(
|
||||
xml.underscorify() + '_server_c',
|
||||
input: xml,
|
||||
output: '@BASENAME@-protocol.c',
|
||||
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
|
||||
)
|
||||
wl_protos_headers += custom_target(
|
||||
xml.underscorify() + '_server_h',
|
||||
input: xml,
|
||||
output: '@BASENAME@-protocol.h',
|
||||
command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'],
|
||||
)
|
||||
|
||||
wl_protos_headers += custom_target(
|
||||
xml.underscorify() + '_client_h',
|
||||
input: xml,
|
||||
output: '@BASENAME@-client-protocol.h',
|
||||
command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
|
||||
)
|
||||
|
||||
lib_wl_protos = static_library(
|
||||
'wl_protos',
|
||||
wl_protos_src + wl_protos_headers,
|
||||
dependencies: wayland_client.partial_dependency(compile_args: true),
|
||||
)
|
||||
|
||||
wlr_protos = declare_dependency(
|
||||
link_with: lib_wl_protos,
|
||||
sources: wl_protos_headers,
|
||||
)
|
207
protocols/wlr-screencopy-unstable-v1.xml
Normal file
207
protocols/wlr-screencopy-unstable-v1.xml
Normal file
|
@ -0,0 +1,207 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_screencopy_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2018 Simon Ser
|
||||
Copyright © 2019 Andri Yngvason
|
||||
|
||||
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.
|
||||
</copyright>
|
||||
|
||||
<description summary="screen content capturing on client buffers">
|
||||
This protocol allows clients to ask the compositor to copy part of the
|
||||
screen content to a client buffer.
|
||||
|
||||
Warning! The protocol described in this file is experimental and
|
||||
backward incompatible changes may be made. Backward compatible changes
|
||||
may be added together with the corresponding interface version bump.
|
||||
Backward incompatible changes are done by bumping the version number in
|
||||
the protocol and interface names and resetting the interface version.
|
||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
version number in the protocol and interface names are removed and the
|
||||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_screencopy_manager_v1" version="2">
|
||||
<description summary="manager to inform clients and begin capturing">
|
||||
This object is a manager which offers requests to start capturing from a
|
||||
source.
|
||||
</description>
|
||||
|
||||
<request name="capture_output">
|
||||
<description summary="capture an output">
|
||||
Capture the next frame of an entire output.
|
||||
</description>
|
||||
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
|
||||
<arg name="overlay_cursor" type="int"
|
||||
summary="composite cursor onto the frame"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</request>
|
||||
|
||||
<request name="capture_output_region">
|
||||
<description summary="capture an output's region">
|
||||
Capture the next frame of an output's region.
|
||||
|
||||
The region is given in output logical coordinates, see
|
||||
xdg_output.logical_size. The region will be clipped to the output's
|
||||
extents.
|
||||
</description>
|
||||
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
|
||||
<arg name="overlay_cursor" type="int"
|
||||
summary="composite cursor onto the frame"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
<arg name="x" type="int"/>
|
||||
<arg name="y" type="int"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the manager">
|
||||
All objects created by the manager will still remain valid, until their
|
||||
appropriate destroy request has been called.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_screencopy_frame_v1" version="2">
|
||||
<description summary="a frame ready for copy">
|
||||
This object represents a single frame.
|
||||
|
||||
When created, a "buffer" event will be sent. The client will then be able
|
||||
to send a "copy" request. If the capture is successful, the compositor
|
||||
will send a "flags" followed by a "ready" event.
|
||||
|
||||
If the capture failed, the "failed" event is sent. This can happen anytime
|
||||
before the "ready" event.
|
||||
|
||||
Once either a "ready" or a "failed" event is received, the client should
|
||||
destroy the frame.
|
||||
</description>
|
||||
|
||||
<event name="buffer">
|
||||
<description summary="buffer information">
|
||||
Provides information about the frame's buffer. This event is sent once
|
||||
as soon as the frame is created.
|
||||
|
||||
The client should then create a buffer with the provided attributes, and
|
||||
send a "copy" request.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="buffer format"/>
|
||||
<arg name="width" type="uint" summary="buffer width"/>
|
||||
<arg name="height" type="uint" summary="buffer height"/>
|
||||
<arg name="stride" type="uint" summary="buffer stride"/>
|
||||
</event>
|
||||
|
||||
<request name="copy">
|
||||
<description summary="copy the frame">
|
||||
Copy the frame to the supplied buffer. The buffer must have a the
|
||||
correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to
|
||||
have a supported format.
|
||||
|
||||
If the frame is successfully copied, a "flags" and a "ready" events are
|
||||
sent. Otherwise, a "failed" event is sent.
|
||||
</description>
|
||||
<arg name="buffer" type="object" interface="wl_buffer"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_used" value="0"
|
||||
summary="the object has already been used to copy a wl_buffer"/>
|
||||
<entry name="invalid_buffer" value="1"
|
||||
summary="buffer attributes are invalid"/>
|
||||
</enum>
|
||||
|
||||
<enum name="flags" bitfield="true">
|
||||
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
|
||||
</enum>
|
||||
|
||||
<event name="flags">
|
||||
<description summary="frame flags">
|
||||
Provides flags about the frame. This event is sent once before the
|
||||
"ready" event.
|
||||
</description>
|
||||
<arg name="flags" type="uint" enum="flags" summary="frame flags"/>
|
||||
</event>
|
||||
|
||||
<event name="ready">
|
||||
<description summary="indicates frame is available for reading">
|
||||
Called as soon as the frame is copied, indicating it is available
|
||||
for reading. This event includes the time at which presentation happened
|
||||
at.
|
||||
|
||||
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]. The seconds part
|
||||
may have an arbitrary offset at start.
|
||||
|
||||
After receiving this event, the client should destroy the object.
|
||||
</description>
|
||||
<arg name="tv_sec_hi" type="uint"
|
||||
summary="high 32 bits of the seconds part of the timestamp"/>
|
||||
<arg name="tv_sec_lo" type="uint"
|
||||
summary="low 32 bits of the seconds part of the timestamp"/>
|
||||
<arg name="tv_nsec" type="uint"
|
||||
summary="nanoseconds part of the timestamp"/>
|
||||
</event>
|
||||
|
||||
<event name="failed">
|
||||
<description summary="frame copy failed">
|
||||
This event indicates that the attempted frame copy has failed.
|
||||
|
||||
After receiving this event, the client should destroy the object.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="delete this object, used or not">
|
||||
Destroys the frame. This request can be sent at any time by the client.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
<request name="copy_with_damage" since="2">
|
||||
<description summary="copy the frame when it's damaged">
|
||||
Same as copy, except it waits until there is damage to copy.
|
||||
</description>
|
||||
<arg name="buffer" type="object" interface="wl_buffer"/>
|
||||
</request>
|
||||
|
||||
<event name="damage" since="2">
|
||||
<description summary="carries the coordinates of the damaged region">
|
||||
This event is sent right before the ready event when copy_with_damage is
|
||||
requested. It may be generated multiple times for each copy_with_damage
|
||||
request.
|
||||
|
||||
The arguments describe a box around an area that has changed since the
|
||||
last copy request that was derived from the current screencopy manager
|
||||
instance.
|
||||
|
||||
The union of all regions received between the call to copy_with_damage
|
||||
and a ready event is the total damage since the prior ready event.
|
||||
</description>
|
||||
<arg name="x" type="uint" summary="damaged x coordinates"/>
|
||||
<arg name="y" type="uint" summary="damaged y coordinates"/>
|
||||
<arg name="width" type="uint" summary="current width"/>
|
||||
<arg name="height" type="uint" summary="current height"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
|
@ -15,6 +15,7 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
init_screenshot(bus);
|
||||
init_screencast(bus);
|
||||
|
||||
ret = sd_bus_request_name(bus, service_name, 0);
|
||||
if (ret < 0) {
|
|
@ -8,9 +8,25 @@ static const char interface_name[] = "org.freedesktop.impl.portal.Request";
|
|||
|
||||
static int method_close(sd_bus_message *msg, void *data,
|
||||
sd_bus_error *ret_error) {
|
||||
|
||||
int ret = 0;
|
||||
// struct xdpw_request *req = data;
|
||||
// TODO
|
||||
printf("Request.Close\n");
|
||||
|
||||
sd_bus_message *reply = NULL;
|
||||
ret = sd_bus_message_new_method_return(msg, &reply);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sd_bus_send(NULL, reply, NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sd_bus_message_unref(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -8,9 +8,25 @@ static const char interface_name[] = "org.freedesktop.impl.portal.Session";
|
|||
|
||||
static int method_close(sd_bus_message *msg, void *data,
|
||||
sd_bus_error *ret_error) {
|
||||
|
||||
int ret = 0;
|
||||
// struct xdpw_session *session = data;
|
||||
// TODO
|
||||
printf("Session.Close\n");
|
||||
|
||||
sd_bus_message *reply = NULL;
|
||||
ret = sd_bus_message_new_method_return(msg, &reply);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sd_bus_send(NULL, reply, NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sd_bus_message_unref(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
227
src/screencast/pipewire_screencast.c
Normal file
227
src/screencast/pipewire_screencast.c
Normal file
|
@ -0,0 +1,227 @@
|
|||
#include "pipewire_screencast.h"
|
||||
|
||||
static inline void init_type(struct pwr_type *type, struct pw_type *map) {
|
||||
pw_type_get(map, SPA_TYPE__MediaType, &type->media_type);
|
||||
pw_type_get(map, SPA_TYPE__MediaSubtype, &type->media_subtype);
|
||||
pw_type_get(map, SPA_TYPE_FORMAT__Video, &type->format_video);
|
||||
pw_type_get(map, SPA_TYPE__VideoFormat, &type->video_format);
|
||||
pw_type_get(map, SPA_TYPE_META__Cursor, &type->meta_cursor);
|
||||
};
|
||||
|
||||
static void writeFrameData(void *pwFramePointer, void *wlrFramePointer,
|
||||
uint32_t height, uint32_t stride, bool inverted) {
|
||||
|
||||
if (!inverted) {
|
||||
memcpy(pwFramePointer, wlrFramePointer, height * stride);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (size_t)height; ++i) {
|
||||
void *flippedWlrRowPointer = wlrFramePointer + ((height - i - 1) * stride);
|
||||
void *pwRowPointer = pwFramePointer + (i * stride);
|
||||
memcpy(pwRowPointer, flippedWlrRowPointer, stride);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void pwr_on_event(void *data, uint64_t expirations) {
|
||||
struct screencast_context *ctx = data;
|
||||
struct pw_buffer *pw_buf;
|
||||
struct spa_buffer *spa_buf;
|
||||
struct spa_meta_header *h;
|
||||
struct spa_data *d;
|
||||
|
||||
if (!ctx->stream_state) {
|
||||
wlr_frame_free(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
logger("pw event fired\n");
|
||||
|
||||
if ((pw_buf = pw_stream_dequeue_buffer(ctx->stream)) == NULL) {
|
||||
printf("out of buffers\n");
|
||||
return;
|
||||
}
|
||||
|
||||
spa_buf = pw_buf->buffer;
|
||||
d = spa_buf->datas;
|
||||
if ((d[0].data) == NULL)
|
||||
return;
|
||||
if ((h = spa_buffer_find_meta(spa_buf, ctx->t->meta.Header))) {
|
||||
h->pts = -1;
|
||||
h->flags = 0;
|
||||
h->seq = ctx->seq++;
|
||||
h->dts_offset = 0;
|
||||
}
|
||||
|
||||
d[0].type = ctx->t->data.MemPtr;
|
||||
d[0].maxsize = ctx->simple_frame.size;
|
||||
// d[0].data = ctx->simple_frame.data;
|
||||
d[0].mapoffset = 0;
|
||||
d[0].chunk->size = ctx->simple_frame.size;
|
||||
d[0].chunk->stride = ctx->simple_frame.stride;
|
||||
d[0].chunk->offset = 0;
|
||||
d[0].flags = 0;
|
||||
d[0].fd = -1;
|
||||
|
||||
writeFrameData(d[0].data, ctx->simple_frame.data, ctx->simple_frame.height,
|
||||
ctx->simple_frame.stride, ctx->simple_frame.y_invert);
|
||||
|
||||
logger("************** \n");
|
||||
logger("pointer: %p\n", d[0].data);
|
||||
logger("size: %d\n", d[0].maxsize);
|
||||
logger("stride: %d\n", d[0].chunk->stride);
|
||||
logger("width: %d\n", ctx->simple_frame.width);
|
||||
logger("height: %d\n", ctx->simple_frame.height);
|
||||
logger("y_invert: %d\n", ctx->simple_frame.y_invert);
|
||||
logger("************** \n");
|
||||
|
||||
pw_stream_queue_buffer(ctx->stream, pw_buf);
|
||||
|
||||
wlr_frame_free(ctx);
|
||||
}
|
||||
|
||||
static void pwr_handle_stream_state_changed(void *data,
|
||||
enum pw_stream_state old,
|
||||
enum pw_stream_state state,
|
||||
const char *error) {
|
||||
struct screencast_context *ctx = data;
|
||||
ctx->node_id = pw_stream_get_node_id(ctx->stream);
|
||||
|
||||
printf("stream state: \"%s\"\n", pw_stream_state_as_string(state));
|
||||
printf("node id: %d\n", ctx->node_id);
|
||||
|
||||
switch (state) {
|
||||
case PW_STREAM_STATE_PAUSED:
|
||||
ctx->stream_state = false;
|
||||
break;
|
||||
case PW_STREAM_STATE_STREAMING:
|
||||
ctx->stream_state = true;
|
||||
break;
|
||||
default:
|
||||
ctx->stream_state = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pwr_handle_stream_format_changed(void *data,
|
||||
const struct spa_pod *format) {
|
||||
struct screencast_context *ctx = data;
|
||||
struct pw_stream *stream = ctx->stream;
|
||||
struct pw_type *t = ctx->t;
|
||||
uint8_t params_buffer[1024];
|
||||
struct spa_pod_builder b =
|
||||
SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
|
||||
const struct spa_pod *params[2];
|
||||
|
||||
if (format == NULL) {
|
||||
pw_stream_finish_format(stream, 0, NULL, 0);
|
||||
return;
|
||||
}
|
||||
spa_format_video_raw_parse(format, &ctx->pwr_format, &ctx->type.format_video);
|
||||
|
||||
params[0] = spa_pod_builder_object(
|
||||
&b, t->param.idBuffers, t->param_buffers.Buffers, ":",
|
||||
t->param_buffers.size, "i", ctx->simple_frame.size, ":",
|
||||
t->param_buffers.stride, "i", ctx->simple_frame.stride, ":",
|
||||
t->param_buffers.buffers, "iru", BUFFERS, SPA_POD_PROP_MIN_MAX(1, 32),
|
||||
":", t->param_buffers.align, "i", ALIGN);
|
||||
|
||||
params[1] = spa_pod_builder_object(&b, t->param.idMeta, t->param_meta.Meta,
|
||||
":", t->param_meta.type, "I",
|
||||
t->meta.Header, ":", t->param_meta.size,
|
||||
"i", sizeof(struct spa_meta_header));
|
||||
|
||||
pw_stream_finish_format(stream, 0, params, 2);
|
||||
}
|
||||
|
||||
static const struct pw_stream_events pwr_stream_events = {
|
||||
PW_VERSION_STREAM_EVENTS,
|
||||
.state_changed = pwr_handle_stream_state_changed,
|
||||
.format_changed = pwr_handle_stream_format_changed,
|
||||
};
|
||||
|
||||
static void pwr_handle_state_changed(void *data, enum pw_remote_state old,
|
||||
enum pw_remote_state state,
|
||||
const char *error) {
|
||||
struct screencast_context *ctx = data;
|
||||
struct pw_remote *remote = ctx->remote;
|
||||
|
||||
switch (state) {
|
||||
case PW_REMOTE_STATE_ERROR:
|
||||
printf("remote error: %s\n", error);
|
||||
pw_main_loop_quit(ctx->loop);
|
||||
break;
|
||||
|
||||
case PW_REMOTE_STATE_CONNECTED: {
|
||||
const struct spa_pod *params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
|
||||
logger("remote state: \"%s\"\n", pw_remote_state_as_string(state));
|
||||
|
||||
ctx->stream = pw_stream_new(
|
||||
remote, "wlr_screeencopy",
|
||||
pw_properties_new("media.class", "Video/Source", PW_NODE_PROP_MEDIA,
|
||||
"Video", PW_NODE_PROP_CATEGORY, "Source",
|
||||
PW_NODE_PROP_ROLE, "Screen", NULL));
|
||||
|
||||
params[0] = spa_pod_builder_object(
|
||||
&b, ctx->t->param.idEnumFormat, ctx->t->spa_format, "I",
|
||||
ctx->type.media_type.video, "I", ctx->type.media_subtype.raw, ":",
|
||||
ctx->type.format_video.format, "I", pipewire_from_wl_shm(ctx), ":",
|
||||
ctx->type.format_video.size, "Rru",
|
||||
&SPA_RECTANGLE(ctx->simple_frame.width, ctx->simple_frame.height),
|
||||
SPA_POD_PROP_MIN_MAX(&SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)),
|
||||
":", ctx->type.format_video.framerate, "F",
|
||||
&SPA_FRACTION(ctx->framerate, 1));
|
||||
|
||||
pw_stream_add_listener(ctx->stream, &ctx->stream_listener,
|
||||
&pwr_stream_events, ctx);
|
||||
|
||||
pw_stream_connect(ctx->stream, PW_DIRECTION_OUTPUT, NULL,
|
||||
PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS,
|
||||
params, 1);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
logger("remote state: \"%s\"\n", pw_remote_state_as_string(state));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct pw_remote_events pwr_remote_events = {
|
||||
PW_VERSION_REMOTE_EVENTS,
|
||||
.state_changed = pwr_handle_state_changed,
|
||||
};
|
||||
|
||||
void *pwr_start(void *data) {
|
||||
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
pw_init(NULL, NULL);
|
||||
|
||||
/* create a main loop */
|
||||
ctx->loop = pw_main_loop_new(NULL);
|
||||
ctx->core = pw_core_new(pw_main_loop_get_loop(ctx->loop), NULL);
|
||||
ctx->t = pw_core_get_type(ctx->core);
|
||||
ctx->remote = pw_remote_new(ctx->core, NULL, 0);
|
||||
|
||||
init_type(&ctx->type, ctx->t);
|
||||
|
||||
/* make an event to signal frame ready */
|
||||
ctx->event =
|
||||
pw_loop_add_event(pw_main_loop_get_loop(ctx->loop), pwr_on_event, ctx);
|
||||
|
||||
pw_remote_add_listener(ctx->remote, &ctx->remote_listener, &pwr_remote_events,
|
||||
ctx);
|
||||
pw_remote_connect(ctx->remote);
|
||||
|
||||
/* run the loop, this will trigger the callbacks */
|
||||
pw_main_loop_run(ctx->loop);
|
||||
|
||||
pw_core_destroy(ctx->core);
|
||||
pw_main_loop_destroy(ctx->loop);
|
||||
return NULL;
|
||||
}
|
323
src/screencast/screencast.c
Normal file
323
src/screencast/screencast.c
Normal file
|
@ -0,0 +1,323 @@
|
|||
#include "screencast.h"
|
||||
|
||||
static struct screencast_context ctx;
|
||||
|
||||
static const char object_path[] = "/org/freedesktop/portal/desktop";
|
||||
static const char interface_name[] = "org.freedesktop.impl.portal.ScreenCast";
|
||||
|
||||
int setup_outputs(struct screencast_context *ctx) {
|
||||
|
||||
int output_id;
|
||||
|
||||
struct wayland_output *output, *tmp_o;
|
||||
wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) {
|
||||
printf("Capturable output: %s Model: %s: ID: %i\n", output->make,
|
||||
output->model, output->id);
|
||||
output_id = output->id;
|
||||
}
|
||||
|
||||
output = wlr_find_output(ctx, NULL, output_id);
|
||||
if (!output) {
|
||||
printf("Unable to find output with ID %i!\n", output_id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ctx->target_output = output;
|
||||
ctx->framerate = output->framerate;
|
||||
ctx->with_cursor = true;
|
||||
|
||||
printf("wl_display fd: %d\n", wl_display_get_fd(ctx->display));
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void *start_screencast(void *data){
|
||||
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
pthread_mutex_init(&ctx->lock, NULL);
|
||||
|
||||
wlr_register_cb(ctx);
|
||||
|
||||
pthread_create(&ctx->pwr_thread, NULL, pwr_start, ctx);
|
||||
|
||||
/* Run capture */
|
||||
while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && !ctx->quit);
|
||||
|
||||
pthread_join(ctx->pwr_thread, NULL);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
static int method_screencast_create_session(sd_bus_message *msg, void *data,
|
||||
sd_bus_error *ret_error) {
|
||||
int ret = 0;
|
||||
|
||||
printf("=== CREATE SESSION\n");
|
||||
|
||||
char *request_handle, *session_handle, *app_id;
|
||||
ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sd_bus_message_enter_container(msg, 'a', "{sv}");
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("request_handle: %s\n", request_handle);
|
||||
printf("session_handle: %s\n", session_handle);
|
||||
printf("app_id: %s\n", app_id);
|
||||
|
||||
char* key;
|
||||
int innerRet = 0;
|
||||
while((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) {
|
||||
innerRet = sd_bus_message_read(msg, "s", &key);
|
||||
if (innerRet < 0) {
|
||||
return innerRet;
|
||||
}
|
||||
|
||||
if(strcmp(key, "session_handle_token") == 0) {
|
||||
char* token;
|
||||
sd_bus_message_read(msg, "v", "s", &token);
|
||||
printf("Option token = %s\n", token);
|
||||
} else {
|
||||
printf("Unknown option %s\n", key);
|
||||
sd_bus_message_skip(msg, "v");
|
||||
}
|
||||
|
||||
innerRet = sd_bus_message_exit_container(msg);
|
||||
if (innerRet < 0) {
|
||||
return innerRet;
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sd_bus_message_exit_container(msg);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: cleanup this
|
||||
struct xdpw_request *req =
|
||||
request_create(sd_bus_message_get_bus(msg), request_handle);
|
||||
if (req == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
// TODO: cleanup this
|
||||
struct xdpw_session *sess =
|
||||
session_create(sd_bus_message_get_bus(msg), session_handle);
|
||||
if (sess == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sd_bus_message *reply = NULL;
|
||||
ret = sd_bus_message_new_method_return(msg, &reply);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = sd_bus_send(NULL, reply, NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sd_bus_message_unref(reply);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int method_screencast_select_sources(sd_bus_message *msg, void *data, sd_bus_error *ret_error) {
|
||||
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
printf("=== SELECT SOURCES\n");
|
||||
|
||||
setup_outputs(ctx);
|
||||
|
||||
char *request_handle, *session_handle, *app_id;
|
||||
ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
sd_bus_message_enter_container(msg, 'a', "{sv}");
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("request_handle: %s\n", request_handle);
|
||||
printf("session_handle: %s\n", session_handle);
|
||||
printf("app_id: %s\n", app_id);
|
||||
|
||||
char* key;
|
||||
int innerRet = 0;
|
||||
while((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) {
|
||||
innerRet = sd_bus_message_read(msg, "s", &key);
|
||||
if (innerRet < 0) {
|
||||
return innerRet;
|
||||
}
|
||||
|
||||
if(strcmp(key, "multiple") == 0) {
|
||||
bool multiple;
|
||||
sd_bus_message_read(msg, "v", "b", &multiple);
|
||||
printf("Option multiple, val %x\n", multiple);
|
||||
} else if(strcmp(key, "types") == 0) {
|
||||
uint32_t mask;
|
||||
sd_bus_message_read(msg, "v", "u", &mask);
|
||||
printf("Option types, mask %x\n", mask);
|
||||
} else {
|
||||
printf("Unknown option %s\n", key);
|
||||
sd_bus_message_skip(msg, "v");
|
||||
}
|
||||
|
||||
innerRet = sd_bus_message_exit_container(msg);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = sd_bus_message_exit_container(msg);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
sd_bus_message *reply = NULL;
|
||||
ret = sd_bus_message_new_method_return(msg, &reply);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = sd_bus_send(NULL, reply, NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sd_bus_message_unref(reply);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int method_screencast_start(sd_bus_message *msg, void *data, sd_bus_error *ret_error) {
|
||||
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
printf("=== START\n");
|
||||
|
||||
pthread_t screencast_thread;
|
||||
pthread_create(&screencast_thread, NULL, start_screencast, ctx);
|
||||
|
||||
char *request_handle, *session_handle, *app_id, *parent_window;
|
||||
ret = sd_bus_message_read(msg, "ooss", &request_handle, &session_handle, &app_id, &parent_window);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = sd_bus_message_enter_container(msg, 'a', "{sv}");
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("request_handle: %s\n", request_handle);
|
||||
printf("session_handle: %s\n", session_handle);
|
||||
printf("app_id: %s\n", app_id);
|
||||
printf("parent_window: %s\n", parent_window);
|
||||
|
||||
char* key;
|
||||
int innerRet = 0;
|
||||
while((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) {
|
||||
innerRet = sd_bus_message_read(msg, "s", &key);
|
||||
if (innerRet < 0) {
|
||||
return innerRet;
|
||||
}
|
||||
|
||||
printf("Unknown option %s\n", key);
|
||||
sd_bus_message_skip(msg, "v");
|
||||
|
||||
innerRet = sd_bus_message_exit_container(msg);
|
||||
if (innerRet < 0) {
|
||||
return innerRet;
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = sd_bus_message_exit_container(msg);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sd_bus_message *reply = NULL;
|
||||
ret = sd_bus_message_new_method_return(msg, &reply);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
while(ctx->node_id == 0);
|
||||
|
||||
ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1,
|
||||
"streams", "a(ua{sv})", 1,
|
||||
ctx->node_id, 2,
|
||||
"position", "(ii)", 0, 0,
|
||||
"size", "(ii)", ctx->simple_frame.width, ctx->simple_frame.height);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sd_bus_send(NULL, reply, NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
sd_bus_message_unref(reply);
|
||||
|
||||
while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && !ctx->quit);
|
||||
pthread_join(screencast_thread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const sd_bus_vtable screencast_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
SD_BUS_METHOD("CreateSession", "oosa{sv}", "ua{sv}", method_screencast_create_session, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("SelectSources", "oosa{sv}", "ua{sv}", method_screencast_select_sources, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("Start", "oossa{sv}", "ua{sv}", method_screencast_start, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_VTABLE_END
|
||||
};
|
||||
|
||||
int init_screencast(sd_bus *bus) {
|
||||
// TODO: cleanup
|
||||
sd_bus_slot *slot = NULL;
|
||||
|
||||
//struct screencast_context ctx = (struct screencast_context){0};
|
||||
ctx.simple_frame = (struct simple_frame){0};
|
||||
ctx.simple_frame.damage = &(struct damage){0};
|
||||
|
||||
int err;
|
||||
err = wlr_screencopy_init(&ctx);
|
||||
if (err) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
return sd_bus_add_object_vtable(bus, &slot, object_path, interface_name,
|
||||
screencast_vtable, &ctx);
|
||||
|
||||
end:
|
||||
wlr_screencopy_uninit(&ctx);
|
||||
return err;
|
||||
}
|
35
src/screencast/screencast_common.c
Normal file
35
src/screencast/screencast_common.c
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "screencast_common.h"
|
||||
|
||||
char *strdup(const char *src) {
|
||||
char *dst = malloc(strlen(src) + 1); // Space for length plus nul
|
||||
if (dst == NULL)
|
||||
return NULL; // No memory
|
||||
strcpy(dst, src); // Copy the characters
|
||||
return dst; // Return the new string
|
||||
}
|
||||
|
||||
uint32_t pipewire_from_wl_shm(void *data) {
|
||||
struct screencast_context *ctx = data;
|
||||
switch (ctx->simple_frame.format) {
|
||||
case WL_SHM_FORMAT_ARGB8888:
|
||||
return ctx->type.video_format.BGRA;
|
||||
case WL_SHM_FORMAT_XRGB8888:
|
||||
return ctx->type.video_format.BGRx;
|
||||
case WL_SHM_FORMAT_RGBA8888:
|
||||
return ctx->type.video_format.ABGR;
|
||||
case WL_SHM_FORMAT_RGBX8888:
|
||||
return ctx->type.video_format.xBGR;
|
||||
case WL_SHM_FORMAT_ABGR8888:
|
||||
return ctx->type.video_format.RGBA;
|
||||
case WL_SHM_FORMAT_XBGR8888:
|
||||
return ctx->type.video_format.RGBx;
|
||||
case WL_SHM_FORMAT_BGRA8888:
|
||||
return ctx->type.video_format.ARGB;
|
||||
case WL_SHM_FORMAT_BGRX8888:
|
||||
return ctx->type.video_format.xRGB;
|
||||
case WL_SHM_FORMAT_NV12:
|
||||
return ctx->type.video_format.NV12;
|
||||
default:
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
278
src/screencast/wlr_screencast.c
Normal file
278
src/screencast/wlr_screencast.c
Normal file
|
@ -0,0 +1,278 @@
|
|||
#include "wlr_screencast.h"
|
||||
|
||||
void wlr_frame_free(struct screencast_context *ctx) {
|
||||
|
||||
zwlr_screencopy_frame_v1_destroy(ctx->wlr_frame);
|
||||
munmap(ctx->simple_frame.data, ctx->simple_frame.size);
|
||||
wl_buffer_destroy(ctx->simple_frame.buffer);
|
||||
logger("wlr frame destroyed\n");
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
}
|
||||
|
||||
static struct wl_buffer *create_shm_buffer(struct screencast_context *ctx,
|
||||
enum wl_shm_format fmt, int width,
|
||||
int height, int stride,
|
||||
void **data_out) {
|
||||
int size = stride * height;
|
||||
|
||||
const char shm_name[] = "/wlroots-screencopy";
|
||||
int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "shm_open failed\n");
|
||||
return NULL;
|
||||
}
|
||||
shm_unlink(shm_name);
|
||||
|
||||
int ret;
|
||||
while ((ret = ftruncate(fd, size)) == EINTR) {
|
||||
// No-op
|
||||
}
|
||||
if (ret < 0) {
|
||||
close(fd);
|
||||
fprintf(stderr, "ftruncate failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
fprintf(stderr, "mmap failed: %m\n");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(ctx->shm, fd, size);
|
||||
close(fd);
|
||||
struct wl_buffer *buffer =
|
||||
wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
*data_out = data;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void wlr_frame_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame,
|
||||
uint32_t format, uint32_t width, uint32_t height,
|
||||
uint32_t stride) {
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
logger("wlr buffer event handler\n");
|
||||
ctx->wlr_frame = frame;
|
||||
ctx->simple_frame.width = width;
|
||||
ctx->simple_frame.height = height;
|
||||
ctx->simple_frame.stride = stride;
|
||||
ctx->simple_frame.size = stride * height;
|
||||
ctx->simple_frame.format = format;
|
||||
ctx->simple_frame.buffer = create_shm_buffer(ctx, format, width, height,
|
||||
stride, &ctx->simple_frame.data);
|
||||
if (ctx->simple_frame.buffer == NULL) {
|
||||
fprintf(stderr, "failed to create buffer\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// zwlr_screencopy_frame_v1_copy(frame, ctx->simple_frame.buffer);
|
||||
zwlr_screencopy_frame_v1_copy_with_damage(frame, ctx->simple_frame.buffer);
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
static void wlr_frame_flags(void *data, struct zwlr_screencopy_frame_v1 *frame,
|
||||
uint32_t flags) {
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
logger("wlr flags event handler\n");
|
||||
ctx->simple_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT;
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
static void wlr_frame_ready(void *data, struct zwlr_screencopy_frame_v1 *frame,
|
||||
uint32_t tv_sec_hi, uint32_t tv_sec_lo,
|
||||
uint32_t tv_nsec) {
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
logger("wlr ready event handler\n");
|
||||
|
||||
ctx->simple_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo);
|
||||
ctx->simple_frame.tv_nsec = tv_nsec;
|
||||
|
||||
if (!ctx->quit && !ctx->err) {
|
||||
pw_loop_signal_event(pw_main_loop_get_loop(ctx->loop), ctx->event);
|
||||
// sleep(1);
|
||||
wlr_register_cb(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void wlr_frame_failed(void *data,
|
||||
struct zwlr_screencopy_frame_v1 *frame) {
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
logger("wlr failed event handler\n");
|
||||
|
||||
wlr_frame_free(ctx);
|
||||
ctx->err = true;
|
||||
}
|
||||
|
||||
static void wlr_frame_damage(void *data, struct zwlr_screencopy_frame_v1 *frame,
|
||||
uint32_t x, uint32_t y, uint32_t width,
|
||||
uint32_t height) {
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
ctx->simple_frame.damage->x = x;
|
||||
ctx->simple_frame.damage->y = y;
|
||||
ctx->simple_frame.damage->width = width;
|
||||
ctx->simple_frame.damage->height = height;
|
||||
}
|
||||
|
||||
static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = {
|
||||
.buffer = wlr_frame_buffer,
|
||||
.flags = wlr_frame_flags,
|
||||
.ready = wlr_frame_ready,
|
||||
.failed = wlr_frame_failed,
|
||||
.damage = wlr_frame_damage,
|
||||
};
|
||||
|
||||
void wlr_register_cb(struct screencast_context *ctx) {
|
||||
ctx->frame_callback = zwlr_screencopy_manager_v1_capture_output(
|
||||
ctx->screencopy_manager, ctx->with_cursor, ctx->target_output->output);
|
||||
|
||||
zwlr_screencopy_frame_v1_add_listener(ctx->frame_callback,
|
||||
&wlr_frame_listener, ctx);
|
||||
logger("wlr callbacks registered\n");
|
||||
}
|
||||
|
||||
static void wlr_output_handle_geometry(void *data, struct wl_output *wl_output,
|
||||
int32_t x, int32_t y, int32_t phys_width,
|
||||
int32_t phys_height, int32_t subpixel,
|
||||
const char *make, const char *model,
|
||||
int32_t transform) {
|
||||
struct wayland_output *output = data;
|
||||
output->make = strdup(make);
|
||||
output->model = strdup(model);
|
||||
}
|
||||
|
||||
static void wlr_output_handle_mode(void *data, struct wl_output *wl_output,
|
||||
uint32_t flags, int32_t width,
|
||||
int32_t height, int32_t refresh) {
|
||||
if (flags & WL_OUTPUT_MODE_CURRENT) {
|
||||
struct wayland_output *output = data;
|
||||
output->framerate = (float)refresh * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
static void wlr_output_handle_done(void *data, struct wl_output *wl_output) {
|
||||
/* Nothing to do */
|
||||
}
|
||||
|
||||
static void wlr_output_handle_scale(void *data, struct wl_output *wl_output,
|
||||
int32_t factor) {
|
||||
/* Nothing to do */
|
||||
}
|
||||
|
||||
static const struct wl_output_listener wlr_output_listener = {
|
||||
.geometry = wlr_output_handle_geometry,
|
||||
.mode = wlr_output_handle_mode,
|
||||
.done = wlr_output_handle_done,
|
||||
.scale = wlr_output_handle_scale,
|
||||
};
|
||||
|
||||
struct wayland_output *wlr_find_output(struct screencast_context *ctx,
|
||||
struct wl_output *out, uint32_t id) {
|
||||
struct wayland_output *output, *tmp;
|
||||
wl_list_for_each_safe(output, tmp, &ctx->output_list, link) {
|
||||
if ((output->output == out) || (output->id == id)) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void wlr_remove_output(struct wayland_output *out) {
|
||||
wl_list_remove(&out->link);
|
||||
}
|
||||
|
||||
static void wlr_registry_handle_add(void *data, struct wl_registry *reg,
|
||||
uint32_t id, const char *interface,
|
||||
uint32_t ver) {
|
||||
struct screencast_context *ctx = data;
|
||||
|
||||
if (!strcmp(interface, wl_output_interface.name)) {
|
||||
struct wayland_output *output = malloc(sizeof(*output));
|
||||
|
||||
output->id = id;
|
||||
output->output = wl_registry_bind(reg, id, &wl_output_interface, 1);
|
||||
|
||||
wl_output_add_listener(output->output, &wlr_output_listener, output);
|
||||
wl_list_insert(&ctx->output_list, &output->link);
|
||||
}
|
||||
|
||||
if (!strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) {
|
||||
ctx->screencopy_manager = wl_registry_bind(
|
||||
reg, id, &zwlr_screencopy_manager_v1_interface, SC_MANAGER_VERSION);
|
||||
}
|
||||
|
||||
if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
ctx->shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void wlr_registry_handle_remove(void *data, struct wl_registry *reg,
|
||||
uint32_t id) {
|
||||
wlr_remove_output(
|
||||
wlr_find_output((struct screencast_context *)data, NULL, id));
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener wlr_registry_listener = {
|
||||
.global = wlr_registry_handle_add,
|
||||
.global_remove = wlr_registry_handle_remove,
|
||||
};
|
||||
|
||||
int wlr_screencopy_init(struct screencast_context *ctx) {
|
||||
// connect to wayland display WAYLAND_DISPLAY or 'wayland-0' if not set
|
||||
ctx->display = wl_display_connect(NULL);
|
||||
if (!ctx->display) {
|
||||
printf("Failed to connect to display!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// retrieve list of outputs
|
||||
wl_list_init(&ctx->output_list);
|
||||
|
||||
// retrieve registry
|
||||
ctx->registry = wl_display_get_registry(ctx->display);
|
||||
wl_registry_add_listener(ctx->registry, &wlr_registry_listener, ctx);
|
||||
|
||||
wl_display_roundtrip(ctx->display);
|
||||
wl_display_dispatch(ctx->display);
|
||||
|
||||
// make sure our wlroots supports screencopy protocol
|
||||
if (!ctx->shm) {
|
||||
printf("Compositor doesn't support %s!\n", "wl_shm");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// make sure our wlroots supports screencopy protocol
|
||||
if (!ctx->screencopy_manager) {
|
||||
printf("Compositor doesn't support %s!\n",
|
||||
zwlr_screencopy_manager_v1_interface.name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wlr_screencopy_uninit(struct screencast_context *ctx) {
|
||||
struct wayland_output *output, *tmp_o;
|
||||
wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) {
|
||||
wl_list_remove(&output->link);
|
||||
}
|
||||
|
||||
if (ctx->screencopy_manager) {
|
||||
zwlr_screencopy_manager_v1_destroy(ctx->screencopy_manager);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue