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