Remoting plugin support streaming image of virtual output on drm-backend to remote output. By appending remote-output section in weston.ini, weston loads remoting plugin module and creates virtual outputs via remoting plugin. The mode, host, and port properties are configurable in remote-output section. This plugin send motion jpeg images to client via RTP using gstreamer. Client can receive by using following pipeline of gst-launch. gst-launch-1.0 rtpbin name=rtpbin \ udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000, encoding-name=JPEG,payload=26" port=[PORTNUMBER] ! rtpbin.recv_rtp_sink_0 \ rtpbin. ! rtpjpegdepay ! jpegdec ! autovideosink \ udpsrc port=[PORTNUMBER+1] ! rtpbin.recv_rtcp_sink_0 \ rtpbin.send_rtcp_src_0 ! udpsink port=[PORTNUMBER+2] sync=false async=false where, PORTNUMBER is specified in weston.ini. Signed-off-by: Tomohito Esaki <etom@igel.co.jp>dev
parent
f59dc1112b
commit
f709d22038
@ -0,0 +1,38 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# Copyright © 2018 Renesas Electronics Corp. |
||||||
|
# |
||||||
|
# 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. |
||||||
|
# |
||||||
|
# Authors: IGEL Co., Ltd. |
||||||
|
|
||||||
|
# By using this script, client can receive remoted output via gstreamer. |
||||||
|
# Usage: |
||||||
|
# remoting-client-receive.bash <PORT NUMBER> |
||||||
|
|
||||||
|
gst-launch-1.0 rtpbin name=rtpbin \ |
||||||
|
udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=JPEG,payload=26" port=$1 ! \ |
||||||
|
rtpbin.recv_rtp_sink_0 \ |
||||||
|
rtpbin. ! rtpjpegdepay ! jpegdec ! autovideosink \ |
||||||
|
udpsrc port=$(($1 + 1)) ! rtpbin.recv_rtcp_sink_0 \ |
||||||
|
rtpbin.send_rtcp_src_0 ! \ |
||||||
|
udpsink port=$(($1 + 2)) sync=false async=false |
@ -0,0 +1,28 @@ |
|||||||
|
Remoting plugin for Weston |
||||||
|
|
||||||
|
|
||||||
|
The Remoting plugin creates a streaming image of a virtual output and transmits |
||||||
|
it to a remote host. It is currently only supported on the drm-backend. Virtual |
||||||
|
outputs are created and configured by adding a remote-output section to |
||||||
|
weston.ini. See man weston-drm(7) for configuration details. This plugin is |
||||||
|
loaded automatically if any remote-output sections are present. |
||||||
|
|
||||||
|
This plugin sends motion jpeg images to a client via RTP using gstreamer, and |
||||||
|
so requires gstreamer-1.0. This plugin starts sending images immediately when |
||||||
|
weston is run, and keeps sending them until weston shuts down. The image stream |
||||||
|
can be received by any appropriately configured RTP client, but a sample |
||||||
|
gstreamer RTP client script can be found at doc/remoting-client-receive.bash. |
||||||
|
|
||||||
|
Script usage: |
||||||
|
remoting-client-receive.bash <PORT NUMBER> |
||||||
|
|
||||||
|
|
||||||
|
How to compile |
||||||
|
--------------- |
||||||
|
Set --enable-remoting=true when configuring weston. The remoting-plugin.so |
||||||
|
module is created and installed in the libweston path. |
||||||
|
|
||||||
|
|
||||||
|
How to configure weston.ini |
||||||
|
---------------------------- |
||||||
|
See man weston-drm(7). |
@ -0,0 +1,907 @@ |
|||||||
|
/*
|
||||||
|
* Copyright © 2018 Renesas Electronics Corp. |
||||||
|
* |
||||||
|
* Based on vaapi-recorder by: |
||||||
|
* Copyright (c) 2012 Intel Corporation. All Rights Reserved. |
||||||
|
* Copyright © 2013 Intel 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. |
||||||
|
* |
||||||
|
* Authors: IGEL Co., Ltd. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "config.h" |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <string.h> |
||||||
|
#include <unistd.h> |
||||||
|
#include <sys/types.h> |
||||||
|
#include <fcntl.h> |
||||||
|
#include <gbm.h> |
||||||
|
|
||||||
|
#include <gst/gst.h> |
||||||
|
#include <gst/allocators/gstdmabuf.h> |
||||||
|
#include <gst/app/gstappsrc.h> |
||||||
|
#include <gst/video/gstvideometa.h> |
||||||
|
|
||||||
|
#include "remoting-plugin.h" |
||||||
|
#include "compositor-drm.h" |
||||||
|
#include "shared/helpers.h" |
||||||
|
#include "shared/timespec-util.h" |
||||||
|
|
||||||
|
#define MAX_RETRY_COUNT 3 |
||||||
|
|
||||||
|
struct weston_remoting { |
||||||
|
struct weston_compositor *compositor; |
||||||
|
struct wl_list output_list; |
||||||
|
struct wl_listener destroy_listener; |
||||||
|
const struct weston_drm_virtual_output_api *virtual_output_api; |
||||||
|
|
||||||
|
GstAllocator *allocator; |
||||||
|
}; |
||||||
|
|
||||||
|
struct remoted_gstpipe { |
||||||
|
int readfd; |
||||||
|
int writefd; |
||||||
|
struct wl_event_source *source; |
||||||
|
}; |
||||||
|
|
||||||
|
/* supported gbm format list */ |
||||||
|
struct remoted_output_support_gbm_format { |
||||||
|
uint32_t gbm_format; |
||||||
|
const char *gst_format_string; |
||||||
|
GstVideoFormat gst_video_format; |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct remoted_output_support_gbm_format supported_formats[] = { |
||||||
|
{ |
||||||
|
.gbm_format = GBM_FORMAT_XRGB8888, |
||||||
|
.gst_format_string = "BGRx", |
||||||
|
.gst_video_format = GST_VIDEO_FORMAT_BGRx, |
||||||
|
}, { |
||||||
|
.gbm_format = GBM_FORMAT_RGB565, |
||||||
|
.gst_format_string = "RGB16", |
||||||
|
.gst_video_format = GST_VIDEO_FORMAT_RGB16, |
||||||
|
}, { |
||||||
|
.gbm_format = GBM_FORMAT_XRGB2101010, |
||||||
|
.gst_format_string = "r210", |
||||||
|
.gst_video_format = GST_VIDEO_FORMAT_r210, |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
struct remoted_output { |
||||||
|
struct weston_output *output; |
||||||
|
void (*saved_destroy)(struct weston_output *output); |
||||||
|
int (*saved_enable)(struct weston_output *output); |
||||||
|
int (*saved_disable)(struct weston_output *output); |
||||||
|
void (*saved_start_repaint_loop)(struct weston_output *output); |
||||||
|
|
||||||
|
char *host; |
||||||
|
int port; |
||||||
|
const struct remoted_output_support_gbm_format *format; |
||||||
|
|
||||||
|
struct weston_head *head; |
||||||
|
|
||||||
|
struct weston_remoting *remoting; |
||||||
|
struct wl_event_source *finish_frame_timer; |
||||||
|
struct wl_list link; |
||||||
|
bool submitted_frame; |
||||||
|
int fence_sync_fd; |
||||||
|
struct wl_event_source *fence_sync_event_source; |
||||||
|
|
||||||
|
GstElement *pipeline; |
||||||
|
GstAppSrc *appsrc; |
||||||
|
GstBus *bus; |
||||||
|
struct remoted_gstpipe gstpipe; |
||||||
|
GstClockTime start_time; |
||||||
|
int retry_count; |
||||||
|
}; |
||||||
|
|
||||||
|
struct mem_free_cb_data { |
||||||
|
struct remoted_output *output; |
||||||
|
struct drm_fb *output_buffer; |
||||||
|
}; |
||||||
|
|
||||||
|
struct gst_frame_buffer_data { |
||||||
|
struct remoted_output *output; |
||||||
|
GstBuffer *buffer; |
||||||
|
}; |
||||||
|
|
||||||
|
/* message type for pipe */ |
||||||
|
#define GSTPIPE_MSG_BUS_SYNC 1 |
||||||
|
#define GSTPIPE_MSG_BUFFER_RELEASE 2 |
||||||
|
|
||||||
|
struct gstpipe_msg_data { |
||||||
|
int type; |
||||||
|
void *data; |
||||||
|
}; |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_gst_init(struct weston_remoting *remoting) |
||||||
|
{ |
||||||
|
GError *err = NULL; |
||||||
|
|
||||||
|
if (!gst_init_check(NULL, NULL, &err)) { |
||||||
|
weston_log("GStreamer initialization error: %s\n", |
||||||
|
err->message); |
||||||
|
g_error_free(err); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
remoting->allocator = gst_dmabuf_allocator_new(); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_gst_deinit(struct weston_remoting *remoting) |
||||||
|
{ |
||||||
|
gst_object_unref(remoting->allocator); |
||||||
|
} |
||||||
|
|
||||||
|
static GstBusSyncReply |
||||||
|
remoting_gst_bus_sync_handler(GstBus *bus, GstMessage *message, |
||||||
|
gpointer user_data) |
||||||
|
{ |
||||||
|
struct remoted_gstpipe *pipe = user_data; |
||||||
|
struct gstpipe_msg_data msg = { |
||||||
|
.type = GSTPIPE_MSG_BUS_SYNC, |
||||||
|
.data = NULL |
||||||
|
}; |
||||||
|
ssize_t ret; |
||||||
|
|
||||||
|
ret = write(pipe->writefd, &msg, sizeof(msg)); |
||||||
|
if (ret != sizeof(msg)) |
||||||
|
weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", |
||||||
|
ret, errno); |
||||||
|
|
||||||
|
return GST_BUS_PASS; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_gst_pipeline_init(struct remoted_output *output) |
||||||
|
{ |
||||||
|
char pipeline_str[1024]; |
||||||
|
GstCaps *caps; |
||||||
|
GError *err = NULL; |
||||||
|
GstStateChangeReturn ret; |
||||||
|
struct weston_mode *mode = output->output->current_mode; |
||||||
|
|
||||||
|
/* TODO: use encodebin instead of jpegenc */ |
||||||
|
snprintf(pipeline_str, sizeof(pipeline_str), |
||||||
|
"rtpbin name=rtpbin " |
||||||
|
"appsrc name=src ! videoconvert ! video/x-raw,format=I420 ! " |
||||||
|
"jpegenc ! rtpjpegpay ! rtpbin.send_rtp_sink_0 " |
||||||
|
"rtpbin.send_rtp_src_0 ! udpsink name=sink host=%s port=%d " |
||||||
|
"rtpbin.send_rtcp_src_0 ! " |
||||||
|
"udpsink host=%s port=%d sync=false async=false " |
||||||
|
"udpsrc port=%d ! rtpbin.recv_rtcp_sink_0", |
||||||
|
output->host, output->port, output->host, output->port + 1, |
||||||
|
output->port + 2); |
||||||
|
weston_log("GST pipeline: %s\n", pipeline_str); |
||||||
|
|
||||||
|
output->pipeline = gst_parse_launch(pipeline_str, &err); |
||||||
|
if (!output->pipeline) { |
||||||
|
weston_log("Could not create gstreamer pipeline. Error: %s\n", |
||||||
|
err->message); |
||||||
|
g_error_free(err); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
output->appsrc = (GstAppSrc*) |
||||||
|
gst_bin_get_by_name(GST_BIN(output->pipeline), "src"); |
||||||
|
if (!output->appsrc) { |
||||||
|
weston_log("Could not get appsrc from gstreamer pipeline\n"); |
||||||
|
goto err; |
||||||
|
} |
||||||
|
|
||||||
|
caps = gst_caps_new_simple("video/x-raw", |
||||||
|
"format", G_TYPE_STRING, |
||||||
|
output->format->gst_format_string, |
||||||
|
"width", G_TYPE_INT, mode->width, |
||||||
|
"height", G_TYPE_INT, mode->height, |
||||||
|
"framerate", GST_TYPE_FRACTION, |
||||||
|
mode->refresh, 1000, |
||||||
|
NULL); |
||||||
|
if (!caps) { |
||||||
|
weston_log("Could not create gstreamer caps.\n"); |
||||||
|
goto err; |
||||||
|
} |
||||||
|
g_object_set(G_OBJECT(output->appsrc), |
||||||
|
"caps", caps, |
||||||
|
"stream-type", 0, |
||||||
|
"format", GST_FORMAT_TIME, |
||||||
|
"is-live", TRUE, |
||||||
|
NULL); |
||||||
|
gst_caps_unref(caps); |
||||||
|
|
||||||
|
output->bus = gst_pipeline_get_bus(GST_PIPELINE(output->pipeline)); |
||||||
|
if (!output->bus) { |
||||||
|
weston_log("Could not get bus from gstreamer pipeline\n"); |
||||||
|
goto err; |
||||||
|
} |
||||||
|
gst_bus_set_sync_handler(output->bus, remoting_gst_bus_sync_handler, |
||||||
|
&output->gstpipe, NULL); |
||||||
|
|
||||||
|
output->start_time = 0; |
||||||
|
ret = gst_element_set_state(output->pipeline, GST_STATE_PLAYING); |
||||||
|
if (ret == GST_STATE_CHANGE_FAILURE) { |
||||||
|
weston_log("Couldn't set GST_STATE_PLAYING to pipeline\n"); |
||||||
|
goto err; |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
|
||||||
|
err: |
||||||
|
gst_object_unref(GST_OBJECT(output->pipeline)); |
||||||
|
output->pipeline = NULL; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_gst_pipeline_deinit(struct remoted_output *output) |
||||||
|
{ |
||||||
|
if (!output->pipeline) |
||||||
|
return; |
||||||
|
|
||||||
|
gst_element_set_state(output->pipeline, GST_STATE_NULL); |
||||||
|
if (output->bus) |
||||||
|
gst_object_unref(GST_OBJECT(output->bus)); |
||||||
|
gst_object_unref(GST_OBJECT(output->pipeline)); |
||||||
|
output->pipeline = NULL; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_output_disable(struct weston_output *output); |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_gst_restart(void *data) |
||||||
|
{ |
||||||
|
struct remoted_output *output = data; |
||||||
|
|
||||||
|
if (remoting_gst_pipeline_init(output) < 0) { |
||||||
|
weston_log("gst: Could not restart pipeline!!\n"); |
||||||
|
remoting_output_disable(output->output); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_gst_schedule_restart(struct remoted_output *output) |
||||||
|
{ |
||||||
|
struct wl_event_loop *loop; |
||||||
|
struct weston_compositor *c = output->remoting->compositor; |
||||||
|
|
||||||
|
loop = wl_display_get_event_loop(c->wl_display); |
||||||
|
wl_event_loop_add_idle(loop, remoting_gst_restart, output); |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_gst_bus_message_handler(struct remoted_output *output) |
||||||
|
{ |
||||||
|
GstMessage *message; |
||||||
|
GError *error; |
||||||
|
gchar *debug; |
||||||
|
|
||||||
|
/* get message from bus queue */ |
||||||
|
message = gst_bus_pop(output->bus); |
||||||
|
if (!message) |
||||||
|
return; |
||||||
|
|
||||||
|
switch (GST_MESSAGE_TYPE(message)) { |
||||||
|
case GST_MESSAGE_STATE_CHANGED: { |
||||||
|
GstState new_state; |
||||||
|
gst_message_parse_state_changed(message, NULL, &new_state, |
||||||
|
NULL); |
||||||
|
if (!strcmp(GST_OBJECT_NAME(message->src), "sink") && |
||||||
|
new_state == GST_STATE_PLAYING) |
||||||
|
output->retry_count = 0; |
||||||
|
break; |
||||||
|
} |
||||||
|
case GST_MESSAGE_WARNING: |
||||||
|
gst_message_parse_warning(message, &error, &debug); |
||||||
|
weston_log("gst: Warning: %s: %s\n", |
||||||
|
GST_OBJECT_NAME(message->src), error->message); |
||||||
|
break; |
||||||
|
case GST_MESSAGE_ERROR: |
||||||
|
gst_message_parse_error(message, &error, &debug); |
||||||
|
weston_log("gst: Error: %s: %s\n", |
||||||
|
GST_OBJECT_NAME(message->src), error->message); |
||||||
|
if (output->retry_count < MAX_RETRY_COUNT) { |
||||||
|
output->retry_count++; |
||||||
|
remoting_gst_pipeline_deinit(output); |
||||||
|
remoting_gst_schedule_restart(output); |
||||||
|
} else { |
||||||
|
remoting_output_disable(output->output); |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_buffer_release(struct remoted_output *output, void *buffer) |
||||||
|
{ |
||||||
|
const struct weston_drm_virtual_output_api *api |
||||||
|
= output->remoting->virtual_output_api; |
||||||
|
|
||||||
|
api->buffer_released(buffer); |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_gstpipe_handler(int fd, uint32_t mask, void *data) |
||||||
|
{ |
||||||
|
ssize_t ret; |
||||||
|
struct gstpipe_msg_data msg; |
||||||
|
struct remoted_output *output = data; |
||||||
|
|
||||||
|
/* recieve message */ |
||||||
|
ret = read(fd, &msg, sizeof(msg)); |
||||||
|
if (ret != sizeof(msg)) { |
||||||
|
weston_log("ERROR: failed to read, ret=%zd, errno=%d\n", |
||||||
|
ret, errno); |
||||||
|
remoting_output_disable(output->output); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
switch (msg.type) { |
||||||
|
case GSTPIPE_MSG_BUS_SYNC: |
||||||
|
remoting_gst_bus_message_handler(output); |
||||||
|
break; |
||||||
|
case GSTPIPE_MSG_BUFFER_RELEASE: |
||||||
|
remoting_output_buffer_release(output, msg.data); |
||||||
|
break; |
||||||
|
default: |
||||||
|
weston_log("Recieved unknown message! msg=%d\n", msg.type); |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_gstpipe_init(struct weston_compositor *c, |
||||||
|
struct remoted_output *output) |
||||||
|
{ |
||||||
|
struct wl_event_loop *loop; |
||||||
|
int fd[2]; |
||||||
|
|
||||||
|
if (pipe2(fd, O_CLOEXEC) == -1) |
||||||
|
return -1; |
||||||
|
|
||||||
|
output->gstpipe.readfd = fd[0]; |
||||||
|
output->gstpipe.writefd = fd[1]; |
||||||
|
loop = wl_display_get_event_loop(c->wl_display); |
||||||
|
output->gstpipe.source = |
||||||
|
wl_event_loop_add_fd(loop, output->gstpipe.readfd, |
||||||
|
WL_EVENT_READABLE, |
||||||
|
remoting_gstpipe_handler, output); |
||||||
|
if (!output->gstpipe.source) { |
||||||
|
close(fd[0]); |
||||||
|
close(fd[1]); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_gstpipe_release(struct remoted_gstpipe *pipe) |
||||||
|
{ |
||||||
|
wl_event_source_remove(pipe->source); |
||||||
|
close(pipe->readfd); |
||||||
|
close(pipe->writefd); |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_destroy(struct weston_output *output); |
||||||
|
|
||||||
|
static void |
||||||
|
weston_remoting_destroy(struct wl_listener *l, void *data) |
||||||
|
{ |
||||||
|
struct weston_remoting *remoting = |
||||||
|
container_of(l, struct weston_remoting, destroy_listener); |
||||||
|
struct remoted_output *output, *next; |
||||||
|
|
||||||
|
wl_list_for_each_safe(output, next, &remoting->output_list, link) |
||||||
|
remoting_output_destroy(output->output); |
||||||
|
|
||||||
|
/* Finalize gstreamer */ |
||||||
|
remoting_gst_deinit(remoting); |
||||||
|
|
||||||
|
wl_list_remove(&remoting->destroy_listener.link); |
||||||
|
free(remoting); |
||||||
|
} |
||||||
|
|
||||||
|
static struct weston_remoting * |
||||||
|
weston_remoting_get(struct weston_compositor *compositor) |
||||||
|
{ |
||||||
|
struct wl_listener *listener; |
||||||
|
struct weston_remoting *remoting; |
||||||
|
|
||||||
|
listener = wl_signal_get(&compositor->destroy_signal, |
||||||
|
weston_remoting_destroy); |
||||||
|
if (!listener) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
remoting = wl_container_of(listener, remoting, destroy_listener); |
||||||
|
return remoting; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_output_finish_frame_handler(void *data) |
||||||
|
{ |
||||||
|
struct remoted_output *output = data; |
||||||
|
const struct weston_drm_virtual_output_api *api |
||||||
|
= output->remoting->virtual_output_api; |
||||||
|
struct timespec now; |
||||||
|
int64_t msec; |
||||||
|
|
||||||
|
if (output->submitted_frame) { |
||||||
|
struct weston_compositor *c = output->remoting->compositor; |
||||||
|
output->submitted_frame = false; |
||||||
|
weston_compositor_read_presentation_clock(c, &now); |
||||||
|
api->finish_frame(output->output, &now, 0); |
||||||
|
} |
||||||
|
|
||||||
|
msec = millihz_to_nsec(output->output->current_mode->refresh) / 1000000; |
||||||
|
wl_event_source_timer_update(output->finish_frame_timer, msec); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_gst_mem_free_cb(struct mem_free_cb_data *cb_data, GstMiniObject *obj) |
||||||
|
{ |
||||||
|
struct remoted_output *output = cb_data->output; |
||||||
|
struct remoted_gstpipe *pipe = &output->gstpipe; |
||||||
|
struct gstpipe_msg_data msg = { |
||||||
|
.type = GSTPIPE_MSG_BUFFER_RELEASE, |
||||||
|
.data = cb_data->output_buffer |
||||||
|
}; |
||||||
|
ssize_t ret; |
||||||
|
|
||||||
|
ret = write(pipe->writefd, &msg, sizeof(msg)); |
||||||
|
if (ret != sizeof(msg)) |
||||||
|
weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", ret, |
||||||
|
errno); |
||||||
|
free(cb_data); |
||||||
|
} |
||||||
|
|
||||||
|
static struct remoted_output * |
||||||
|
lookup_remoted_output(struct weston_output *output) |
||||||
|
{ |
||||||
|
struct weston_compositor *c = output->compositor; |
||||||
|
struct weston_remoting *remoting = weston_remoting_get(c); |
||||||
|
struct remoted_output *remoted_output; |
||||||
|
|
||||||
|
wl_list_for_each(remoted_output, &remoting->output_list, link) { |
||||||
|
if (remoted_output->output == output) |
||||||
|
return remoted_output; |
||||||
|
} |
||||||
|
|
||||||
|
weston_log("%s: %s: could not find output\n", __FILE__, __func__); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_gst_push_buffer(struct remoted_output *output, |
||||||
|
GstBuffer *buffer) |
||||||
|
{ |
||||||
|
struct timespec current_frame_ts; |
||||||
|
GstClockTime ts, current_frame_time; |
||||||
|
|
||||||
|
weston_compositor_read_presentation_clock(output->remoting->compositor, |
||||||
|
¤t_frame_ts); |
||||||
|
current_frame_time = GST_TIMESPEC_TO_TIME(current_frame_ts); |
||||||
|
if (output->start_time == 0) |
||||||
|
output->start_time = current_frame_time; |
||||||
|
ts = current_frame_time - output->start_time; |
||||||
|
|
||||||
|
if (GST_CLOCK_TIME_IS_VALID(ts)) |
||||||
|
GST_BUFFER_PTS(buffer) = ts; |
||||||
|
else |
||||||
|
GST_BUFFER_PTS(buffer) = GST_CLOCK_TIME_NONE; |
||||||
|
GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE; |
||||||
|
|
||||||
|
gst_app_src_push_buffer(output->appsrc, buffer); |
||||||
|
output->submitted_frame = true; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_output_fence_sync_handler(int fd, uint32_t mask, void *data) |
||||||
|
{ |
||||||
|
struct gst_frame_buffer_data *frame_data = data; |
||||||
|
struct remoted_output *output = frame_data->output; |
||||||
|
|
||||||
|
remoting_output_gst_push_buffer(output, frame_data->buffer); |
||||||
|
|
||||||
|
wl_event_source_remove(output->fence_sync_event_source); |
||||||
|
close(output->fence_sync_fd); |
||||||
|
free(frame_data); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_output_frame(struct weston_output *output_base, int fd, int stride, |
||||||
|
struct drm_fb *output_buffer) |
||||||
|
{ |
||||||
|
struct remoted_output *output = lookup_remoted_output(output_base); |
||||||
|
struct weston_remoting *remoting = output->remoting; |
||||||
|
struct weston_mode *mode; |
||||||
|
const struct weston_drm_virtual_output_api *api |
||||||
|
= output->remoting->virtual_output_api; |
||||||
|
struct wl_event_loop *loop; |
||||||
|
GstBuffer *buf; |
||||||
|
GstMemory *mem; |
||||||
|
gsize offset = 0; |
||||||
|
struct mem_free_cb_data *cb_data; |
||||||
|
struct gst_frame_buffer_data *frame_data; |
||||||
|
|
||||||
|
if (!output) |
||||||
|
return -1; |
||||||
|
|
||||||
|
cb_data = zalloc(sizeof *cb_data); |
||||||
|
if (!cb_data) |
||||||
|
return -1; |
||||||
|
|
||||||
|
mode = output->output->current_mode; |
||||||
|
buf = gst_buffer_new(); |
||||||
|
mem = gst_dmabuf_allocator_alloc(remoting->allocator, fd, |
||||||
|
stride * mode->height); |
||||||
|
gst_buffer_append_memory(buf, mem); |
||||||
|
gst_buffer_add_video_meta_full(buf, |
||||||
|
GST_VIDEO_FRAME_FLAG_NONE, |
||||||
|
output->format->gst_video_format, |
||||||
|
mode->width, |
||||||
|
mode->height, |
||||||
|
1, |
||||||
|
&offset, |
||||||
|
&stride); |
||||||
|
|
||||||
|
cb_data->output = output; |
||||||
|
cb_data->output_buffer = output_buffer; |
||||||
|
gst_mini_object_weak_ref(GST_MINI_OBJECT(mem), |
||||||
|
(GstMiniObjectNotify)remoting_gst_mem_free_cb, |
||||||
|
cb_data); |
||||||
|
|
||||||
|
output->fence_sync_fd = api->get_fence_sync_fd(output->output); |
||||||
|
/* Push buffer to gstreamer immediately on get_fence_sync_fd failure */ |
||||||
|
if (output->fence_sync_fd == -1) { |
||||||
|
remoting_output_gst_push_buffer(output, buf); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
frame_data = zalloc(sizeof *frame_data); |
||||||
|
if (!frame_data) { |
||||||
|
close(output->fence_sync_fd); |
||||||
|
remoting_output_gst_push_buffer(output, buf); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
frame_data->output = output; |
||||||
|
frame_data->buffer = buf; |
||||||
|
loop = wl_display_get_event_loop(remoting->compositor->wl_display); |
||||||
|
output->fence_sync_event_source = |
||||||
|
wl_event_loop_add_fd(loop, output->fence_sync_fd, |
||||||
|
WL_EVENT_READABLE, |
||||||
|
remoting_output_fence_sync_handler, |
||||||
|
frame_data); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_destroy(struct weston_output *output) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
struct weston_mode *mode, *next; |
||||||
|
|
||||||
|
wl_list_for_each_safe(mode, next, &output->mode_list, link) { |
||||||
|
wl_list_remove(&mode->link); |
||||||
|
free(mode); |
||||||
|
} |
||||||
|
|
||||||
|
remoted_output->saved_destroy(output); |
||||||
|
|
||||||
|
remoting_gst_pipeline_deinit(remoted_output); |
||||||
|
remoting_gstpipe_release(&remoted_output->gstpipe); |
||||||
|
|
||||||
|
if (remoted_output->host) |
||||||
|
free(remoted_output->host); |
||||||
|
|
||||||
|
wl_list_remove(&remoted_output->link); |
||||||
|
weston_head_release(remoted_output->head); |
||||||
|
free(remoted_output->head); |
||||||
|
free(remoted_output); |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_start_repaint_loop(struct weston_output *output) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
int64_t msec; |
||||||
|
|
||||||
|
remoted_output->saved_start_repaint_loop(output); |
||||||
|
|
||||||
|
msec = millihz_to_nsec(remoted_output->output->current_mode->refresh) |
||||||
|
/ 1000000; |
||||||
|
wl_event_source_timer_update(remoted_output->finish_frame_timer, msec); |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_output_enable(struct weston_output *output) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
struct weston_compositor *c = output->compositor; |
||||||
|
const struct weston_drm_virtual_output_api *api |
||||||
|
= remoted_output->remoting->virtual_output_api; |
||||||
|
struct wl_event_loop *loop; |
||||||
|
int ret; |
||||||
|
|
||||||
|
api->set_submit_frame_cb(output, remoting_output_frame); |
||||||
|
|
||||||
|
ret = remoted_output->saved_enable(output); |
||||||
|
if (ret < 0) |
||||||
|
return ret; |
||||||
|
|
||||||
|
remoted_output->saved_start_repaint_loop = output->start_repaint_loop; |
||||||
|
output->start_repaint_loop = remoting_output_start_repaint_loop; |
||||||
|
|
||||||
|
ret = remoting_gst_pipeline_init(remoted_output); |
||||||
|
if (ret < 0) { |
||||||
|
remoted_output->saved_disable(output); |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
loop = wl_display_get_event_loop(c->wl_display); |
||||||
|
remoted_output->finish_frame_timer = |
||||||
|
wl_event_loop_add_timer(loop, |
||||||
|
remoting_output_finish_frame_handler, |
||||||
|
remoted_output); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_output_disable(struct weston_output *output) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
|
||||||
|
wl_event_source_remove(remoted_output->finish_frame_timer); |
||||||
|
remoting_gst_pipeline_deinit(remoted_output); |
||||||
|
|
||||||
|
return remoted_output->saved_disable(output); |
||||||
|
} |
||||||
|
|
||||||
|
static struct weston_output * |
||||||
|
remoting_output_create(struct weston_compositor *c, char *name) |
||||||
|
{ |
||||||
|
struct weston_remoting *remoting = weston_remoting_get(c); |
||||||
|
struct remoted_output *output; |
||||||
|
struct weston_head *head; |
||||||
|
const struct weston_drm_virtual_output_api *api; |
||||||
|
const char *make = "Renesas"; |
||||||
|
const char *model = "Virtual Display"; |
||||||
|
const char *serial_number = "unknown"; |
||||||
|
const char *connector_name = "remoting"; |
||||||
|
|
||||||
|
if (!name || !strlen(name)) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
api = remoting->virtual_output_api; |
||||||
|
|
||||||
|
output = zalloc(sizeof *output); |
||||||
|
if (!output) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
head = zalloc(sizeof *head); |
||||||
|
if (!head) |
||||||
|
goto err; |
||||||
|
|
||||||
|
if (remoting_gstpipe_init(c, output) < 0) { |
||||||
|
weston_log("Can not create pipe for gstreamer\n"); |
||||||
|
goto err; |
||||||
|
} |
||||||
|
|
||||||
|
output->output = api->create_output(c, name); |
||||||
|
if (!output->output) { |
||||||
|
weston_log("Can not create virtual output\n"); |
||||||
|
goto err; |
||||||
|
} |
||||||
|
|
||||||
|
output->saved_destroy = output->output->destroy; |
||||||
|
output->output->destroy = remoting_output_destroy; |
||||||
|
output->saved_enable = output->output->enable; |
||||||
|
output->output->enable = remoting_output_enable; |
||||||
|
output->saved_disable = output->output->disable; |
||||||
|
output->output->disable = remoting_output_disable; |
||||||
|
output->remoting = remoting; |
||||||
|
wl_list_insert(remoting->output_list.prev, &output->link); |
||||||
|
|
||||||
|
weston_head_init(head, connector_name); |
||||||
|
weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); |
||||||
|
weston_head_set_monitor_strings(head, make, model, serial_number); |
||||||
|
head->compositor = c; |
||||||
|
|
||||||
|
weston_output_attach_head(output->output, head); |
||||||
|
output->head = head; |
||||||
|
|
||||||
|
/* set XRGB8888 format */ |
||||||
|
output->format = &supported_formats[0]; |
||||||
|
|
||||||
|
return output->output; |
||||||
|
|
||||||
|
err: |
||||||
|
if (output->gstpipe.source) |
||||||
|
remoting_gstpipe_release(&output->gstpipe); |
||||||
|
if (head) |
||||||
|
free(head); |
||||||
|
free(output); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
static bool |
||||||
|
remoting_output_is_remoted(struct weston_output *output) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
|
||||||
|
if (remoted_output) |
||||||
|
return true; |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
remoting_output_set_mode(struct weston_output *output, const char *modeline) |
||||||
|
{ |
||||||
|
struct weston_mode *mode; |
||||||
|
int n, width, height, refresh = 0; |
||||||
|
|
||||||
|
if (!remoting_output_is_remoted(output)) { |
||||||
|
weston_log("Output is not remoted.\n"); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!modeline) |
||||||
|
return -1; |
||||||
|
|
||||||
|
n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh); |
||||||
|
if (n != 2 && n != 3) |
||||||
|
return -1; |
||||||
|
|
||||||
|
mode = zalloc(sizeof *mode); |
||||||
|
if (!mode) |
||||||
|
return -1; |
||||||
|
|
||||||
|
mode->flags = WL_OUTPUT_MODE_CURRENT; |
||||||
|
mode->width = width; |
||||||
|
mode->height = height; |
||||||
|
mode->refresh = (refresh ? refresh : 60) * 1000LL; |
||||||
|
|
||||||
|
wl_list_insert(output->mode_list.prev, &mode->link); |
||||||
|
|
||||||
|
output->current_mode = mode; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_set_gbm_format(struct weston_output *output, |
||||||
|
const char *gbm_format) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
const struct weston_drm_virtual_output_api *api; |
||||||
|
uint32_t format, i; |
||||||
|
|
||||||
|
if (!remoted_output) |
||||||
|
return; |
||||||
|
|
||||||
|
api = remoted_output->remoting->virtual_output_api; |
||||||
|
format = api->set_gbm_format(output, gbm_format); |
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_LENGTH(supported_formats); i++) { |
||||||
|
if (format == supported_formats[i].gbm_format) { |
||||||
|
remoted_output->format = &supported_formats[i]; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_set_seat(struct weston_output *output, const char *seat) |
||||||
|
{ |
||||||
|
/* for now, nothing todo */ |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_set_host(struct weston_output *output, char *host) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
|
||||||
|
if (!remoted_output) |
||||||
|
return; |
||||||
|
|
||||||
|
if (remoted_output->host) |
||||||
|
free(remoted_output->host); |
||||||
|
remoted_output->host = strdup(host); |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
remoting_output_set_port(struct weston_output *output, int port) |
||||||
|
{ |
||||||
|
struct remoted_output *remoted_output = lookup_remoted_output(output); |
||||||
|
|
||||||
|
if (remoted_output) |
||||||
|
remoted_output->port = port; |
||||||
|
} |
||||||
|
|
||||||
|
static const struct weston_remoting_api remoting_api = { |
||||||
|
remoting_output_create, |
||||||
|
remoting_output_is_remoted, |
||||||
|
remoting_output_set_mode, |
||||||
|
remoting_output_set_gbm_format, |
||||||
|
remoting_output_set_seat, |
||||||
|
remoting_output_set_host, |
||||||
|
remoting_output_set_port, |
||||||
|
}; |
||||||
|
|
||||||
|
WL_EXPORT int |
||||||
|
weston_module_init(struct weston_compositor *compositor) |
||||||
|
{ |
||||||
|
int ret; |
||||||
|
struct weston_remoting *remoting; |
||||||
|
const struct weston_drm_virtual_output_api *api = |
||||||
|
weston_drm_virtual_output_get_api(compositor); |
||||||
|
|
||||||
|
if (!api) |
||||||
|
return -1; |
||||||
|
|
||||||
|
remoting = zalloc(sizeof *remoting); |
||||||
|
if (!remoting) |
||||||
|
return -1; |
||||||
|
|
||||||
|
remoting->virtual_output_api = api; |
||||||
|
remoting->compositor = compositor; |
||||||
|
wl_list_init(&remoting->output_list); |
||||||
|
|
||||||
|
ret = weston_plugin_api_register(compositor, WESTON_REMOTING_API_NAME, |
||||||
|
&remoting_api, sizeof(remoting_api)); |
||||||
|
|
||||||
|
if (ret < 0) { |
||||||
|
weston_log("Failed to register remoting API.\n"); |
||||||
|
goto failed; |
||||||
|
} |
||||||
|
|
||||||
|
/* Initialize gstreamer */ |
||||||
|
ret = remoting_gst_init(remoting); |
||||||
|
if (ret < 0) { |
||||||
|
weston_log("Failed to initialize gstreamer.\n"); |
||||||
|
goto failed; |
||||||
|
} |
||||||
|
|
||||||
|
remoting->destroy_listener.notify = weston_remoting_destroy; |
||||||
|
wl_signal_add(&compositor->destroy_signal, &remoting->destroy_listener); |
||||||
|
return 0; |
||||||
|
|
||||||
|
failed: |
||||||
|
free(remoting); |
||||||
|
return -1; |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
/*
|
||||||
|
* Copyright © 2018 Renesas Electronics Corp. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* Authors: IGEL Co., Ltd. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef REMOTING_PLUGIN_H |
||||||
|
#define REMOTING_PLUGIN_H |
||||||
|
|
||||||
|
#include "compositor.h" |
||||||
|
#include "plugin-registry.h" |
||||||
|
|
||||||
|
#define WESTON_REMOTING_API_NAME "weston_remoting_api_v1" |
||||||
|
|
||||||
|
struct weston_remoting_api { |
||||||
|
/** Create remoted outputs
|
||||||
|
* |
||||||
|
* Returns 0 on success, -1 on failure. |
||||||
|
*/ |
||||||
|
struct weston_output *(*create_output)(struct weston_compositor *c, |
||||||
|
char *name); |
||||||
|
|
||||||
|
/** Check if output is remoted */ |
||||||
|
bool (*is_remoted_output)(struct weston_output *output); |
||||||
|
|
||||||
|
/** Set mode */ |
||||||
|
int (*set_mode)(struct weston_output *output, const char *modeline); |
||||||
|
|
||||||
|
/** Set gbm format */ |
||||||
|
void (*set_gbm_format)(struct weston_output *output, |
||||||
|
const char *gbm_format); |
||||||
|
|
||||||
|
/** Set seat */ |
||||||
|
void (*set_seat)(struct weston_output *output, const char *seat); |
||||||
|
|
||||||
|
/** Set the destination Host(IP Address) */ |
||||||
|
void (*set_host)(struct weston_output *output, char *ip); |
||||||
|
|
||||||
|
/** Set the port number */ |
||||||
|
void (*set_port)(struct weston_output *output, int port); |
||||||
|
}; |
||||||
|
|
||||||
|
static inline const struct weston_remoting_api * |
||||||
|
weston_remoting_get_api(struct weston_compositor *compositor) |
||||||
|
{ |
||||||
|
const void *api; |
||||||
|
api = weston_plugin_api_get(compositor, WESTON_REMOTING_API_NAME, |
||||||
|
sizeof(struct weston_remoting_api)); |
||||||
|
|
||||||
|
return (const struct weston_remoting_api *)api; |
||||||
|
} |
||||||
|
|
||||||
|
#endif /* REMOTING_PLUGIN_H */ |
Loading…
Reference in new issue