]> git.sesse.net Git - vlc/blob - modules/video_output/msw/glwin32.c
88ba76c84b67544f448870b92951ea8cf35f7a32
[vlc] / modules / video_output / msw / glwin32.c
1 /*****************************************************************************
2  * glwin32.c: Windows OpenGL provider
3  *****************************************************************************
4  * Copyright (C) 2001-2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include <errno.h>                                                 /* ENOMEM */
25
26 #include <vlc/vlc.h>
27 #include <vlc_interface.h>
28 #include <vlc_vout.h>
29
30 #include <windows.h>
31 #include <ddraw.h>
32 #include <commctrl.h>
33
34 #include <multimon.h>
35 #undef GetSystemMetrics
36
37 #ifndef MONITOR_DEFAULTTONEAREST
38 #   define MONITOR_DEFAULTTONEAREST 2
39 #endif
40
41 #include <GL/gl.h>
42
43 #include "vout.h"
44
45 /*****************************************************************************
46  * Local prototypes.
47  *****************************************************************************/
48 static int  OpenVideo  ( vlc_object_t * );
49 static void CloseVideo ( vlc_object_t * );
50
51 static int  Init      ( vout_thread_t * );
52 static void End       ( vout_thread_t * );
53 static int  Manage    ( vout_thread_t * );
54 static void GLSwapBuffers( vout_thread_t * );
55 static void FirstSwap( vout_thread_t * );
56
57 /*****************************************************************************
58  * Module descriptor
59  *****************************************************************************/
60 vlc_module_begin();
61     set_category( CAT_VIDEO );
62     set_subcategory( SUBCAT_VIDEO_VOUT );
63     set_shortname( "OpenGL" );
64     set_description( _("OpenGL video output") );
65     set_capability( "opengl provider", 100 );
66     add_shortcut( "glwin32" );
67     set_callbacks( OpenVideo, CloseVideo );
68
69     /* FIXME: Hack to avoid unregistering our window class */
70     linked_with_a_crap_library_which_uses_atexit( );
71 vlc_module_end();
72
73 #if 0 /* FIXME */
74     /* check if we registered a window class because we need to
75      * unregister it */
76     WNDCLASS wndclass;
77     if( GetClassInfo( GetModuleHandle(NULL), "VLC DirectX", &wndclass ) )
78         UnregisterClass( "VLC DirectX", GetModuleHandle(NULL) );
79 #endif
80
81 /*****************************************************************************
82  * OpenVideo: allocate OpenGL provider
83  *****************************************************************************
84  * This function creates and initializes a video window.
85  *****************************************************************************/
86 static int OpenVideo( vlc_object_t *p_this )
87 {
88     vout_thread_t * p_vout = (vout_thread_t *)p_this;
89     vlc_value_t val;
90
91     /* Allocate structure */
92     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
93     if( p_vout->p_sys == NULL )
94     {
95         msg_Err( p_vout, "out of memory" );
96         return VLC_ENOMEM;
97     }
98     memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
99
100     /* Initialisations */
101     p_vout->pf_init = Init;
102     p_vout->pf_end = End;
103     p_vout->pf_manage = Manage;
104     p_vout->pf_swap = FirstSwap;
105
106     p_vout->p_sys->hwnd = p_vout->p_sys->hvideownd = NULL;
107     p_vout->p_sys->hparent = p_vout->p_sys->hfswnd = NULL;
108     p_vout->p_sys->i_changes = 0;
109     vlc_mutex_init( p_vout, &p_vout->p_sys->lock );
110     SetRectEmpty( &p_vout->p_sys->rect_display );
111     SetRectEmpty( &p_vout->p_sys->rect_parent );
112
113     var_Create( p_vout, "video-title", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
114
115     p_vout->p_sys->b_cursor_hidden = 0;
116     p_vout->p_sys->i_lastmoved = mdate();
117
118     /* Set main window's size */
119     p_vout->p_sys->i_window_width = p_vout->i_window_width;
120     p_vout->p_sys->i_window_height = p_vout->i_window_height;
121
122     /* Create the Vout EventThread, this thread is created by us to isolate
123      * the Win32 PeekMessage function calls. We want to do this because
124      * Windows can stay blocked inside this call for a long time, and when
125      * this happens it thus blocks vlc's video_output thread.
126      * Vout EventThread will take care of the creation of the video
127      * window (because PeekMessage has to be called from the same thread which
128      * created the window). */
129     msg_Dbg( p_vout, "creating Vout EventThread" );
130     p_vout->p_sys->p_event =
131         vlc_object_create( p_vout, sizeof(event_thread_t) );
132     p_vout->p_sys->p_event->p_vout = p_vout;
133     if( vlc_thread_create( p_vout->p_sys->p_event, "Vout Events Thread",
134                            E_(EventThread), 0, 1 ) )
135     {
136         msg_Err( p_vout, "cannot create Vout EventThread" );
137         vlc_object_destroy( p_vout->p_sys->p_event );
138         p_vout->p_sys->p_event = NULL;
139         goto error;
140     }
141
142     if( p_vout->p_sys->p_event->b_error )
143     {
144         msg_Err( p_vout, "Vout EventThread failed" );
145         goto error;
146     }
147
148     vlc_object_attach( p_vout->p_sys->p_event, p_vout );
149
150     msg_Dbg( p_vout, "Vout EventThread running" );
151
152     /* Variable to indicate if the window should be on top of others */
153     /* Trigger a callback right now */
154     var_Get( p_vout, "video-on-top", &val );
155     var_Set( p_vout, "video-on-top", val );
156
157     return VLC_SUCCESS;
158
159  error:
160     CloseVideo( VLC_OBJECT(p_vout) );
161     return VLC_EGENERIC;
162 }
163
164 /*****************************************************************************
165  * Init: initialize video thread output method
166  *****************************************************************************/
167 static int Init( vout_thread_t *p_vout )
168 {
169     PIXELFORMATDESCRIPTOR pfd;
170     int iFormat;
171
172     /* Change the window title bar text */
173     PostMessage( p_vout->p_sys->hwnd, WM_VLC_CHANGE_TEXT, 0, 0 );
174
175     p_vout->p_sys->hGLDC = GetDC( p_vout->p_sys->hvideownd );
176
177     /* Set the pixel format for the DC */
178     memset( &pfd, 0, sizeof( pfd ) );
179     pfd.nSize = sizeof( pfd );
180     pfd.nVersion = 1;
181     pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
182     pfd.iPixelType = PFD_TYPE_RGBA;
183     pfd.cColorBits = 24;
184     pfd.cDepthBits = 16;
185     pfd.iLayerType = PFD_MAIN_PLANE;
186     iFormat = ChoosePixelFormat( p_vout->p_sys->hGLDC, &pfd );
187     SetPixelFormat( p_vout->p_sys->hGLDC, iFormat, &pfd );
188
189     /* Create and enable the render context */
190     p_vout->p_sys->hGLRC = wglCreateContext( p_vout->p_sys->hGLDC );
191     wglMakeCurrent( p_vout->p_sys->hGLDC, p_vout->p_sys->hGLRC );
192
193     return VLC_SUCCESS;
194 }
195
196 /*****************************************************************************
197  * End: terminate Sys video thread output method
198  *****************************************************************************
199  * Terminate an output method created by Create.
200  * It is called at the end of the thread.
201  *****************************************************************************/
202 static void End( vout_thread_t *p_vout )
203 {
204     wglMakeCurrent( NULL, NULL );
205     wglDeleteContext( p_vout->p_sys->hGLRC );
206     ReleaseDC( p_vout->p_sys->hvideownd, p_vout->p_sys->hGLDC );
207     return;
208 }
209
210 /*****************************************************************************
211  * CloseVideo: destroy Sys video thread output method
212  *****************************************************************************
213  * Terminate an output method created by Create
214  *****************************************************************************/
215 static void CloseVideo( vlc_object_t *p_this )
216 {
217     vout_thread_t * p_vout = (vout_thread_t *)p_this;
218
219     msg_Dbg( p_vout, "closing video" );
220
221     if( p_vout->p_sys->p_event )
222     {
223         vlc_object_detach( p_vout->p_sys->p_event );
224
225         /* Kill Vout EventThread */
226         vlc_object_kill( p_vout->p_sys->p_event );
227
228         /* we need to be sure Vout EventThread won't stay stuck in
229          * GetMessage, so we send a fake message */
230         if( p_vout->p_sys->hwnd )
231         {
232             PostMessage( p_vout->p_sys->hwnd, WM_NULL, 0, 0);
233         }
234
235         vlc_thread_join( p_vout->p_sys->p_event );
236         vlc_object_destroy( p_vout->p_sys->p_event );
237     }
238
239     vlc_mutex_destroy( &p_vout->p_sys->lock );
240
241     if( p_vout->p_sys )
242     {
243         free( p_vout->p_sys );
244         p_vout->p_sys = NULL;
245     }
246 }
247
248 /*****************************************************************************
249  * Manage: handle Sys events
250  *****************************************************************************
251  * This function should be called regularly by the video output thread.
252  * It returns a non null value if an error occurred.
253  *****************************************************************************/
254 static int Manage( vout_thread_t *p_vout )
255 {
256     int i_width = p_vout->p_sys->rect_dest.right -
257         p_vout->p_sys->rect_dest.left;
258     int i_height = p_vout->p_sys->rect_dest.bottom -
259         p_vout->p_sys->rect_dest.top;
260     glViewport( 0, 0, i_width, i_height );
261
262     /* If we do not control our window, we check for geometry changes
263      * ourselves because the parent might not send us its events. */
264     vlc_mutex_lock( &p_vout->p_sys->lock );
265     if( p_vout->p_sys->hparent && !p_vout->b_fullscreen )
266     {
267         RECT rect_parent;
268         POINT point;
269
270         vlc_mutex_unlock( &p_vout->p_sys->lock );
271
272         GetClientRect( p_vout->p_sys->hparent, &rect_parent );
273         point.x = point.y = 0;
274         ClientToScreen( p_vout->p_sys->hparent, &point );
275         OffsetRect( &rect_parent, point.x, point.y );
276
277         if( !EqualRect( &rect_parent, &p_vout->p_sys->rect_parent ) )
278         {
279             p_vout->p_sys->rect_parent = rect_parent;
280
281             /* This one is to force the update even if only
282              * the position has changed */
283             SetWindowPos( p_vout->p_sys->hwnd, 0, 1, 1,
284                           rect_parent.right - rect_parent.left,
285                           rect_parent.bottom - rect_parent.top, 0 );
286
287             SetWindowPos( p_vout->p_sys->hwnd, 0, 0, 0,
288                           rect_parent.right - rect_parent.left,
289                           rect_parent.bottom - rect_parent.top, 0 );
290         }
291     }
292     else
293     {
294         vlc_mutex_unlock( &p_vout->p_sys->lock );
295     }
296
297     /* Check for cropping / aspect changes */
298     if( p_vout->i_changes & VOUT_CROP_CHANGE ||
299         p_vout->i_changes & VOUT_ASPECT_CHANGE )
300     {
301         p_vout->i_changes &= ~VOUT_CROP_CHANGE;
302         p_vout->i_changes &= ~VOUT_ASPECT_CHANGE;
303
304         p_vout->fmt_out.i_x_offset = p_vout->fmt_in.i_x_offset;
305         p_vout->fmt_out.i_y_offset = p_vout->fmt_in.i_y_offset;
306         p_vout->fmt_out.i_visible_width = p_vout->fmt_in.i_visible_width;
307         p_vout->fmt_out.i_visible_height = p_vout->fmt_in.i_visible_height;
308         p_vout->fmt_out.i_aspect = p_vout->fmt_in.i_aspect;
309         p_vout->fmt_out.i_sar_num = p_vout->fmt_in.i_sar_num;
310         p_vout->fmt_out.i_sar_den = p_vout->fmt_in.i_sar_den;
311         p_vout->output.i_aspect = p_vout->fmt_in.i_aspect;
312         E_(UpdateRects)( p_vout, VLC_TRUE );
313     }
314
315     /* We used to call the Win32 PeekMessage function here to read the window
316      * messages. But since window can stay blocked into this function for a
317      * long time (for example when you move your window on the screen), I
318      * decided to isolate PeekMessage in another thread. */
319
320     /*
321      * Fullscreen change
322      */
323     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE
324         || p_vout->p_sys->i_changes & VOUT_FULLSCREEN_CHANGE )
325     {
326         Win32ToggleFullscreen( p_vout );
327
328         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
329         p_vout->p_sys->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
330     }
331
332     /*
333      * Pointer change
334      */
335     if( p_vout->b_fullscreen && !p_vout->p_sys->b_cursor_hidden &&
336         (mdate() - p_vout->p_sys->i_lastmoved) > 5000000 )
337     {
338         POINT point;
339         HWND hwnd;
340
341         /* Hide the cursor only if it is inside our window */
342         GetCursorPos( &point );
343         hwnd = WindowFromPoint(point);
344         if( hwnd == p_vout->p_sys->hwnd || hwnd == p_vout->p_sys->hvideownd )
345         {
346             PostMessage( p_vout->p_sys->hwnd, WM_VLC_HIDE_MOUSE, 0, 0 );
347         }
348         else
349         {
350             p_vout->p_sys->i_lastmoved = mdate();
351         }
352     }
353
354     /*
355      * "Always on top" status change
356      */
357     if( p_vout->p_sys->b_on_top_change )
358     {
359         vlc_value_t val;
360         HMENU hMenu = GetSystemMenu( p_vout->p_sys->hwnd, FALSE );
361
362         var_Get( p_vout, "video-on-top", &val );
363
364         /* Set the window on top if necessary */
365         if( val.b_bool && !( GetWindowLong( p_vout->p_sys->hwnd, GWL_EXSTYLE )
366                            & WS_EX_TOPMOST ) )
367         {
368             CheckMenuItem( hMenu, IDM_TOGGLE_ON_TOP,
369                            MF_BYCOMMAND | MFS_CHECKED );
370             SetWindowPos( p_vout->p_sys->hwnd, HWND_TOPMOST, 0, 0, 0, 0,
371                           SWP_NOSIZE | SWP_NOMOVE );
372         }
373         else
374         /* The window shouldn't be on top */
375         if( !val.b_bool && ( GetWindowLong( p_vout->p_sys->hwnd, GWL_EXSTYLE )
376                            & WS_EX_TOPMOST ) )
377         {
378             CheckMenuItem( hMenu, IDM_TOGGLE_ON_TOP,
379                            MF_BYCOMMAND | MFS_UNCHECKED );
380             SetWindowPos( p_vout->p_sys->hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
381                           SWP_NOSIZE | SWP_NOMOVE );
382         }
383
384         p_vout->p_sys->b_on_top_change = VLC_FALSE;
385     }
386
387     /* Check if the event thread is still running */
388     if( p_vout->p_sys->p_event->b_die )
389     {
390         return VLC_EGENERIC; /* exit */
391     }
392
393     return VLC_SUCCESS;
394 }
395
396 /*****************************************************************************
397  * GLSwapBuffers: swap front/back buffers
398  *****************************************************************************/
399 static void GLSwapBuffers( vout_thread_t *p_vout )
400 {
401     SwapBuffers( p_vout->p_sys->hGLDC );
402 }
403
404 /*
405 ** this function is only used once when the first picture is received
406 ** this function will show the video window once a picture is ready
407 */
408
409 static void FirstSwap( vout_thread_t *p_vout )
410 {
411     /* get initial picture buffer swapped to front buffer */
412     GLSwapBuffers( p_vout );
413
414     /*
415     ** Video window is initially hidden, show it now since we got a 
416     ** picture to show.
417     */
418     SetWindowPos( p_vout->p_sys->hvideownd, NULL, 0, 0, 0, 0, 
419         SWP_ASYNCWINDOWPOS|
420         SWP_FRAMECHANGED|
421         SWP_SHOWWINDOW|
422         SWP_NOMOVE|
423         SWP_NOSIZE|
424         SWP_NOZORDER );
425
426     /* use and restores proper swap function for further pictures */
427     p_vout->pf_swap = GLSwapBuffers;
428 }