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