click in the video area can move the window now
[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 FileQuitCB(Widget w, void *udata, void *cdata);
39 static void PlayRepeatCB(Widget w, void *udata, void *cdata);
40 static void PlayRepeatListCB(Widget w, void *udata, void *cdata);
41 static void PlayAutoPlayCB(Widget w, void *udata, void *cdata);
42 static void ViewFullscreenCB(Widget w, void *udata, void *cdata);
43
44 static void WindowRealized(MainWindow *win);
45
46 static int blank_cursor_init = 0;
47 static Pixmap blank_cursor_pixmap;
48 static Cursor blank_cursor;
49
50 static void init_blank_cursor(Widget w) {
51     char data = 0;
52     
53     XColor c;
54     
55     blank_cursor_pixmap = XCreateBitmapFromData(XtDisplay(w), XtWindow(w), &data, 1, 1);
56     if(!blank_cursor_pixmap) return;
57     
58     blank_cursor = XCreatePixmapCursor(XtDisplay(w), blank_cursor_pixmap, blank_cursor_pixmap, &c, &c, 0, 0);
59     
60     XFreePixmap(XtDisplay(w), blank_cursor_pixmap);
61     blank_cursor_init = 1;
62 }
63
64 static void window_close_handler(Widget window, void *udata, void *cdata) {
65     FileQuitCB(window, NULL, NULL);
66 }
67
68 static unsigned int keycodeF;
69
70 static void windowKeyEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
71     MainWindow *win = data;
72     if(win->fullscreen && event->xkey.keycode == keycodeF) {
73         WindowFullscreen(main_window, FALSE);
74         *dispatch = FALSE;
75     }
76 }
77
78 static int main_window_is_realized = 0;
79
80 static void resizeEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {  
81     if(!main_window_is_realized) {
82         if(XtIsRealized(widget)) {
83             main_window_is_realized = 1;
84             WindowRealized(data);
85         }
86     }
87     WindowAdjustAspectRatio(data);
88 }
89
90 static void WindowRealized(MainWindow *win) {
91     char *open_file = GetOpenFileArg();
92     if(open_file) {
93         size_t len = strlen(open_file);
94         char *file = XtMalloc(len+1);
95         memcpy(file, open_file, len);
96         file[len] = 0;
97         WindowSetFile(win, file);
98         PlayerOpenFile(win);
99         CleanOpenFileArg();
100     }
101     
102     if(!blank_cursor_init) {
103         init_blank_cursor(win->player_widget);
104     }
105 }
106
107 static void playerWidgetInputCB(Widget widget, XtPointer u, XtPointer c) {
108     MainWindow *win = u;
109     XmDrawingAreaCallbackStruct *cb = c;
110     
111     if(win->player && win->player->isactive) {
112         PlayerHandleInput(win, win->player, cb);
113     }
114 }
115
116 static void windowGrabButton(MainWindow *win) {
117     //printf("grab\n");
118     XtGrabButton(
119                 win->player_widget,
120                 AnyButton,
121                 AnyModifier,
122                 True,
123                 ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask,
124                 GrabModeAsync,
125                 GrabModeAsync,
126                 None,
127                 None);
128     win->buttongrab = True;
129 }
130
131 static void playerEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
132     MainWindow *win = data;
133     int etype = event->type;
134     
135     ///*
136     if(etype == EnterNotify) {
137         //printf("enter\n");
138         windowGrabButton(win);
139         return;
140     }
141     if(etype == LeaveNotify) {
142         //printf("leave\n");
143         //XtUngrabButton(win->player_widget, AnyButton, AnyModifier); 
144         //win->buttongrab = False;
145         return;
146     }
147     
148     int pass = 0;
149     if(etype == ButtonPress || etype == ButtonRelease || etype == KeyPress || etype == KeyRelease) {
150         //printf("button press\n");
151         pass = 1;
152     }
153     
154     if(!win->player || win->player->window == 0) return;
155     
156     WindowHandlePlayerEvent(win, event);
157     
158     if(pass) {
159         // redirect key events to the player window
160         //printf("redirect\n");
161         event->xkey.window = win->player->window;
162         XSendEvent(
163                 XtDisplay(win->player_widget),
164                 win->player->window,
165                 True,
166                 0,
167                 event);
168     }
169 }
170
171 #define IGNORE_MOTION_THRESHOLD_MS 1000
172 #define MOTION_POS_THRESHOLD_PIX   5
173
174 #define DOUBLE_CLICK_TIME_MS       500
175
176 void WindowHandlePlayerEvent(MainWindow *win, XEvent *event) {
177     // event handler for intercepted player mouse events
178     // win->player is not NULL
179     
180     int etype = event->type;
181     
182     if(etype == MotionNotify) {
183         Time cur_motion_time = event->xmotion.time;
184         int x = event->xmotion.x_root;
185         int y = event->xmotion.y_root;
186         if(win->cursorhidden && cur_motion_time - win->player_event_time < IGNORE_MOTION_THRESHOLD_MS) {
187             int diff_x = abs(x - win->mouse_x);
188             int diff_y = abs(y - win->mouse_y);
189             if(diff_x > MOTION_POS_THRESHOLD_PIX || diff_y > MOTION_POS_THRESHOLD_PIX) {
190                 WindowShowPlayerCursor(win);
191             }
192         } else {
193             win->mouse_x = x;
194             win->mouse_y = y;
195         }
196         win->player_event_time = cur_motion_time;
197         win->motion_playback_time = win->player->playback_time;
198         
199         
200         
201         if(win->pwbuttonpressed) {
202             Display *dp = XtDisplay(win->window);
203                 
204             XtUngrabPointer(win->player_widget, CurrentTime);
205
206             XEvent xev;
207             memset(&xev, 0, sizeof(xev));
208             xev.type = ClientMessage;
209             xev.xclient.message_type = XInternAtom(dp, "_NET_WM_MOVERESIZE", False);
210             xev.xclient.window = XtWindow(win->window);
211             xev.xclient.format = 32;
212             xev.xclient.data.l[0] = x;
213             xev.xclient.data.l[1] = y;
214             xev.xclient.data.l[2] = 8; // _NET_WM_MOVERESIZE_MOVE
215             xev.xclient.data.l[3] = 1; // button1
216             xev.xclient.data.l[4] = 1; // source indication
217
218             XSendEvent(dp, DefaultRootWindow(dp), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
219
220             win->pwbuttonpressed = FALSE;  
221         }
222     } else if(etype == ButtonPress) {
223         Time t = event->xbutton.time;
224         if(t - win->button_press_time < DOUBLE_CLICK_TIME_MS) {
225             // double click
226             WindowFullscreen(main_window, !win->fullscreen);
227             win->button_press_time = 0;
228         } else {
229             win->button_press_time = t;
230         }
231         win->pwbuttonpressed = 1;
232     } else if(etype == ButtonRelease) {
233         win->player_event_time = event->xbutton.time;
234         win->pwbuttonpressed = FALSE;
235     }
236 }
237
238 MainWindow* WindowCreate(Display *display) {
239     Arg args[32];
240     int n;
241      
242     MainWindow *window = malloc(sizeof(MainWindow));
243     memset(window, 0, sizeof(MainWindow));
244     main_window = window;
245     
246     // toplevel window
247     n = 0;
248     XtSetArg(args[n], XmNtitle, APP_NAME); n++;
249     window->window = XtAppCreateShell(
250             APP_NAME,
251             APP_CLASS,
252             applicationShellWidgetClass,
253             //vendorShellWidgetClass,
254             display,
255             args,
256             n);
257     
258     // close handler
259     Atom wm_delete_window;
260     wm_delete_window = XmInternAtom(
261             display,
262             "WM_DELETE_WINDOW",
263             0);
264     XmAddWMProtocolCallback(
265             window->window,
266             wm_delete_window,
267             window_close_handler,
268             window);
269     
270     // resize handler
271     XtAddEventHandler(window->window, StructureNotifyMask, False, resizeEH, window);
272     
273     n = 0;
274     XtSetArg(args[n], XmNwidth, 360); n++;
275     XtSetArg(args[n], XmNheight, 220); n++;
276     XtSetArg(args[n], XmNshadowThickness, 0); n++;
277     Widget container = XmCreateForm(window->window, "form", args, n);
278     XtManageChild(container);
279     XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
280     
281     n = 0;
282     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
283     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
284     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
285     WindowCreateMenu(window, container, args, n);
286     
287     n = 0;
288     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
289     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
290     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
291     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
292     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
293     XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
294     window->player_widget = XmCreateDrawingArea(container, "player", args, n);
295     XtManageChild(window->player_widget);
296     XtAddCallback(window->player_widget, XmNinputCallback, playerWidgetInputCB, window);
297     XmProcessTraversal(window->player_widget, XmTRAVERSE_CURRENT);
298     XtAddEventHandler(window->player_widget, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
299                  EnterWindowMask | KeyPressMask | KeyReleaseMask |
300                   LeaveWindowMask, FALSE, playerEH, window);
301     
302     
303     // get F keycode
304     keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
305     
306     return window;
307 }
308
309 MainWindow* GetMainWindow(void) {
310     return main_window;
311 }
312
313 void WindowShow(MainWindow *win) {
314     XtRealizeWidget(win->window);
315 }
316
317 /*
318  * Creates a XmPushButton menu item
319  */
320 static Widget createMenuItem(
321         Widget menu,
322         char *name,
323         char *label,
324         char mnemonic,
325         const char *accelerator,
326         char *accelerator_text,
327         XtCallbackProc callback,
328         void *cbData)
329 {
330     Arg args[16];
331     int n = 0;
332     
333     XmString s1 = XmStringCreateSimple(label);
334     XtSetArg(args[n], XmNlabelString, s1); n++;
335     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
336     XmString at = NULL;
337     if(accelerator && accelerator_text) {
338         at = XmStringCreateSimple(accelerator_text);
339         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
340         XtSetArg(args[n], XmNacceleratorText, at); n++;
341     }
342     
343     Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
344     XtManageChild(menuItem);
345     XmStringFree(s1);
346     if(at) XmStringFree(at);
347     
348     if(callback) {
349         XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
350     }
351     
352     return menuItem;
353 }
354
355 /*
356  * Creates a XmToggleButton menu item
357  */
358 static Widget createToggleMenuItem(
359         Widget menu,
360         char *name,
361         char *label,
362         char mnemonic,
363         Boolean defaultValue,
364         const char *accelerator,
365         char *accelerator_text,
366         XtCallbackProc callback,
367         void *cbData)
368 {
369     Arg args[16];
370     int n = 0;
371     
372     XmString s1 = XmStringCreateSimple(label);
373     XtSetArg(args[n], XmNlabelString, s1); n++;
374     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
375     XtSetArg(args[n], XmNset, defaultValue); n++;
376     XmString at = NULL;
377     if(accelerator && accelerator_text) {
378         at = XmStringCreateSimple(accelerator_text);
379         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
380         XtSetArg(args[n], XmNacceleratorText, at); n++;
381     }
382     
383     Widget menuItem = XmCreateToggleButtonGadget(menu, name, args, n);
384     XtManageChild(menuItem);
385     XmStringFree(s1);
386     if(at) XmStringFree(at);
387     
388     if(callback) {
389         XtAddCallback(menuItem, XmNvalueChangedCallback, (XtCallbackProc)callback, cbData);
390     }
391     
392     return menuItem;
393 }
394
395 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
396     Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
397     XtManageChild(menubar);
398     win->menubar = menubar;
399     
400     Arg args[16];
401     int n;
402     
403     // menus
404     XmString s = XmStringCreateSimple("File");
405     XtVaCreateManagedWidget(
406             "menuitem",
407             xmCascadeButtonWidgetClass,
408             menubar,
409             XmNlabelString, s,
410             NULL);
411     XmStringFree(s);
412     Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
413     
414     s = XmStringCreateSimple("Playback");
415     XtVaCreateManagedWidget(
416             "menuitem",
417             xmCascadeButtonWidgetClass,
418             menubar,
419             XmNlabelString, s,
420             NULL);
421     XmStringFree(s);
422     Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
423     
424     s = XmStringCreateSimple("View");
425     Widget viewMenuItem = XtVaCreateManagedWidget(
426             "menuitem",
427             xmCascadeButtonWidgetClass,
428             menubar,
429             XmNlabelString, s,
430             NULL);
431     XmStringFree(s);
432     Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
433     
434     // file menu
435     createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
436     createMenuItem(fileMenu, "fileQuit", "Exit", 'E', "Ctrl<Key>Q", "Ctrl+Q", FileQuitCB, NULL);
437     
438     // play menu
439     win->playRepeatTrackButton = createToggleMenuItem(playMenu, "playRepeatTrack", "Repeat", 'R', False, NULL, NULL, PlayRepeatCB, win);
440     win->playRepeatListButton = createToggleMenuItem(playMenu, "playRepeatList", "Repeat List", 'L', False, NULL, NULL, PlayRepeatListCB, win);
441     win->playAutoPlayButton = createToggleMenuItem(playMenu, "playAutoNext", "Autoplay Folder", 'A', False, NULL, NULL, PlayAutoPlayCB, win);
442     XtVaSetValues(win->playRepeatTrackButton, XmNindicatorType, XmONE_OF_MANY, NULL);
443     XtVaSetValues(win->playRepeatListButton, XmNindicatorType, XmONE_OF_MANY, NULL);
444     XtVaSetValues(win->playAutoPlayButton, XmNindicatorType, XmONE_OF_MANY, NULL);
445     
446     // view menu
447     createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
448 }
449
450 void go_fullscreen(Display *dsp, Window win)
451 {
452   XEvent xev;
453   Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
454   Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
455   memset(&xev, 0, sizeof(xev));
456   xev.type = ClientMessage;
457   xev.xclient.window = win;
458   xev.xclient.message_type = wm_state;
459   xev.xclient.format = 32;
460   xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
461   xev.xclient.data.l[1] = fullscreen;
462   xev.xclient.data.l[2] = 0;
463   XSendEvent(dsp, DefaultRootWindow(dsp), False,
464     SubstructureNotifyMask, &xev);
465 }
466
467 static Atom net_wm_state;
468 static Atom net_wm_state_fullscreen;
469 static int net_wm_atoms_initialized = 0;
470
471 void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
472     Display *dpy = XtDisplay(win->window);
473     
474     // init net_wm_state atoms
475     if(!net_wm_atoms_initialized) {
476         net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
477         net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
478         net_wm_atoms_initialized = 1;
479     }
480     
481     WindowMenubarSetVisible(win, !enableFullscreen);
482     if(enableFullscreen && !win->fullscreen) {
483         XtUnmanageChild(main_window->menubar);
484         main_window->fullscreen = TRUE;
485     } else if(!enableFullscreen && win->fullscreen) {
486         XtManageChild(main_window->menubar);
487         main_window->fullscreen = FALSE;
488     }
489     
490     XEvent ev;
491     memset(&ev, 0, sizeof(XEvent));
492     ev.type = ClientMessage;
493     ev.xclient.window = XtWindow(win->window);
494     ev.xclient.message_type = net_wm_state;
495     ev.xclient.format = 32;
496     ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
497     ev.xclient.data.l[1] = net_wm_state_fullscreen;
498     ev.xclient.data.l[2] = 0;
499     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
500 }
501
502 void WindowMenubarSetVisible(MainWindow *win, bool visible) {
503     if(visible) {
504         XtManageChild(main_window->menubar);
505         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
506     } else {
507         XtUnmanageChild(main_window->menubar);
508         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
509     }
510 }
511
512 void WindowSetFile(MainWindow *win, char *file) {
513     if(win->file) {
514         XtFree(win->file);
515     }
516     win->file = file;
517 }
518
519 static void filedialog_end(
520         Widget widget,
521         MainWindow *data,
522         XmFileSelectionBoxCallbackStruct *selection)
523 {
524     XtUnmanageChild(widget);
525     XtDestroyWidget(widget);
526 }
527
528 static void filedialog_select(
529         Widget widget,
530         MainWindow *data,
531         XmFileSelectionBoxCallbackStruct *selection)
532 {
533     char *value = NULL;
534     if(selection->value) {
535         XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
536         if(value) {
537             WindowSetFile(data, value);
538             // no need to free the value, because it is stored in MainWindow
539             
540             PlayerOpenFile(data);
541         }
542     }
543     filedialog_end(widget, data, NULL);
544 }
545
546
547
548
549 static void FileOpenCB(Widget w, void *udata, void *cdata) {
550     MainWindow *win = main_window;
551     
552     Arg args[16];
553     int n = 0;
554     
555     XtSetArg(args[n], XnNshowViewMenu, 1); n++;
556     Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", args, n);
557     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
558     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
559     
560     Widget dirUp = XnFileSelectionBoxGetChild(dialog, XnFSB_DIR_UP_BUTTON);
561     XtUnmanageChild(dirUp);
562     
563     XtManageChild(dialog);
564 }
565
566 static void FileQuitCB(Widget w, void *udata, void *cdata) {
567     WindowClosePlayer(main_window);
568     ApplicationExit();
569 }
570
571 static void PlayRepeatCB(Widget w, void *udata, void *cdata) {
572     MainWindow *win = udata;
573     win->repeatTrack = XmToggleButtonGadgetGetState(w);
574     win->repeatList = 0;
575     win->autoplayFolder = 0;
576     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
577     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
578 }
579
580 static void PlayRepeatListCB(Widget w, void *udata, void *cdata) {
581     MainWindow *win = udata;
582     win->repeatList = XmToggleButtonGadgetGetState(w);
583     win->repeatTrack = 0;
584     win->autoplayFolder = 0;
585     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
586     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
587 }
588
589 static void PlayAutoPlayCB(Widget w, void *udata, void *cdata) {
590     MainWindow *win = udata;
591     win->autoplayFolder = XmToggleButtonGadgetGetState(w);
592     win->repeatTrack = 0;
593     win->repeatList = 0;
594     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
595     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
596 }
597
598 static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
599     if(main_window->fullscreen) {
600         WindowFullscreen(main_window, FALSE);
601     } else {
602         WindowFullscreen(main_window, TRUE);
603     }
604     
605 }
606
607 void WindowAdjustAspectRatio(MainWindow *win) {
608     if(!win->player) return;
609     if(!win->player->isactive || win->player->width <= 0 || win->player->height <= 0) return;
610       
611     // we have a running player width video
612     // adjust window aspect ratio (the window aspect ratio is different from
613     // the video, because of window decoration, menubar and other extra controls)
614     
615     Dimension win_width, win_height;
616     XtVaGetValues(win->window, XmNwidth, &win_width, XmNheight, &win_height, NULL);
617     Dimension player_width, player_height;
618     XtVaGetValues(win->player_widget, XmNwidth, &player_width, XmNheight, &player_height, NULL);
619     
620     double r = (double)win->player->width / (double)win->player->height;
621     double p_width = player_width;
622     double p_height = p_width / r;
623     
624     Dimension new_width = p_width + win_width - player_width;
625     Dimension new_height = p_height + win_height - player_height;
626     
627     XSizeHints hints;
628     hints.flags = PAspect;
629     hints.min_aspect.x = new_width;
630     hints.min_aspect.y = new_height;
631     hints.max_aspect.x = new_width;
632     hints.max_aspect.y = new_height;
633     XSetWMNormalHints(XtDisplay(win->window), XtWindow(win->window), &hints);
634 }
635
636 void WindowClosePlayer(MainWindow *win) {
637     if(win->player) {
638         PlayerDestroy(win->player);
639     }
640     win->player = NULL;
641     WindowShowPlayerCursor(win);
642 }
643
644 void WindowHidePlayerCursor(MainWindow *win) {
645     if(!win->cursorhidden && win->player && win->player->window != 0) {
646         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), blank_cursor);
647         win->cursorhidden = True;
648         XFlush(XtDisplay(win->player_widget));
649     }
650 }
651
652 void WindowShowPlayerCursor(MainWindow *win) {
653     if(win->cursorhidden && win->player && win->player->window != 0) {
654         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), None);
655         XFlush(XtDisplay(win->player_widget));
656     }
657     win->cursorhidden = False;
658 }