load file from arg
[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 WindowRealized(MainWindow *win);
41
42 static void window_close_handler(Widget window, void *udata, void *cdata) {
43     WindowClosePlayer(main_window);
44     ApplicationExit();
45 }
46
47 static unsigned int keycodeF;
48
49 static void windowKeyEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
50     MainWindow *win = data;
51     if(win->fullscreen && event->xkey.keycode == keycodeF) {
52         WindowFullscreen(main_window, FALSE);
53         *dispatch = FALSE;
54     }
55 }
56
57 static int main_window_is_realized = 0;
58
59 static void resizeEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
60     if(!main_window_is_realized) {
61         if(XtIsRealized(widget)) {
62             main_window_is_realized = 1;
63             WindowRealized(data);
64         }
65     }
66     WindowAdjustAspectRatio(data);
67 }
68
69 static void WindowRealized(MainWindow *win) {
70     char *open_file = GetOpenFileArg();
71     if(open_file) {
72         size_t len = strlen(open_file);
73         char *file = XtMalloc(len+1);
74         memcpy(file, open_file, len);
75         file[len] = 0;
76         WindowSetFile(win, file);
77         PlayerOpenFile(win);
78         CleanOpenFileArg();
79     }
80 }
81
82 static void playerWidgetInputCB(Widget widget, XtPointer u, XtPointer c) {
83     MainWindow *win = u;
84     XmDrawingAreaCallbackStruct *cb = c;
85     
86     if(win->player && win->player->isactive) {
87         PlayerHandleInput(win, win->player, cb);
88     }
89 }
90
91 static void playerEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
92     MainWindow *win = data;
93     if(!win->player || win->player->window == 0) return;
94     
95     /*
96     if(event->type == EnterNotify) {
97         printf("enter: grab pointer\n");
98         
99         XtGrabPointer(
100                 win->player_widget,
101                 True,
102                 ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask,
103                 GrabModeAsync,
104                 GrabModeAsync,
105                 None,
106                 None,
107                 CurrentTime);
108         
109         return;
110     }
111     if(event->type == LeaveNotify) {
112         printf("leave\n");
113         XtUngrabPointer(win->player_widget, CurrentTime); 
114         return;
115     }
116     
117     if(event->type == MotionNotify) {
118         static int testv = 0;
119         printf("test %d\n", testv++);
120     }
121     */
122     
123     if(event->type == KeyPress || event->type == KeyRelease) {
124         // redirect key events to the player window
125         event->xkey.window = win->player->window;
126         XSendEvent(
127                 XtDisplay(win->player_widget),
128                 win->player->window,
129                 True,
130                 0,
131                 event);
132     }
133 }
134
135 MainWindow* WindowCreate(Display *display) {
136     Arg args[32];
137     int n;
138     
139     MainWindow *window = malloc(sizeof(MainWindow));
140     memset(window, 0, sizeof(MainWindow));
141     main_window = window;
142     
143     // toplevel window
144     n = 0;
145     XtSetArg(args[n], XmNtitle, APP_NAME); n++;
146     window->window = XtAppCreateShell(
147             APP_NAME,
148             APP_CLASS,
149             applicationShellWidgetClass,
150             //vendorShellWidgetClass,
151             display,
152             args,
153             n);
154     
155     // close handler
156     Atom wm_delete_window;
157     wm_delete_window = XmInternAtom(
158             display,
159             "WM_DELETE_WINDOW",
160             0);
161     XmAddWMProtocolCallback(
162             window->window,
163             wm_delete_window,
164             window_close_handler,
165             window);
166     
167     // resize handler
168     XtAddEventHandler(window->window, StructureNotifyMask, False, resizeEH, window);
169     
170     n = 0;
171     XtSetArg(args[n], XmNwidth, 360); n++;
172     XtSetArg(args[n], XmNheight, 220); n++;
173     XtSetArg(args[n], XmNshadowThickness, 0); n++;
174     Widget container = XmCreateForm(window->window, "form", args, n);
175     XtManageChild(container);
176     XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
177     
178     n = 0;
179     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
180     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
181     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
182     WindowCreateMenu(window, container, args, n);
183     
184     n = 0;
185     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
186     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
187     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
188     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
189     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
190     XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
191     window->player_widget = XmCreateDrawingArea(container, "player", args, n);
192     XtManageChild(window->player_widget);
193     XtAddCallback(window->player_widget, XmNinputCallback, playerWidgetInputCB, window);
194     XmProcessTraversal(window->player_widget, XmTRAVERSE_CURRENT);
195     XtAddEventHandler(window->player_widget, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
196                  EnterWindowMask | KeyPressMask | KeyReleaseMask |
197                   LeaveWindowMask, FALSE, playerEH, window);
198     
199     
200     // get F keycode
201     keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
202     
203     return window;
204 }
205
206 MainWindow* GetMainWindow(void) {
207     return main_window;
208 }
209
210 void WindowShow(MainWindow *win) {
211     XtRealizeWidget(win->window);
212 }
213
214 /*
215  * Creates a XmPushButton menu item
216  */
217 static Widget createMenuItem(
218         Widget menu,
219         char *name,
220         char *label,
221         char mnemonic,
222         const char *accelerator,
223         char *accelerator_text,
224         XtCallbackProc callback,
225         void *cbData)
226 {
227     Arg args[16];
228     int n = 0;
229     
230     XmString s1 = XmStringCreateSimple(label);
231     XtSetArg(args[n], XmNlabelString, s1); n++;
232     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
233     XmString at = NULL;
234     if(accelerator && accelerator_text) {
235         at = XmStringCreateSimple(accelerator_text);
236         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
237         XtSetArg(args[n], XmNacceleratorText, at); n++;
238     }
239     
240     Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
241     XtManageChild(menuItem);
242     XmStringFree(s1);
243     if(at) XmStringFree(at);
244     
245     if(callback) {
246         XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
247     }
248     
249     return menuItem;
250 }
251
252 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
253     Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
254     XtManageChild(menubar);
255     win->menubar = menubar;
256     
257     Arg args[16];
258     int n;
259     
260     // menus
261     XmString s = XmStringCreateSimple("File");
262     Widget fileMenuItem = XtVaCreateManagedWidget(
263             "menuitem",
264             xmCascadeButtonWidgetClass,
265             menubar,
266             XmNlabelString, s,
267             NULL);
268     XmStringFree(s);
269     Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
270     
271     s = XmStringCreateSimple("Playback");
272     Widget playMenuItem = XtVaCreateManagedWidget(
273             "menuitem",
274             xmCascadeButtonWidgetClass,
275             menubar,
276             XmNlabelString, s,
277             NULL);
278     XmStringFree(s);
279     Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
280     
281     s = XmStringCreateSimple("View");
282     Widget viewMenuItem = XtVaCreateManagedWidget(
283             "menuitem",
284             xmCascadeButtonWidgetClass,
285             menubar,
286             XmNlabelString, s,
287             NULL);
288     XmStringFree(s);
289     Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
290     
291     // file menu
292     createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
293     
294     // view menu
295     createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
296 }
297
298 void go_fullscreen(Display *dsp, Window win)
299 {
300   XEvent xev;
301   Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
302   Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
303   memset(&xev, 0, sizeof(xev));
304   xev.type = ClientMessage;
305   xev.xclient.window = win;
306   xev.xclient.message_type = wm_state;
307   xev.xclient.format = 32;
308   xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
309   xev.xclient.data.l[1] = fullscreen;
310   xev.xclient.data.l[2] = 0;
311   XSendEvent(dsp, DefaultRootWindow(dsp), False,
312     SubstructureNotifyMask, &xev);
313 }
314
315 static Atom net_wm_state;
316 static Atom net_wm_state_fullscreen;
317 static int net_wm_atoms_initialized = 0;
318
319 void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
320     Display *dpy = XtDisplay(win->window);
321     
322     // init net_wm_state atoms
323     if(!net_wm_atoms_initialized) {
324         net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
325         net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
326         net_wm_atoms_initialized = 1;
327     }
328     
329     WindowMenubarSetVisible(win, !enableFullscreen);
330     if(enableFullscreen && !win->fullscreen) {
331         XtUnmanageChild(main_window->menubar);
332         main_window->fullscreen = TRUE;
333     } else if(!enableFullscreen && win->fullscreen) {
334         XtManageChild(main_window->menubar);
335         main_window->fullscreen = FALSE;
336     }
337     
338     XEvent ev;
339     memset(&ev, 0, sizeof(XEvent));
340     ev.type = ClientMessage;
341     ev.xclient.window = XtWindow(win->window);
342     ev.xclient.message_type = net_wm_state;
343     ev.xclient.format = 32;
344     ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
345     ev.xclient.data.l[1] = net_wm_state_fullscreen;
346     ev.xclient.data.l[2] = 0;
347     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
348 }
349
350 void WindowMenubarSetVisible(MainWindow *win, bool visible) {
351     if(visible) {
352         XtManageChild(main_window->menubar);
353         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
354     } else {
355         XtUnmanageChild(main_window->menubar);
356         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
357     }
358 }
359
360 void WindowSetFile(MainWindow *win, char *file) {
361     if(win->file) {
362         XtFree(win->file);
363     }
364     win->file = file;
365 }
366
367 static void filedialog_end(
368         Widget widget,
369         MainWindow *data,
370         XmFileSelectionBoxCallbackStruct *selection)
371 {
372     XtUnmanageChild(widget);
373     XtDestroyWidget(widget);
374 }
375
376 static void filedialog_select(
377         Widget widget,
378         MainWindow *data,
379         XmFileSelectionBoxCallbackStruct *selection)
380 {
381     char *value = NULL;
382     if(selection->value) {
383         XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
384         if(value) {
385             WindowSetFile(data, value);
386             // no need to free the value, because it is stored in MainWindow
387             
388             PlayerOpenFile(data);
389         }
390     }
391     filedialog_end(widget, data, NULL);
392 }
393
394
395
396
397 static void FileOpenCB(Widget w, void *udata, void *cdata) {
398     MainWindow *win = main_window;
399     
400     Arg args[16];
401     int n = 0;
402     
403     XtSetArg(args[n], XnNshowViewMenu, 1); n++;
404     Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", args, n);
405     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
406     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
407     
408     Widget dirUp = XnFileSelectionBoxGetChild(dialog, XnFSB_DIR_UP_BUTTON);
409     XtUnmanageChild(dirUp);
410     
411     XtManageChild(dialog);
412 }
413
414 static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
415     if(main_window->fullscreen) {
416         WindowFullscreen(main_window, FALSE);
417     } else {
418         WindowFullscreen(main_window, TRUE);
419     }
420     
421 }
422
423 void WindowAdjustAspectRatio(MainWindow *win) {
424     if(!win->player) return;
425     if(!win->player->isactive || win->player->width <= 0 || win->player->height <= 0) return;
426     
427     // we have a running player width video
428     // adjust window aspect ratio (the window aspect ratio is different from
429     // the video, because of window decoration, menubar and other extra controls)
430     
431     Dimension win_width, win_height;
432     XtVaGetValues(win->window, XmNwidth, &win_width, XmNheight, &win_height, NULL);
433     Dimension player_width, player_height;
434     XtVaGetValues(win->player_widget, XmNwidth, &player_width, XmNheight, &player_height, NULL);
435     
436     double r = (double)win->player->width / (double)win->player->height;
437     double p_width = player_width;
438     double p_height = p_width / r;
439     
440     Dimension new_width = p_width + win_width - player_width;
441     Dimension new_height = p_height + win_height - player_height;
442     
443     XSizeHints hints;
444     hints.flags = PAspect;
445     hints.min_aspect.x = new_width;
446     hints.min_aspect.y = new_height;
447     hints.max_aspect.x = new_width;
448     hints.max_aspect.y = new_height;
449     XSetWMNormalHints(XtDisplay(win->window), XtWindow(win->window), &hints);
450 }
451
452 void WindowClosePlayer(MainWindow *win) {
453     if(win->player) {
454         PlayerDestroy(win->player);
455     }
456     win->player = NULL;
457 }