redirect key events to the player window
[uwplayer.git] / application / window.c
1 /*
2  * Copyright 2022 Olaf Wintermann
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a 
5  * copy of this software and associated documentation files (the "Software"), 
6  * to deal in the Software without restriction, including without limitation 
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
8  * and/or sell copies of the Software, and to permit persons to whom the 
9  * Software is furnished to do so, subject to the following conditions:
10  * 
11  * The above copyright notice and this permission notice shall be included in 
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
17  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
20  * DEALINGS IN THE SOFTWARE.
21  */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "window.h"
28 #include "main.h"
29 #include "player.h"
30
31 #include "Fsb.h"
32
33 static MainWindow *main_window;
34
35 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *args, int nargs);
36
37 static void FileOpenCB(Widget w, void *udata, void *cdata);
38 static void ViewFullscreenCB(Widget w, void *udata, void *cdata);
39
40 static void window_close_handler(Widget window, void *udata, void *cdata) {
41     WindowClosePlayer(main_window);
42     ApplicationExit();
43 }
44
45 static unsigned int keycodeF;
46
47 static void windowKeyEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
48     MainWindow *win = data;
49     if(win->fullscreen && event->xkey.keycode == keycodeF) {
50         WindowFullscreen(main_window, FALSE);
51         *dispatch = FALSE;
52     }
53 }
54
55 static void resizeEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
56     WindowAdjustAspectRatio(data);
57 }
58
59 static void playerWidgetInputCB(Widget widget, XtPointer u, XtPointer c) {
60     MainWindow *win = u;
61     XmDrawingAreaCallbackStruct *cb = c;
62     
63     if(win->player && win->player->isactive) {
64         PlayerHandleInput(win, win->player, cb);
65     }
66 }
67
68 static void playerEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
69     MainWindow *win = data;
70     if(!win->player || win->player->window == 0) return;
71     
72     /*
73     if(event->type == EnterNotify) {
74         printf("enter: grab pointer\n");
75         
76         XtGrabPointer(
77                 win->player_widget,
78                 True,
79                 ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask,
80                 GrabModeAsync,
81                 GrabModeAsync,
82                 None,
83                 None,
84                 CurrentTime);
85         
86         return;
87     }
88     if(event->type == LeaveNotify) {
89         printf("leave\n");
90         XtUngrabPointer(win->player_widget, CurrentTime); 
91         return;
92     }
93     
94     if(event->type == MotionNotify) {
95         static int testv = 0;
96         printf("test %d\n", testv++);
97     }
98     */
99     
100     if(event->type == KeyPress || event->type == KeyRelease) {
101         // redirect key events to the player window
102         event->xkey.window = win->player->window;
103         XSendEvent(
104                 XtDisplay(win->player_widget),
105                 win->player->window,
106                 True,
107                 0,
108                 event);
109     }
110 }
111
112 MainWindow* WindowCreate(Display *display) {
113     Arg args[32];
114     int n;
115     
116     MainWindow *window = malloc(sizeof(MainWindow));
117     memset(window, 0, sizeof(MainWindow));
118     main_window = window;
119     
120     // toplevel window
121     n = 0;
122     XtSetArg(args[n], XmNtitle, APP_NAME); n++;
123     window->window = XtAppCreateShell(
124             APP_NAME,
125             APP_CLASS,
126             applicationShellWidgetClass,
127             //vendorShellWidgetClass,
128             display,
129             args,
130             n);
131     
132     // close handler
133     Atom wm_delete_window;
134     wm_delete_window = XmInternAtom(
135             display,
136             "WM_DELETE_WINDOW",
137             0);
138     XmAddWMProtocolCallback(
139             window->window,
140             wm_delete_window,
141             window_close_handler,
142             window);
143     
144     // resize handler
145     XtAddEventHandler(window->window, StructureNotifyMask, False, resizeEH, window);
146        
147     n = 0;
148     XtSetArg(args[n], XmNwidth, 360); n++;
149     XtSetArg(args[n], XmNheight, 220); n++;
150     XtSetArg(args[n], XmNshadowThickness, 0); n++;
151     Widget container = XmCreateForm(window->window, "form", args, n);
152     XtManageChild(container);
153     XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
154     
155     n = 0;
156     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
157     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
158     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
159     WindowCreateMenu(window, container, args, n);
160     
161     n = 0;
162     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
163     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
164     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
165     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
166     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
167     XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
168     window->player_widget = XmCreateDrawingArea(container, "player", args, n);
169     XtManageChild(window->player_widget);
170     XtAddCallback(window->player_widget, XmNinputCallback, playerWidgetInputCB, window);
171     XmProcessTraversal(window->player_widget, XmTRAVERSE_CURRENT);
172     XtAddEventHandler(window->player_widget, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
173                  EnterWindowMask | KeyPressMask | KeyReleaseMask |
174                   LeaveWindowMask, FALSE, playerEH, window);
175     
176     
177     // get F keycode
178     keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
179     
180     return window;
181 }
182
183 MainWindow* GetMainWindow(void) {
184     return main_window;
185 }
186
187 void WindowShow(MainWindow *win) {
188     XtRealizeWidget(win->window);
189 }
190
191 /*
192  * Creates a XmPushButton menu item
193  */
194 static Widget createMenuItem(
195         Widget menu,
196         char *name,
197         char *label,
198         char mnemonic,
199         const char *accelerator,
200         char *accelerator_text,
201         XtCallbackProc callback,
202         void *cbData)
203 {
204     Arg args[16];
205     int n = 0;
206     
207     XmString s1 = XmStringCreateSimple(label);
208     XtSetArg(args[n], XmNlabelString, s1); n++;
209     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
210     XmString at = NULL;
211     if(accelerator && accelerator_text) {
212         at = XmStringCreateSimple(accelerator_text);
213         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
214         XtSetArg(args[n], XmNacceleratorText, at); n++;
215     }
216     
217     Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
218     XtManageChild(menuItem);
219     XmStringFree(s1);
220     if(at) XmStringFree(at);
221     
222     if(callback) {
223         XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
224     }
225     
226     return menuItem;
227 }
228
229 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
230     Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
231     XtManageChild(menubar);
232     win->menubar = menubar;
233     
234     Arg args[16];
235     int n;
236     
237     // menus
238     XmString s = XmStringCreateSimple("File");
239     Widget fileMenuItem = XtVaCreateManagedWidget(
240             "menuitem",
241             xmCascadeButtonWidgetClass,
242             menubar,
243             XmNlabelString, s,
244             NULL);
245     XmStringFree(s);
246     Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
247     
248     s = XmStringCreateSimple("Playback");
249     Widget playMenuItem = XtVaCreateManagedWidget(
250             "menuitem",
251             xmCascadeButtonWidgetClass,
252             menubar,
253             XmNlabelString, s,
254             NULL);
255     XmStringFree(s);
256     Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
257     
258     s = XmStringCreateSimple("View");
259     Widget viewMenuItem = XtVaCreateManagedWidget(
260             "menuitem",
261             xmCascadeButtonWidgetClass,
262             menubar,
263             XmNlabelString, s,
264             NULL);
265     XmStringFree(s);
266     Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
267     
268     // file menu
269     createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
270     
271     // view menu
272     createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
273 }
274
275 void go_fullscreen(Display *dsp, Window win)
276 {
277   XEvent xev;
278   Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
279   Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
280   memset(&xev, 0, sizeof(xev));
281   xev.type = ClientMessage;
282   xev.xclient.window = win;
283   xev.xclient.message_type = wm_state;
284   xev.xclient.format = 32;
285   xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
286   xev.xclient.data.l[1] = fullscreen;
287   xev.xclient.data.l[2] = 0;
288   XSendEvent(dsp, DefaultRootWindow(dsp), False,
289     SubstructureNotifyMask, &xev);
290 }
291
292 static Atom net_wm_state;
293 static Atom net_wm_state_fullscreen;
294 static int net_wm_atoms_initialized = 0;
295
296 void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
297     Display *dpy = XtDisplay(win->window);
298     
299     // init net_wm_state atoms
300     if(!net_wm_atoms_initialized) {
301         net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
302         net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
303         net_wm_atoms_initialized = 1;
304     }
305     
306     WindowMenubarSetVisible(win, !enableFullscreen);
307     if(enableFullscreen && !win->fullscreen) {
308         XtUnmanageChild(main_window->menubar);
309         main_window->fullscreen = TRUE;
310     } else if(!enableFullscreen && win->fullscreen) {
311         XtManageChild(main_window->menubar);
312         main_window->fullscreen = FALSE;
313     }
314     
315     XEvent ev;
316     memset(&ev, 0, sizeof(XEvent));
317     ev.type = ClientMessage;
318     ev.xclient.window = XtWindow(win->window);
319     ev.xclient.message_type = net_wm_state;
320     ev.xclient.format = 32;
321     ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
322     ev.xclient.data.l[1] = net_wm_state_fullscreen;
323     ev.xclient.data.l[2] = 0;
324     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
325 }
326
327 void WindowMenubarSetVisible(MainWindow *win, bool visible) {
328     if(visible) {
329         XtManageChild(main_window->menubar);
330         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
331     } else {
332         XtUnmanageChild(main_window->menubar);
333         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
334     }
335 }
336
337
338
339 static void filedialog_end(
340         Widget widget,
341         MainWindow *data,
342         XmFileSelectionBoxCallbackStruct *selection)
343 {
344     XtUnmanageChild(widget);
345     XtDestroyWidget(widget);
346 }
347
348 static void filedialog_select(
349         Widget widget,
350         MainWindow *data,
351         XmFileSelectionBoxCallbackStruct *selection)
352 {
353     char *value = NULL;
354     if(selection->value) {
355         XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
356         if(value) {
357             if(data->file) {
358                 XtFree(data->file);
359             }
360             data->file = value;
361             // no need to free the value, because it is stored in MainWindow
362             
363             PlayerOpenFile(data);
364         }
365     }
366     filedialog_end(widget, data, NULL);
367 }
368
369
370
371
372 static void FileOpenCB(Widget w, void *udata, void *cdata) {
373     MainWindow *win = main_window;
374     
375     Arg args[16];
376     int n = 0;
377     
378     XtSetArg(args[n], XnNshowViewMenu, 1); n++;
379     Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", args, n);
380     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
381     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
382     
383     Widget dirUp = XnFileSelectionBoxGetChild(dialog, XnFSB_DIR_UP_BUTTON);
384     XtUnmanageChild(dirUp);
385     
386     XtManageChild(dialog);
387 }
388
389 static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
390     if(main_window->fullscreen) {
391         WindowFullscreen(main_window, FALSE);
392     } else {
393         WindowFullscreen(main_window, TRUE);
394     }
395     
396 }
397
398 void WindowAdjustAspectRatio(MainWindow *win) {
399     if(!win->player) return;
400     if(!win->player->isactive || win->player->width <= 0 || win->player->height <= 0) return;
401     
402     // we have a running player width video
403     // adjust window aspect ratio (the window aspect ratio is different from
404     // the video, because of window decoration, menubar and other extra controls)
405     
406     Dimension win_width, win_height;
407     XtVaGetValues(win->window, XmNwidth, &win_width, XmNheight, &win_height, NULL);
408     Dimension player_width, player_height;
409     XtVaGetValues(win->player_widget, XmNwidth, &player_width, XmNheight, &player_height, NULL);
410     
411     double r = (double)win->player->width / (double)win->player->height;
412     double p_width = player_width;
413     double p_height = p_width / r;
414     
415     Dimension new_width = p_width + win_width - player_width;
416     Dimension new_height = p_height + win_height - player_height;
417     
418     XSizeHints hints;
419     hints.flags = PAspect;
420     hints.min_aspect.x = new_width;
421     hints.min_aspect.y = new_height;
422     hints.max_aspect.x = new_width;
423     hints.max_aspect.y = new_height;
424     XSetWMNormalHints(XtDisplay(win->window), XtWindow(win->window), &hints);
425 }
426
427 void WindowClosePlayer(MainWindow *win) {
428     if(win->player) {
429         PlayerDestroy(win->player);
430     }
431     win->player = NULL;
432 }