events.c 36.4 KB
Newer Older
1
/*****************************************************************************
2
 * events.c: Windows video output events handler
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2001-2009 VLC authors and VideoLAN
5
 * $Id$
6
 *
gbazin's avatar
gbazin committed
7
 * Authors: Gildas Bazin <gbazin@videolan.org>
Martell Malone's avatar
Martell Malone committed
8
 *          Martell Malone <martellmalone@gmail.com>
9
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
10
11
12
 * 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
13
14
15
16
 * (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
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
17
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
19
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
20
21
22
 * 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.
23
24
25
26
27
28
 *****************************************************************************/

/*****************************************************************************
 * Preamble: This file contains the functions related to the creation of
 *             a window and the handling of its messages (events).
 *****************************************************************************/
29

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
30
31
32
33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34
#include "win32touch.h"
35

36
#include <vlc_common.h>
37
#include <vlc_vout_display.h>
38

39
#include <stdatomic.h>
40
#include <windows.h>
41
42
#include <windowsx.h>                                        /* GET_X_LPARAM */
#include <shellapi.h>                                         /* ExtractIcon */
43

44
45
#define vout_display_sys_win32_t vout_display_sys_t

46
#include "common.h"
47
48
49
50

/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
51
#define WM_VLC_CHANGE_TEXT  (WM_APP + 1)
Laurent Aimar's avatar
Laurent Aimar committed
52

53
54
struct event_thread_t
{
55
    vout_display_t *vd;
56
57
58
59
60
61
62
63
64

    /* */
    vlc_thread_t thread;
    vlc_mutex_t  lock;
    vlc_cond_t   wait;
    bool         b_ready;
    bool         b_done;
    bool         b_error;

65
66
67
68
    /* */
    bool use_desktop;
    bool use_overlay;

69
    /* Mouse */
70
71
72
    bool is_cursor_hidden;
    HCURSOR cursor_arrow;
    HCURSOR cursor_empty;
73
    unsigned button_pressed;
74
75
    mtime_t hide_timeout;
    mtime_t last_moved;
76

77
78
79
    /* Gestures */
    win32_gesture_sys_t *p_gesture;

80
81
82
    /* Sensors */
    void *p_sensors;

83
84
    /* Title */
    char *psz_title;
85

86
87
88
    int i_window_style;
    int x, y;
    unsigned width, height;
89

90
91
    /* */
    vout_window_t *parent_window;
92
93
    TCHAR class_main[256];
    TCHAR class_video[256];
94
95
96
97
    HWND hparent;
    HWND hwnd;
    HWND hvideownd;
    HWND hfswnd;
98
99
100
    video_format_t       source;
    vout_display_place_t place;

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
101
102
    HICON vlc_icon;

103
    atomic_bool has_moved;
104
105
};

106
107
108
109
/***************************
 * Local Prototypes        *
 ***************************/
/* Window Creation */
110
111
112
static int  Win32VoutCreateWindow( event_thread_t * );
static void Win32VoutCloseWindow ( event_thread_t * );
static long FAR PASCAL WinVoutEventProc( HWND, UINT, WPARAM, LPARAM );
113
static int  Win32VoutConvertKey( int i_key );
114

115
116
117
/* Display/Hide Cursor */
static void UpdateCursor( event_thread_t *p_event, bool b_show );
static HCURSOR EmptyCursor( HINSTANCE instance );
118

119
120
121
122
/* Mouse events sending functions */
static void MouseReleased( event_thread_t *p_event, unsigned button );
static void MousePressed( event_thread_t *p_event, HWND hwnd, unsigned button );

123
124
static void CALLBACK HideMouse(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
125
126
127
128
129
130
    VLC_UNUSED(uMsg); VLC_UNUSED(dwTime);
    if (hwnd)
    {
        event_thread_t *p_event = (event_thread_t *)idEvent;
        UpdateCursor( p_event, false );
    }
131
132
133
134
135
136
}

static void UpdateCursorMoved( event_thread_t *p_event )
{
    UpdateCursor( p_event, true );
    p_event->last_moved = mdate();
137
138
    if( p_event->hwnd )
        SetTimer( p_event->hwnd, (UINT_PTR)p_event, p_event->hide_timeout, HideMouse );
139
140
}

141
/* Local helpers */
142
143
144
145
146
147
148
149
150
151
152
static inline bool isMouseEvent( WPARAM type )
{
    return type >= WM_MOUSEFIRST &&
           type <= WM_MOUSELAST;
}

static inline bool isKeyEvent( WPARAM type )
{
    return type >= WM_KEYFIRST &&
           type <= WM_KEYLAST;
}
153
/*****************************************************************************
154
 * EventThread: Create video window & handle its messages
155
156
157
158
159
160
 *****************************************************************************
 * This function creates a video window and then enters an infinite loop
 * that handles the messages sent to that window.
 * The main goal of this thread is to isolate the Win32 PeekMessage function
 * because this one can block for a long time.
 *****************************************************************************/
161
static void *EventThread( void *p_this )
162
{
ivoire's avatar
ivoire committed
163
    event_thread_t *p_event = (event_thread_t *)p_this;
164
    vout_display_t *vd = p_event->vd;
165
    MSG msg;
gbazin's avatar
gbazin committed
166
    POINT old_mouse_pos = {0,0}, mouse_pos;
167
    int canc = vlc_savecancel ();
168

169
170
171
    bool b_mouse_support = var_InheritBool( p_event->vd, "mouse-events" );
    bool b_key_support = var_InheritBool( p_event->vd, "keyboard-events" );

172
    vlc_mutex_lock( &p_event->lock );
173
174
    /* Create a window for the video */
    /* Creating a window under Windows also initializes the thread's event
175
     * message queue */
176
    if( Win32VoutCreateWindow( p_event ) )
177
178
179
180
181
182
183
184
185
        p_event->b_error = true;

    p_event->b_ready = true;
    vlc_cond_signal( &p_event->wait );

    const bool b_error = p_event->b_error;
    vlc_mutex_unlock( &p_event->lock );

    if( b_error )
geal's avatar
geal committed
186
    {
187
        vlc_restorecancel( canc );
geal's avatar
geal committed
188
189
        return NULL;
    }
190

191
    /* Prevent monitor from powering off */
192
    if (var_GetBool(vd, "disable-screensaver"))
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
193
        SetThreadExecutionState( ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED | ES_CONTINUOUS );
194

195
196
    /* Main loop */
    /* GetMessage will sleep if there's no message in the queue */
197
    for( ;; )
198
    {
199
200
201
        vout_display_place_t place;
        video_format_t       source;

202
203
204
205
206
207
208
209
        if( !GetMessage( &msg, 0, 0, 0 ) )
        {
            vlc_mutex_lock( &p_event->lock );
            p_event->b_done = true;
            vlc_mutex_unlock( &p_event->lock );
            break;
        }

210
        /* Check if we are asked to exit */
211
212
213
214
        vlc_mutex_lock( &p_event->lock );
        const bool b_done = p_event->b_done;
        vlc_mutex_unlock( &p_event->lock );
        if( b_done )
215
216
            break;

217
218
219
220
221
222
        if( !b_mouse_support && isMouseEvent( msg.message ) )
            continue;

        if( !b_key_support && isKeyEvent( msg.message ) )
            continue;

223
224
225
226
227
228
229
230
231
232
        /* Handle mouse state */
        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;
233
                UpdateCursorMoved( p_event );
234
235
236
237
            }
        }
        else if( isMouseEvent( msg.message ) )
        {
238
            UpdateCursorMoved( p_event );
239
240
        }

241
        /* */
242
243
244
        switch( msg.message )
        {
        case WM_MOUSEMOVE:
245
            vlc_mutex_lock( &p_event->lock );
246
247
            place  = p_event->place;
            source = p_event->source;
248
            vlc_mutex_unlock( &p_event->lock );
249

250
            if( place.width > 0 && place.height > 0 )
251
            {
252
253
254
255
256
257
258
259
260
261
262
                if( msg.hwnd == p_event->hvideownd )
                {
                    /* Child window */
                    place.x = 0;
                    place.y = 0;
                }
                const int x = source.i_x_offset +
                    (int64_t)(GET_X_LPARAM(msg.lParam) - place.x) * source.i_width  / place.width;
                const int y = source.i_y_offset +
                    (int64_t)(GET_Y_LPARAM(msg.lParam) - place.y) * source.i_height / place.height;
                vout_display_SendEventMouseMoved(vd, x, y);
gbazin's avatar
gbazin committed
263
            }
264
            break;
265
        case WM_NCMOUSEMOVE:
266
267
            break;

gbazin's avatar
   
gbazin committed
268
        case WM_LBUTTONDOWN:
269
            MousePressed( p_event, msg.hwnd, MOUSE_BUTTON_LEFT );
gbazin's avatar
   
gbazin committed
270
271
            break;
        case WM_LBUTTONUP:
272
            MouseReleased( p_event, MOUSE_BUTTON_LEFT );
gbazin's avatar
   
gbazin committed
273
274
            break;
        case WM_LBUTTONDBLCLK:
275
            vout_display_SendEventMouseDoubleClick(vd);
gbazin's avatar
   
gbazin committed
276
277
278
            break;

        case WM_MBUTTONDOWN:
279
            MousePressed( p_event, msg.hwnd, MOUSE_BUTTON_CENTER );
gbazin's avatar
   
gbazin committed
280
281
            break;
        case WM_MBUTTONUP:
282
            MouseReleased( p_event, MOUSE_BUTTON_CENTER );
gbazin's avatar
   
gbazin committed
283
284
285
            break;

        case WM_RBUTTONDOWN:
286
            MousePressed( p_event, msg.hwnd, MOUSE_BUTTON_RIGHT );
gbazin's avatar
   
gbazin committed
287
            break;
288
        case WM_RBUTTONUP:
289
            MouseReleased( p_event, MOUSE_BUTTON_RIGHT );
290
291
292
            break;

        case WM_KEYDOWN:
gbazin's avatar
   
gbazin committed
293
        case WM_SYSKEYDOWN:
294
        {
295
296
297
            /* 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. */
298
            int i_key = Win32VoutConvertKey( msg.wParam );
299
            if( !i_key )
300
            {
301
                /* This appears to be a "normal" (ascii) key */
302
                i_key = tolower( (unsigned char)MapVirtualKey( msg.wParam, 2 ) );
303
            }
304

305
            if( i_key )
306
307
            {
                if( GetKeyState(VK_CONTROL) & 0x8000 )
308
                {
309
                    i_key |= KEY_MODIFIER_CTRL;
310
                }
gbazin's avatar
   
gbazin committed
311
                if( GetKeyState(VK_SHIFT) & 0x8000 )
gbazin's avatar
   
gbazin committed
312
                {
313
                    i_key |= KEY_MODIFIER_SHIFT;
gbazin's avatar
   
gbazin committed
314
                }
gbazin's avatar
   
gbazin committed
315
                if( GetKeyState(VK_MENU) & 0x8000 )
316
                {
317
                    i_key |= KEY_MODIFIER_ALT;
318
319
                }

320
                vout_display_SendEventKey(vd, i_key);
321
322
            }
            break;
323
        }
324
325

        case WM_MOUSEWHEEL:
326
327
        {
            int i_key;
328
329
            if( GET_WHEEL_DELTA_WPARAM( msg.wParam ) > 0 )
            {
330
                i_key = KEY_MOUSEWHEELUP;
331
332
333
            }
            else
            {
334
                i_key = KEY_MOUSEWHEELDOWN;
335
            }
336
            if( i_key )
337
338
339
            {
                if( GetKeyState(VK_CONTROL) & 0x8000 )
                {
340
                    i_key |= KEY_MODIFIER_CTRL;
341
342
343
                }
                if( GetKeyState(VK_SHIFT) & 0x8000 )
                {
344
                    i_key |= KEY_MODIFIER_SHIFT;
345
346
                }
                if( GetKeyState(VK_MENU) & 0x8000 )
gbazin's avatar
   
gbazin committed
347
                {
348
                    i_key |= KEY_MODIFIER_ALT;
gbazin's avatar
   
gbazin committed
349
                }
350
                vout_display_SendEventKey(vd, i_key);
351
352
            }
            break;
353
        }
354

355
        case WM_VLC_CHANGE_TEXT:
356
357
358
359
        {
            vlc_mutex_lock( &p_event->lock );
            wchar_t *pwz_title = NULL;
            if( p_event->psz_title )
360
            {
361
                const size_t i_length = strlen(p_event->psz_title);
Thomas Guillem's avatar
Thomas Guillem committed
362
                pwz_title = vlc_alloc( i_length + 1, 2 );
363
                if( pwz_title )
364
                {
365
366
                    mbstowcs( pwz_title, p_event->psz_title, 2 * i_length );
                    pwz_title[i_length] = 0;
367
                }
368
            }
369
            vlc_mutex_unlock( &p_event->lock );
370

371
372
            if( pwz_title )
            {
Laurent Aimar's avatar
Laurent Aimar committed
373
                SetWindowTextW( p_event->hwnd, pwz_title );
374
                if( p_event->hfswnd )
Laurent Aimar's avatar
Laurent Aimar committed
375
                    SetWindowTextW( p_event->hfswnd, pwz_title );
376
377
                free( pwz_title );
            }
378
            break;
379
        }
380

381
382
383
384
385
386
387
388
389
390
391
        default:
            /* Messages we don't handle directly are dispatched to the
             * window procedure */
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            break;

        } /* End Switch */

    } /* End Main loop */

392
    /* Check for WM_QUIT if we created the window */
393
    if( !p_event->hparent && msg.message == WM_QUIT )
394
    {
395
        msg_Warn( vd, "WM_QUIT... should not happen!!" );
396
        p_event->hwnd = NULL; /* Window already destroyed */
397
398
    }

399
    msg_Dbg( vd, "Win32 Vout EventThread terminating" );
400

401
    Win32VoutCloseWindow( p_event );
402
    vlc_restorecancel(canc);
ivoire's avatar
ivoire committed
403
    return NULL;
404
405
}

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
void EventThreadUpdateTitle( event_thread_t *p_event, const char *psz_fallback )
{
    char *psz_title = var_InheritString( p_event->vd, "video-title" );
    if( !psz_title )
        psz_title = strdup( psz_fallback );
    if( !psz_title )
        return;

    vlc_mutex_lock( &p_event->lock );
    free( p_event->psz_title );
    p_event->psz_title = psz_title;
    vlc_mutex_unlock( &p_event->lock );

    PostMessage( p_event->hwnd, WM_VLC_CHANGE_TEXT, 0, 0 );
}
int EventThreadGetWindowStyle( event_thread_t *p_event )
{
    /* No need to lock, it is serialized by EventThreadStart */
    return p_event->i_window_style;
}

void EventThreadUpdateWindowPosition( event_thread_t *p_event,
                                      bool *pb_moved, bool *pb_resized,
                                      int x, int y, unsigned w, unsigned h )
{
    vlc_mutex_lock( &p_event->lock );
432
433
434
435
436
437
438
    *pb_moved   = x != p_event->x || y != p_event->y;
    *pb_resized = w != p_event->width || h != p_event->height;

    p_event->x      = x;
    p_event->y      = y;
    p_event->width  = w;
    p_event->height = h;
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
    vlc_mutex_unlock( &p_event->lock );
}

void EventThreadUpdateSourceAndPlace( event_thread_t *p_event,
                                      const video_format_t *p_source,
                                      const vout_display_place_t *p_place )
{
    vlc_mutex_lock( &p_event->lock );
    p_event->source = *p_source;
    p_event->place  = *p_place;
    vlc_mutex_unlock( &p_event->lock );
}

void EventThreadUseOverlay( event_thread_t *p_event, bool b_used )
{
    vlc_mutex_lock( &p_event->lock );
    p_event->use_overlay = b_used;
    vlc_mutex_unlock( &p_event->lock );
}
bool EventThreadGetAndResetHasMoved( event_thread_t *p_event )
{
460
    return atomic_exchange(&p_event->has_moved, false);
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
}

event_thread_t *EventThreadCreate( vout_display_t *vd)
{
     /* Create the Vout EventThread, this thread is created by us to isolate
     * the Win32 PeekMessage function calls. We want to do this because
     * Windows can stay blocked inside this call for a long time, and when
     * this happens it thus blocks vlc's video_output thread.
     * Vout EventThread will take care of the creation of the video
     * window (because PeekMessage has to be called from the same thread which
     * created the window). */
    msg_Dbg( vd, "creating Vout EventThread" );
    event_thread_t *p_event = malloc( sizeof(*p_event) );
    if( !p_event )
        return NULL;

    p_event->vd = vd;
    vlc_mutex_init( &p_event->lock );
    vlc_cond_init( &p_event->wait );

    p_event->is_cursor_hidden = false;
    p_event->button_pressed = 0;
    p_event->psz_title = NULL;
    p_event->source = vd->source;
485
    p_event->hwnd = NULL;
486
    atomic_init(&p_event->has_moved, false);
487
488
489
    vout_display_PlacePicture(&p_event->place, &vd->source, vd->cfg, false);

    _sntprintf( p_event->class_main, sizeof(p_event->class_main)/sizeof(*p_event->class_main),
490
               _T("VLC video main %p"), (void *)p_event );
491
    _sntprintf( p_event->class_video, sizeof(p_event->class_video)/sizeof(*p_event->class_video),
492
               _T("VLC video output %p"), (void *)p_event );
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
    return p_event;
}

void EventThreadDestroy( event_thread_t *p_event )
{
    free( p_event->psz_title );
    vlc_cond_destroy( &p_event->wait );
    vlc_mutex_destroy( &p_event->lock );
    free( p_event );
}

int EventThreadStart( event_thread_t *p_event, event_hwnd_t *p_hwnd, const event_cfg_t *p_cfg )
{
    p_event->use_desktop = p_cfg->use_desktop;
    p_event->use_overlay = p_cfg->use_overlay;
508
509
510
511
    p_event->x           = p_cfg->x;
    p_event->y           = p_cfg->y;
    p_event->width       = p_cfg->width;
    p_event->height      = p_cfg->height;
512

513
    atomic_store(&p_event->has_moved, false);
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590

    p_event->b_ready = false;
    p_event->b_done  = false;
    p_event->b_error = false;

    if( vlc_clone( &p_event->thread, EventThread, p_event,
                   VLC_THREAD_PRIORITY_LOW ) )
    {
        msg_Err( p_event->vd, "cannot create Vout EventThread" );
        return VLC_EGENERIC;
    }

    vlc_mutex_lock( &p_event->lock );
    while( !p_event->b_ready )
        vlc_cond_wait( &p_event->wait, &p_event->lock );
    const bool b_error = p_event->b_error;
    vlc_mutex_unlock( &p_event->lock );

    if( b_error )
    {
        vlc_join( p_event->thread, NULL );
        p_event->b_ready = false;
        return VLC_EGENERIC;
    }
    msg_Dbg( p_event->vd, "Vout EventThread running" );

    /* */
    p_hwnd->parent_window = p_event->parent_window;
    p_hwnd->hparent       = p_event->hparent;
    p_hwnd->hwnd          = p_event->hwnd;
    p_hwnd->hvideownd     = p_event->hvideownd;
    p_hwnd->hfswnd        = p_event->hfswnd;
    return VLC_SUCCESS;
}

void EventThreadStop( event_thread_t *p_event )
{
    if( !p_event->b_ready )
        return;

    vlc_mutex_lock( &p_event->lock );
    p_event->b_done = true;
    vlc_mutex_unlock( &p_event->lock );

    /* we need to be sure Vout EventThread won't stay stuck in
     * GetMessage, so we send a fake message */
    if( p_event->hwnd )
        PostMessage( p_event->hwnd, WM_NULL, 0, 0);

    vlc_join( p_event->thread, NULL );
    p_event->b_ready = false;
}


/***********************************
 * Local functions implementations *
 ***********************************/
static void UpdateCursor( event_thread_t *p_event, bool b_show )
{
    if( p_event->is_cursor_hidden == !b_show )
        return;
    p_event->is_cursor_hidden = !b_show;

#if 1
    HCURSOR cursor = b_show ? p_event->cursor_arrow : p_event->cursor_empty;
    if( p_event->hvideownd )
        SetClassLongPtr( p_event->hvideownd, GCLP_HCURSOR, (LONG_PTR)cursor );
    if( p_event->hwnd )
        SetClassLongPtr( p_event->hwnd, GCLP_HCURSOR, (LONG_PTR)cursor );
#endif

    /* FIXME I failed to find a cleaner way to force a redraw of the cursor */
    POINT p;
    GetCursorPos(&p);
    HWND hwnd = WindowFromPoint(p);
    if( hwnd == p_event->hvideownd || hwnd == p_event->hwnd )
    {
591
        SetCursor( cursor );
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
    }
}

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 void MousePressed( event_thread_t *p_event, HWND hwnd, unsigned button )
{
    if( !p_event->button_pressed )
        SetCapture( hwnd );
    p_event->button_pressed |= 1 << button;
    vout_display_SendEventMousePressed( p_event->vd, button );
}

static void MouseReleased( event_thread_t *p_event, unsigned button )
{
    p_event->button_pressed &= ~(1 << button);
    if( !p_event->button_pressed )
        ReleaseCapture();
    vout_display_SendEventMouseReleased( p_event->vd, button );
}

Martell Malone's avatar
Martell Malone committed
631
#if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
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, _T("SHELLDLL_DefView"), NULL );
        if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SysListView32"), NULL );
        if( hwnd )
        {
            *wnd = hwnd;
            return false;
        }
    }
    return true;
}

static HWND GetDesktopHandle(vout_display_t *vd)
{
    /* Find Program Manager */
    HWND hwnd = FindWindow( _T("Progman"), NULL );
    if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SHELLDLL_DefView"), NULL );
    if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SysListView32"), NULL );
    if( hwnd )
        return hwnd;
662

663
664
665
666
667
668
    msg_Dbg( vd, "Couldn't find desktop icon window,. Trying the hard way." );

    EnumWindows( enumWindowsProc, (LPARAM)&hwnd );
    return hwnd;
}
#endif
669
670

/*****************************************************************************
671
 * Win32VoutCreateWindow: create a window for the video.
672
673
674
675
676
 *****************************************************************************
 * Before creating a direct draw surface, we need to create a window in which
 * the video will be displayed. This window will also allow us to capture the
 * events.
 *****************************************************************************/
677
static int Win32VoutCreateWindow( event_thread_t *p_event )
678
{
679
    vout_display_t *vd = p_event->vd;
680
    HINSTANCE  hInstance;
681
    HMENU      hMenu;
682
    RECT       rect_window;
683
    WNDCLASS   wc;                            /* window class components */
684
    TCHAR      vlc_path[MAX_PATH+1];
685
    int        i_style;
686

687
    msg_Dbg( vd, "Win32VoutCreateWindow" );
688

689
    /* Get this module's instance */
690
691
    hInstance = GetModuleHandle(NULL);

Martell Malone's avatar
Martell Malone committed
692
    #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
693
    if( !p_event->use_desktop )
694
    #endif
695
    {
696
        /* If an external window was specified, we'll draw in it. */
697
        p_event->parent_window = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_HWND);
698
        if( p_event->parent_window )
699
            p_event->hparent = p_event->parent_window->handle.hwnd;
700
701
        else
            p_event->hparent = NULL;
702
    }
Martell Malone's avatar
Martell Malone committed
703
    #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
704
705
    else
    {
706
        vout_display_DeleteWindow(vd, NULL);
707
        p_event->parent_window = NULL;
708
        p_event->hparent = GetDesktopHandle(vd);
709
710
    }
    #endif
711
712
    p_event->cursor_arrow = LoadCursor(NULL, IDC_ARROW);
    p_event->cursor_empty = EmptyCursor(hInstance);
713

gbazin's avatar
gbazin committed
714
    /* Get the Icon from the main app */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
715
    p_event->vlc_icon = NULL;
gbazin's avatar
gbazin committed
716
    if( GetModuleFileName( NULL, vlc_path, MAX_PATH ) )
717
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
718
        p_event->vlc_icon = ExtractIcon( hInstance, vlc_path, 0 );
719
    }
720
721
    p_event->hide_timeout = var_InheritInteger( p_event->vd, "mouse-hide-timeout" );
    UpdateCursorMoved( p_event );
gbazin's avatar
gbazin committed
722
723

    /* Fill in the window class structure */
724
    wc.style         = CS_OWNDC|CS_DBLCLKS;          /* style: dbl click */
725
    wc.lpfnWndProc   = (WNDPROC)WinVoutEventProc;       /* event handler */
gbazin's avatar
gbazin committed
726
727
728
    wc.cbClsExtra    = 0;                         /* no extra class data */
    wc.cbWndExtra    = 0;                        /* no extra window data */
    wc.hInstance     = hInstance;                            /* instance */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
729
    wc.hIcon         = p_event->vlc_icon;       /* load the vlc big icon */
730
731
    wc.hCursor       = p_event->is_cursor_hidden ? p_event->cursor_empty :
                                                   p_event->cursor_arrow;
Martell Malone's avatar
Martell Malone committed
732
#if !VLC_WINSTORE_APP
gbazin's avatar
gbazin committed
733
    wc.hbrBackground = GetStockObject(BLACK_BRUSH);  /* background color */
Martell Malone's avatar
Martell Malone committed
734
735
736
#else
    wc.hbrBackground = NULL;
#endif
gbazin's avatar
gbazin committed
737
    wc.lpszMenuName  = NULL;                                  /* no menu */
738
    wc.lpszClassName = p_event->class_main;       /* use a special class */
gbazin's avatar
gbazin committed
739
740

    /* Register the window class */
741
    if( !RegisterClass(&wc) )
742
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
743
744
        if( p_event->vlc_icon )
            DestroyIcon( p_event->vlc_icon );
745

746
        msg_Err( vd, "Win32VoutCreateWindow RegisterClass FAILED (err=%lu)", GetLastError() );
747
        return VLC_EGENERIC;
gbazin's avatar
gbazin committed
748
    }
749

750
    /* Register the video sub-window class */
751
752
    wc.lpszClassName = p_event->class_video;
    wc.hIcon = 0;
753
    wc.hbrBackground = NULL; /* no background color */
754
    if( !RegisterClass(&wc) )
755
    {
756
        msg_Err( vd, "Win32VoutCreateWindow RegisterClass FAILED (err=%lu)", GetLastError() );
757
        return VLC_EGENERIC;
758
759
    }

gbazin's avatar
gbazin committed
760
761
762
763
764
    /* When you create a window you give the dimensions you wish it to
     * have. Unfortunatly these dimensions will include the borders and
     * titlebar. We use the following function to find out the size of
     * the window corresponding to the useable surface we want */
    rect_window.left   = 10;
765
    rect_window.top    = 10;
766
767
    rect_window.right  = rect_window.left + p_event->width;
    rect_window.bottom = rect_window.top  + p_event->height;
768

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
769
    i_style = var_GetBool( vd, "video-deco" )
770
        /* Open with window decoration */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
771
        ? WS_OVERLAPPEDWINDOW|WS_SIZEBOX
772
        /* No window decoration */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
773
774
775
        : WS_POPUP;
    AdjustWindowRect( &rect_window, i_style, 0 );
    i_style |= WS_VISIBLE|WS_CLIPCHILDREN;
776

777
    if( p_event->hparent )
778
    {
gbazin's avatar
gbazin committed
779
        i_style = WS_VISIBLE|WS_CLIPCHILDREN|WS_CHILD;
780
781
782
783
784
785

        /* allow user to regain control over input events if requested */
        bool b_mouse_support = var_InheritBool( vd, "mouse-events" );
        bool b_key_support = var_InheritBool( vd, "keyboard-events" );
        if( !b_mouse_support && !b_key_support )
            i_style |= WS_DISABLED;
786
    }
787

788
    p_event->i_window_style = i_style;
789

gbazin's avatar
gbazin committed
790
    /* Create the window */
791
    p_event->hwnd =
792
        CreateWindowEx( WS_EX_NOPARENTNOTIFY,
793
                    p_event->class_main,             /* name of window class */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
794
                    _T(VOUT_TITLE) _T(" (VLC Video Output)"),/* window title */
gbazin's avatar
gbazin committed
795
                    i_style,                                 /* window style */
796
797
798
799
                    (!p_event->x) ? (UINT)CW_USEDEFAULT :
                        (UINT)p_event->x,            /* default X coordinate */
                    (!p_event->y) ? (UINT)CW_USEDEFAULT :
                        (UINT)p_event->y,            /* default Y coordinate */
800
801
                    rect_window.right - rect_window.left,    /* window width */
                    rect_window.bottom - rect_window.top,   /* window height */
802
                    p_event->hparent,                       /* parent window */
803
804
                    NULL,                          /* no menu in this window */
                    hInstance,            /* handle of this program instance */
805
                    (LPVOID)p_event );           /* send vd to WM_CREATE */
806

807
    if( !p_event->hwnd )
gbazin's avatar
gbazin committed
808
    {
809
        msg_Warn( vd, "Win32VoutCreateWindow create window FAILED (err=%lu)", GetLastError() );
gbazin's avatar
gbazin committed
810
811
812
        return VLC_EGENERIC;
    }

813
814
    bool b_isProjected  = (vd->fmt.projection_mode != PROJECTION_MODE_RECTANGULAR);
    InitGestures( p_event->hwnd, &p_event->p_gesture, b_isProjected );
815

816
817
    p_event->p_sensors = HookWindowsSensors(vd, p_event->hwnd);

818
    if( p_event->hparent )
gbazin's avatar
gbazin committed
819
820
821
822
    {
        LONG i_style;

        /* We don't want the window owner to overwrite our client area */
823
        i_style = GetWindowLong( p_event->hparent, GWL_STYLE );
824
825
826

        if( !(i_style & WS_CLIPCHILDREN) )
            /* Hmmm, apparently this is a blocking call... */
827
            SetWindowLong( p_event->hparent, GWL_STYLE,
828
                           i_style | WS_CLIPCHILDREN );
829
830

        /* Create our fullscreen window */
831
        p_event->hfswnd =
832
            CreateWindowEx( WS_EX_APPWINDOW, p_event->class_main,
833
                            _T(VOUT_TITLE) _T(" (VLC Fullscreen Video Output)"),
834
835
836
837
                            WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_SIZEBOX,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            NULL, NULL, hInstance, NULL );
838
    }
839
840
841
842
    else
    {
        p_event->hfswnd = NULL;
    }
843

844
    /* Append a "Always On Top" entry in the system menu */
845
    hMenu = GetSystemMenu( p_event->hwnd, FALSE );
846
    AppendMenu( hMenu, MF_SEPARATOR, 0, _T("") );
847
    AppendMenu( hMenu, MF_STRING | MF_UNCHECKED,
848
                       IDM_TOGGLE_ON_TOP, _T("Always on &Top") );
849

850
851
852
    /* Create video sub-window. This sub window will always exactly match
     * the size of the video, which allows us to use crazy overlay colorkeys
     * without having them shown outside of the video area. */
853
    /* FIXME vd->source.i_width/i_height seems wrong */
854
    p_event->hvideownd =
855
    CreateWindow( p_event->class_video, _T(""),   /* window class */
856
        WS_CHILD,                   /* window style, not visible initially */
damienf's avatar
damienf committed
857
        0, 0,
858
859
860
        vd->source.i_width,          /* default width */
        vd->source.i_height,        /* default height */
        p_event->hwnd,               /* parent window */
damienf's avatar
damienf committed
861
        NULL, hInstance,
862
        (LPVOID)p_event );    /* send vd to WM_CREATE */
863

864
    if( !p_event->hvideownd )
865
        msg_Warn( vd, "can't create video sub-window" );
866
    else
867
        msg_Dbg( vd, "created video sub-window" );
868
869

    /* Now display the window */
870
    ShowWindow( p_event->hwnd, SW_SHOW );
871

872
    return VLC_SUCCESS;
873
874
875
}

/*****************************************************************************
876
 * Win32VoutCloseWindow: close the window created by Win32VoutCreateWindow
877
 *****************************************************************************
878
 * This function returns all resources allocated by Win32VoutCreateWindow.
879
 *****************************************************************************/
880
static void Win32VoutCloseWindow( event_thread_t *p_event )
881
{
882
    vout_display_t *vd = p_event->vd;
883
    msg_Dbg( vd, "Win32VoutCloseWindow" );
884

Martell Malone's avatar
Martell Malone committed
885
    #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
886
887
    DestroyWindow( p_event->hvideownd );
    #endif
888
    DestroyWindow( p_event->hwnd );
889
890
    if( p_event->hfswnd )
        DestroyWindow( p_event->hfswnd );
gbazin's avatar
gbazin committed
891

Martell Malone's avatar
Martell Malone committed
892
    #if defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)
893
    if( !p_event->use_desktop )
894
    #endif
895
        vout_display_DeleteWindow( vd, p_event->parent_window );
896
    p_event->hwnd = NULL;
897

898
899
900
901
    HINSTANCE hInstance = GetModuleHandle(NULL);
    UnregisterClass( p_event->class_video, hInstance );
    UnregisterClass( p_event->class_main, hInstance );

902
903
904
    if( p_event->vlc_icon )
        DestroyIcon( p_event->vlc_icon );

905
    DestroyCursor( p_event->cursor_empty );
906

907
908
    UnhookWindowsSensors(p_event->p_sensors);

909
    CloseGestures( p_event->p_gesture);
910
911
912
}

/*****************************************************************************
913
 * WinVoutEventProc: This is the window event processing function.
914
915
916
917
918
919
920
921
922
 *****************************************************************************
 * 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...)
 *****************************************************************************/
923
static long FAR PASCAL WinVoutEventProc( HWND hwnd, UINT message,
924
925
                                         WPARAM wParam, LPARAM lParam )
{
926
    event_thread_t *p_event;
927

928
929
    if( message == WM_CREATE )
    {
930
        /* Store vd for future use */
931
932
        p_event = (event_thread_t *)((CREATESTRUCT *)lParam)->lpCreateParams;
        SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)p_event );
damienf's avatar
damienf committed
933
        return TRUE;
934
935
    }
    else
936
    {
937
        LONG_PTR p_user_data = GetWindowLongPtr( hwnd, GWLP_USERDATA );
938
939
        p_event = (event_thread_t *)p_user_data;
        if( !p_event )
damienf's avatar
damienf committed
940
941
942
943
944
        {
            /* Hmmm mozilla does manage somehow to save the pointer to our
             * windowproc and still calls it after the vout has been closed. */
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
945
    }
946
    vout_display_t *vd = p_event->vd;
947

948
949
950
951
952
953
954
955
#if 0
    if( message == WM_SETCURSOR )
    {
        msg_Err(vd, "WM_SETCURSOR: %d (t2)", p_event->is_cursor_hidden);
        SetCursor( p_event->is_cursor_hidden ? p_event->cursor_empty : p_event->cursor_arrow );
        return 1;
    }
#endif
956
957
958
959
960
961
962
963
964
965
966
967
968
    if( message == WM_CAPTURECHANGED )
    {
        for( int button = 0; p_event->button_pressed; button++ )
        {
            unsigned m = 1 << button;
            if( p_event->button_pressed & m )
                vout_display_SendEventMouseReleased( p_event->vd, button );
            p_event->button_pressed &= ~m;
        }
        p_event->button_pressed = 0;
        return 0;
    }

969
    if( hwnd == p_event->hvideownd )
970
    {
971
#ifdef MODULE_NAME_IS_directdraw
972
973
974
975
976
        vlc_mutex_lock( &p_event->lock );
        const bool use_overlay = p_event->use_overlay;
        vlc_mutex_unlock( &p_event->lock );
#endif

977
978
        switch( message )
        {
979
#ifdef MODULE_NAME_IS_directdraw
980
        case WM_ERASEBKGND:
981
        /* For overlay, we need to erase background */
982
            return !use_overlay ? 1 : DefWindowProc(hwnd, message, wParam, lParam);
983
        case WM_PAINT:
984
985
986
        /*
        ** For overlay, DefWindowProc() will erase dirty regions
        ** with colorkey.
987
        ** For non-overlay, vout will paint the whole window at
988
989
990
        ** regular interval, therefore dirty regions can be ignored
        ** to minimize repaint.
        */
991
            if( !use_overlay )
992
993
994
            {
                ValidateRect(hwnd, NULL);
            }
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
            // fall through to default
#else
        /*
        ** For OpenGL and Direct3D, vout will update the whole
        ** window at regular interval, therefore dirty region
        ** can be ignored to minimize repaint.
        */
        case WM_ERASEBKGND:
            /* nothing to erase */
            return 1;
        case WM_PAINT:
            /* nothing to repaint */
            ValidateRect(hwnd, NULL);
1008
            // fall through
1009
#endif
1010
1011
1012
1013
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
    }
1014

1015
1016
1017
1018
    switch( message )
    {

    case WM_WINDOWPOSCHANGED:
1019
        atomic_store(&p_event->has_moved, true);
Sam Hocevar's avatar