Add gamescope-pipewire protocol

Can be tested with:

https://git.sr.ht/~emersion/gamescope-pipewire-demo
This commit is contained in:
Simon Ser 2021-07-29 10:09:10 +02:00
parent 59e8aa4abc
commit ef64b60cec
6 changed files with 126 additions and 32 deletions

View file

@ -73,8 +73,6 @@ add_project_arguments(
language: 'cpp',
)
subdir('protocol')
src = [
'src/steamcompmgr.cpp',
'src/main.cpp',
@ -86,13 +84,14 @@ src = [
'src/rendervulkan.cpp',
'src/log.cpp',
spirv_shader,
gamescope_xwayland_proto_files,
]
if pipewire_dep.found()
src += 'src/pipewire.cpp'
endif
subdir('protocol')
executable(
'gamescope',
src,

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="gamescope_pipewire">
<copyright>
Copyright © 2021 Valve Corporation
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="gamescope-specific PipeWire protocol">
This is a private Gamescope protocol. Regular Wayland clients must not use
it.
</description>
<interface name="gamescope_pipewire" version="1">
<request name="destroy" type="destructor"></request>
<event name="stream_node">
<description summary="pipewire stream node advertisement">
This event advertises a PipeWire stream node identifier suitable for
capturing the main output.
A roundtrip after binding to the gamescope_pipewire global ensures this
event has been received.
</description>
<arg name="node_id" type="uint" summary="PipeWire stream node ID"/>
</event>
</interface>
</protocol>

View file

@ -2,18 +2,25 @@ wayland_scanner_dep = dependency('wayland-scanner', native: true)
wayland_scanner_path = wayland_scanner_dep.get_variable(pkgconfig: 'wayland_scanner')
wayland_scanner = find_program(wayland_scanner_path, native: true)
code = custom_target(
'gamescope-xwayland-protocol.c',
input: 'gamescope-xwayland.xml',
output: '@BASENAME@-protocol.c',
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
protocols = [
'gamescope-xwayland',
'gamescope-pipewire',
]
server_header = custom_target(
'gamescope-xwayland-protocol.h',
input: 'gamescope-xwayland.xml',
output: '@BASENAME@-protocol.h',
command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'],
)
foreach name : protocols
code = custom_target(
name + '-protocol.c',
input: name + '.xml',
output: '@BASENAME@-protocol.c',
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
gamescope_xwayland_proto_files = [code, server_header]
server_header = custom_target(
name + '-protocol.h',
input: name + '.xml',
output: '@BASENAME@-protocol.h',
command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'],
)
src += [code, server_header]
endforeach

View file

@ -11,6 +11,7 @@
#include "main.hpp"
#include "pipewire.hpp"
static struct pipewire_state pipewire_state = { .stream_node_id = SPA_ID_INVALID };
static int nudgePipe[2] = { -1, -1 };
static std::atomic<struct pipewire_buffer *> out_buffer;
@ -246,44 +247,44 @@ static void run_pipewire(struct pipewire_state *state)
bool init_pipewire(void)
{
pw_init(nullptr, nullptr);
struct pipewire_state *state = &pipewire_state;
static struct pipewire_state state = { .stream_node_id = SPA_ID_INVALID };
pw_init(nullptr, nullptr);
if (pipe2(nudgePipe, O_CLOEXEC | O_NONBLOCK) != 0) {
perror("pipewire: pipe2 failed");
return false;
}
state.loop = pw_loop_new(nullptr);
if (!state.loop) {
state->loop = pw_loop_new(nullptr);
if (!state->loop) {
fprintf(stderr, "pipewire: pw_loop_new failed\n");
return false;
}
state.context = pw_context_new(state.loop, nullptr, 0);
if (!state.context) {
state->context = pw_context_new(state->loop, nullptr, 0);
if (!state->context) {
fprintf(stderr, "pipewire: pw_context_new failed\n");
return false;
}
state.core = pw_context_connect(state.context, nullptr, 0);
if (!state.core) {
state->core = pw_context_connect(state->context, nullptr, 0);
if (!state->core) {
fprintf(stderr, "pipewire: pw_context_connect failed\n");
return false;
}
state.stream = pw_stream_new(state.core, "gamescope",
state->stream = pw_stream_new(state->core, "gamescope",
pw_properties_new(
PW_KEY_MEDIA_CLASS, "Video/Source",
nullptr));
if (!state.stream) {
if (!state->stream) {
fprintf(stderr, "pipewire: pw_stream_new failed\n");
return false;
}
static struct spa_hook stream_hook;
pw_stream_add_listener(state.stream, &stream_hook, &stream_events, &state);
pw_stream_add_listener(state->stream, &stream_hook, &stream_events, state);
uint8_t buf[1024];
struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
@ -291,28 +292,33 @@ bool init_pipewire(void)
// TODO: PW_STREAM_FLAG_ALLOC_BUFFERS
enum pw_stream_flags flags = (enum pw_stream_flags)(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS);
int ret = pw_stream_connect(state.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, flags, &format_param, 1);
int ret = pw_stream_connect(state->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, flags, &format_param, 1);
if (ret != 0) {
fprintf(stderr, "pipewire: pw_stream_connect failed\n");
return false;
}
while (state.stream_node_id == SPA_ID_INVALID) {
int ret = pw_loop_iterate(state.loop, -1);
while (state->stream_node_id == SPA_ID_INVALID) {
int ret = pw_loop_iterate(state->loop, -1);
if (ret < 0) {
fprintf(stderr, "pipewire: pw_loop_iterate failed\n");
return false;
}
}
fprintf(stderr, "pipewire: stream available on node ID: %u\n", state.stream_node_id);
fprintf(stderr, "pipewire: stream available on node ID: %u\n", state->stream_node_id);
std::thread thread(run_pipewire, &state);
std::thread thread(run_pipewire, state);
thread.detach();
return true;
}
uint32_t get_pipewire_stream_node_id(void)
{
return pipewire_state.stream_node_id;
}
struct pipewire_buffer *dequeue_pipewire_buffer(void)
{
return out_buffer.exchange(nullptr);

View file

@ -26,6 +26,7 @@ struct pipewire_buffer {
};
bool init_pipewire(void);
uint32_t get_pipewire_stream_node_id(void);
struct pipewire_buffer *dequeue_pipewire_buffer(void);
void push_pipewire_buffer(struct pipewire_buffer *buffer);
void nudge_pipewire(void);

View file

@ -39,12 +39,17 @@ extern "C" {
}
#include "gamescope-xwayland-protocol.h"
#include "gamescope-pipewire-protocol.h"
#include "wlserver.hpp"
#include "drm.hpp"
#include "main.hpp"
#include "steamcompmgr.hpp"
#if HAVE_PIPEWIRE
#include "pipewire.hpp"
#endif
#include "gpuvis_trace_utils.h"
static struct wlserver_t wlserver = {};
@ -474,6 +479,33 @@ static void create_gamescope_xwayland( void )
wl_global_create( wlserver.display, &gamescope_xwayland_interface, version, NULL, gamescope_xwayland_bind );
}
static void gamescope_pipewire_handle_destroy( struct wl_client *client, struct wl_resource *resource )
{
wl_resource_destroy( resource );
}
static const struct gamescope_pipewire_interface gamescope_pipewire_impl = {
.destroy = gamescope_pipewire_handle_destroy,
};
static void gamescope_pipewire_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id )
{
struct wl_resource *resource = wl_resource_create( client, &gamescope_pipewire_interface, version, id );
wl_resource_set_implementation( resource, &gamescope_pipewire_impl, NULL, NULL );
#if HAVE_PIPEWIRE
gamescope_pipewire_send_stream_node( resource, get_pipewire_stream_node_id() );
#else
assert( 0 ); // unreachable
#endif
}
static void create_gamescope_pipewire( void )
{
uint32_t version = 1;
wl_global_create( wlserver.display, &gamescope_pipewire_interface, version, NULL, gamescope_pipewire_bind );
}
static void handle_session_active( struct wl_listener *listener, void *data )
{
if (wlserver.wlr.session->active) {
@ -612,6 +644,10 @@ int wlserver_init(int argc, char **argv, bool bIsNested) {
create_gamescope_xwayland();
#if HAVE_PIPEWIRE
create_gamescope_pipewire();
#endif
int result = -1;
int display_slot = 0;