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