sbGStreamerPlatformWin32.cpp
Go to the documentation of this file.
1 /*
2 //
3 // BEGIN SONGBIRD GPL
4 //
5 // This file is part of the Songbird web player.
6 //
7 // Copyright(c) 2005-2008 POTI, Inc.
8 // http://songbirdnest.com
9 //
10 // This file may be licensed under the terms of of the
11 // GNU General Public License Version 2 (the "GPL").
12 //
13 // Software distributed under the License is distributed
14 // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
15 // express or implied. See the GPL for the specific language
16 // governing rights and limitations.
17 //
18 // You should have received a copy of the GPL along with this
19 // program. If not, go to http://www.gnu.org/licenses/gpl.html
20 // or write to the Free Software Foundation, Inc.,
21 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 // END SONGBIRD GPL
24 //
25 */
26 
28 #include "sbGStreamerMediacore.h"
29 
30 #include <prlog.h>
31 #include <nsDebug.h>
32 
33 #include <nsIThread.h>
34 #include <nsThreadUtils.h>
35 
36 #include <sbVariantUtils.h>
37 
44 #ifdef PR_LOGGING
45 
46 static PRLogModuleInfo* gGStreamerPlatformWin32 =
47  PR_NewLogModule("sbGStreamerPlatformWin32");
48 
49 #define LOG(args) \
50  if (gGStreamerPlatformWin32) \
51  PR_LOG(gGStreamerPlatformWin32, PR_LOG_WARNING, args)
52 
53 #define TRACE(args) \
54  if (gGStreamerPlatformWin32) \
55  PR_LOG(gGStreamerPlatformWin32, PR_LOG_DEBUG, args)
56 
57 #else /* PR_LOGGING */
58 
59 #define LOG(args) /* nothing */
60 #define TRACE(args) /* nothing */
61 
62 #endif /* PR_LOGGING */
63 
64 
65 #define SB_VIDEOWINDOW_CLASSNAME L"SBGStreamerVideoWindow"
66 
67 #define CURSOR_HIDE_ID 1 //timer id
68 #define CURSOR_HIDE_DELAY 3000 //delay in milliseconds
69 
70 // This is normally defined in a Windows header but this header is not
71 // part of the free toolset provided by MS so we define our own if it's
72 // not defined.
73 #ifndef GET_X_LPARAM
74 #define GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam))
75 #endif
76 #ifndef GET_Y_LPARAM
77 #define GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam))
78 #endif
79 
80 /* static */ LRESULT APIENTRY
81 Win32PlatformInterface::VideoWindowProc(HWND hWnd, UINT message,
82  WPARAM wParam, LPARAM lParam)
83 {
84  Win32PlatformInterface *platform =
85  (Win32PlatformInterface *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
86 
87  switch (message) {
88  case WM_KEYDOWN: {
89  nsCOMPtr<nsIDOMKeyEvent> keyEvent;
90  nsresult rv = platform->CreateDOMKeyEvent(getter_AddRefs(keyEvent));
91  if(NS_SUCCEEDED(rv)) {
92  PRBool shiftKeyState = HIBYTE(GetKeyState(VK_SHIFT)) > 0;
93  PRBool ctrlKeyState = HIBYTE(GetKeyState(VK_CONTROL)) > 0;
94  PRBool altKeyState = HIBYTE(GetKeyState(VK_MENU)) > 0;
95  PRBool winKeyStateL = HIBYTE(GetKeyState(VK_LWIN)) > 0;
96  PRBool winKeyStateR = HIBYTE(GetKeyState(VK_RWIN)) > 0;
97 
98  PRInt32 keyCode = wParam;
99  PRInt32 charCode = LOWORD(MapVirtualKey(wParam, MAPVK_VK_TO_CHAR));
100 
101  rv = keyEvent->InitKeyEvent(NS_LITERAL_STRING("keypress"),
102  PR_TRUE,
103  PR_TRUE,
104  nsnull,
105  ctrlKeyState,
106  altKeyState,
107  shiftKeyState,
108  winKeyStateL || winKeyStateR,
109  keyCode,
110  charCode);
111  if(NS_SUCCEEDED(rv)) {
112  nsCOMPtr<nsIDOMEvent> event(do_QueryInterface(keyEvent));
113  platform->DispatchDOMEvent(event);
114 
115  return 0;
116  }
117  }
118  }
119  break;
120 
121  case WM_LBUTTONDOWN: {
122  nsCOMPtr<nsIDOMMouseEvent> mouseEvent;
123  nsresult rv = platform->CreateDOMMouseEvent(getter_AddRefs(mouseEvent));
124  if(NS_SUCCEEDED(rv)) {
125  PRBool shiftKeyState = HIBYTE(GetKeyState(VK_SHIFT)) > 0;
126  PRBool ctrlKeyState = HIBYTE(GetKeyState(VK_CONTROL)) > 0;
127  PRBool altKeyState = HIBYTE(GetKeyState(VK_MENU)) > 0;
128  PRBool winKeyStateL = HIBYTE(GetKeyState(VK_LWIN)) > 0;
129  PRBool winKeyStateR = HIBYTE(GetKeyState(VK_RWIN)) > 0;
130 
131  PRInt32 clientX = GET_X_LPARAM(lParam);
132  PRInt32 clientY = GET_Y_LPARAM(lParam);
133 
134  POINT point = {0};
135  point.x = clientX;
136  point.y = clientY;
137 
138  BOOL success = ClientToScreen(hWnd, &point);
139  NS_WARN_IF_FALSE(success,
140  "Failed to convert coordinates, popup menu will be positioned wrong");
141 
142  rv = mouseEvent->InitMouseEvent(NS_LITERAL_STRING("click"),
143  PR_TRUE,
144  PR_TRUE,
145  nsnull,
146  0,
147  point.x,
148  point.y,
149  clientX,
150  clientY,
151  ctrlKeyState,
152  altKeyState,
153  shiftKeyState,
154  winKeyStateL || winKeyStateR,
155  0,
156  nsnull);
157  if(NS_SUCCEEDED(rv)) {
158  nsCOMPtr<nsIDOMEvent> event(do_QueryInterface(mouseEvent));
159  platform->DispatchDOMEvent(event);
160 
161  return 0;
162  }
163  }
164  }
165  break;
166 
167  case WM_RBUTTONUP:
168  case WM_CONTEXTMENU: {
169  nsCOMPtr<nsIDOMMouseEvent> mouseEvent;
170  nsresult rv = platform->CreateDOMMouseEvent(getter_AddRefs(mouseEvent));
171  if(NS_SUCCEEDED(rv)) {
172  PRBool shiftKeyState = HIBYTE(GetKeyState(VK_SHIFT)) > 0;
173  PRBool ctrlKeyState = HIBYTE(GetKeyState(VK_CONTROL)) > 0;
174  PRBool altKeyState = HIBYTE(GetKeyState(VK_MENU)) > 0;
175  PRBool winKeyStateL = HIBYTE(GetKeyState(VK_LWIN)) > 0;
176  PRBool winKeyStateR = HIBYTE(GetKeyState(VK_RWIN)) > 0;
177 
178  PRInt32 screenX = GET_X_LPARAM(lParam);
179  PRInt32 screenY = GET_Y_LPARAM(lParam);
180 
181  PRInt32 clientX = screenX;
182  PRInt32 clientY = screenY;
183 
184  POINT point = {0};
185  BOOL success = FALSE;
186 
187  if(message == WM_RBUTTONUP) {
188  point.x = clientX;
189  point.y = clientY;
190 
191  success = ClientToScreen(hWnd, &point);
192 
193  screenX = point.x;
194  screenY = point.y;
195  }
196  else {
197  point.x = screenX;
198  point.y = screenY;
199 
200  success = ScreenToClient(hWnd, &point);
201  }
202 
203  NS_WARN_IF_FALSE(success,
204  "Failed to convert coordinates, popup menu will be positioned wrong");
205 
206  rv = mouseEvent->InitMouseEvent(NS_LITERAL_STRING("contextmenu"),
207  PR_TRUE,
208  PR_TRUE,
209  nsnull,
210  0,
211  screenX,
212  screenY,
213  clientX,
214  clientY,
215  ctrlKeyState,
216  altKeyState,
217  shiftKeyState,
218  winKeyStateL || winKeyStateR,
219  2,
220  nsnull);
221  if(NS_SUCCEEDED(rv)) {
222  nsCOMPtr<nsIDOMEvent> event(do_QueryInterface(mouseEvent));
223  platform->DispatchDOMEvent(event);
224 
225  return 0;
226  }
227  }
228  }
229  break;
230 
231  case WM_MOUSEMOVE: {
232  PRInt32 clientX = GET_X_LPARAM(lParam);
233  PRInt32 clientY = GET_Y_LPARAM(lParam);
234 
235  if(platform->HasMouseMoved(clientX, clientY)) {
236 
237  if(platform->mFullscreen) {
238  // setup tracking of mouse so that we know when it leaves
239  // the fullscreen window. This is extremely important
240  // for multi monitor setups so that we do not end up
241  // hiding the cursor when it should be shown!
242  TRACKMOUSEEVENT tme = {0};
243  tme.cbSize = sizeof(TRACKMOUSEEVENT);
244  tme.dwFlags = TME_LEAVE;
245  tme.hwndTrack = hWnd;
246  tme.dwHoverTime = HOVER_DEFAULT;
247 
248  // we don't really care if this fails as there's not much
249  // we can do to recover other than try and call again
250  // which will happen on it's own as the user moves the mouse.
251  ::TrackMouseEvent(&tme);
252 
253  if(!platform->mCursorShowing) {
254  ::ShowCursor(TRUE);
255  platform->mCursorShowing = PR_TRUE;
256  }
257  ::SetTimer(hWnd, CURSOR_HIDE_ID, CURSOR_HIDE_DELAY, NULL);
258  }
259 
260  nsCOMPtr<nsIDOMMouseEvent> mouseEvent;
261  nsresult rv = platform->CreateDOMMouseEvent(getter_AddRefs(mouseEvent));
262  if(NS_SUCCEEDED(rv)) {
263  PRBool shiftKeyState = HIBYTE(GetKeyState(VK_SHIFT)) > 0;
264  PRBool ctrlKeyState = HIBYTE(GetKeyState(VK_CONTROL)) > 0;
265  PRBool altKeyState = HIBYTE(GetKeyState(VK_MENU)) > 0;
266  PRBool winKeyStateL = HIBYTE(GetKeyState(VK_LWIN)) > 0;
267  PRBool winKeyStateR = HIBYTE(GetKeyState(VK_RWIN)) > 0;
268 
269  POINT point = {0};
270  point.x = clientX;
271  point.y = clientY;
272 
273  BOOL success = ClientToScreen(hWnd, &point);
274  NS_WARN_IF_FALSE(success,
275  "Failed to convert coords, mousemove will have wrong screen coords");
276 
277  rv = mouseEvent->InitMouseEvent(NS_LITERAL_STRING("mousemove"),
278  PR_TRUE,
279  PR_TRUE,
280  nsnull,
281  0,
282  point.x,
283  point.y,
284  clientX,
285  clientY,
286  ctrlKeyState,
287  altKeyState,
288  shiftKeyState,
289  winKeyStateL || winKeyStateR,
290  0,
291  nsnull);
292  if(NS_SUCCEEDED(rv)) {
293  nsCOMPtr<nsIDOMEvent> event(do_QueryInterface(mouseEvent));
294  platform->DispatchDOMEvent(event);
295 
296  return 0;
297  }
298  }
299  }
300  }
301  break;
302 
303  case WM_MOUSELEAVE: {
304  if(!platform->mCursorShowing) {
305  ::ShowCursor(TRUE);
306  platform->mCursorShowing = TRUE;
307  }
308 
309  ::KillTimer(hWnd, CURSOR_HIDE_ID);
310  }
311  break;
312 
313  case WM_TIMER: {
314  if(wParam == CURSOR_HIDE_ID && platform->mFullscreen) {
315  if(platform->mCursorShowing) {
316  ::ShowCursor(FALSE);
317  platform->mCursorShowing = PR_FALSE;
318  }
319 
320  ::KillTimer(hWnd, CURSOR_HIDE_ID);
321 
322  return 0;
323  }
324  }
325  break;
326  }
327 
328  return DefWindowProc(hWnd, message, wParam, lParam);
329 }
330 
332 : BasePlatformInterface(aCore)
333 , mWindow(NULL)
334 , mFullscreenWindow(NULL)
335 , mParentWindow(NULL)
336 , mCursorShowing(PR_TRUE)
337 , mLastMouseX(-1)
338 , mLastMouseY(-1)
339 {
340 
341 }
342 
343 nsresult
344 Win32PlatformInterface::SetVideoBox(nsIBoxObject *aBoxObject,
345  nsIWidget *aWidget)
346 {
347  // First let the superclass do its thing.
348  nsresult rv = BasePlatformInterface::SetVideoBox (aBoxObject, aWidget);
349  NS_ENSURE_SUCCESS(rv, rv);
350 
351  if (aWidget) {
352  mParentWindow = (HWND)aWidget->GetNativeData(NS_NATIVE_WIDGET);
353  NS_ENSURE_TRUE(mParentWindow != NULL, NS_ERROR_FAILURE);
354 
355  // There always be at least one child. If there isn't, we can
356  // parent ourselves directly to mParentWnd.
357  HWND actualParent = SelectParentWindow(mParentWindow);
358 
359  WNDCLASS WndClass;
360 
361  ::ZeroMemory(&WndClass, sizeof (WNDCLASS));
362 
363  WndClass.style = CS_HREDRAW | CS_VREDRAW;
364  WndClass.hInstance = GetModuleHandle(NULL);
365  WndClass.lpszClassName = SB_VIDEOWINDOW_CLASSNAME;
366  WndClass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
367  WndClass.cbClsExtra = 0;
368  WndClass.cbWndExtra = 0;
369  WndClass.lpfnWndProc = VideoWindowProc;
370  WndClass.hCursor = ::LoadCursor(NULL, IDC_ARROW);
371 
372  ::RegisterClass(&WndClass);
373 
374  mWindow = ::CreateWindowEx(
375  0, // extended window style
376  SB_VIDEOWINDOW_CLASSNAME, // Class name
377  L"Songbird GStreamer Video Window", // Window name
378  WS_CHILD | WS_CLIPCHILDREN, // window style
379  0, 0, // X,Y offset
380  0, 0, // Width, height
381  actualParent, // Parent window
382  NULL, // Menu, or child identifier
383  WndClass.hInstance, // Module handle
384  NULL); // Extra parameter
385 
386  ::SetWindowLongPtr(mWindow, GWLP_USERDATA, (LONG)this);
387 
388  // Display our normal window
389  ::ShowWindow(mWindow, SW_SHOW);
390  }
391  else {
392  // Hide, unparent, then destroy our video window
393  ::ShowWindow(mWindow, SW_HIDE);
394  ::SetParent(mWindow, NULL);
395 
396  ::DestroyWindow(mWindow);
397 
398  mWindow = NULL;
399  mParentWindow = NULL;
400  }
401  return NS_OK;
402 }
403 
405 {
406  if(mFullscreen) {
407  UnFullScreen();
408  }
409 
410  // Must free sink before destroying window.
411  if (mVideoSink) {
412  gst_object_unref(mVideoSink);
413  mVideoSink = NULL;
414  }
415 
416  if (mWindow) {
417  ::DestroyWindow(mWindow);
418  }
419 }
420 
421 void
423 {
424  NS_ASSERTION(mFullscreenWindow == NULL, "Fullscreen window is not null");
425 
426  HMONITOR monitor;
427  MONITORINFO info;
428 
429  monitor = ::MonitorFromWindow(mWindow, MONITOR_DEFAULTTONEAREST);
430  info.cbSize = sizeof (MONITORINFO);
431  ::GetMonitorInfo(monitor, &info);
432 
433  mFullscreenWindow = ::CreateWindowEx(
434  WS_EX_NOACTIVATE, // This prevents the window from getting an entry in the
435  // Taskbar.
437  L"Songbird Fullscreen Video Window",
438  WS_POPUP,
439  info.rcMonitor.left, info.rcMonitor.top,
440  abs(info.rcMonitor.right - info.rcMonitor.left),
441  abs(info.rcMonitor.bottom - info.rcMonitor.top),
442  NULL, NULL, NULL, NULL);
443 
444  ::SetWindowLongPtr(mFullscreenWindow, GWLP_USERDATA, (LONG)this);
445 
446  ::SetParent(mWindow, mFullscreenWindow);
447  ::ShowWindow(mFullscreenWindow, SW_SHOWMAXIMIZED);
448 
449  //
450  // When a window is MAXIMIZED on a monitor, it's coordinates are not
451  // in virtual screen space anymore but in actual display coordinates.
452  //
453  // e.g. Top left corner of display becomes 0,0 even if it's at virtual
454  // coordinate 1600,0. Because of this, we should always use 0,0 for x and y.
455  //
456  SetDisplayArea(0, 0,
457  abs(info.rcMonitor.right - info.rcMonitor.left),
458  abs(info.rcMonitor.bottom - info.rcMonitor.top));
459  ResizeVideo();
460 }
461 
462 void
464 {
465  NS_ASSERTION(mFullscreenWindow, "Fullscreen window is null");
466 
467  // Hide it before we reparent.
468  ::ShowWindow(mWindow, SW_HIDE);
469 
470  // There always be at least one child. If there isn't, we can
471  // parent ourselves directly to mParentWnd.
472  HWND actualParent = SelectParentWindow(mParentWindow);
473 
474  // Reparent to video window box.
475  ::SetParent(mWindow, actualParent);
476 
477  // Our caller should call Resize() after this to make sure we get moved to
478  // the correct location
479  ::ShowWindow(mWindow, SW_SHOW);
480 
481  ::DestroyWindow(mFullscreenWindow);
482  mFullscreenWindow = NULL;
483 
484  if(!mCursorShowing) {
485  ::ShowCursor(TRUE);
486  mCursorShowing = TRUE;
487  }
488 }
489 
490 
492  int width, int height)
493 {
494  if (mWindow) {
495  // Use SWP_ASYNCWINDOWPOS to avoid a possible deadlock since
496  // we may be calling this from a non-main thread (and the window
497  // was created on the main thread).
498  ::SetWindowPos(mWindow, NULL, x, y, width, height,
499  SWP_NOZORDER | SWP_ASYNCWINDOWPOS);
500  }
501 }
502 
503 
504 GstElement *
506 {
507  if (mVideoSink) {
508  gst_object_unref(mVideoSink);
509  mVideoSink = NULL;
510  }
511 
512  mVideoSink = aVideoSink;
513 
514  if (!mVideoSink)
515  mVideoSink = ::gst_element_factory_make("dshowvideosink", NULL);
516  if (!mVideoSink)
517  mVideoSink = ::gst_element_factory_make("autovideosink", NULL);
518 
519  // Keep a reference to it.
520  if (mVideoSink)
521  gst_object_ref(mVideoSink);
522 
523  return mVideoSink;
524 }
525 
526 GstElement *
528 {
529  if (mAudioSink) {
530  gst_object_unref(mAudioSink);
531  mAudioSink = NULL;
532  }
533 
534  mAudioSink = aAudioSink;
535 
536  if (!mAudioSink) {
537  mAudioSink = gst_element_factory_make("directsoundsink", "audio-sink");
538  }
539 
540  if (!mAudioSink) {
541  // Hopefully autoaudiosink will pick something appropriate...
542  mAudioSink = gst_element_factory_make("autoaudiosink", "audio-sink");
543  }
544 
545  // Keep a reference to it.
546  if (mAudioSink)
547  gst_object_ref(mAudioSink);
548 
549  return mAudioSink;
550 }
551 
552 void
554 {
555  /* GstXOverlay is confusingly named - it's actually generic enough for windows
556  * too, so the windows videosink implements it too.
557  * So, we use the GstXOverlay interface to set the window handle here
558  */
559  nsresult rv;
560 
561  if (!mWindow) {
562  // If we haven't already had a window explicitly set on us, then request
563  // one from the mediacore manager. This needs to be main-thread, as it does
564  // DOM stuff internally.
565  nsCOMPtr<nsIThread> mainThread;
566  rv = NS_GetMainThread(getter_AddRefs(mainThread));
567  NS_ENSURE_SUCCESS(rv, /* void */);
568 
569  nsCOMPtr<nsIRunnable> runnable =
570  NS_NEW_RUNNABLE_METHOD (sbGStreamerMediacore,
571  mCore,
572  RequestVideoWindow);
573 
574  rv = mainThread->Dispatch(runnable, NS_DISPATCH_SYNC);
575  NS_ENSURE_SUCCESS(rv, /* void */);
576  }
577 
578  if (mWindow) {
579  gst_x_overlay_set_xwindow_id(aXOverlay, (glong)mWindow);
580 
581  LOG(("Set xoverlay %p to HWND %x\n", aXOverlay, mWindow));
582  }
583 }
584 
585 HWND
586 Win32PlatformInterface::SelectParentWindow(HWND hWnd)
587 {
588  // Select Parent Window attempts to select the best parent for the video
589  // window so that all events get propagated through the various WindowProcs
590  // as expected.
591 
592  HWND retWnd = NULL;
593  HWND firstChildWnd = ::GetWindow(hWnd, GW_CHILD);
594 
595  if(firstChildWnd != NULL) {
596  retWnd = ::GetWindow(firstChildWnd, GW_HWNDLAST);
597  }
598 
599  if(!retWnd) {
600  retWnd = hWnd;
601  }
602 
603  return retWnd;
604 }
605 
606 PRBool
607 Win32PlatformInterface::HasMouseMoved(PRInt32 aX, PRInt32 aY)
608 {
609  PRBool hasMoved = PR_FALSE;
610 
611  if(mLastMouseX != aX) {
612  mLastMouseX = aX;
613  hasMoved = PR_TRUE;
614  }
615 
616  if(mLastMouseY != aY) {
617  mLastMouseY = aY;
618  hasMoved = PR_TRUE;
619  }
620 
621  return hasMoved;
622 }
sbGStreamerMediacore * mCore
nsresult SetVideoBox(nsIBoxObject *aBoxObject, nsIWidget *aWidget)
return NS_OK
#define SB_VIDEOWINDOW_CLASSNAME
#define GET_X_LPARAM(lParam)
var event
#define LOG(args)
#define CURSOR_HIDE_ID
Songbird Variant Utility Definitions.
void SetXOverlayWindowID(GstXOverlay *aXOverlay)
nsresult DispatchDOMEvent(nsIDOMEvent *aEvent)
#define CURSOR_HIDE_DELAY
function width(ele) rect(ele).width
virtual nsresult SetVideoBox(nsIBoxObject *aVideoBox, nsIWidget *aWidget)
long LONG
Definition: MultiMonitor.h:38
void SetDisplayArea(int x, int y, int width, int height)
GstElement * SetVideoSink(GstElement *aVideoSink)
GstElement * SetAudioSink(GstElement *aAudioSink)
Win32PlatformInterface(sbGStreamerMediacore *aCore)
nsresult CreateDOMMouseEvent(nsIDOMMouseEvent **aMouseEvent)
#define GET_Y_LPARAM(lParam)
GstMessage * message
_updateDatepicker height
void MoveVideoWindow(int x, int y, int width, int height)
nsresult CreateDOMKeyEvent(nsIDOMKeyEvent **aKeyEvent)