1 /*
2  *  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
3  *
4  *  This is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This software is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this software; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
17  *  USA.
18  */
19 
20 /*
21  * fullscreen.c - functions to deal with full-screen mode.
22  */
23 
24 #include <vncviewer.h>
25 #include <X11/Xaw/Form.h>
26 #include <X11/Xaw/Viewport.h>
27 #include <X11/Xaw/Toggle.h>
28 
29 static Bool DoBumpScroll();
30 static void BumpScrollTimerCallback(XtPointer clientData, XtIntervalId *id);
31 static XtIntervalId timer;
32 static Bool timerSet = False;
33 static Bool scrollLeft, scrollRight, scrollUp, scrollDown;
34 static Position desktopX, desktopY;
35 static Dimension viewportWidth, viewportHeight;
36 static Dimension scrollbarWidth, scrollbarHeight;
37 
38 
39 
40 /*
41  * FullScreenOn goes into full-screen mode.  It makes the toplevel window
42  * unmanaged by the window manager and sets its geometry appropriately.
43  *
44  * We have toplevel -> form -> viewport -> desktop.  "form" must always be the
45  * same size as "toplevel".  "desktop" should always be fixed at the size of
46  * the VNC desktop.  Normally "viewport" is the same size as "toplevel" (<=
47  * size of "desktop"), and "viewport" deals with any difference by putting up
48  * scrollbars.
49  *
50  * When we go into full-screen mode, we allow "viewport" and "form" to be
51  * different sizes, and we effectively need to work out all the geometries
52  * ourselves.  There are two cases to deal with:
53  *
54  * 1. When the desktop is smaller than the display, "viewport" is simply the
55  *    size of the desktop and "toplevel" (and "form") are the size of the
56  *    display.  "form" is visible around the edges of the desktop.
57  *
58  * 2. When the desktop is bigger than the display in either or both dimensions,
59  *    we force "viewport" to have scrollbars.
60  *
61  *    If the desktop width is bigger than the display width, then the width of
62  *    "viewport" is the display width plus the scrollbar width, otherwise it's
63  *    the desktop width plus the scrollbar width.  The width of "toplevel" (and
64  *    "form") is then either the same as "viewport", or just the display width,
65  *    respectively.  Similarly for the height of "viewport" and the height of
66  *    "toplevel".
67  *
68  *    So if the desktop is bigger than the display in both dimensions then both
69  *    the scrollbars will be just off the screen.  If it's bigger in only one
70  *    dimension then that scrollbar _will_ be visible, with the other one just
71  *    off the screen.  We treat this as a "feature" rather than a problem - you
72  *    can't easily get around it if you want to use the Athena viewport for
73  *    doing the scrolling.
74  *
75  * In either case, we position "viewport" in the middle of "form".
76  *
77  * We store the calculated size of "viewport" and the scrollbars in global
78  * variables so that FullScreenOff can use them.
79  */
80 
81 void
FullScreenOn()82 FullScreenOn()
83 {
84   Dimension toplevelWidth, toplevelHeight;
85   Dimension oldViewportWidth, oldViewportHeight, clipWidth, clipHeight;
86   Position viewportX, viewportY;
87 
88   appData.fullScreen = True;
89 
90   if (si.framebufferWidth > dpyWidth || si.framebufferHeight > dpyHeight) {
91 
92     XtVaSetValues(viewport, XtNforceBars, True, NULL);
93     XtVaGetValues(viewport, XtNwidth, &oldViewportWidth,
94 		  XtNheight, &oldViewportHeight, NULL);
95     XtVaGetValues(XtNameToWidget(viewport, "clip"),
96 		  XtNwidth, &clipWidth, XtNheight, &clipHeight, NULL);
97 
98     scrollbarWidth = oldViewportWidth - clipWidth;
99     scrollbarHeight = oldViewportHeight - clipHeight;
100 
101     if (si.framebufferWidth > dpyWidth) {
102       viewportWidth = toplevelWidth = dpyWidth + scrollbarWidth;
103     } else {
104       viewportWidth = si.framebufferWidth + scrollbarWidth;
105       toplevelWidth = dpyWidth;
106     }
107 
108     if (si.framebufferHeight > dpyHeight) {
109       viewportHeight = toplevelHeight = dpyHeight + scrollbarHeight;
110     } else {
111       viewportHeight = si.framebufferHeight + scrollbarHeight;
112       toplevelHeight = dpyHeight;
113     }
114 
115   } else {
116     viewportWidth = si.framebufferWidth;
117     viewportHeight = si.framebufferHeight;
118     toplevelWidth = dpyWidth;
119     toplevelHeight = dpyHeight;
120   }
121 
122   viewportX = (toplevelWidth - viewportWidth) / 2;
123   viewportY = (toplevelHeight - viewportHeight) / 2;
124 
125 
126   /* We want to stop the window manager from managing our toplevel window.
127      This is not really a nice thing to do, so may not work properly with every
128      window manager.  We do this simply by setting overrideRedirect and
129      reparenting our window to the root.  The window manager will get a
130      ReparentNotify and hopefully clean up its frame window. */
131 
132   XtVaSetValues(toplevel, XtNoverrideRedirect, True, NULL);
133 
134   XReparentWindow(dpy, XtWindow(toplevel), DefaultRootWindow(dpy), 0, 0);
135 
136   /* Some WMs does not obey x,y values of XReparentWindow; the window
137      is not placed in the upper, left corner. The code below fixes
138      this: It manually moves the window, after the Xserver is done
139      with XReparentWindow. The last XSync seems to prevent losing
140      focus, but I don't know why. */
141   XSync(dpy, False);
142   XMoveWindow(dpy, XtWindow(toplevel), 0, 0);
143   XSync(dpy, False);
144 
145   /* Now we want to fix the size of "viewport".  We shouldn't just change it
146      directly.  Instead we set "toplevel" to the required size (which should
147      propagate through "form" to "viewport").  Then we remove "viewport" from
148      being managed by "form", change its resources to position it and make sure
149      that "form" won't attempt to resize it, then ask "form" to manage it
150      again. */
151 
152   XtResizeWidget(toplevel, viewportWidth, viewportHeight, 0);
153 
154   XtUnmanageChild(viewport);
155 
156   XtVaSetValues(viewport,
157 		XtNhorizDistance, viewportX,
158 		XtNvertDistance, viewportY,
159 		XtNleft, XtChainLeft,
160 		XtNright, XtChainLeft,
161 		XtNtop, XtChainTop,
162 		XtNbottom, XtChainTop,
163 		NULL);
164 
165   XtManageChild(viewport);
166 
167   /* Now we can set "toplevel" to its proper size. */
168 
169   XtResizeWidget(toplevel, toplevelWidth, toplevelHeight, 0);
170 
171   /* Set the popup to overrideRedirect too */
172 
173   XtVaSetValues(popup, XtNoverrideRedirect, True, NULL);
174 
175   /* Try to get the input focus. */
176 
177   XSetInputFocus(dpy, DefaultRootWindow(dpy), RevertToPointerRoot,
178 		 CurrentTime);
179 
180   /* Optionally, grab the keyboard. */
181 
182   if (appData.grabKeyboard &&
183       XtGrabKeyboard(desktop, True, GrabModeAsync,
184 		     GrabModeAsync, CurrentTime) != GrabSuccess) {
185     fprintf(stderr, "XtGrabKeyboard() failed.\n");
186   }
187 }
188 
189 
190 /*
191  * FullScreenOff leaves full-screen mode.  It makes the toplevel window
192  * managed by the window manager and sets its geometry appropriately.
193  *
194  * We also want to reestablish the link between the geometry of "form" and
195  * "viewport".  We do this similarly to the way we broke it in FullScreenOn, by
196  * making "viewport" unmanaged, changing certain resources on it and asking
197  * "form" to manage it again.
198  *
199  * There seems to be a slightly strange behaviour with setting forceBars back
200  * to false, which results in "desktop" being stretched by the size of the
201  * scrollbars under certain circumstances.  Resizing both "toplevel" and
202  * "viewport" to the full-screen viewport size minus the scrollbar size seems
203  * to fix it, though I'm not entirely sure why. */
204 
205 void
FullScreenOff()206 FullScreenOff()
207 {
208   int toplevelWidth = si.framebufferWidth;
209   int toplevelHeight = si.framebufferHeight;
210 
211   appData.fullScreen = False;
212 
213   if (appData.grabKeyboard)
214     XtUngrabKeyboard(desktop, CurrentTime);
215 
216   XtUnmapWidget(toplevel);
217 
218   XtResizeWidget(toplevel,
219 		 viewportWidth - scrollbarWidth,
220 		 viewportHeight - scrollbarHeight, 0);
221   XtResizeWidget(viewport,
222 		 viewportWidth - scrollbarWidth,
223 		 viewportHeight - scrollbarHeight, 0);
224 
225   XtVaSetValues(viewport, XtNforceBars, False, NULL);
226 
227   XtUnmanageChild(viewport);
228 
229   XtVaSetValues(viewport,
230 		XtNhorizDistance, 0,
231 		XtNvertDistance, 0,
232 		XtNleft, XtChainLeft,
233 		XtNright, XtChainRight,
234 		XtNtop, XtChainTop,
235 		XtNbottom, XtChainBottom,
236 		NULL);
237 
238   XtManageChild(viewport);
239 
240   XtVaSetValues(toplevel, XtNoverrideRedirect, False, NULL);
241 
242   if ((toplevelWidth + appData.wmDecorationWidth) >= dpyWidth)
243     toplevelWidth = dpyWidth - appData.wmDecorationWidth;
244 
245   if ((toplevelHeight + appData.wmDecorationHeight) >= dpyHeight)
246     toplevelHeight = dpyHeight - appData.wmDecorationHeight;
247 
248   XtResizeWidget(toplevel, toplevelWidth, toplevelHeight, 0);
249 
250   XtMapWidget(toplevel);
251   XSync(dpy, False);
252 
253   /* Set the popup back to non-overrideRedirect */
254 
255   XtVaSetValues(popup, XtNoverrideRedirect, False, NULL);
256 }
257 
258 
259 /*
260  * SetFullScreenState is an action which sets the "state" resource of a toggle
261  * widget to reflect whether we're in full-screen mode.
262  */
263 
264 void
SetFullScreenState(Widget w,XEvent * ev,String * params,Cardinal * num_params)265 SetFullScreenState(Widget w, XEvent *ev, String *params, Cardinal *num_params)
266 {
267   if (appData.fullScreen)
268     XtVaSetValues(w, XtNstate, True, NULL);
269   else
270     XtVaSetValues(w, XtNstate, False, NULL);
271 }
272 
273 
274 /*
275  * ToggleFullScreen is an action which toggles in and out of full-screen mode.
276  */
277 
278 void
ToggleFullScreen(Widget w,XEvent * ev,String * params,Cardinal * num_params)279 ToggleFullScreen(Widget w, XEvent *ev, String *params, Cardinal *num_params)
280 {
281   if (appData.fullScreen) {
282     FullScreenOff();
283   } else {
284     FullScreenOn();
285   }
286 }
287 
288 
289 /*
290  * BumpScroll is called when in full-screen mode and the mouse is against one
291  * of the edges of the screen.  It returns true if any scrolling was done.
292  */
293 
294 Bool
BumpScroll(XEvent * ev)295 BumpScroll(XEvent *ev)
296 {
297   scrollLeft = scrollRight = scrollUp = scrollDown = False;
298 
299   if (ev->xmotion.x_root >= dpyWidth - 3)
300     scrollRight = True;
301   else if (ev->xmotion.x_root <= 2)
302     scrollLeft = True;
303 
304   if (ev->xmotion.y_root >= dpyHeight - 3)
305     scrollDown = True;
306   else if (ev->xmotion.y_root <= 2)
307     scrollUp = True;
308 
309   if (scrollLeft || scrollRight || scrollUp || scrollDown) {
310     if (timerSet)
311       return True;
312 
313     XtVaGetValues(desktop, XtNx, &desktopX, XtNy, &desktopY, NULL);
314     desktopX = -desktopX;
315     desktopY = -desktopY;
316 
317     return DoBumpScroll();
318   }
319 
320   if (timerSet) {
321     XtRemoveTimeOut(timer);
322     timerSet = False;
323   }
324 
325   return False;
326 }
327 
328 static Bool
DoBumpScroll()329 DoBumpScroll()
330 {
331   int oldx = desktopX, oldy = desktopY;
332 
333   if (scrollRight) {
334     if (desktopX < si.framebufferWidth - dpyWidth) {
335       desktopX += appData.bumpScrollPixels;
336       if (desktopX > si.framebufferWidth - dpyWidth)
337 	desktopX = si.framebufferWidth - dpyWidth;
338     }
339   } else if (scrollLeft) {
340     if (desktopX > 0) {
341       desktopX -= appData.bumpScrollPixels;
342       if (desktopX < 0)
343 	desktopX = 0;
344     }
345   }
346 
347   if (scrollDown) {
348     if (desktopY < si.framebufferHeight - dpyHeight) {
349       desktopY += appData.bumpScrollPixels;
350       if (desktopY > si.framebufferHeight - dpyHeight)
351 	desktopY = si.framebufferHeight - dpyHeight;
352     }
353   } else if (scrollUp) {
354     if (desktopY > 0) {
355       desktopY -= appData.bumpScrollPixels;
356       if (desktopY < 0)
357 	desktopY = 0;
358     }
359   }
360 
361   if (oldx != desktopX || oldy != desktopY) {
362     XawViewportSetCoordinates(viewport, desktopX, desktopY);
363     timer = XtAppAddTimeOut(appContext, appData.bumpScrollTime,
364 			    BumpScrollTimerCallback, NULL);
365     timerSet = True;
366     return True;
367   }
368 
369   timerSet = False;
370   return False;
371 }
372 
373 static void
BumpScrollTimerCallback(XtPointer clientData,XtIntervalId * id)374 BumpScrollTimerCallback(XtPointer clientData, XtIntervalId *id)
375 {
376   DoBumpScroll();
377 }
378