/*****************************************************************************
* video_window.c: vout-specific window management
*****************************************************************************
* Copyright © 2014-2021 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <stdlib.h>
#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_window.h>
#include <vlc_vout.h>
#include <vlc_vout_display.h>
#include <vlc_ancillary.h>
#include "video_window.h"
#include "vout_internal.h"
#define DOUBLE_CLICK_TIME VLC_TICK_FROM_MS(300)
struct vlc_window_ack_data {
vlc_window_t *window;
vlc_window_ack_cb callback;
unsigned width;
unsigned height;
void *opaque;
};
static void vlc_window_Ack(void *data)
{
struct vlc_window_ack_data *cb_data = data;
if (cb_data->callback != NULL)
cb_data->callback(cb_data->window, cb_data->width, cb_data->height,
cb_data->opaque);
}
typedef struct vout_display_window
{
vout_thread_t *vout;
video_format_t format;
struct vout_display_placement display;
vlc_mutex_t lock;
struct {
vlc_mouse_t window;
vlc_mouse_t video;
vlc_tick_t last_left_press;
vlc_mouse_event event;
void *opaque;
} mouse;
} vout_display_window_t;
static void vout_display_window_ResizeNotify(vlc_window_t *window,
unsigned width, unsigned height,
vlc_window_ack_cb cb,
void *opaque)
{
vout_display_window_t *state = window->owner.sys;
vout_thread_t *vout = state->vout;
struct vlc_window_ack_data data = { window, cb, width, height, opaque };
vlc_mutex_lock(&state->lock);
state->display.width = width;
state->display.height = height;
vlc_mutex_unlock(&state->lock);
msg_Dbg(window, "resized to %ux%u", width, height);
vout_ChangeDisplaySize(vout, width, height, vlc_window_Ack, &data);
}
static void vout_display_window_CloseNotify(vlc_window_t *window)
{
/* TODO: Nowhere to dispatch to currently.
* Needs callback to ES output to deselect ES? */
msg_Err(window, "window closed");
}
static void vout_display_window_StateNotify(vlc_window_t *window,
unsigned window_state)
{
vout_display_window_t *state = window->owner.sys;
vout_thread_t *vout = state->vout;
static const char states[][8] = {
[VLC_WINDOW_STATE_NORMAL] = "normal",
[VLC_WINDOW_STATE_ABOVE] = "above",
[VLC_WINDOW_STATE_BELOW] = "below",
};
assert(window_state < ARRAY_SIZE(states));
msg_Dbg(window, "window state changed: %s", states[window_state]);
var_SetInteger(vout, "window-state", window_state);
}
static void vout_display_window_FullscreenNotify(vlc_window_t *window,
const char *id)
{
vout_display_window_t *state = window->owner.sys;
vout_thread_t *vout = state->vout;
msg_Dbg(window, (id != NULL) ? "window set to fullscreen on %s"
: "window set to fullscreen", id);
var_SetString(vout, "window-fullscreen-output",
(id != NULL) ? id : "");
var_SetBool(vout, "window-fullscreen", true);
}
static void vout_display_window_WindowingNotify(vlc_window_t *window)
{
vout_display_window_t *state = window->owner.sys;
vout_thread_t *vout = state->vout;
msg_Dbg(window, "window set windowed");
var_SetBool(vout, "window-fullscreen", false);
}
static void vout_display_window_MouseEvent(vlc_window_t *window,
const vlc_window_mouse_event_t *ev)
{
vout_display_window_t *state = window->owner.sys;
vout_thread_t *vout = state->vout;
vlc_mouse_t *m = &state->mouse.window;
m->b_double_click = false;
switch (ev->type)
{
case VLC_WINDOW_MOUSE_MOVED:
vlc_mouse_SetPosition(m, ev->x, ev->y);
state->mouse.last_left_press = VLC_TICK_MIN;
break;
case VLC_WINDOW_MOUSE_PRESSED:
if (!window->info.has_double_click
&& ev->button_mask == MOUSE_BUTTON_LEFT
&& !vlc_mouse_IsLeftPressed(m))
{
const vlc_tick_t now = vlc_tick_now();
if (state->mouse.last_left_press != VLC_TICK_MIN
&& now - state->mouse.last_left_press < DOUBLE_CLICK_TIME)
{
m->b_double_click = true;
state->mouse.last_left_press = VLC_TICK_MIN;
}
else
state->mouse.last_left_press = now;
}
vlc_mouse_SetPressed(m, ev->button_mask);
break;
case VLC_WINDOW_MOUSE_RELEASED:
vlc_mouse_SetReleased(m, ev->button_mask);
break;
case VLC_WINDOW_MOUSE_DOUBLE_CLICK:
assert(window->info.has_double_click);
m->b_double_click = true;
vlc_mouse_SetPressed(m, ev->button_mask);
break;
default:
vlc_assert_unreachable();
}
vlc_mouse_t video_mouse = *m;
vlc_mutex_lock(&state->lock);
if (likely(state->format.i_visible_width != 0
&& state->format.i_visible_height != 0
&& state->display.width != 0 && state->display.height != 0))
vout_display_TranslateCoordinates(&video_mouse.i_x, &video_mouse.i_y,
&state->format, &state->display);
vlc_mutex_unlock(&state->lock);
if (video_mouse.b_double_click && vlc_mouse_IsLeftPressed(&video_mouse))
var_ToggleBool(vout, "fullscreen");
/* Stop propagation if the event was consumed by a video filter */
if (vout_FilterMouse(vout, &video_mouse))
return;
/* Check if the mouse state actually changed and emit events. */
/* NOTE: sys->mouse is only used here, so no need to lock. */
if (vlc_mouse_HasMoved(&state->mouse.video, &video_mouse))
var_SetCoords(vout, "mouse-moved", m->i_x, m->i_y);
if (vlc_mouse_HasButton(&state->mouse.video, &video_mouse))
var_SetInteger(vout, "mouse-button-down", video_mouse.i_pressed);
state->mouse.video = video_mouse;
vlc_mutex_lock(&state->lock);
/* Mouse events are only initialised if the display exists. */
if (state->mouse.event != NULL)
state->mouse.event(&video_mouse, state->mouse.opaque);
vlc_mutex_unlock(&state->lock);
}
static void vout_display_window_KeyboardEvent(vlc_window_t *window,
unsigned key)
{
var_SetInteger(vlc_object_instance(window), "key-pressed", key);
}
static void vout_display_window_OutputEvent(vlc_window_t *window,
const char *name, const char *desc)
{
if (desc != NULL)
msg_Dbg(window, "fullscreen output %s (%s) added", name, desc);
else
msg_Dbg(window, "fullscreen output %s removed", name);
}
static void vout_display_window_IccEvent(vlc_window_t *window,
vlc_icc_profile_t *profile)
{
vout_display_window_t *state = window->owner.sys;
vout_thread_t *vout = state->vout;
if (profile != NULL)
msg_Dbg(window, "window got ICC profile (%zu bytes)", profile->size);
else
msg_Dbg(window, "window ICC profile cleared");
vout_ChangeIccProfile(vout, profile);
}
static const struct vlc_window_callbacks vout_display_window_cbs = {
.resized = vout_display_window_ResizeNotify,
.closed = vout_display_window_CloseNotify,
.state_changed = vout_display_window_StateNotify,
.fullscreened = vout_display_window_FullscreenNotify,
.windowed = vout_display_window_WindowingNotify,
.mouse_event = vout_display_window_MouseEvent,
.keyboard_event = vout_display_window_KeyboardEvent,
.output_event = vout_display_window_OutputEvent,
.icc_event = vout_display_window_IccEvent,
};
void vout_display_window_SetMouseHandler(vlc_window_t *window,
vlc_mouse_event event, void *opaque)
{
vout_display_window_t *state = window->owner.sys;
/*
* Note that the current implementation of this function is technically
* reentrant, but better not rely on this in calling code.
*/
vlc_mutex_lock(&state->lock);
if (state->mouse.event != NULL)
state->mouse.event(NULL, state->mouse.opaque);
state->mouse.event = event;
state->mouse.opaque = opaque;
vlc_mutex_unlock(&state->lock);
}
static
void vout_display_SizeWindow(unsigned *restrict width,
unsigned *restrict height,
const video_format_t *restrict original,
const vlc_rational_t *restrict dar,
const struct vout_crop *restrict crop,
const struct vout_display_placement *restrict dp)
{
unsigned w = original->i_visible_width;
unsigned h = original->i_visible_height;
unsigned sar_num = original->i_sar_num;
unsigned sar_den = original->i_sar_den;
if (dar->num > 0 && dar->den > 0) {
unsigned num = dar->num * h;
unsigned den = dar->den * w;
vlc_ureduce(&sar_num, &sar_den, num, den, 0);
}
switch (crop->mode) {
case VOUT_CROP_NONE:
break;
case VOUT_CROP_RATIO: {
unsigned num = crop->ratio.num;
unsigned den = crop->ratio.den;
if (w * den > h * num)
w = h * num / den;
else
h = w * den / num;
break;
}
case VOUT_CROP_WINDOW:
w = crop->window.width;
h = crop->window.height;
break;
case VOUT_CROP_BORDER:
w = crop->border.right - crop->border.left;
h = crop->border.bottom - crop->border.top;
break;
}
*width = dp->width;
*height = dp->height;
/* If both width and height are forced, keep them as is. */
if (*width != 0 && *height != 0)
return;
/* Compute intended video resolution from source. */
assert(sar_num > 0 && sar_den > 0);
w = (w * sar_num) / sar_den;
/* Adjust video size for orientation and pixel A/R. */
if (ORIENT_IS_SWAP(original->orientation)) {
unsigned x = w;
w = h;
h = x;
}
if (dp->sar.num > 0 && dp->sar.den > 0)
w = (w * dp->sar.den) / dp->sar.num;
/* If width is forced, adjust height according to the aspect ratio */
if (*width != 0) {
*height = (*width * h) / w;
return;
}
/* If height is forced, adjust width according to the aspect ratio */
if (*height != 0) {
*width = (*height * w) / h;
return;
}
/* If neither width nor height are forced, use the requested zoom. */
*width = (w * dp->zoom.num) / dp->zoom.den;
*height = (h * dp->zoom.num) / dp->zoom.den;
}
void vout_display_ResizeWindow(struct vlc_window *window,
const video_format_t *restrict original,
const vlc_rational_t *restrict dar,
const struct vout_crop *restrict crop,
const struct vout_display_placement *restrict dp)
{
vout_display_window_t *state = window->owner.sys;
unsigned width, height;
vlc_mutex_lock(&state->lock);
video_format_Clean(&state->format);
video_format_Copy(&state->format, original);
width = state->display.width;
height = state->display.height;
state->display = *dp;
state->display.width = width;
state->display.height = height;
vlc_mutex_unlock(&state->lock);
vout_display_SizeWindow(&width, &height, original, dar, crop, dp);
msg_Dbg(window, "requested window size: %ux%u", width, height);
vlc_window_SetSize(window, width, height);
}
/**
* Creates a video window, initially without any attached display.
*/
vlc_window_t *vout_display_window_New(vout_thread_t *vout)
{
vout_display_window_t *state = malloc(sizeof (*state));
if (state == NULL)
return NULL;
video_format_Init(&state->format, 0);
state->display.width = state->display.height = 0;
vlc_mutex_init(&state->lock);
vlc_mouse_Init(&state->mouse.window);
vlc_mouse_Init(&state->mouse.video);
state->mouse.last_left_press = VLC_TICK_MIN;
state->mouse.event = NULL;
state->vout = vout;
char *modlist = var_InheritString(vout, "window");
vlc_window_owner_t owner = {
.cbs = &vout_display_window_cbs,
.sys = state,
};
vlc_window_cfg_t cfg = {
.is_fullscreen = var_GetBool(&vout->obj, "fullscreen"),
.is_decorated = var_InheritBool(&vout->obj, "video-deco"),
#if defined(__APPLE__) || defined(_WIN32)
.x = var_InheritInteger(&vout->obj, "video-x"),
.y = var_InheritInteger(&vout->obj, "video-y"),
#endif
};
vlc_window_t *window;
var_Create(vout, "window-state", VLC_VAR_INTEGER);
var_Create(vout, "window-fullscreen", VLC_VAR_BOOL);
var_Create(vout, "window-fullscreen-output", VLC_VAR_STRING);
window = vlc_window_New(VLC_OBJECT(vout), modlist, &owner, &cfg);
free(modlist);
if (window == NULL)
free(state);
return window;
}
/**
* Destroys a video window.
* \note The window must be detached.
*/
void vout_display_window_Delete(vlc_window_t *window)
{
vout_display_window_t *state = window->owner.sys;
vout_thread_t *vout = state->vout;
vlc_window_Delete(window);
var_Destroy(vout, "window-fullscreen-output");
var_Destroy(vout, "window-fullscreen");
var_Destroy(vout, "window-state");
video_format_Clean(&state->format);
free(state);
}
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|