/**
* @file window.c
* @brief Win32 non-embedded video window provider
*/
/*****************************************************************************
* Copyright © 2007-2010 VLC authors and VideoLAN
* 2016-2019 VideoLabs, VLC authors and VideoLAN
*
* Authors: Filippo Carone <filippo@carone.org>
* Pierre d'Herbemont <pdherbemont # videolan.org>
*
* 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 <stdatomic.h>
#include <stdarg.h>
#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_charset.h>
#include <vlc_frame.h>
#include <vlc_plugin.h>
#include <vlc_window.h>
#include <vlc_mouse.h>
#include <vlc_actions.h>
#include <vlc_ancillary.h>
#include <shellapi.h> /* ExtractIcon */
#include "../wasync_resize_compressor.h"
#define RECTWidth(r) (LONG)((r).right - (r).left)
#define RECTHeight(r) (LONG)((r).bottom - (r).top)
#define WM_VLC_CHANGE_TEXT (WM_APP + 1)
#define WM_VLC_SET_TOP_STATE (WM_APP + 2)
#define IDM_TOGGLE_ON_TOP (WM_USER + 1)
typedef struct vout_window_sys_t
{
vlc_thread_t thread;
vlc_sem_t ready;
HWND hwnd;
HMONITOR monitor; /* last monitor associated with window */
WCHAR class_main[256];
HICON vlc_icon;
/* icc profile */
char *icc_profile;
/* mouse */
unsigned button_pressed;
/* cursor */
bool is_cursor_hidden;
HCURSOR cursor_arrow;
HCURSOR cursor_empty;
int64_t hide_timeout; /* mouse hiding timeout in ms */
vlc_tick_t last_moved;
/* state before fullscreen */
WINDOWPLACEMENT window_placement;
LONG i_window_style;
/* Title */
wchar_t *_Atomic pwz_title;
vlc_wasync_resize_compressor_t compressor;
} vout_window_sys_t;
static void Resize(vlc_window_t *wnd, unsigned width, unsigned height)
{
vout_window_sys_t *sys = wnd->sys;
/* When you create a window you give the dimensions you wish it to
* have. Unfortunately these dimensions will include the borders and
* titlebar. We use the following function to find out the size of
* the window corresponding to the usable surface we want */
RECT decorated_window = {
.right = width,
.bottom = height,
};
LONG i_window_style = GetWindowLong(sys->hwnd, GWL_STYLE);
AdjustWindowRect( &decorated_window, i_window_style, 0 );
SetWindowPos(sys->hwnd, 0, 0, 0,
RECTWidth(decorated_window), RECTHeight(decorated_window),
SWP_NOZORDER|SWP_NOMOVE);
}
static int Enable(vlc_window_t *wnd, const vlc_window_cfg_t *cfg)
{
vout_window_sys_t *sys = wnd->sys;
LONG i_window_style;
if (cfg->is_decorated)
i_window_style = WS_OVERLAPPEDWINDOW | WS_SIZEBOX;
else
i_window_style = WS_POPUP;
i_window_style |= WS_CLIPCHILDREN;
/* allow user to regain control over input events if requested */
bool b_mouse_support = var_InheritBool( wnd, "mouse-events" );
bool b_key_support = var_InheritBool( wnd, "keyboard-events" );
if( !b_mouse_support && !b_key_support )
i_window_style |= WS_DISABLED;
SetWindowLong(sys->hwnd, GWL_STYLE, i_window_style);
if (cfg->x || cfg->y)
MoveWindow(sys->hwnd, cfg->x, cfg->y, cfg->width, cfg->height, TRUE);
Resize(wnd, cfg->width, cfg->height);
ShowWindow( sys->hwnd, SW_SHOW );
return VLC_SUCCESS;
}
static void Disable(struct vlc_window *wnd)
{
vout_window_sys_t *sys = wnd->sys;
ShowWindow( sys->hwnd, SW_HIDE );
}
static void SetState(vlc_window_t *wnd, unsigned state)
{
vout_window_sys_t *sys = wnd->sys;
PostMessage( sys->hwnd, WM_VLC_SET_TOP_STATE, state, 0);
}
static void SetTitle(vlc_window_t *wnd, const char *title)
{
vout_window_sys_t *sys = wnd->sys;
char *psz_title = var_InheritString( wnd, "video-title" );
wchar_t *pwz_title = ToWide(psz_title ? psz_title : title);
free(psz_title);
if (unlikely(pwz_title == NULL))
return;
free(atomic_exchange(&sys->pwz_title, pwz_title));
PostMessage( sys->hwnd, WM_VLC_CHANGE_TEXT, 0, 0 );
}
static void SetFullscreen(vlc_window_t *wnd, const char *id)
{
VLC_UNUSED(id);
msg_Dbg(wnd, "entering fullscreen mode");
vout_window_sys_t *sys = wnd->sys;
sys->window_placement.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(sys->hwnd, &sys->window_placement);
sys->i_window_style = GetWindowLong(sys->hwnd, GWL_STYLE);
/* Change window style, no borders and no title bar */
SetWindowLong(sys->hwnd, GWL_STYLE, WS_CLIPCHILDREN | WS_VISIBLE);
/* Retrieve current window position so fullscreen will happen
* on the right screen */
HMONITOR hmon = MonitorFromWindow(sys->hwnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi;
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(hmon, &mi))
SetWindowPos(sys->hwnd, 0,
mi.rcMonitor.left,
mi.rcMonitor.top,
RECTWidth(mi.rcMonitor),
RECTHeight(mi.rcMonitor),
SWP_NOZORDER|SWP_FRAMECHANGED);
}
static void UnsetFullscreen(vlc_window_t *wnd)
{
msg_Dbg(wnd, "leaving fullscreen mode");
vout_window_sys_t *sys = wnd->sys;
/* return to normal window for non embedded vout */
if (sys->window_placement.length)
{
SetWindowLong(sys->hwnd, GWL_STYLE, sys->i_window_style);
SetWindowPlacement(sys->hwnd, &sys->window_placement);
sys->window_placement.length = 0;
}
ShowWindow(sys->hwnd, SW_SHOWNORMAL);
}
static void SetAbove( vlc_window_t *wnd, enum vlc_window_state state )
{
vout_window_sys_t *sys = wnd->sys;
switch (state) {
case VLC_WINDOW_STATE_NORMAL:
if ((GetWindowLong(sys->hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST))
{
HMENU hMenu = GetSystemMenu(sys->hwnd, FALSE);
CheckMenuItem(hMenu, IDM_TOGGLE_ON_TOP, MF_BYCOMMAND | MFS_UNCHECKED);
SetWindowPos(sys->hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE);
}
break;
case VLC_WINDOW_STATE_ABOVE:
if (!(GetWindowLong(sys->hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST))
{
HMENU hMenu = GetSystemMenu(sys->hwnd, FALSE);
CheckMenuItem(hMenu, IDM_TOGGLE_ON_TOP, MF_BYCOMMAND | MFS_CHECKED);
SetWindowPos(sys->hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}
break;
case VLC_WINDOW_STATE_BELOW:
SetWindowPos(sys->hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
break;
}
}
static void MousePressed( vlc_window_t *wnd, HWND hwnd, unsigned button )
{
vout_window_sys_t *sys = wnd->sys;
if( !sys->button_pressed )
SetCapture( hwnd );
sys->button_pressed |= 1 << button;
vlc_window_ReportMousePressed(wnd, button);
}
static void MouseReleased( vlc_window_t *wnd, unsigned button )
{
vout_window_sys_t *sys = wnd->sys;
sys->button_pressed &= ~(1 << button);
if( !sys->button_pressed )
ReleaseCapture();
vlc_window_ReportMouseReleased(wnd, button);
}
#define MAX_ICC_FILE_SIZE (100*1024*1024)
static void UpdateICCProfile(vlc_window_t *wnd, const wchar_t *pathw)
{
vout_window_sys_t *sys = wnd->sys;
vlc_icc_profile_t *icc = NULL;
if (!pathw) {
/* Profile cleared */
if (sys->icc_profile) {
free(sys->icc_profile);
sys->icc_profile = NULL;
vlc_window_ReportICCProfile(wnd, NULL);
}
return;
}
char *path = FromWide(pathw);
if (!path)
goto error;
if (sys->icc_profile && strcmp(path, sys->icc_profile) == 0) {
/* No change to profile */
free(path);
return;
}
free(sys->icc_profile);
sys->icc_profile = path;
/* Open ICC profile */
vlc_frame_t *frame = vlc_frame_FilePath(path, false);
if (!frame)
goto error;
vlc_frame_cleanup_push(frame);
icc = malloc(sizeof(*icc) + frame->i_buffer);
if (!icc)
goto error;
icc->size = frame->i_buffer;
memcpy(icc->data, frame->p_buffer, icc->size);
/* fall through */
error:
if (!icc)
msg_Err(wnd, "Failed to open ICC profile: %s", path ? path : "(unknown)");
vlc_cleanup_pop();
vlc_window_ReportICCProfile(wnd, icc);
}
static void MonitorChanged(vlc_window_t *wnd, HMONITOR monitor)
{
MONITORINFOEXW mi = { .cbSize = sizeof(mi) };
GetMonitorInfoW(monitor, (LPMONITORINFO) &mi);
/* Try updating ICC profile */
HDC ic = CreateICW(mi.szDevice, NULL, NULL, NULL);
if (!ic)
return;
wchar_t iccw[MAX_PATH];
DWORD iccw_len = ARRAY_SIZE(iccw);
if (GetICMProfileW(ic, &iccw_len, iccw)) {
UpdateICCProfile(wnd, iccw);
} else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
wchar_t *piccw = malloc( iccw_len * sizeof(*piccw) );
if (piccw != NULL && GetICMProfileW(ic, &iccw_len, piccw))
UpdateICCProfile(wnd, piccw);
else
UpdateICCProfile(wnd, NULL);
free(piccw);
} else {
UpdateICCProfile(wnd, NULL);
}
DeleteDC(ic);
}
static struct
{
int i_dxkey;
int i_vlckey;
} dxkeys_to_vlckeys[] =
{
{ VK_F1, KEY_F1 }, { VK_F2, KEY_F2 }, { VK_F3, KEY_F3 }, { VK_F4, KEY_F4 },
{ VK_F5, KEY_F5 }, { VK_F6, KEY_F6 }, { VK_F7, KEY_F7 }, { VK_F8, KEY_F8 },
{ VK_F9, KEY_F9 }, { VK_F10, KEY_F10 }, { VK_F11, KEY_F11 },
{ VK_F12, KEY_F12 }, { VK_F13, KEY_F(13) }, { VK_F14, KEY_F(14) },
{ VK_F15, KEY_F(15) }, { VK_F16, KEY_F(16) }, { VK_F17, KEY_F(17) },
{ VK_F18, KEY_F(18) }, { VK_F19, KEY_F(19) }, { VK_F20, KEY_F(20) },
{ VK_F21, KEY_F(21) }, { VK_F22, KEY_F(22) }, { VK_F23, KEY_F(23) },
{ VK_F24, KEY_F(24) },
{ VK_RETURN, KEY_ENTER },
{ VK_SPACE, ' ' },
{ VK_ESCAPE, KEY_ESC },
{ VK_LEFT, KEY_LEFT },
{ VK_RIGHT, KEY_RIGHT },
{ VK_UP, KEY_UP },
{ VK_DOWN, KEY_DOWN },
{ VK_HOME, KEY_HOME },
{ VK_END, KEY_END },
{ VK_PRIOR, KEY_PAGEUP },
{ VK_NEXT, KEY_PAGEDOWN },
{ VK_INSERT, KEY_INSERT },
{ VK_DELETE, KEY_DELETE },
{ VK_CONTROL, 0 },
{ VK_SHIFT, 0 },
{ VK_MENU, 0 },
{ VK_BROWSER_BACK, KEY_BROWSER_BACK },
{ VK_BROWSER_FORWARD, KEY_BROWSER_FORWARD },
{ VK_BROWSER_REFRESH, KEY_BROWSER_REFRESH },
{ VK_BROWSER_STOP, KEY_BROWSER_STOP },
{ VK_BROWSER_SEARCH, KEY_BROWSER_SEARCH },
{ VK_BROWSER_FAVORITES, KEY_BROWSER_FAVORITES },
{ VK_BROWSER_HOME, KEY_BROWSER_HOME },
{ VK_VOLUME_MUTE, KEY_VOLUME_MUTE },
{ VK_VOLUME_DOWN, KEY_VOLUME_DOWN },
{ VK_VOLUME_UP, KEY_VOLUME_UP },
{ VK_MEDIA_NEXT_TRACK, KEY_MEDIA_NEXT_TRACK },
{ VK_MEDIA_PREV_TRACK, KEY_MEDIA_PREV_TRACK },
{ VK_MEDIA_STOP, KEY_MEDIA_STOP },
{ VK_MEDIA_PLAY_PAUSE, KEY_MEDIA_PLAY_PAUSE },
{ 0, 0 }
};
static int Win32VoutConvertKey( int i_key )
{
for( int i = 0; dxkeys_to_vlckeys[i].i_dxkey != 0; i++ )
{
if( dxkeys_to_vlckeys[i].i_dxkey == i_key )
{
return dxkeys_to_vlckeys[i].i_vlckey;
}
}
return 0;
}
/*****************************************************************************
* WinVoutEventProc: This is the window event processing function.
*****************************************************************************
* On Windows, when you create a window you have to attach an event processing
* function to it. The aim of this function is to manage "Queued Messages" and
* "Nonqueued Messages".
* Queued Messages are those picked up and retransmitted by vout_Manage
* (using the GetMessage and DispatchMessage functions).
* Nonqueued Messages are those that Windows will send directly to this
* procedure (like WM_DESTROY, WM_WINDOWPOSCHANGED...)
*****************************************************************************/
static LRESULT CALLBACK WinVoutEventProc( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam )
{
if( message == WM_CREATE )
{
/* Store wnd for future use */
CREATESTRUCT *c = (CREATESTRUCT *)lParam;
SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)c->lpCreateParams );
return 0;
}
LONG_PTR p_user_data = GetWindowLongPtr( hwnd, GWLP_USERDATA );
if( p_user_data == 0 )
return DefWindowProc(hwnd, message, wParam, lParam);
vlc_window_t *wnd = (vlc_window_t *)p_user_data;
switch( message )
{
case WM_CLOSE:
{
vout_window_sys_t *sys = wnd->sys;
/* wait for all pending timers */
vlc_wasync_resize_compressor_dropOrWait(&sys->compressor);
vlc_window_ReportClose(wnd);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_SIZE:
{
vout_window_sys_t *sys = wnd->sys;
vlc_wasync_resize_compressor_reportSize(&sys->compressor, LOWORD(lParam), HIWORD(lParam));
return 0;
}
case WM_MOUSEMOVE:
vlc_window_ReportMouseMoved(wnd, LOWORD(lParam), HIWORD(lParam));
break;
case WM_NCMOUSEMOVE:
break;
case WM_DISPLAYCHANGE:
{
vout_window_sys_t *sys = wnd->sys;
sys->monitor = NULL;
}
/* fall through */
case WM_MOVE:
{
vout_window_sys_t *sys = wnd->sys;
HMONITOR hmon = MonitorFromWindow(sys->hwnd, MONITOR_DEFAULTTONEAREST);
if (hmon != sys->monitor) {
sys->monitor = hmon;
MonitorChanged(wnd, hmon);
}
break;
}
case WM_CAPTURECHANGED:
{
vout_window_sys_t *sys = wnd->sys;
for( int button = 0; sys->button_pressed; button++ )
{
unsigned m = 1 << button;
if( sys->button_pressed & m )
vlc_window_ReportMouseReleased(wnd, button);
sys->button_pressed &= ~m;
}
sys->button_pressed = 0;
break;
}
case WM_LBUTTONDOWN:
MousePressed( wnd, hwnd, MOUSE_BUTTON_LEFT );
return 0;
case WM_LBUTTONUP:
MouseReleased( wnd, MOUSE_BUTTON_LEFT );
return 0;
case WM_LBUTTONDBLCLK:
vlc_window_ReportMouseDoubleClick(wnd, MOUSE_BUTTON_LEFT);
return 0;
case WM_MBUTTONDOWN:
MousePressed( wnd, hwnd, MOUSE_BUTTON_CENTER );
return 0;
case WM_MBUTTONUP:
MouseReleased( wnd, MOUSE_BUTTON_CENTER );
return 0;
case WM_MBUTTONDBLCLK:
vlc_window_ReportMouseDoubleClick(wnd, MOUSE_BUTTON_CENTER);
return 0;
case WM_RBUTTONDOWN:
MousePressed( wnd, hwnd, MOUSE_BUTTON_RIGHT );
return 0;
case WM_RBUTTONUP:
MouseReleased( wnd, MOUSE_BUTTON_RIGHT );
return 0;
case WM_RBUTTONDBLCLK:
vlc_window_ReportMouseDoubleClick(wnd, MOUSE_BUTTON_RIGHT);
return 0;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
/* The key events are first processed here and not translated
* into WM_CHAR events because we need to know the status of the
* modifier keys. */
int i_key = Win32VoutConvertKey( wParam );
if( !i_key )
{
/* This appears to be a "normal" (ascii) key */
i_key = tolower( (unsigned char)MapVirtualKey( wParam, 2 ) );
}
if( i_key )
{
if( GetKeyState(VK_CONTROL) & KF_UP )
{
i_key |= KEY_MODIFIER_CTRL;
}
if( GetKeyState(VK_SHIFT) & KF_UP )
{
i_key |= KEY_MODIFIER_SHIFT;
}
if( GetKeyState(VK_MENU) & KF_UP )
{
i_key |= KEY_MODIFIER_ALT;
}
vlc_window_ReportKeyPress(wnd, i_key);
}
break;
}
case WM_SYSCOMMAND:
switch (wParam)
{
case IDM_TOGGLE_ON_TOP: /* toggle the "on top" status */
{
HMENU hMenu = GetSystemMenu(hwnd, FALSE);
const bool is_on_top = (GetMenuState(hMenu, IDM_TOGGLE_ON_TOP, MF_BYCOMMAND) & MF_CHECKED) == 0;
SetAbove( wnd, is_on_top ? VLC_WINDOW_STATE_ABOVE : VLC_WINDOW_STATE_NORMAL );
return 0;
}
default:
break;
}
break;
case WM_VLC_SET_TOP_STATE:
SetAbove( wnd, (enum vlc_window_state) wParam);
return 0;
case WM_VLC_CHANGE_TEXT:
{
vout_window_sys_t *sys = wnd->sys;
wchar_t *pwz_title = atomic_exchange(&sys->pwz_title, NULL);
if( pwz_title )
{
SetWindowTextW( hwnd, pwz_title );
free( pwz_title );
}
break;
}
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static void Close(vlc_window_t *wnd)
{
vout_window_sys_t *sys = wnd->sys;
if (sys->hwnd)
PostMessage( sys->hwnd, WM_CLOSE, 0, 0 );
vlc_join(sys->thread, NULL);
free(atomic_load(&sys->pwz_title));
vlc_wasync_resize_compressor_destroy(&sys->compressor);
HINSTANCE hInstance = GetModuleHandle(NULL);
UnregisterClass( sys->class_main, hInstance );
DestroyCursor( sys->cursor_empty );
if( sys->vlc_icon )
DestroyIcon( sys->vlc_icon );
wnd->sys = NULL;
}
static int CALLBACK enumWindowsProc(HWND hwnd, LPARAM lParam)
{
HWND *wnd = (HWND *)lParam;
char name[128];
name[0] = '\0';
GetClassNameA( hwnd, name, 128 );
if( !strcasecmp( name, "WorkerW" ) )
{
hwnd = FindWindowEx( hwnd, NULL, TEXT("SHELLDLL_DefView"), NULL );
if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, TEXT("SysListView32"), NULL );
if( hwnd )
{
*wnd = hwnd;
return false;
}
}
return true;
}
static HWND GetDesktopHandle(vlc_object_t *obj)
{
/* Find Program Manager */
HWND hwnd = FindWindow( TEXT("Progman"), NULL );
if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, TEXT("SHELLDLL_DefView"), NULL );
if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, TEXT("SysListView32"), NULL );
if( hwnd )
return hwnd;
msg_Dbg( obj, "Couldn't find desktop icon window,. Trying the hard way." );
EnumWindows( enumWindowsProc, (LPARAM)&hwnd );
return hwnd;
}
static void UpdateCursor( vlc_window_t *wnd, bool b_show )
{
vout_window_sys_t *sys = wnd->sys;
if( sys->is_cursor_hidden == !b_show )
return;
sys->is_cursor_hidden = !b_show;
HCURSOR cursor = b_show ? sys->cursor_arrow : sys->cursor_empty;
SetClassLongPtr( sys->hwnd, GCLP_HCURSOR, (LONG_PTR)cursor );
/* FIXME I failed to find a cleaner way to force a redraw of the cursor */
SetCursor( cursor );
}
static void CALLBACK HideMouse(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
VLC_UNUSED(uMsg); VLC_UNUSED(dwTime);
if (hwnd)
{
vlc_window_t *wnd = (vlc_window_t *)idEvent;
UpdateCursor( wnd, false );
}
}
static void UpdateCursorMoved( vlc_window_t *wnd )
{
vout_window_sys_t *sys = wnd->sys;
UpdateCursor( wnd, true );
sys->last_moved = vlc_tick_now();
SetTimer( sys->hwnd, (UINT_PTR)wnd, sys->hide_timeout, HideMouse );
}
static HCURSOR EmptyCursor( HINSTANCE instance )
{
const int cw = GetSystemMetrics(SM_CXCURSOR);
const int ch = GetSystemMetrics(SM_CYCURSOR);
HCURSOR cursor = NULL;
uint8_t *and = malloc(cw * ch);
uint8_t *xor = malloc(cw * ch);
if( and && xor )
{
memset(and, 0xff, cw * ch );
memset(xor, 0x00, cw * ch );
cursor = CreateCursor( instance, 0, 0, cw, ch, and, xor);
}
free( and );
free( xor );
return cursor;
}
static inline bool isMouseEvent( WPARAM type )
{
return type >= WM_MOUSEFIRST &&
type <= WM_MOUSELAST;
}
static void *EventThread( void *p_this )
{
vlc_thread_set_name("vlc-window-win");
vlc_window_t *wnd = (vlc_window_t *)p_this;
vout_window_sys_t *sys = wnd->sys;
int canc = vlc_savecancel ();
HINSTANCE hInstance = GetModuleHandle(NULL);
LONG i_window_style;
HWND hwParent;
if (var_InheritBool( wnd, "video-wallpaper" ))
{
hwParent = GetDesktopHandle(p_this);
i_window_style = WS_CLIPCHILDREN|WS_CHILD;
}
else
{
hwParent = 0;
i_window_style = WS_OVERLAPPEDWINDOW | WS_SIZEBOX | WS_CLIPCHILDREN;
}
/* allow user to regain control over input events if requested */
bool b_mouse_support = var_InheritBool( wnd, "mouse-events" );
bool b_key_support = var_InheritBool( wnd, "keyboard-events" );
if( !b_mouse_support && !b_key_support )
i_window_style |= WS_DISABLED;
/* Create the window */
sys->hwnd =
CreateWindowEx( WS_EX_NOPARENTNOTIFY,
sys->class_main, /* name of window class */
TEXT(VOUT_TITLE) TEXT(" (VLC Video Output)"),/* window title */
i_window_style, /* window style */
CW_USEDEFAULT, /* default X coordinate */
CW_USEDEFAULT, /* default Y coordinate */
CW_USEDEFAULT, /* window width */
CW_USEDEFAULT, /* window height */
hwParent, /* parent window */
NULL, /* no menu in this window */
hInstance, /* handle of this program instance */
wnd ); /* send vd to WM_CREATE */
vlc_sem_post( &sys->ready );
if (sys->hwnd == NULL)
{
vlc_restorecancel( canc );
return NULL;
}
/* Detect starting monitor */
sys->monitor = MonitorFromWindow(sys->hwnd, MONITOR_DEFAULTTOPRIMARY);
MonitorChanged(wnd, sys->monitor);
/* Append a "Always On Top" entry in the system menu */
HMENU hMenu = GetSystemMenu( sys->hwnd, FALSE );
AppendMenu( hMenu, MF_SEPARATOR, 0, TEXT("") );
AppendMenu( hMenu, MF_STRING | MF_UNCHECKED,
IDM_TOGGLE_ON_TOP, TEXT("Always on &Top") );
POINT mouse_pos = {0}, old_mouse_pos = {0};
for( ;; )
{
MSG msg;
if( !GetMessage( &msg, 0, 0, 0 ) )
{
break;
}
/* cursor handling */
if( msg.message == WM_MOUSEMOVE || msg.message == WM_NCMOUSEMOVE )
{
GetCursorPos( &mouse_pos );
/* FIXME, why this >2 limits ? */
if( (abs(mouse_pos.x - old_mouse_pos.x) > 2 ||
(abs(mouse_pos.y - old_mouse_pos.y)) > 2 ) )
{
old_mouse_pos = mouse_pos;
UpdateCursorMoved( wnd );
}
}
else if( isMouseEvent( msg.message ) )
{
UpdateCursorMoved( wnd );
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
vlc_restorecancel(canc);
return NULL;
}
static const struct vlc_window_operations ops = {
.enable = Enable,
.disable = Disable,
.resize = Resize,
.set_title = SetTitle,
.set_state = SetState,
.set_fullscreen = SetFullscreen,
.unset_fullscreen = UnsetFullscreen,
.destroy = Close,
};
static int Open(vlc_window_t *wnd)
{
vout_window_sys_t *sys = vlc_obj_calloc(VLC_OBJECT(wnd), 1, sizeof(vout_window_sys_t));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
_snwprintf( sys->class_main, ARRAY_SIZE(sys->class_main),
TEXT("VLC standalone window %p"), (void *)sys );
HINSTANCE hInstance = GetModuleHandle(NULL);
WCHAR app_path[MAX_PATH];
DWORD read = GetModuleFileNameW( NULL, app_path, ARRAY_SIZE(app_path) );
if (read != 0 && read != ARRAY_SIZE(app_path))
sys->vlc_icon = ExtractIconW( hInstance, app_path, 0 );
else
sys->vlc_icon = NULL;
sys->button_pressed = 0;
sys->is_cursor_hidden = false;
sys->hide_timeout = var_InheritInteger( wnd, "mouse-hide-timeout" );
sys->cursor_arrow = LoadCursor(NULL, IDC_ARROW);
sys->cursor_empty = EmptyCursor(hInstance);
WNDCLASS wc = { 0 };
/* Fill in the window class structure */
wc.style = CS_OWNDC|CS_DBLCLKS; /* style: dbl click */
wc.lpfnWndProc = WinVoutEventProc; /* event handler */
wc.hInstance = hInstance; /* instance */
wc.hIcon = sys->vlc_icon; /* load the vlc big icon */
wc.lpszClassName = sys->class_main; /* use a special class */
wc.hCursor = sys->cursor_arrow;
/* Register the window class */
if( !RegisterClass(&wc) )
{
if( sys->vlc_icon )
DestroyIcon( sys->vlc_icon );
msg_Err( wnd, "RegisterClass FAILED (err=%lu)", GetLastError() );
return VLC_EGENERIC;
}
vlc_sem_init( &sys->ready, 0 );
wnd->sys = sys;
if( vlc_wasync_resize_compressor_init( &sys->compressor, wnd ) )
{
msg_Err( wnd, "Failed to init compressor" );
Close(wnd);
return VLC_EGENERIC;
}
if( vlc_clone( &sys->thread, EventThread, wnd ) )
{
Close(wnd);
return VLC_EGENERIC;
}
vlc_sem_wait( &sys->ready );
if (sys->hwnd == NULL)
{
Close(wnd);
return VLC_EGENERIC;
}
wnd->type = VLC_WINDOW_TYPE_HWND;
wnd->handle.hwnd = sys->hwnd;
wnd->ops = &ops;
wnd->info.has_double_click = true;
return VLC_SUCCESS;
}
vlc_module_begin()
set_shortname(N_("Win32 window"))
set_description(N_("Win32 window"))
set_subcategory(SUBCAT_VIDEO_VOUT)
set_capability("vout window", 10)
set_callback(Open)
vlc_module_end()
¤ Dauer der Verarbeitung: 0.5 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|