examples: win32-videooverlay: Add test for fullscreen mode switch
[platform/upstream/gstreamer.git] / tests / examples / overlay / win32-videooverlay.c
1 /*
2  * GStreamer
3  * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <gst/gst.h>
26 #include <gst/video/videooverlay.h>
27 #include <gst/video/gstvideosink.h>
28 #include <windows.h>
29 #include <string.h>
30
31 static GMainLoop *loop = NULL;
32 static gboolean visible = FALSE;
33 static gboolean test_reuse = FALSE;
34 static HWND hwnd = NULL;
35 static gboolean test_fullscreen = FALSE;
36 static gboolean fullscreen = FALSE;
37 static LONG prev_style = 0;
38 static RECT prev_rect = { 0, };
39
40 #define DEFAULT_VIDEO_SINK "glimagesink"
41
42 static gboolean
43 get_monitor_size (RECT * rect)
44 {
45   HMONITOR monitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTONEAREST);
46   MONITORINFOEX monitor_info;
47   DEVMODE dev_mode;
48
49   monitor_info.cbSize = sizeof (monitor_info);
50   if (!GetMonitorInfo (monitor, (LPMONITORINFO) & monitor_info)) {
51     return FALSE;
52   }
53
54   dev_mode.dmSize = sizeof (dev_mode);
55   dev_mode.dmDriverExtra = sizeof (POINTL);
56   dev_mode.dmFields = DM_POSITION;
57   if (!EnumDisplaySettings
58       (monitor_info.szDevice, ENUM_CURRENT_SETTINGS, &dev_mode)) {
59     return FALSE;
60   }
61
62   SetRect (rect, 0, 0, dev_mode.dmPelsWidth, dev_mode.dmPelsHeight);
63
64   return TRUE;
65 }
66
67 static void
68 switch_fullscreen_mode (void)
69 {
70   if (!hwnd)
71     return;
72
73   fullscreen = !fullscreen;
74
75   gst_print ("Full screen %s\n", fullscreen ? "on" : "off");
76
77   if (!fullscreen) {
78     /* Restore the window's attributes and size */
79     SetWindowLong (hwnd, GWL_STYLE, prev_style);
80
81     SetWindowPos (hwnd, HWND_NOTOPMOST,
82         prev_rect.left,
83         prev_rect.top,
84         prev_rect.right - prev_rect.left,
85         prev_rect.bottom - prev_rect.top, SWP_FRAMECHANGED | SWP_NOACTIVATE);
86
87     ShowWindow (hwnd, SW_NORMAL);
88   } else {
89     RECT fullscreen_rect;
90
91     /* show window before change style */
92     ShowWindow (hwnd, SW_SHOW);
93
94     /* Save the old window rect so we can restore it when exiting
95      * fullscreen mode */
96     GetWindowRect (hwnd, &prev_rect);
97     prev_style = GetWindowLong (hwnd, GWL_STYLE);
98
99     if (!get_monitor_size (&fullscreen_rect)) {
100       g_warning ("Couldn't get monitor size");
101
102       fullscreen = !fullscreen;
103       return;
104     }
105
106     /* Make the window borderless so that the client area can fill the screen */
107     SetWindowLong (hwnd, GWL_STYLE,
108         prev_style &
109         ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU |
110             WS_THICKFRAME));
111
112     SetWindowPos (hwnd, HWND_NOTOPMOST,
113         fullscreen_rect.left,
114         fullscreen_rect.top,
115         fullscreen_rect.right,
116         fullscreen_rect.bottom, SWP_FRAMECHANGED | SWP_NOACTIVATE);
117
118     ShowWindow (hwnd, SW_MAXIMIZE);
119   }
120 }
121
122 static LRESULT CALLBACK
123 window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
124 {
125   switch (message) {
126     case WM_DESTROY:
127       hwnd = NULL;
128
129       if (loop) {
130         g_main_loop_quit (loop);
131       }
132       return 0;
133     case WM_KEYUP:
134       if (!test_fullscreen)
135         break;
136
137       if (wParam == VK_SPACE)
138         switch_fullscreen_mode ();
139       break;
140     case WM_RBUTTONUP:
141       if (!test_fullscreen)
142         break;
143
144       switch_fullscreen_mode ();
145       break;
146     default:
147       break;
148   }
149
150   return DefWindowProc (hWnd, message, wParam, lParam);
151 }
152
153 static gboolean
154 bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
155 {
156   GstElement *pipeline = GST_ELEMENT (user_data);
157   switch (GST_MESSAGE_TYPE (msg)) {
158     case GST_MESSAGE_ASYNC_DONE:
159       /* make window visible when we have something to show */
160       if (!visible && hwnd) {
161         ShowWindow (hwnd, SW_SHOW);
162         visible = TRUE;
163       }
164
165       gst_element_set_state (pipeline, GST_STATE_PLAYING);
166       break;
167     case GST_MESSAGE_ERROR:{
168       GError *err;
169       gchar *dbg;
170
171       gst_message_parse_error (msg, &err, &dbg);
172       g_printerr ("ERROR %s \n", err->message);
173       if (dbg != NULL)
174         g_printerr ("ERROR debug information: %s\n", dbg);
175       g_clear_error (&err);
176       g_free (dbg);
177       test_reuse = FALSE;
178
179       g_main_loop_quit (loop);
180       break;
181     }
182     default:
183       break;
184   }
185
186   return TRUE;
187 }
188
189 static gboolean
190 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
191 {
192   MSG msg;
193
194   if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
195     return G_SOURCE_CONTINUE;
196
197   TranslateMessage (&msg);
198   DispatchMessage (&msg);
199
200   return G_SOURCE_CONTINUE;
201 }
202
203 static gboolean
204 timeout_cb (gpointer user_data)
205 {
206   g_main_loop_quit ((GMainLoop *) user_data);
207
208   return G_SOURCE_REMOVE;
209 }
210
211 gint
212 main (gint argc, gchar ** argv)
213 {
214   GstElement *pipeline, *src, *sink;
215   GstStateChangeReturn sret;
216   WNDCLASSEX wc = { 0, };
217   HINSTANCE hinstance = GetModuleHandle (NULL);
218   GIOChannel *msg_io_channel;
219   GOptionContext *option_ctx;
220   GError *error = NULL;
221   gchar *video_sink = NULL;
222   gchar *title = NULL;
223   RECT wr = { 0, 0, 320, 240 };
224   gint exitcode = 0;
225   gboolean ret;
226   GOptionEntry options[] = {
227     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
228         "Video sink to use (default is glimagesink)", NULL}
229     ,
230     {"repeat", 0, 0, G_OPTION_ARG_NONE, &test_reuse,
231         "Test reuse video sink element", NULL}
232     ,
233     {"fullscreen", 0, 0, G_OPTION_ARG_NONE, &test_fullscreen,
234         "Test full screen (borderless topmost) mode switching via "
235           "\"SPACE\" key or \"right mouse button\" click", NULL}
236     ,
237     {NULL}
238   };
239   gint num_repeat = 0;
240
241   option_ctx = g_option_context_new ("WIN32 video overlay example");
242   g_option_context_add_main_entries (option_ctx, options, NULL);
243   g_option_context_add_group (option_ctx, gst_init_get_option_group ());
244   ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
245   g_option_context_free (option_ctx);
246
247   if (!ret) {
248     g_printerr ("option parsing failed: %s\n", error->message);
249     g_clear_error (&error);
250     exit (1);
251   }
252
253   /* prepare window */
254   wc.cbSize = sizeof (WNDCLASSEX);
255   wc.style = CS_HREDRAW | CS_VREDRAW;
256   wc.lpfnWndProc = (WNDPROC) window_proc;
257   wc.hInstance = hinstance;
258   wc.hCursor = LoadCursor (NULL, IDC_ARROW);
259   wc.lpszClassName = "GstWIN32VideoOverlay";
260   RegisterClassEx (&wc);
261
262   if (!video_sink)
263     video_sink = g_strdup (DEFAULT_VIDEO_SINK);
264
265   title = g_strdup_printf ("%s - Win32-VideoOverlay", video_sink);
266
267   AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
268   hwnd = CreateWindowEx (0, wc.lpszClassName,
269       title,
270       WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
271       CW_USEDEFAULT, CW_USEDEFAULT,
272       wr.right - wr.left, wr.bottom - wr.top, (HWND) NULL, (HMENU) NULL,
273       hinstance, NULL);
274
275   loop = g_main_loop_new (NULL, FALSE);
276   msg_io_channel = g_io_channel_win32_new_messages (0);
277   g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, NULL);
278
279   /* prepare the pipeline */
280   pipeline = gst_pipeline_new ("win32-overlay");
281   src = gst_element_factory_make ("videotestsrc", NULL);
282   sink = gst_element_factory_make (video_sink, NULL);
283
284   if (!sink) {
285     g_printerr ("%s element is not available\n", video_sink);
286     exitcode = 1;
287
288     goto terminate;
289   }
290
291   gst_bin_add_many (GST_BIN (pipeline), src, sink, NULL);
292   gst_element_link (src, sink);
293
294   gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_msg, pipeline);
295
296   do {
297     gst_print ("Running loop %d\n", num_repeat++);
298
299     gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink),
300         (guintptr) hwnd);
301
302     sret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
303     if (sret == GST_STATE_CHANGE_FAILURE) {
304       g_printerr ("Pipeline doesn't want to pause\n");
305       break;
306     } else {
307       /* add timer to repeat and reuse pipeline  */
308       if (test_reuse) {
309         GSource *timeout_source = g_timeout_source_new_seconds (3);
310
311         g_source_set_callback (timeout_source,
312             (GSourceFunc) timeout_cb, loop, NULL);
313         g_source_attach (timeout_source, NULL);
314         g_source_unref (timeout_source);
315       }
316
317       g_main_loop_run (loop);
318     }
319     gst_element_set_state (pipeline, GST_STATE_NULL);
320
321     visible = FALSE;
322   } while (test_reuse);
323
324   gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
325
326 terminate:
327   if (hwnd)
328     DestroyWindow (hwnd);
329
330   gst_object_unref (pipeline);
331   g_io_channel_unref (msg_io_channel);
332   g_main_loop_unref (loop);
333   g_free (title);
334   g_free (video_sink);
335
336   return exitcode;
337 }