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