mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
waylandsink: stack the video subsurface into another subsurface that covers the whole render rectangle
The main reason behind this is that when the video caps change and the video subsurface needs to resize and change position, the wl_subsurface.set_position call needs a commit in its parent in order to take effect. Previously, the parent was the application's surface, over which there is no control. Now, the parent is inside the sink, so we can commit it and change size smoothly. As a side effect, this also allows the sink to draw its black borders on its own, without the need for the application to do that. And another side effect is that this can now allow resizing the sink when it is in top-level mode and have it respect the aspect ratio.
This commit is contained in:
parent
ee7968dd4a
commit
5b1c5dbf99
6 changed files with 163 additions and 90 deletions
|
@ -333,11 +333,7 @@ gst_wayland_sink_change_state (GstElement * element, GstStateChange transition)
|
|||
g_clear_object (&sink->window);
|
||||
} else {
|
||||
/* remove buffer from surface, show nothing */
|
||||
wl_surface_attach (sink->window->surface, NULL, 0, 0);
|
||||
wl_surface_damage (sink->window->surface, 0, 0,
|
||||
sink->window->surface_width, sink->window->surface_height);
|
||||
wl_surface_commit (sink->window->surface);
|
||||
wl_display_flush (sink->display->display);
|
||||
gst_wl_window_render (sink->window, NULL, NULL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -558,6 +554,7 @@ static void
|
|||
render_last_buffer (GstWaylandSink * sink)
|
||||
{
|
||||
GstWlBuffer *wlbuffer;
|
||||
const GstVideoInfo *info = NULL;
|
||||
struct wl_surface *surface;
|
||||
struct wl_callback *callback;
|
||||
|
||||
|
@ -568,12 +565,11 @@ render_last_buffer (GstWaylandSink * sink)
|
|||
callback = wl_surface_frame (surface);
|
||||
wl_callback_add_listener (callback, &frame_callback_listener, sink);
|
||||
|
||||
gst_wl_buffer_attach (wlbuffer, sink->window);
|
||||
wl_surface_damage (surface, 0, 0, sink->window->surface_width,
|
||||
sink->window->surface_height);
|
||||
|
||||
wl_surface_commit (surface);
|
||||
wl_display_flush (sink->display->display);
|
||||
if (G_UNLIKELY (sink->video_info_changed)) {
|
||||
info = &sink->video_info;
|
||||
sink->video_info_changed = FALSE;
|
||||
}
|
||||
gst_wl_window_render (sink->window, wlbuffer, info);
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
|
@ -595,33 +591,19 @@ gst_wayland_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
|
|||
gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (sink));
|
||||
g_mutex_lock (&sink->render_lock);
|
||||
|
||||
if (sink->window) {
|
||||
/* inform the window about our caps */
|
||||
gst_wl_window_set_video_info (sink->window, &sink->video_info);
|
||||
} else {
|
||||
if (!sink->window) {
|
||||
/* if we were not provided a window, create one ourselves */
|
||||
sink->window =
|
||||
gst_wl_window_new_toplevel (sink->display, &sink->video_info);
|
||||
}
|
||||
sink->video_info_changed = FALSE;
|
||||
}
|
||||
|
||||
/* drop buffers until we get a frame callback */
|
||||
if (g_atomic_int_get (&sink->redraw_pending) == TRUE)
|
||||
goto done;
|
||||
|
||||
if (G_UNLIKELY (sink->video_info_changed)) {
|
||||
gst_wl_window_set_video_info (sink->window, &sink->video_info);
|
||||
sink->video_info_changed = FALSE;
|
||||
}
|
||||
|
||||
/* now that we have for sure set the video info on the window, it must have
|
||||
* a valid size, otherwise this means that the application has called
|
||||
* set_window_handle() without calling set_render_rectangle(), which is
|
||||
* absolutely necessary for us.
|
||||
*/
|
||||
if (G_UNLIKELY (sink->window->surface_width == 0 ||
|
||||
sink->window->surface_height == 0))
|
||||
/* make sure that the application has called set_render_rectangle() */
|
||||
if (G_UNLIKELY (sink->window->render_rectangle.w == 0))
|
||||
goto no_window_size;
|
||||
|
||||
wlbuffer = gst_buffer_get_wl_buffer (buffer);
|
||||
|
@ -822,14 +804,14 @@ gst_wayland_sink_begin_geometry_change (GstWaylandVideo * video)
|
|||
g_return_if_fail (sink != NULL);
|
||||
|
||||
g_mutex_lock (&sink->render_lock);
|
||||
if (!sink->window || !sink->window->subsurface) {
|
||||
if (!sink->window || !sink->window->area_subsurface) {
|
||||
g_mutex_unlock (&sink->render_lock);
|
||||
GST_INFO_OBJECT (sink,
|
||||
"begin_geometry_change called without window, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
wl_subsurface_set_sync (sink->window->subsurface);
|
||||
wl_subsurface_set_sync (sink->window->area_subsurface);
|
||||
g_mutex_unlock (&sink->render_lock);
|
||||
}
|
||||
|
||||
|
@ -840,14 +822,14 @@ gst_wayland_sink_end_geometry_change (GstWaylandVideo * video)
|
|||
g_return_if_fail (sink != NULL);
|
||||
|
||||
g_mutex_lock (&sink->render_lock);
|
||||
if (!sink->window || !sink->window->subsurface) {
|
||||
if (!sink->window || !sink->window->area_subsurface) {
|
||||
g_mutex_unlock (&sink->render_lock);
|
||||
GST_INFO_OBJECT (sink,
|
||||
"end_geometry_change called without window, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
wl_subsurface_set_desync (sink->window->subsurface);
|
||||
wl_subsurface_set_desync (sink->window->area_subsurface);
|
||||
g_mutex_unlock (&sink->render_lock);
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ static const struct wl_buffer_listener buffer_listener = {
|
|||
buffer_release
|
||||
};
|
||||
|
||||
void
|
||||
GstWlBuffer *
|
||||
gst_buffer_add_wl_buffer (GstBuffer * gstbuffer, struct wl_buffer *wlbuffer,
|
||||
GstWlDisplay * display)
|
||||
{
|
||||
|
@ -137,6 +137,8 @@ gst_buffer_add_wl_buffer (GstBuffer * gstbuffer, struct wl_buffer *wlbuffer,
|
|||
|
||||
gst_mini_object_set_qdata ((GstMiniObject *) gstbuffer,
|
||||
gst_wl_buffer_qdata_quark (), self, g_object_unref);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
GstWlBuffer *
|
||||
|
@ -172,11 +174,11 @@ gst_wl_buffer_force_release_and_unref (GstWlBuffer * self)
|
|||
}
|
||||
|
||||
void
|
||||
gst_wl_buffer_attach (GstWlBuffer * self, GstWlWindow * target)
|
||||
gst_wl_buffer_attach (GstWlBuffer * self, struct wl_surface *surface)
|
||||
{
|
||||
g_return_if_fail (self->used_by_compositor == FALSE);
|
||||
|
||||
wl_surface_attach (target->surface, self->wlbuffer, 0, 0);
|
||||
wl_surface_attach (surface, self->wlbuffer, 0, 0);
|
||||
|
||||
/* Add a reference to the buffer. This represents the fact that
|
||||
* the compositor is using the buffer and it should not return
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#ifndef __GST_WL_BUFFER_H__
|
||||
#define __GST_WL_BUFFER_H__
|
||||
|
||||
#include "wlwindow.h"
|
||||
#include "wldisplay.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
@ -54,13 +54,13 @@ struct _GstWlBufferClass
|
|||
|
||||
GType gst_wl_buffer_get_type (void);
|
||||
|
||||
void gst_buffer_add_wl_buffer (GstBuffer * gstbuffer,
|
||||
GstWlBuffer * gst_buffer_add_wl_buffer (GstBuffer * gstbuffer,
|
||||
struct wl_buffer * wlbuffer, GstWlDisplay * display);
|
||||
GstWlBuffer * gst_buffer_get_wl_buffer (GstBuffer * gstbuffer);
|
||||
|
||||
void gst_wl_buffer_force_release_and_unref (GstWlBuffer * self);
|
||||
|
||||
void gst_wl_buffer_attach (GstWlBuffer * self, GstWlWindow * target);
|
||||
void gst_wl_buffer_attach (GstWlBuffer * self, struct wl_surface *surface);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#ifndef __GST_WL_VIDEO_FORMAT_H__
|
||||
#define __GST_WL_VIDEO_FORMAT_H__
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#endif
|
||||
|
||||
#include "wlwindow.h"
|
||||
#include "wlshmallocator.h"
|
||||
#include "wlbuffer.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gstwayland_debug);
|
||||
#define GST_CAT_DEFAULT gstwayland_debug
|
||||
|
@ -78,12 +80,15 @@ gst_wl_window_finalize (GObject * gobject)
|
|||
wl_shell_surface_destroy (self->shell_surface);
|
||||
}
|
||||
|
||||
if (self->subsurface) {
|
||||
wl_subsurface_destroy (self->subsurface);
|
||||
}
|
||||
wl_viewport_destroy (self->video_viewport);
|
||||
wl_subsurface_destroy (self->video_subsurface);
|
||||
wl_surface_destroy (self->video_surface);
|
||||
|
||||
wl_viewport_destroy (self->viewport);
|
||||
wl_surface_destroy (self->surface);
|
||||
if (self->area_subsurface) {
|
||||
wl_subsurface_destroy (self->area_subsurface);
|
||||
}
|
||||
wl_viewport_destroy (self->area_viewport);
|
||||
wl_surface_destroy (self->area_surface);
|
||||
|
||||
g_clear_object (&self->display);
|
||||
|
||||
|
@ -91,44 +96,84 @@ gst_wl_window_finalize (GObject * gobject)
|
|||
}
|
||||
|
||||
static GstWlWindow *
|
||||
gst_wl_window_new_internal (GstWlDisplay * display, struct wl_surface *surface)
|
||||
gst_wl_window_new_internal (GstWlDisplay * display)
|
||||
{
|
||||
GstWlWindow *window;
|
||||
GstVideoInfo info;
|
||||
GstBuffer *buf;
|
||||
GstMapInfo mapinfo;
|
||||
struct wl_buffer *wlbuf;
|
||||
GstWlBuffer *gwlbuf;
|
||||
struct wl_region *region;
|
||||
|
||||
g_return_val_if_fail (surface != NULL, NULL);
|
||||
|
||||
window = g_object_new (GST_TYPE_WL_WINDOW, NULL);
|
||||
window->display = g_object_ref (display);
|
||||
window->surface = surface;
|
||||
|
||||
/* make sure the surface runs on our local queue */
|
||||
wl_proxy_set_queue ((struct wl_proxy *) surface, display->queue);
|
||||
window->area_surface = wl_compositor_create_surface (display->compositor);
|
||||
window->video_surface = wl_compositor_create_surface (display->compositor);
|
||||
|
||||
window->viewport = wl_scaler_get_viewport (display->scaler, window->surface);
|
||||
wl_proxy_set_queue ((struct wl_proxy *) window->area_surface, display->queue);
|
||||
wl_proxy_set_queue ((struct wl_proxy *) window->video_surface,
|
||||
display->queue);
|
||||
|
||||
/* embed video_surface in area_surface */
|
||||
window->video_subsurface =
|
||||
wl_subcompositor_get_subsurface (display->subcompositor,
|
||||
window->video_surface, window->area_surface);
|
||||
wl_subsurface_set_desync (window->video_subsurface);
|
||||
|
||||
window->area_viewport = wl_scaler_get_viewport (display->scaler,
|
||||
window->area_surface);
|
||||
window->video_viewport = wl_scaler_get_viewport (display->scaler,
|
||||
window->video_surface);
|
||||
|
||||
/* draw the area_subsurface */
|
||||
gst_video_info_set_format (&info,
|
||||
/* we want WL_SHM_FORMAT_XRGB8888 */
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
GST_VIDEO_FORMAT_xRGB,
|
||||
#else
|
||||
GST_VIDEO_FORMAT_BGRx,
|
||||
#endif
|
||||
1, 1);
|
||||
|
||||
buf = gst_buffer_new_allocate (gst_wl_shm_allocator_get (), info.size, NULL);
|
||||
gst_buffer_map (buf, &mapinfo, GST_MAP_WRITE);
|
||||
*((guint32 *) mapinfo.data) = 0; /* paint it black */
|
||||
gst_buffer_unmap (buf, &mapinfo);
|
||||
wlbuf =
|
||||
gst_wl_shm_memory_construct_wl_buffer (gst_buffer_peek_memory (buf, 0),
|
||||
display, &info);
|
||||
gwlbuf = gst_buffer_add_wl_buffer (buf, wlbuf, display);
|
||||
gst_wl_buffer_attach (gwlbuf, window->area_surface);
|
||||
|
||||
/* at this point, the GstWlBuffer keeps the buffer
|
||||
* alive and will free it on wl_buffer::release */
|
||||
gst_buffer_unref (buf);
|
||||
|
||||
/* do not accept input */
|
||||
region = wl_compositor_create_region (display->compositor);
|
||||
wl_surface_set_input_region (surface, region);
|
||||
wl_surface_set_input_region (window->area_surface, region);
|
||||
wl_region_destroy (region);
|
||||
|
||||
region = wl_compositor_create_region (display->compositor);
|
||||
wl_surface_set_input_region (window->video_surface, region);
|
||||
wl_region_destroy (region);
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
GstWlWindow *
|
||||
gst_wl_window_new_toplevel (GstWlDisplay * display, GstVideoInfo * video_info)
|
||||
gst_wl_window_new_toplevel (GstWlDisplay * display, const GstVideoInfo * info)
|
||||
{
|
||||
GstWlWindow *window;
|
||||
gint width;
|
||||
|
||||
window = gst_wl_window_new_internal (display,
|
||||
wl_compositor_create_surface (display->compositor));
|
||||
|
||||
gst_wl_window_set_video_info (window, video_info);
|
||||
gst_wl_window_set_render_rectangle (window, 0, 0, window->video_width,
|
||||
window->video_height);
|
||||
window = gst_wl_window_new_internal (display);
|
||||
|
||||
/* go toplevel */
|
||||
window->shell_surface = wl_shell_get_shell_surface (display->shell,
|
||||
window->surface);
|
||||
window->area_surface);
|
||||
|
||||
if (window->shell_surface) {
|
||||
wl_shell_surface_add_listener (window->shell_surface,
|
||||
|
@ -141,6 +186,11 @@ gst_wl_window_new_toplevel (GstWlDisplay * display, GstVideoInfo * video_info)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* set the initial size to be the same as the reported video size */
|
||||
width =
|
||||
gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
|
||||
gst_wl_window_set_render_rectangle (window, 0, 0, width, info->height);
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
|
@ -149,13 +199,13 @@ gst_wl_window_new_in_surface (GstWlDisplay * display,
|
|||
struct wl_surface * parent)
|
||||
{
|
||||
GstWlWindow *window;
|
||||
window = gst_wl_window_new_internal (display);
|
||||
|
||||
window = gst_wl_window_new_internal (display,
|
||||
wl_compositor_create_surface (display->compositor));
|
||||
|
||||
window->subsurface = wl_subcompositor_get_subsurface (display->subcompositor,
|
||||
window->surface, parent);
|
||||
wl_subsurface_set_desync (window->subsurface);
|
||||
/* embed in parent */
|
||||
window->area_subsurface =
|
||||
wl_subcompositor_get_subsurface (display->subcompositor,
|
||||
window->area_surface, parent);
|
||||
wl_subsurface_set_desync (window->area_subsurface);
|
||||
|
||||
return window;
|
||||
}
|
||||
|
@ -173,7 +223,7 @@ gst_wl_window_get_wl_surface (GstWlWindow * window)
|
|||
{
|
||||
g_return_val_if_fail (window != NULL, NULL);
|
||||
|
||||
return window->surface;
|
||||
return window->video_surface;
|
||||
}
|
||||
|
||||
gboolean
|
||||
|
@ -185,22 +235,21 @@ gst_wl_window_is_toplevel (GstWlWindow * window)
|
|||
}
|
||||
|
||||
static void
|
||||
gst_wl_window_resize_internal (GstWlWindow * window, gboolean commit)
|
||||
gst_wl_window_resize_video_surface (GstWlWindow * window, gboolean commit)
|
||||
{
|
||||
GstVideoRectangle src, res;
|
||||
|
||||
/* center the video_subsurface inside area_subsurface */
|
||||
src.w = window->video_width;
|
||||
src.h = window->video_height;
|
||||
gst_video_sink_center_rect (src, window->render_rectangle, &res, TRUE);
|
||||
|
||||
if (window->subsurface)
|
||||
wl_subsurface_set_position (window->subsurface,
|
||||
window->render_rectangle.x + res.x, window->render_rectangle.y + res.y);
|
||||
wl_viewport_set_destination (window->viewport, res.w, res.h);
|
||||
wl_subsurface_set_position (window->video_subsurface, res.x, res.y);
|
||||
wl_viewport_set_destination (window->video_viewport, res.w, res.h);
|
||||
|
||||
if (commit) {
|
||||
wl_surface_damage (window->surface, 0, 0, res.w, res.h);
|
||||
wl_surface_commit (window->surface);
|
||||
wl_surface_damage (window->video_surface, 0, 0, res.w, res.h);
|
||||
wl_surface_commit (window->video_surface);
|
||||
}
|
||||
|
||||
/* this is saved for use in wl_surface_damage */
|
||||
|
@ -209,16 +258,37 @@ gst_wl_window_resize_internal (GstWlWindow * window, gboolean commit)
|
|||
}
|
||||
|
||||
void
|
||||
gst_wl_window_set_video_info (GstWlWindow * window, GstVideoInfo * info)
|
||||
gst_wl_window_render (GstWlWindow * window, GstWlBuffer * buffer,
|
||||
const GstVideoInfo * info)
|
||||
{
|
||||
g_return_if_fail (window != NULL);
|
||||
if (G_UNLIKELY (info)) {
|
||||
window->video_width =
|
||||
gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
|
||||
window->video_height = info->height;
|
||||
|
||||
window->video_width =
|
||||
gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
|
||||
window->video_height = info->height;
|
||||
wl_subsurface_set_sync (window->video_subsurface);
|
||||
gst_wl_window_resize_video_surface (window, FALSE);
|
||||
}
|
||||
|
||||
if (window->render_rectangle.w != 0)
|
||||
gst_wl_window_resize_internal (window, FALSE);
|
||||
if (G_LIKELY (buffer))
|
||||
gst_wl_buffer_attach (buffer, window->video_surface);
|
||||
else
|
||||
wl_surface_attach (window->video_surface, NULL, 0, 0);
|
||||
|
||||
wl_surface_damage (window->video_surface, 0, 0, window->surface_width,
|
||||
window->surface_height);
|
||||
wl_surface_commit (window->video_surface);
|
||||
|
||||
if (G_UNLIKELY (info)) {
|
||||
/* commit also the parent (area_surface) in order to change
|
||||
* the position of the video_subsurface */
|
||||
wl_surface_damage (window->area_surface, 0, 0, window->render_rectangle.w,
|
||||
window->render_rectangle.h);
|
||||
wl_surface_commit (window->area_surface);
|
||||
wl_subsurface_set_desync (window->video_subsurface);
|
||||
}
|
||||
|
||||
wl_display_flush (window->display->display);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -232,6 +302,21 @@ gst_wl_window_set_render_rectangle (GstWlWindow * window, gint x, gint y,
|
|||
window->render_rectangle.w = w;
|
||||
window->render_rectangle.h = h;
|
||||
|
||||
/* position the area inside the parent - needs a parent commit to apply */
|
||||
if (window->area_subsurface)
|
||||
wl_subsurface_set_position (window->area_subsurface, x, y);
|
||||
|
||||
/* change the size of the area */
|
||||
wl_viewport_set_destination (window->area_viewport, w, h);
|
||||
|
||||
if (window->video_width != 0) {
|
||||
wl_subsurface_set_sync (window->video_subsurface);
|
||||
gst_wl_window_resize_video_surface (window, TRUE);
|
||||
}
|
||||
|
||||
wl_surface_damage (window->area_surface, 0, 0, w, h);
|
||||
wl_surface_commit (window->area_surface);
|
||||
|
||||
if (window->video_width != 0)
|
||||
gst_wl_window_resize_internal (window, TRUE);
|
||||
wl_subsurface_set_desync (window->video_subsurface);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#define __GST_WL_WINDOW_H__
|
||||
|
||||
#include "wldisplay.h"
|
||||
#include "wlbuffer.h"
|
||||
#include <gst/video/video.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
@ -41,16 +42,19 @@ struct _GstWlWindow
|
|||
GObject parent_instance;
|
||||
|
||||
GstWlDisplay *display;
|
||||
struct wl_surface *surface;
|
||||
struct wl_subsurface *subsurface;
|
||||
struct wl_viewport *viewport;
|
||||
struct wl_surface *area_surface;
|
||||
struct wl_subsurface *area_subsurface;
|
||||
struct wl_viewport *area_viewport;
|
||||
struct wl_surface *video_surface;
|
||||
struct wl_subsurface *video_subsurface;
|
||||
struct wl_viewport *video_viewport;
|
||||
struct wl_shell_surface *shell_surface;
|
||||
|
||||
/* the size of the destination area where we are overlaying our subsurface */
|
||||
/* the size and position of the area_(sub)surface */
|
||||
GstVideoRectangle render_rectangle;
|
||||
/* the size of the video in the buffers */
|
||||
gint video_width, video_height;
|
||||
/* the size of the (sub)surface */
|
||||
/* the size of the video_(sub)surface */
|
||||
gint surface_width, surface_height;
|
||||
};
|
||||
|
||||
|
@ -62,7 +66,7 @@ struct _GstWlWindowClass
|
|||
GType gst_wl_window_get_type (void);
|
||||
|
||||
GstWlWindow *gst_wl_window_new_toplevel (GstWlDisplay * display,
|
||||
GstVideoInfo * video_info);
|
||||
const GstVideoInfo * info);
|
||||
GstWlWindow *gst_wl_window_new_in_surface (GstWlDisplay * display,
|
||||
struct wl_surface * parent);
|
||||
|
||||
|
@ -70,8 +74,8 @@ GstWlDisplay *gst_wl_window_get_display (GstWlWindow * window);
|
|||
struct wl_surface *gst_wl_window_get_wl_surface (GstWlWindow * window);
|
||||
gboolean gst_wl_window_is_toplevel (GstWlWindow *window);
|
||||
|
||||
/* functions to manipulate the size on non-toplevel windows */
|
||||
void gst_wl_window_set_video_info (GstWlWindow * window, GstVideoInfo * info);
|
||||
void gst_wl_window_render (GstWlWindow * window, GstWlBuffer * buffer,
|
||||
const GstVideoInfo * info);
|
||||
void gst_wl_window_set_render_rectangle (GstWlWindow * window, gint x, gint y,
|
||||
gint w, gint h);
|
||||
|
||||
|
|
Loading…
Reference in a new issue