destroy player on exit
[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 MainWindow* WindowCreate(Display *display) {
60     Arg args[32];
61     int n;
62     
63     MainWindow *window = malloc(sizeof(MainWindow));
64     memset(window, 0, sizeof(MainWindow));
65     main_window = window;
66     
67     // toplevel window
68     n = 0;
69     XtSetArg(args[n], XmNtitle, APP_NAME); n++;
70     window->window = XtAppCreateShell(
71             APP_NAME,
72             APP_CLASS,
73             applicationShellWidgetClass,
74             //vendorShellWidgetClass,
75             display,
76             args,
77             n);
78     
79     // close handler
80     Atom wm_delete_window;
81     wm_delete_window = XmInternAtom(
82             display,
83             "WM_DELETE_WINDOW",
84             0);
85     XmAddWMProtocolCallback(
86             window->window,
87             wm_delete_window,
88             window_close_handler,
89             window);
90     
91     // resize handler
92     XtAddEventHandler(window->window, StructureNotifyMask, False, resizeEH, window);
93        
94     n = 0;
95     XtSetArg(args[n], XmNwidth, 360); n++;
96     XtSetArg(args[n], XmNheight, 220); n++;
97     XtSetArg(args[n], XmNshadowThickness, 0); n++;
98     Widget container = XmCreateForm(window->window, "form", args, n);
99     XtManageChild(container);
100     XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
101     
102     n = 0;
103     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
104     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
105     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
106     WindowCreateMenu(window, container, args, n);
107     
108     n = 0;
109     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
110     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
111     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
112     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
113     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
114     XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
115     window->player_widget = XmCreateDrawingArea(container, "player", args, n);
116     XtManageChild(window->player_widget);
117     XmProcessTraversal(window->player_widget, XmTRAVERSE_CURRENT);
118     
119     // get F keycode
120     keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
121     
122     return window;
123 }
124
125 MainWindow* GetMainWindow(void) {
126     return main_window;
127 }
128
129 void WindowShow(MainWindow *win) {
130     XtRealizeWidget(win->window);
131 }
132
133 /*
134  * Creates a XmPushButton menu item
135  */
136 static Widget createMenuItem(
137         Widget menu,
138         char *name,
139         char *label,
140         char mnemonic,
141         const char *accelerator,
142         char *accelerator_text,
143         XtCallbackProc callback,
144         void *cbData)
145 {
146     Arg args[16];
147     int n = 0;
148     
149     XmString s1 = XmStringCreateSimple(label);
150     XtSetArg(args[n], XmNlabelString, s1); n++;
151     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
152     XmString at = NULL;
153     if(accelerator && accelerator_text) {
154         at = XmStringCreateSimple(accelerator_text);
155         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
156         XtSetArg(args[n], XmNacceleratorText, at); n++;
157     }
158     
159     Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
160     XtManageChild(menuItem);
161     XmStringFree(s1);
162     if(at) XmStringFree(at);
163     
164     if(callback) {
165         XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
166     }
167     
168     return menuItem;
169 }
170
171 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
172     Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
173     XtManageChild(menubar);
174     win->menubar = menubar;
175     
176     Arg args[16];
177     int n;
178     
179     // menus
180     XmString s = XmStringCreateSimple("File");
181     Widget fileMenuItem = XtVaCreateManagedWidget(
182             "menuitem",
183             xmCascadeButtonWidgetClass,
184             menubar,
185             XmNlabelString, s,
186             NULL);
187     XmStringFree(s);
188     Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
189     
190     s = XmStringCreateSimple("Playback");
191     Widget playMenuItem = XtVaCreateManagedWidget(
192             "menuitem",
193             xmCascadeButtonWidgetClass,
194             menubar,
195             XmNlabelString, s,
196             NULL);
197     XmStringFree(s);
198     Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
199     
200     s = XmStringCreateSimple("View");
201     Widget viewMenuItem = XtVaCreateManagedWidget(
202             "menuitem",
203             xmCascadeButtonWidgetClass,
204             menubar,
205             XmNlabelString, s,
206             NULL);
207     XmStringFree(s);
208     Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
209     
210     // file menu
211     createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
212     
213     // view menu
214     createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
215 }
216
217 void go_fullscreen(Display *dsp, Window win)
218 {
219   XEvent xev;
220   Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
221   Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
222   memset(&xev, 0, sizeof(xev));
223   xev.type = ClientMessage;
224   xev.xclient.window = win;
225   xev.xclient.message_type = wm_state;
226   xev.xclient.format = 32;
227   xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
228   xev.xclient.data.l[1] = fullscreen;
229   xev.xclient.data.l[2] = 0;
230   XSendEvent(dsp, DefaultRootWindow(dsp), False,
231     SubstructureNotifyMask, &xev);
232 }
233
234 static Atom net_wm_state;
235 static Atom net_wm_state_fullscreen;
236 static int net_wm_atoms_initialized = 0;
237
238 void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
239     Display *dpy = XtDisplay(win->window);
240     
241     // init net_wm_state atoms
242     if(!net_wm_atoms_initialized) {
243         net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
244         net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
245         net_wm_atoms_initialized = 1;
246     }
247     
248     WindowMenubarSetVisible(win, !enableFullscreen);
249     if(enableFullscreen && !win->fullscreen) {
250         XtUnmanageChild(main_window->menubar);
251         main_window->fullscreen = TRUE;
252     } else if(!enableFullscreen && win->fullscreen) {
253         XtManageChild(main_window->menubar);
254         main_window->fullscreen = FALSE;
255     }
256     
257     XEvent ev;
258     memset(&ev, 0, sizeof(XEvent));
259     ev.type = ClientMessage;
260     ev.xclient.window = XtWindow(win->window);
261     ev.xclient.message_type = net_wm_state;
262     ev.xclient.format = 32;
263     ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
264     ev.xclient.data.l[1] = net_wm_state_fullscreen;
265     ev.xclient.data.l[2] = 0;
266     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
267 }
268
269 void WindowMenubarSetVisible(MainWindow *win, bool visible) {
270     if(visible) {
271         XtManageChild(main_window->menubar);
272         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
273     } else {
274         XtUnmanageChild(main_window->menubar);
275         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
276     }
277 }
278
279
280
281 static void filedialog_end(
282         Widget widget,
283         MainWindow *data,
284         XmFileSelectionBoxCallbackStruct *selection)
285 {
286     XtUnmanageChild(widget);
287     XtDestroyWidget(widget);
288 }
289
290 static void filedialog_select(
291         Widget widget,
292         MainWindow *data,
293         XmFileSelectionBoxCallbackStruct *selection)
294 {
295     char *value = NULL;
296     if(selection->value) {
297         XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
298         if(value) {
299             if(data->file) {
300                 XtFree(data->file);
301             }
302             data->file = value;
303             // no need to free the value, because it is stored in MainWindow
304             
305             PlayerOpenFile(data);
306         }
307     }
308     filedialog_end(widget, data, NULL);
309 }
310
311
312
313
314 static void FileOpenCB(Widget w, void *udata, void *cdata) {
315     MainWindow *win = main_window;
316     
317     Arg args[16];
318     int n = 0;
319     
320     XtSetArg(args[n], XnNshowViewMenu, 1); n++;
321     Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", args, n);
322     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
323     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
324     
325     Widget dirUp = XnFileSelectionBoxGetChild(dialog, XnFSB_DIR_UP_BUTTON);
326     XtUnmanageChild(dirUp);
327     
328     XtManageChild(dialog);
329 }
330
331 static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
332     if(main_window->fullscreen) {
333         WindowFullscreen(main_window, FALSE);
334     } else {
335         WindowFullscreen(main_window, TRUE);
336     }
337     
338 }
339
340 void WindowAdjustAspectRatio(MainWindow *win) {
341     if(!win->player) return;
342     if(!win->player->isactive || win->player->width <= 0 || win->player->height <= 0) return;
343     
344     // we have a running player width video
345     // adjust window aspect ratio (the window aspect ratio is different from
346     // the video, because of window decoration, menubar and other extra controls)
347     
348     Dimension win_width, win_height;
349     XtVaGetValues(win->window, XmNwidth, &win_width, XmNheight, &win_height, NULL);
350     Dimension player_width, player_height;
351     XtVaGetValues(win->player_widget, XmNwidth, &player_width, XmNheight, &player_height, NULL);
352     
353     double r = (double)win->player->width / (double)win->player->height;
354     double p_width = player_width;
355     double p_height = p_width / r;
356     
357     Dimension new_width = p_width + win_width - player_width;
358     Dimension new_height = p_height + win_height - player_height;
359     
360     XSizeHints hints;
361     hints.flags = PAspect;
362     hints.min_aspect.x = new_width;
363     hints.min_aspect.y = new_height;
364     hints.max_aspect.x = new_width;
365     hints.max_aspect.y = new_height;
366     XSetWMNormalHints(XtDisplay(win->window), XtWindow(win->window), &hints);
367 }
368
369 void WindowClosePlayer(MainWindow *win) {
370     if(win->player) {
371         PlayerDestroy(win->player);
372     }
373     win->player = NULL;
374 }