1 /*****************************************************************************
2 * events.c: Windows video output events handler
3 *****************************************************************************
4 * Copyright (C) 2001-2009 VLC authors and VideoLAN
5 * $Id: cea69d95e8dfece2651ac1dfb31bec48e8f5f537 $
6 *
7 * Authors: Gildas Bazin <gbazin@videolan.org>
8 * Martell Malone <martellmalone@gmail.com>
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
24
25 /*****************************************************************************
26 * Preamble: This file contains the functions related to the creation of
27 * a window and the handling of its messages (events).
28 *****************************************************************************/
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include "win32touch.h"
35
36 #include <vlc_common.h>
37 #include <vlc_vout_display.h>
38 #include <vlc_atomic.h>
39
40 #include <windows.h>
41 #include <windowsx.h> /* GET_X_LPARAM */
42 #include <shellapi.h> /* ExtractIcon */
43
44 #define vout_display_sys_win32_t vout_display_sys_t
45
46 #include "common.h"
47
48 /*****************************************************************************
49 * Local prototypes.
50 *****************************************************************************/
51 #define WM_VLC_CHANGE_TEXT (WM_APP + 1)
52
53 struct event_thread_t
54 {
55 vout_display_t *vd;
56
57 /* */
58 vlc_thread_t thread;
59 vlc_mutex_t lock;
60 vlc_cond_t wait;
61 bool b_ready;
62 bool b_done;
63 bool b_error;
64
65 /* */
66 bool use_desktop;
67 bool use_overlay;
68
69 /* Mouse */
70 bool is_cursor_hidden;
71 HCURSOR cursor_arrow;
72 HCURSOR cursor_empty;
73 unsigned button_pressed;
74 mtime_t hide_timeout;
75 mtime_t last_moved;
76
77 /* Gestures */
78 win32_gesture_sys_t *p_gesture;
79
80 /* Sensors */
81 void *p_sensors;
82
83 /* Title */
84 char *psz_title;
85
86 int i_window_style;
87 int x, y;
88 unsigned width, height;
89
90 /* */
91 vout_window_t *parent_window;
92 TCHAR class_main[256];
93 TCHAR class_video[256];
94 HWND hparent;
95 HWND hwnd;
96 HWND hvideownd;
97 HWND hfswnd;
98 video_format_t source;
99 vout_display_place_t place;
100
101 HICON vlc_icon;
102
103 atomic_bool has_moved;
104 };
105
106 /***************************
107 * Local Prototypes *
108 ***************************/
109 /* Window Creation */
110 static int Win32VoutCreateWindow( event_thread_t * );
111 static void Win32VoutCloseWindow ( event_thread_t * );
112 static long FAR PASCAL WinVoutEventProc( HWND, UINT, WPARAM, LPARAM );
113 static int Win32VoutConvertKey( int i_key );
114
115 /* Display/Hide Cursor */
116 static void UpdateCursor( event_thread_t *p_event, bool b_show );
117 static HCURSOR EmptyCursor( HINSTANCE instance );
118
119 /* Mouse events sending functions */
120 static void MouseReleased( event_thread_t *p_event, unsigned button );
121 static void MousePressed( event_thread_t *p_event, HWND hwnd, unsigned button );
122
HideMouse(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)123 static void CALLBACK HideMouse(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
124 {
125 VLC_UNUSED(uMsg); VLC_UNUSED(dwTime);
126 if (hwnd)
127 {
128 event_thread_t *p_event = (event_thread_t *)idEvent;
129 UpdateCursor( p_event, false );
130 }
131 }
132
UpdateCursorMoved(event_thread_t * p_event)133 static void UpdateCursorMoved( event_thread_t *p_event )
134 {
135 UpdateCursor( p_event, true );
136 p_event->last_moved = mdate();
137 if( p_event->hwnd )
138 SetTimer( p_event->hwnd, (UINT_PTR)p_event, p_event->hide_timeout, HideMouse );
139 }
140
141 /* Local helpers */
isMouseEvent(WPARAM type)142 static inline bool isMouseEvent( WPARAM type )
143 {
144 return type >= WM_MOUSEFIRST &&
145 type <= WM_MOUSELAST;
146 }
147
isKeyEvent(WPARAM type)148 static inline bool isKeyEvent( WPARAM type )
149 {
150 return type >= WM_KEYFIRST &&
151 type <= WM_KEYLAST;
152 }
153 /*****************************************************************************
154 * EventThread: Create video window & handle its messages
155 *****************************************************************************
156 * This function creates a video window and then enters an infinite loop
157 * that handles the messages sent to that window.
158 * The main goal of this thread is to isolate the Win32 PeekMessage function
159 * because this one can block for a long time.
160 *****************************************************************************/
EventThread(void * p_this)161 static void *EventThread( void *p_this )
162 {
163 event_thread_t *p_event = (event_thread_t *)p_this;
164 vout_display_t *vd = p_event->vd;
165 MSG msg;
166 POINT old_mouse_pos = {0,0}, mouse_pos;
167 int canc = vlc_savecancel ();
168
169 bool b_mouse_support = var_InheritBool( p_event->vd, "mouse-events" );
170 bool b_key_support = var_InheritBool( p_event->vd, "keyboard-events" );
171
172 vlc_mutex_lock( &p_event->lock );
173 /* Create a window for the video */
174 /* Creating a window under Windows also initializes the thread's event
175 * message queue */
176 if( Win32VoutCreateWindow( p_event ) )
177 p_event->b_error = true;
178
179 p_event->b_ready = true;
180 vlc_cond_signal( &p_event->wait );
181
182 const bool b_error = p_event->b_error;
183 vlc_mutex_unlock( &p_event->lock );
184
185 if( b_error )
186 {
187 vlc_restorecancel( canc );
188 return NULL;
189 }
190
191 /* Main loop */
192 /* GetMessage will sleep if there's no message in the queue */
193 for( ;; )
194 {
195 vout_display_place_t place;
196 video_format_t source;
197
198 if( !GetMessage( &msg, 0, 0, 0 ) )
199 {
200 vlc_mutex_lock( &p_event->lock );
201 p_event->b_done = true;
202 vlc_mutex_unlock( &p_event->lock );
203 break;
204 }
205
206 /* Check if we are asked to exit */
207 vlc_mutex_lock( &p_event->lock );
208 const bool b_done = p_event->b_done;
209 vlc_mutex_unlock( &p_event->lock );
210 if( b_done )
211 break;
212
213 if( !b_mouse_support && isMouseEvent( msg.message ) )
214 continue;
215
216 if( !b_key_support && isKeyEvent( msg.message ) )
217 continue;
218
219 /* Handle mouse state */
220 if( msg.message == WM_MOUSEMOVE ||
221 msg.message == WM_NCMOUSEMOVE )
222 {
223 GetCursorPos( &mouse_pos );
224 /* FIXME, why this >2 limits ? */
225 if( (abs(mouse_pos.x - old_mouse_pos.x) > 2 ||
226 (abs(mouse_pos.y - old_mouse_pos.y)) > 2 ) )
227 {
228 old_mouse_pos = mouse_pos;
229 UpdateCursorMoved( p_event );
230 }
231 }
232 else if( isMouseEvent( msg.message ) )
233 {
234 UpdateCursorMoved( p_event );
235 }
236
237 /* */
238 switch( msg.message )
239 {
240 case WM_MOUSEMOVE:
241 vlc_mutex_lock( &p_event->lock );
242 place = p_event->place;
243 source = p_event->source;
244 vlc_mutex_unlock( &p_event->lock );
245
246 if( place.width > 0 && place.height > 0 )
247 {
248 if( msg.hwnd == p_event->hvideownd )
249 {
250 /* Child window */
251 place.x = 0;
252 place.y = 0;
253 }
254 const int x = source.i_x_offset +
255 (int64_t)(GET_X_LPARAM(msg.lParam) - place.x) * source.i_width / place.width;
256 const int y = source.i_y_offset +
257 (int64_t)(GET_Y_LPARAM(msg.lParam) - place.y) * source.i_height / place.height;
258 vout_display_SendEventMouseMoved(vd, x, y);
259 }
260 break;
261 case WM_NCMOUSEMOVE:
262 break;
263
264 case WM_LBUTTONDOWN:
265 MousePressed( p_event, msg.hwnd, MOUSE_BUTTON_LEFT );
266 break;
267 case WM_LBUTTONUP:
268 MouseReleased( p_event, MOUSE_BUTTON_LEFT );
269 break;
270 case WM_LBUTTONDBLCLK:
271 vout_display_SendEventMouseDoubleClick(vd);
272 break;
273
274 case WM_MBUTTONDOWN:
275 MousePressed( p_event, msg.hwnd, MOUSE_BUTTON_CENTER );
276 break;
277 case WM_MBUTTONUP:
278 MouseReleased( p_event, MOUSE_BUTTON_CENTER );
279 break;
280
281 case WM_RBUTTONDOWN:
282 MousePressed( p_event, msg.hwnd, MOUSE_BUTTON_RIGHT );
283 break;
284 case WM_RBUTTONUP:
285 MouseReleased( p_event, MOUSE_BUTTON_RIGHT );
286 break;
287
288 case WM_KEYDOWN:
289 case WM_SYSKEYDOWN:
290 {
291 /* The key events are first processed here and not translated
292 * into WM_CHAR events because we need to know the status of the
293 * modifier keys. */
294 int i_key = Win32VoutConvertKey( msg.wParam );
295 if( !i_key )
296 {
297 /* This appears to be a "normal" (ascii) key */
298 i_key = tolower( (unsigned char)MapVirtualKey( msg.wParam, 2 ) );
299 }
300
301 if( i_key )
302 {
303 if( GetKeyState(VK_CONTROL) & 0x8000 )
304 {
305 i_key |= KEY_MODIFIER_CTRL;
306 }
307 if( GetKeyState(VK_SHIFT) & 0x8000 )
308 {
309 i_key |= KEY_MODIFIER_SHIFT;
310 }
311 if( GetKeyState(VK_MENU) & 0x8000 )
312 {
313 i_key |= KEY_MODIFIER_ALT;
314 }
315
316 vout_display_SendEventKey(vd, i_key);
317 }
318 break;
319 }
320
321 case WM_MOUSEWHEEL:
322 {
323 int i_key;
324 if( GET_WHEEL_DELTA_WPARAM( msg.wParam ) > 0 )
325 {
326 i_key = KEY_MOUSEWHEELUP;
327 }
328 else
329 {
330 i_key = KEY_MOUSEWHEELDOWN;
331 }
332 if( i_key )
333 {
334 if( GetKeyState(VK_CONTROL) & 0x8000 )
335 {
336 i_key |= KEY_MODIFIER_CTRL;
337 }
338 if( GetKeyState(VK_SHIFT) & 0x8000 )
339 {
340 i_key |= KEY_MODIFIER_SHIFT;
341 }
342 if( GetKeyState(VK_MENU) & 0x8000 )
343 {
344 i_key |= KEY_MODIFIER_ALT;
345 }
346 vout_display_SendEventKey(vd, i_key);
347 }
348 break;
349 }
350
351 case WM_VLC_CHANGE_TEXT:
352 {
353 vlc_mutex_lock( &p_event->lock );
354 wchar_t *pwz_title = NULL;
355 if( p_event->psz_title )
356 {
357 const size_t i_length = strlen(p_event->psz_title);
358 pwz_title = vlc_alloc( i_length + 1, 2 );
359 if( pwz_title )
360 {
361 mbstowcs( pwz_title, p_event->psz_title, 2 * i_length );
362 pwz_title[i_length] = 0;
363 }
364 }
365 vlc_mutex_unlock( &p_event->lock );
366
367 if( pwz_title )
368 {
369 SetWindowTextW( p_event->hwnd, pwz_title );
370 if( p_event->hfswnd )
371 SetWindowTextW( p_event->hfswnd, pwz_title );
372 free( pwz_title );
373 }
374 break;
375 }
376
377 default:
378 /* Messages we don't handle directly are dispatched to the
379 * window procedure */
380 TranslateMessage(&msg);
381 DispatchMessage(&msg);
382 break;
383
384 } /* End Switch */
385
386 } /* End Main loop */
387
388 /* Check for WM_QUIT if we created the window */
389 if( !p_event->hparent && msg.message == WM_QUIT )
390 {
391 msg_Warn( vd, "WM_QUIT... should not happen!!" );
392 p_event->hwnd = NULL; /* Window already destroyed */
393 }
394
395 msg_Dbg( vd, "Win32 Vout EventThread terminating" );
396
397 Win32VoutCloseWindow( p_event );
398 vlc_restorecancel(canc);
399 return NULL;
400 }
401
EventThreadUpdateTitle(event_thread_t * p_event,const char * psz_fallback)402 void EventThreadUpdateTitle( event_thread_t *p_event, const char *psz_fallback )
403 {
404 char *psz_title = var_InheritString( p_event->vd, "video-title" );
405 if( !psz_title )
406 psz_title = strdup( psz_fallback );
407 if( !psz_title )
408 return;
409
410 vlc_mutex_lock( &p_event->lock );
411 free( p_event->psz_title );
412 p_event->psz_title = psz_title;
413 vlc_mutex_unlock( &p_event->lock );
414
415 PostMessage( p_event->hwnd, WM_VLC_CHANGE_TEXT, 0, 0 );
416 }
EventThreadGetWindowStyle(event_thread_t * p_event)417 int EventThreadGetWindowStyle( event_thread_t *p_event )
418 {
419 /* No need to lock, it is serialized by EventThreadStart */
420 return p_event->i_window_style;
421 }
422
EventThreadUpdateWindowPosition(event_thread_t * p_event,bool * pb_moved,bool * pb_resized,int x,int y,unsigned w,unsigned h)423 void EventThreadUpdateWindowPosition( event_thread_t *p_event,
424 bool *pb_moved, bool *pb_resized,
425 int x, int y, unsigned w, unsigned h )
426 {
427 vlc_mutex_lock( &p_event->lock );
428 *pb_moved = x != p_event->x || y != p_event->y;
429 *pb_resized = w != p_event->width || h != p_event->height;
430
431 p_event->x = x;
432 p_event->y = y;
433 p_event->width = w;
434 p_event->height = h;
435 vlc_mutex_unlock( &p_event->lock );
436 }
437
EventThreadUpdateSourceAndPlace(event_thread_t * p_event,const video_format_t * p_source,const vout_display_place_t * p_place)438 void EventThreadUpdateSourceAndPlace( event_thread_t *p_event,
439 const video_format_t *p_source,
440 const vout_display_place_t *p_place )
441 {
442 vlc_mutex_lock( &p_event->lock );
443 p_event->source = *p_source;
444 p_event->place = *p_place;
445 vlc_mutex_unlock( &p_event->lock );
446 }
447
EventThreadUseOverlay(event_thread_t * p_event,bool b_used)448 void EventThreadUseOverlay( event_thread_t *p_event, bool b_used )
449 {
450 vlc_mutex_lock( &p_event->lock );
451 p_event->use_overlay = b_used;
452 vlc_mutex_unlock( &p_event->lock );
453 }
EventThreadGetAndResetHasMoved(event_thread_t * p_event)454 bool EventThreadGetAndResetHasMoved( event_thread_t *p_event )
455 {
456 return atomic_exchange(&p_event->has_moved, false);
457 }
458
EventThreadCreate(vout_display_t * vd)459 event_thread_t *EventThreadCreate( vout_display_t *vd)
460 {
461 /* Create the Vout EventThread, this thread is created by us to isolate
462 * the Win32 PeekMessage function calls. We want to do this because
463 * Windows can stay blocked inside this call for a long time, and when
464 * this happens it thus blocks vlc's video_output thread.
465 * Vout EventThread will take care of the creation of the video
466 * window (because PeekMessage has to be called from the same thread which
467 * created the window). */
468 msg_Dbg( vd, "creating Vout EventThread" );
469 event_thread_t *p_event = malloc( sizeof(*p_event) );
470 if( !p_event )
471 return NULL;
472
473 p_event->vd = vd;
474 vlc_mutex_init( &p_event->lock );
475 vlc_cond_init( &p_event->wait );
476
477 p_event->is_cursor_hidden = false;
478 p_event->button_pressed = 0;
479 p_event->psz_title = NULL;
480 p_event->source = vd->source;
481 p_event->hwnd = NULL;
482 atomic_init(&p_event->has_moved, false);
483 vout_display_PlacePicture(&p_event->place, &vd->source, vd->cfg, false);
484
485 _sntprintf( p_event->class_main, sizeof(p_event->class_main)/sizeof(*p_event->class_main),
486 _T("VLC video main %p"), (void *)p_event );
487 _sntprintf( p_event->class_video, sizeof(p_event->class_video)/sizeof(*p_event->class_video),
488 _T("VLC video output %p"), (void *)p_event );
489 return p_event;
490 }
491
EventThreadDestroy(event_thread_t * p_event)492 void EventThreadDestroy( event_thread_t *p_event )
493 {
494 free( p_event->psz_title );
495 vlc_cond_destroy( &p_event->wait );
496 vlc_mutex_destroy( &p_event->lock );
497 free( p_event );
498 }
499
EventThreadStart(event_thread_t * p_event,event_hwnd_t * p_hwnd,const event_cfg_t * p_cfg)500 int EventThreadStart( event_thread_t *p_event, event_hwnd_t *p_hwnd, const event_cfg_t *p_cfg )
501 {
502 p_event->use_desktop = p_cfg->use_desktop;
503 p_event->use_overlay = p_cfg->use_overlay;
504 p_event->x = p_cfg->x;
505 p_event->y = p_cfg->y;
506 p_event->width = p_cfg->width;
507 p_event->height = p_cfg->height;
508
509 atomic_store(&p_event->has_moved, false);
510
511 p_event->b_ready = false;
512 p_event->b_done = false;
513 p_event->b_error = false;
514
515 if( vlc_clone( &p_event->thread, EventThread, p_event,
516 VLC_THREAD_PRIORITY_LOW ) )
517 {
518 msg_Err( p_event->vd, "cannot create Vout EventThread" );
519 return VLC_EGENERIC;
520 }
521
522 vlc_mutex_lock( &p_event->lock );
523 while( !p_event->b_ready )
524 vlc_cond_wait( &p_event->wait, &p_event->lock );
525 const bool b_error = p_event->b_error;
526 vlc_mutex_unlock( &p_event->lock );
527
528 if( b_error )
529 {
530 vlc_join( p_event->thread, NULL );
531 p_event->b_ready = false;
532 return VLC_EGENERIC;
533 }
534 msg_Dbg( p_event->vd, "Vout EventThread running" );
535
536 /* */
537 p_hwnd->parent_window = p_event->parent_window;
538 p_hwnd->hparent = p_event->hparent;
539 p_hwnd->hwnd = p_event->hwnd;
540 p_hwnd->hvideownd = p_event->hvideownd;
541 p_hwnd->hfswnd = p_event->hfswnd;
542 return VLC_SUCCESS;
543 }
544
EventThreadStop(event_thread_t * p_event)545 void EventThreadStop( event_thread_t *p_event )
546 {
547 if( !p_event->b_ready )
548 return;
549
550 vlc_mutex_lock( &p_event->lock );
551 p_event->b_done = true;
552 vlc_mutex_unlock( &p_event->lock );
553
554 /* we need to be sure Vout EventThread won't stay stuck in
555 * GetMessage, so we send a fake message */
556 if( p_event->hwnd )
557 PostMessage( p_event->hwnd, WM_NULL, 0, 0);
558
559 vlc_join( p_event->thread, NULL );
560 p_event->b_ready = false;
561 }
562
563
564 /***********************************
565 * Local functions implementations *
566 ***********************************/
UpdateCursor(event_thread_t * p_event,bool b_show)567 static void UpdateCursor( event_thread_t *p_event, bool b_show )
568 {
569 if( p_event->is_cursor_hidden == !b_show )
570 return;
571 p_event->is_cursor_hidden = !b_show;
572
573 #if 1
574 HCURSOR cursor = b_show ? p_event->cursor_arrow : p_event->cursor_empty;
575 if( p_event->hvideownd )
576 SetClassLongPtr( p_event->hvideownd, GCLP_HCURSOR, (LONG_PTR)cursor );
577 if( p_event->hwnd )
578 SetClassLongPtr( p_event->hwnd, GCLP_HCURSOR, (LONG_PTR)cursor );
579 #endif
580
581 /* FIXME I failed to find a cleaner way to force a redraw of the cursor */
582 POINT p;
583 GetCursorPos(&p);
584 HWND hwnd = WindowFromPoint(p);
585 if( hwnd == p_event->hvideownd || hwnd == p_event->hwnd )
586 {
587 SetCursor( cursor );
588 }
589 }
590
EmptyCursor(HINSTANCE instance)591 static HCURSOR EmptyCursor( HINSTANCE instance )
592 {
593 const int cw = GetSystemMetrics(SM_CXCURSOR);
594 const int ch = GetSystemMetrics(SM_CYCURSOR);
595
596 HCURSOR cursor = NULL;
597 uint8_t *and = malloc(cw * ch);
598 uint8_t *xor = malloc(cw * ch);
599 if( and && xor )
600 {
601 memset(and, 0xff, cw * ch );
602 memset(xor, 0x00, cw * ch );
603 cursor = CreateCursor( instance, 0, 0, cw, ch, and, xor);
604 }
605 free( and );
606 free( xor );
607
608 return cursor;
609 }
610
MousePressed(event_thread_t * p_event,HWND hwnd,unsigned button)611 static void MousePressed( event_thread_t *p_event, HWND hwnd, unsigned button )
612 {
613 if( !p_event->button_pressed )
614 SetCapture( hwnd );
615 p_event->button_pressed |= 1 << button;
616 vout_display_SendEventMousePressed( p_event->vd, button );
617 }
618
MouseReleased(event_thread_t * p_event,unsigned button)619 static void MouseReleased( event_thread_t *p_event, unsigned button )
620 {
621 p_event->button_pressed &= ~(1 << button);
622 if( !p_event->button_pressed )
623 ReleaseCapture();
624 vout_display_SendEventMouseReleased( p_event->vd, button );
625 }
626
627 #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
628 static int CALLBACK
enumWindowsProc(HWND hwnd,LPARAM lParam)629 enumWindowsProc(HWND hwnd, LPARAM lParam)
630 {
631 HWND *wnd = (HWND *)lParam;
632
633 char name[128];
634 name[0] = '\0';
635 GetClassNameA( hwnd, name, 128 );
636
637 if( !strcasecmp( name, "WorkerW" ) )
638 {
639 hwnd = FindWindowEx( hwnd, NULL, _T("SHELLDLL_DefView"), NULL );
640 if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SysListView32"), NULL );
641 if( hwnd )
642 {
643 *wnd = hwnd;
644 return false;
645 }
646 }
647 return true;
648 }
649
GetDesktopHandle(vout_display_t * vd)650 static HWND GetDesktopHandle(vout_display_t *vd)
651 {
652 /* Find Program Manager */
653 HWND hwnd = FindWindow( _T("Progman"), NULL );
654 if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SHELLDLL_DefView"), NULL );
655 if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SysListView32"), NULL );
656 if( hwnd )
657 return hwnd;
658
659 msg_Dbg( vd, "Couldn't find desktop icon window,. Trying the hard way." );
660
661 EnumWindows( enumWindowsProc, (LPARAM)&hwnd );
662 return hwnd;
663 }
664 #endif
665
666 /*****************************************************************************
667 * Win32VoutCreateWindow: create a window for the video.
668 *****************************************************************************
669 * Before creating a direct draw surface, we need to create a window in which
670 * the video will be displayed. This window will also allow us to capture the
671 * events.
672 *****************************************************************************/
Win32VoutCreateWindow(event_thread_t * p_event)673 static int Win32VoutCreateWindow( event_thread_t *p_event )
674 {
675 vout_display_t *vd = p_event->vd;
676 HINSTANCE hInstance;
677 HMENU hMenu;
678 RECT rect_window;
679 WNDCLASS wc; /* window class components */
680 TCHAR vlc_path[MAX_PATH+1];
681 int i_style;
682
683 msg_Dbg( vd, "Win32VoutCreateWindow" );
684
685 /* Get this module's instance */
686 hInstance = GetModuleHandle(NULL);
687
688 #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
689 if( !p_event->use_desktop )
690 #endif
691 {
692 /* If an external window was specified, we'll draw in it. */
693 p_event->parent_window = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_HWND);
694 if( p_event->parent_window )
695 p_event->hparent = p_event->parent_window->handle.hwnd;
696 else
697 p_event->hparent = NULL;
698 }
699 #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
700 else
701 {
702 vout_display_DeleteWindow(vd, NULL);
703 p_event->parent_window = NULL;
704 p_event->hparent = GetDesktopHandle(vd);
705 }
706 #endif
707 p_event->cursor_arrow = LoadCursor(NULL, IDC_ARROW);
708 p_event->cursor_empty = EmptyCursor(hInstance);
709
710 /* Get the Icon from the main app */
711 p_event->vlc_icon = NULL;
712 if( GetModuleFileName( NULL, vlc_path, MAX_PATH ) )
713 {
714 p_event->vlc_icon = ExtractIcon( hInstance, vlc_path, 0 );
715 }
716 p_event->hide_timeout = var_InheritInteger( p_event->vd, "mouse-hide-timeout" );
717 UpdateCursorMoved( p_event );
718
719 /* Fill in the window class structure */
720 wc.style = CS_OWNDC|CS_DBLCLKS; /* style: dbl click */
721 wc.lpfnWndProc = (WNDPROC)WinVoutEventProc; /* event handler */
722 wc.cbClsExtra = 0; /* no extra class data */
723 wc.cbWndExtra = 0; /* no extra window data */
724 wc.hInstance = hInstance; /* instance */
725 wc.hIcon = p_event->vlc_icon; /* load the vlc big icon */
726 wc.hCursor = p_event->is_cursor_hidden ? p_event->cursor_empty :
727 p_event->cursor_arrow;
728 #if !VLC_WINSTORE_APP
729 wc.hbrBackground = GetStockObject(BLACK_BRUSH); /* background color */
730 #else
731 wc.hbrBackground = NULL;
732 #endif
733 wc.lpszMenuName = NULL; /* no menu */
734 wc.lpszClassName = p_event->class_main; /* use a special class */
735
736 /* Register the window class */
737 if( !RegisterClass(&wc) )
738 {
739 if( p_event->vlc_icon )
740 DestroyIcon( p_event->vlc_icon );
741
742 msg_Err( vd, "Win32VoutCreateWindow RegisterClass FAILED (err=%lu)", GetLastError() );
743 return VLC_EGENERIC;
744 }
745
746 /* Register the video sub-window class */
747 wc.lpszClassName = p_event->class_video;
748 wc.hIcon = 0;
749 wc.hbrBackground = NULL; /* no background color */
750 if( !RegisterClass(&wc) )
751 {
752 msg_Err( vd, "Win32VoutCreateWindow RegisterClass FAILED (err=%lu)", GetLastError() );
753 return VLC_EGENERIC;
754 }
755
756 /* When you create a window you give the dimensions you wish it to
757 * have. Unfortunatly these dimensions will include the borders and
758 * titlebar. We use the following function to find out the size of
759 * the window corresponding to the useable surface we want */
760 rect_window.left = 10;
761 rect_window.top = 10;
762 rect_window.right = rect_window.left + p_event->width;
763 rect_window.bottom = rect_window.top + p_event->height;
764
765 i_style = var_GetBool( vd, "video-deco" )
766 /* Open with window decoration */
767 ? WS_OVERLAPPEDWINDOW|WS_SIZEBOX
768 /* No window decoration */
769 : WS_POPUP;
770 AdjustWindowRect( &rect_window, i_style, 0 );
771 i_style |= WS_VISIBLE|WS_CLIPCHILDREN;
772
773 if( p_event->hparent )
774 {
775 i_style = WS_VISIBLE|WS_CLIPCHILDREN|WS_CHILD;
776
777 /* allow user to regain control over input events if requested */
778 bool b_mouse_support = var_InheritBool( vd, "mouse-events" );
779 bool b_key_support = var_InheritBool( vd, "keyboard-events" );
780 if( !b_mouse_support && !b_key_support )
781 i_style |= WS_DISABLED;
782 }
783
784 p_event->i_window_style = i_style;
785
786 /* Create the window */
787 p_event->hwnd =
788 CreateWindowEx( WS_EX_NOPARENTNOTIFY,
789 p_event->class_main, /* name of window class */
790 _T(VOUT_TITLE) _T(" (VLC Video Output)"),/* window title */
791 i_style, /* window style */
792 (!p_event->x) ? (UINT)CW_USEDEFAULT :
793 (UINT)p_event->x, /* default X coordinate */
794 (!p_event->y) ? (UINT)CW_USEDEFAULT :
795 (UINT)p_event->y, /* default Y coordinate */
796 rect_window.right - rect_window.left, /* window width */
797 rect_window.bottom - rect_window.top, /* window height */
798 p_event->hparent, /* parent window */
799 NULL, /* no menu in this window */
800 hInstance, /* handle of this program instance */
801 (LPVOID)p_event ); /* send vd to WM_CREATE */
802
803 if( !p_event->hwnd )
804 {
805 msg_Warn( vd, "Win32VoutCreateWindow create window FAILED (err=%lu)", GetLastError() );
806 return VLC_EGENERIC;
807 }
808
809 bool b_isProjected = (vd->fmt.projection_mode != PROJECTION_MODE_RECTANGULAR);
810 InitGestures( p_event->hwnd, &p_event->p_gesture, b_isProjected );
811
812 p_event->p_sensors = HookWindowsSensors(vd, p_event->hwnd);
813
814 if( p_event->hparent )
815 {
816 LONG i_style;
817
818 /* We don't want the window owner to overwrite our client area */
819 i_style = GetWindowLong( p_event->hparent, GWL_STYLE );
820
821 if( !(i_style & WS_CLIPCHILDREN) )
822 /* Hmmm, apparently this is a blocking call... */
823 SetWindowLong( p_event->hparent, GWL_STYLE,
824 i_style | WS_CLIPCHILDREN );
825
826 /* Create our fullscreen window */
827 p_event->hfswnd =
828 CreateWindowEx( WS_EX_APPWINDOW, p_event->class_main,
829 _T(VOUT_TITLE) _T(" (VLC Fullscreen Video Output)"),
830 WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_SIZEBOX,
831 CW_USEDEFAULT, CW_USEDEFAULT,
832 CW_USEDEFAULT, CW_USEDEFAULT,
833 NULL, NULL, hInstance, NULL );
834 }
835 else
836 {
837 p_event->hfswnd = NULL;
838 }
839
840 /* Append a "Always On Top" entry in the system menu */
841 hMenu = GetSystemMenu( p_event->hwnd, FALSE );
842 AppendMenu( hMenu, MF_SEPARATOR, 0, _T("") );
843 AppendMenu( hMenu, MF_STRING | MF_UNCHECKED,
844 IDM_TOGGLE_ON_TOP, _T("Always on &Top") );
845
846 /* Create video sub-window. This sub window will always exactly match
847 * the size of the video, which allows us to use crazy overlay colorkeys
848 * without having them shown outside of the video area. */
849 /* FIXME vd->source.i_width/i_height seems wrong */
850 p_event->hvideownd =
851 CreateWindow( p_event->class_video, _T(""), /* window class */
852 WS_CHILD, /* window style, not visible initially */
853 0, 0,
854 vd->source.i_width, /* default width */
855 vd->source.i_height, /* default height */
856 p_event->hwnd, /* parent window */
857 NULL, hInstance,
858 (LPVOID)p_event ); /* send vd to WM_CREATE */
859
860 if( !p_event->hvideownd )
861 msg_Warn( vd, "can't create video sub-window" );
862 else
863 msg_Dbg( vd, "created video sub-window" );
864
865 /* Now display the window */
866 ShowWindow( p_event->hwnd, SW_SHOW );
867
868 return VLC_SUCCESS;
869 }
870
871 /*****************************************************************************
872 * Win32VoutCloseWindow: close the window created by Win32VoutCreateWindow
873 *****************************************************************************
874 * This function returns all resources allocated by Win32VoutCreateWindow.
875 *****************************************************************************/
Win32VoutCloseWindow(event_thread_t * p_event)876 static void Win32VoutCloseWindow( event_thread_t *p_event )
877 {
878 vout_display_t *vd = p_event->vd;
879 msg_Dbg( vd, "Win32VoutCloseWindow" );
880
881 #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
882 DestroyWindow( p_event->hvideownd );
883 #endif
884 DestroyWindow( p_event->hwnd );
885 if( p_event->hfswnd )
886 DestroyWindow( p_event->hfswnd );
887
888 #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
889 if( !p_event->use_desktop )
890 #endif
891 vout_display_DeleteWindow( vd, p_event->parent_window );
892 p_event->hwnd = NULL;
893
894 HINSTANCE hInstance = GetModuleHandle(NULL);
895 UnregisterClass( p_event->class_video, hInstance );
896 UnregisterClass( p_event->class_main, hInstance );
897
898 if( p_event->vlc_icon )
899 DestroyIcon( p_event->vlc_icon );
900
901 DestroyCursor( p_event->cursor_empty );
902
903 UnhookWindowsSensors(p_event->p_sensors);
904
905 CloseGestures( p_event->p_gesture);
906 }
907
908 /*****************************************************************************
909 * WinVoutEventProc: This is the window event processing function.
910 *****************************************************************************
911 * On Windows, when you create a window you have to attach an event processing
912 * function to it. The aim of this function is to manage "Queued Messages" and
913 * "Nonqueued Messages".
914 * Queued Messages are those picked up and retransmitted by vout_Manage
915 * (using the GetMessage and DispatchMessage functions).
916 * Nonqueued Messages are those that Windows will send directly to this
917 * procedure (like WM_DESTROY, WM_WINDOWPOSCHANGED...)
918 *****************************************************************************/
WinVoutEventProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)919 static long FAR PASCAL WinVoutEventProc( HWND hwnd, UINT message,
920 WPARAM wParam, LPARAM lParam )
921 {
922 event_thread_t *p_event;
923
924 if( message == WM_CREATE )
925 {
926 /* Store vd for future use */
927 p_event = (event_thread_t *)((CREATESTRUCT *)lParam)->lpCreateParams;
928 SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)p_event );
929 return TRUE;
930 }
931 else
932 {
933 LONG_PTR p_user_data = GetWindowLongPtr( hwnd, GWLP_USERDATA );
934 p_event = (event_thread_t *)p_user_data;
935 if( !p_event )
936 {
937 /* Hmmm mozilla does manage somehow to save the pointer to our
938 * windowproc and still calls it after the vout has been closed. */
939 return DefWindowProc(hwnd, message, wParam, lParam);
940 }
941 }
942 vout_display_t *vd = p_event->vd;
943
944 #if 0
945 if( message == WM_SETCURSOR )
946 {
947 msg_Err(vd, "WM_SETCURSOR: %d (t2)", p_event->is_cursor_hidden);
948 SetCursor( p_event->is_cursor_hidden ? p_event->cursor_empty : p_event->cursor_arrow );
949 return 1;
950 }
951 #endif
952 if( message == WM_CAPTURECHANGED )
953 {
954 for( int button = 0; p_event->button_pressed; button++ )
955 {
956 unsigned m = 1 << button;
957 if( p_event->button_pressed & m )
958 vout_display_SendEventMouseReleased( p_event->vd, button );
959 p_event->button_pressed &= ~m;
960 }
961 p_event->button_pressed = 0;
962 return 0;
963 }
964
965 if( hwnd == p_event->hvideownd )
966 {
967 #ifdef MODULE_NAME_IS_directdraw
968 vlc_mutex_lock( &p_event->lock );
969 const bool use_overlay = p_event->use_overlay;
970 vlc_mutex_unlock( &p_event->lock );
971 #endif
972
973 switch( message )
974 {
975 #ifdef MODULE_NAME_IS_directdraw
976 case WM_ERASEBKGND:
977 /* For overlay, we need to erase background */
978 return !use_overlay ? 1 : DefWindowProc(hwnd, message, wParam, lParam);
979 case WM_PAINT:
980 /*
981 ** For overlay, DefWindowProc() will erase dirty regions
982 ** with colorkey.
983 ** For non-overlay, vout will paint the whole window at
984 ** regular interval, therefore dirty regions can be ignored
985 ** to minimize repaint.
986 */
987 if( !use_overlay )
988 {
989 ValidateRect(hwnd, NULL);
990 }
991 // fall through to default
992 #else
993 /*
994 ** For OpenGL and Direct3D, vout will update the whole
995 ** window at regular interval, therefore dirty region
996 ** can be ignored to minimize repaint.
997 */
998 case WM_ERASEBKGND:
999 /* nothing to erase */
1000 return 1;
1001 case WM_PAINT:
1002 /* nothing to repaint */
1003 ValidateRect(hwnd, NULL);
1004 // fall through
1005 #endif
1006 default:
1007 return DefWindowProc(hwnd, message, wParam, lParam);
1008 }
1009 }
1010
1011 switch( message )
1012 {
1013
1014 case WM_WINDOWPOSCHANGED:
1015 atomic_store(&p_event->has_moved, true);
1016 return 0;
1017
1018 /* the user wants to close the window */
1019 case WM_CLOSE:
1020 vout_display_SendEventClose(vd);
1021 return 0;
1022
1023 /* the window has been closed so shut down everything now */
1024 case WM_DESTROY:
1025 msg_Dbg( vd, "WinProc WM_DESTROY" );
1026 /* just destroy the window */
1027 PostQuitMessage( 0 );
1028 return 0;
1029
1030 case WM_SYSCOMMAND:
1031 switch (wParam)
1032 {
1033 case IDM_TOGGLE_ON_TOP: /* toggle the "on top" status */
1034 {
1035 msg_Dbg(vd, "WinProc WM_SYSCOMMAND: IDM_TOGGLE_ON_TOP");
1036 HMENU hMenu = GetSystemMenu(vd->sys->hwnd, FALSE);
1037 vout_display_SendWindowState(vd, (GetMenuState(hMenu, IDM_TOGGLE_ON_TOP, MF_BYCOMMAND) & MF_CHECKED) ?
1038 VOUT_WINDOW_STATE_NORMAL : VOUT_WINDOW_STATE_ABOVE);
1039 return 0;
1040 }
1041 default:
1042 break;
1043 }
1044 break;
1045
1046 case WM_PAINT:
1047 case WM_NCPAINT:
1048 case WM_ERASEBKGND:
1049 return DefWindowProc(hwnd, message, wParam, lParam);
1050
1051 case WM_KILLFOCUS:
1052 return 0;
1053
1054 case WM_SETFOCUS:
1055 return 0;
1056
1057 case WM_GESTURE:
1058 return DecodeGesture( VLC_OBJECT(vd), p_event->p_gesture, hwnd, message, wParam, lParam );
1059
1060 default:
1061 //msg_Dbg( vd, "WinProc WM Default %i", message );
1062 break;
1063 }
1064
1065 /* Let windows handle the message */
1066 return DefWindowProc(hwnd, message, wParam, lParam);
1067 }
1068
1069 static struct
1070 {
1071 int i_dxkey;
1072 int i_vlckey;
1073
1074 } dxkeys_to_vlckeys[] =
1075 {
1076 { VK_F1, KEY_F1 }, { VK_F2, KEY_F2 }, { VK_F3, KEY_F3 }, { VK_F4, KEY_F4 },
1077 { VK_F5, KEY_F5 }, { VK_F6, KEY_F6 }, { VK_F7, KEY_F7 }, { VK_F8, KEY_F8 },
1078 { VK_F9, KEY_F9 }, { VK_F10, KEY_F10 }, { VK_F11, KEY_F11 },
1079 { VK_F12, KEY_F12 },
1080
1081 { VK_RETURN, KEY_ENTER },
1082 { VK_SPACE, ' ' },
1083 { VK_ESCAPE, KEY_ESC },
1084
1085 { VK_LEFT, KEY_LEFT },
1086 { VK_RIGHT, KEY_RIGHT },
1087 { VK_UP, KEY_UP },
1088 { VK_DOWN, KEY_DOWN },
1089
1090 { VK_HOME, KEY_HOME },
1091 { VK_END, KEY_END },
1092 { VK_PRIOR, KEY_PAGEUP },
1093 { VK_NEXT, KEY_PAGEDOWN },
1094
1095 { VK_INSERT, KEY_INSERT },
1096 { VK_DELETE, KEY_DELETE },
1097
1098 { VK_CONTROL, 0 },
1099 { VK_SHIFT, 0 },
1100 { VK_MENU, 0 },
1101
1102 { VK_BROWSER_BACK, KEY_BROWSER_BACK },
1103 { VK_BROWSER_FORWARD, KEY_BROWSER_FORWARD },
1104 { VK_BROWSER_REFRESH, KEY_BROWSER_REFRESH },
1105 { VK_BROWSER_STOP, KEY_BROWSER_STOP },
1106 { VK_BROWSER_SEARCH, KEY_BROWSER_SEARCH },
1107 { VK_BROWSER_FAVORITES, KEY_BROWSER_FAVORITES },
1108 { VK_BROWSER_HOME, KEY_BROWSER_HOME },
1109 { VK_VOLUME_MUTE, KEY_VOLUME_MUTE },
1110 { VK_VOLUME_DOWN, KEY_VOLUME_DOWN },
1111 { VK_VOLUME_UP, KEY_VOLUME_UP },
1112 { VK_MEDIA_NEXT_TRACK, KEY_MEDIA_NEXT_TRACK },
1113 { VK_MEDIA_PREV_TRACK, KEY_MEDIA_PREV_TRACK },
1114 { VK_MEDIA_STOP, KEY_MEDIA_STOP },
1115 { VK_MEDIA_PLAY_PAUSE, KEY_MEDIA_PLAY_PAUSE },
1116
1117 { 0, 0 }
1118 };
1119
Win32VoutConvertKey(int i_key)1120 static int Win32VoutConvertKey( int i_key )
1121 {
1122 for( int i = 0; dxkeys_to_vlckeys[i].i_dxkey != 0; i++ )
1123 {
1124 if( dxkeys_to_vlckeys[i].i_dxkey == i_key )
1125 {
1126 return dxkeys_to_vlckeys[i].i_vlckey;
1127 }
1128 }
1129
1130 return 0;
1131 }
1132
1133