1 /* $XTermId: scrollbar.c,v 1.212 2021/06/06 23:14:52 Stelios.Bounanos Exp $ */
2 
3 /*
4  * Copyright 2000-2020,2021 by Thomas E. Dickey
5  *
6  *                         All Rights Reserved
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the
10  * "Software"), to deal in the Software without restriction, including
11  * without limitation the rights to use, copy, modify, merge, publish,
12  * distribute, sublicense, and/or sell copies of the Software, and to
13  * permit persons to whom the Software is furnished to do so, subject to
14  * the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be included
17  * in all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22  * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26  *
27  * Except as contained in this notice, the name(s) of the above copyright
28  * holders shall not be used in advertising or otherwise to promote the
29  * sale, use or other dealings in this Software without prior written
30  * authorization.
31  *
32  *
33  * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
34  *
35  *                         All Rights Reserved
36  *
37  * Permission to use, copy, modify, and distribute this software and its
38  * documentation for any purpose and without fee is hereby granted,
39  * provided that the above copyright notice appear in all copies and that
40  * both that copyright notice and this permission notice appear in
41  * supporting documentation, and that the name of Digital Equipment
42  * Corporation not be used in advertising or publicity pertaining to
43  * distribution of the software without specific, written prior permission.
44  *
45  *
46  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
47  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
48  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
49  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
50  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
51  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
52  * SOFTWARE.
53  */
54 
55 #include <xterm.h>
56 
57 #include <X11/Xatom.h>
58 
59 #if defined(HAVE_LIB_XAW)
60 #include <X11/Xaw/Scrollbar.h>
61 #elif defined(HAVE_LIB_XAW3D)
62 #include <X11/Xaw3d/Scrollbar.h>
63 #elif defined(HAVE_LIB_XAW3DXFT)
64 #include <X11/Xaw3dxft/Scrollbar.h>
65 #elif defined(HAVE_LIB_NEXTAW)
66 #include <X11/neXtaw/Scrollbar.h>
67 #elif defined(HAVE_LIB_XAWPLUS)
68 #include <X11/XawPlus/Scrollbar.h>
69 #endif
70 
71 #if defined(HAVE_XKBQUERYEXTENSION)
72 #include <X11/extensions/XKB.h>
73 #include <X11/XKBlib.h>
74 #endif
75 
76 #include <data.h>
77 #include <error.h>
78 #include <menu.h>
79 #include <xstrings.h>
80 
81 /*
82  * The scrollbar's border overlaps the border of the vt100 window.  If there
83  * is no border for the vt100, there can be no border for the scrollbar.
84  */
85 #define SCROLLBAR_BORDER(xw) (TScreenOf(xw)->scrollBarBorder)
86 #if OPT_TOOLBAR
87 #define ScrollBarBorder(xw) (BorderWidth(xw) ? SCROLLBAR_BORDER(xw) : 0)
88 #else
89 #define ScrollBarBorder(xw) SCROLLBAR_BORDER(xw)
90 #endif
91 
92 /* Event handlers */
93 
94 static void ScrollTextTo PROTO_XT_CALLBACK_ARGS;
95 static void ScrollTextUpDownBy PROTO_XT_CALLBACK_ARGS;
96 
97 /* Resize the text window for a terminal screen, modifying the
98  * appropriate WM_SIZE_HINTS and taking advantage of bit gravity.
99  */
100 void
DoResizeScreen(XtermWidget xw)101 DoResizeScreen(XtermWidget xw)
102 {
103     TScreen *screen = TScreenOf(xw);
104 
105     int border = 2 * screen->border;
106     int min_wide = border + screen->fullVwin.sb_info.width;
107     int min_high = border;
108     XtGeometryResult geomreqresult;
109     Dimension reqWidth, reqHeight, repWidth, repHeight;
110 #ifndef NO_ACTIVE_ICON
111     VTwin *saveWin = WhichVWin(screen);
112 
113     /* all units here want to be in the normal font units */
114     WhichVWin(screen) = &screen->fullVwin;
115 #endif /* NO_ACTIVE_ICON */
116 
117     /*
118      * I'm going to try to explain, as I understand it, why we
119      * have to do XGetWMNormalHints and XSetWMNormalHints here,
120      * although I can't guarantee that I've got it right.
121      *
122      * In a correctly written toolkit program, the Shell widget
123      * parses the user supplied geometry argument.  However,
124      * because of the way xterm does things, the VT100 widget does
125      * the parsing of the geometry option, not the Shell widget.
126      * The result of this is that the Shell widget doesn't set the
127      * correct window manager hints, and doesn't know that the
128      * user has specified a geometry.
129      *
130      * The XtVaSetValues call below tells the Shell widget to
131      * change its hints.  However, since it's confused about the
132      * hints to begin with, it doesn't get them all right when it
133      * does the SetValues -- it undoes some of what the VT100
134      * widget did when it originally set the hints.
135      *
136      * To fix this, we do the following:
137      *
138      * 1. Get the sizehints directly from the window, going around
139      *    the (confused) shell widget.
140      * 2. Call XtVaSetValues to let the shell widget know which
141      *    hints have changed.  Note that this may not even be
142      *    necessary, since we're going to right ahead after that
143      *    and set the hints ourselves, but it's good to put it
144      *    here anyway, so that when we finally do fix the code so
145      *    that the Shell does the right thing with hints, we
146      *    already have the XtVaSetValues in place.
147      * 3. We set the sizehints directly, this fixing up whatever
148      *    damage was done by the Shell widget during the
149      *    XtVaSetValues.
150      *
151      * Gross, huh?
152      *
153      * The correct fix is to redo VTRealize, VTInitialize and
154      * VTSetValues so that font processing happens early enough to
155      * give back responsibility for the size hints to the Shell.
156      *
157      * Someday, we hope to have time to do this.  Someday, we hope
158      * to have time to completely rewrite xterm.
159      */
160 
161     TRACE(("DoResizeScreen\n"));
162 
163 #if 1				/* ndef nothack */
164     /*
165      * NOTE: the hints and the XtVaSetValues() must match.
166      */
167     TRACE(("%s@%d -- ", __FILE__, __LINE__));
168     TRACE_WM_HINTS(xw);
169     getXtermSizeHints(xw);
170 
171     xtermSizeHints(xw, ScrollbarWidth(screen));
172 
173     /* These are obsolete, but old clients may use them */
174     xw->hints.width = MaxCols(screen) * FontWidth(screen) + xw->hints.min_width;
175     xw->hints.height = MaxRows(screen) * FontHeight(screen) + xw->hints.min_height;
176 #if OPT_MAXIMIZE
177     /* assure single-increment resize for fullscreen */
178     if (xw->work.ewmh[0].mode) {
179 	xw->hints.width_inc = 1;
180 	xw->hints.height_inc = 1;
181     }
182 #endif /* OPT_MAXIMIZE */
183 #endif
184 
185     XSetWMNormalHints(screen->display, VShellWindow(xw), &xw->hints);
186 
187     reqWidth = (Dimension) (MaxCols(screen) * FontWidth(screen) + min_wide);
188     reqHeight = (Dimension) (MaxRows(screen) * FontHeight(screen) + min_high);
189 
190 #if OPT_MAXIMIZE
191     /* compensate for fullscreen mode */
192     if (xw->work.ewmh[0].mode) {
193 	Screen *xscreen = DefaultScreenOfDisplay(xw->screen.display);
194 	reqWidth = (Dimension) WidthOfScreen(xscreen);
195 	reqHeight = (Dimension) HeightOfScreen(xscreen);
196 	ScreenResize(xw, reqWidth, reqHeight, &xw->flags);
197     }
198 #endif /* OPT_MAXIMIZE */
199 
200     TRACE(("...requesting screensize chars %dx%d, pixels %dx%d\n",
201 	   MaxRows(screen),
202 	   MaxCols(screen),
203 	   reqHeight, reqWidth));
204 
205     geomreqresult = REQ_RESIZE((Widget) xw, reqWidth, reqHeight,
206 			       &repWidth, &repHeight);
207 
208     if (geomreqresult == XtGeometryAlmost) {
209 	TRACE(("...almost, retry screensize %dx%d\n", repHeight, repWidth));
210 	geomreqresult = REQ_RESIZE((Widget) xw, repWidth,
211 				   repHeight, NULL, NULL);
212     }
213 
214     if (geomreqresult != XtGeometryYes) {
215 	/* The resize wasn't successful, so we might need to adjust
216 	   our idea of how large the screen is. */
217 	TRACE(("...still no (%d) - resize the core-class\n", geomreqresult));
218 	xw->core.widget_class->core_class.resize((Widget) xw);
219     }
220 #if 1				/* ndef nothack */
221     /*
222      * XtMakeResizeRequest() has the undesirable side-effect of clearing
223      * the window manager's hints, even on a failed request.  This would
224      * presumably be fixed if the shell did its own work.
225      */
226     if (xw->hints.flags
227 	&& repHeight
228 	&& repWidth) {
229 	xw->hints.height = repHeight;
230 	xw->hints.width = repWidth;
231 	TRACE_HINTS(&xw->hints);
232 	XSetWMNormalHints(screen->display, VShellWindow(xw), &xw->hints);
233     }
234 #endif
235     XSync(screen->display, False);	/* synchronize */
236     if (xtermAppPending())
237 	xevents(xw);
238 
239 #ifndef NO_ACTIVE_ICON
240     WhichVWin(screen) = saveWin;
241 #endif /* NO_ACTIVE_ICON */
242 }
243 
244 static Widget
CreateScrollBar(XtermWidget xw,int x,int y,int height)245 CreateScrollBar(XtermWidget xw, int x, int y, int height)
246 {
247     Widget result;
248     Arg args[6];
249 
250     XtSetArg(args[0], XtNx, x);
251     XtSetArg(args[1], XtNy, y);
252     XtSetArg(args[2], XtNheight, height);
253     XtSetArg(args[3], XtNreverseVideo, xw->misc.re_verse);
254     XtSetArg(args[4], XtNorientation, XtorientVertical);
255     XtSetArg(args[5], XtNborderWidth, ScrollBarBorder(xw));
256 
257     result = XtCreateWidget("scrollbar", scrollbarWidgetClass,
258 			    (Widget) xw, args, XtNumber(args));
259     XtAddCallback(result, XtNscrollProc, ScrollTextUpDownBy, 0);
260     XtAddCallback(result, XtNjumpProc, ScrollTextTo, 0);
261     return (result);
262 }
263 
264 void
ScrollBarReverseVideo(Widget scrollWidget)265 ScrollBarReverseVideo(Widget scrollWidget)
266 {
267     XtermWidget xw = getXtermWidget(scrollWidget);
268 
269     if (xw != 0) {
270 	SbInfo *sb = &(TScreenOf(xw)->fullVwin.sb_info);
271 	Arg args[4];
272 	Cardinal nargs = XtNumber(args);
273 
274 	/*
275 	 * Remember the scrollbar's original colors.
276 	 */
277 	if (sb->rv_cached == False) {
278 	    XtSetArg(args[0], XtNbackground, &(sb->bg));
279 	    XtSetArg(args[1], XtNforeground, &(sb->fg));
280 	    XtSetArg(args[2], XtNborderColor, &(sb->bdr));
281 	    XtSetArg(args[3], XtNborderPixmap, &(sb->bdpix));
282 	    XtGetValues(scrollWidget, args, nargs);
283 	    sb->rv_cached = True;
284 	    sb->rv_active = 0;
285 	}
286 
287 	sb->rv_active = !(sb->rv_active);
288 	if (sb->rv_active) {
289 	    XtSetArg(args[0], XtNbackground, sb->fg);
290 	    XtSetArg(args[1], XtNforeground, sb->bg);
291 	} else {
292 	    XtSetArg(args[0], XtNbackground, sb->bg);
293 	    XtSetArg(args[1], XtNforeground, sb->fg);
294 	}
295 	nargs = 2;		/* don't set border_pixmap */
296 	if (sb->bdpix == XtUnspecifiedPixmap) {
297 	    /* if not pixmap then pixel */
298 	    if (sb->rv_active) {
299 		/* keep border visible */
300 		XtSetArg(args[2], XtNborderColor, args[1].value);
301 	    } else {
302 		XtSetArg(args[2], XtNborderColor, sb->bdr);
303 	    }
304 	    nargs = 3;
305 	}
306 	XtSetValues(scrollWidget, args, nargs);
307     }
308 }
309 
310 void
ScrollBarDrawThumb(XtermWidget xw,int mode)311 ScrollBarDrawThumb(XtermWidget xw, int mode)
312 {
313     TScreen *screen = TScreenOf(xw);
314 
315     if (screen->scrollWidget != 0) {
316 	int thumbTop, thumbHeight, totalHeight;
317 
318 #if USE_DOUBLE_BUFFER
319 	if (resource.buffered) {
320 	    if (mode == 1) {
321 		screen->buffered_sb++;
322 		return;
323 	    } else if (mode == 2) {
324 		if (screen->buffered_sb == 0)
325 		    return;
326 	    }
327 	}
328 	screen->buffered_sb = 0;
329 #else
330 	(void) mode;
331 #endif
332 
333 	thumbTop = ROW2INX(screen, screen->savedlines);
334 	thumbHeight = MaxRows(screen);
335 	totalHeight = thumbHeight + screen->savedlines;
336 
337 	XawScrollbarSetThumb(screen->scrollWidget,
338 			     ((float) thumbTop) / (float) totalHeight,
339 			     ((float) thumbHeight) / (float) totalHeight);
340     }
341 }
342 
343 void
ResizeScrollBar(XtermWidget xw)344 ResizeScrollBar(XtermWidget xw)
345 {
346     TScreen *screen = TScreenOf(xw);
347 
348     if (screen->scrollWidget != 0) {
349 	int height = screen->fullVwin.height + screen->border * 2;
350 	int width = screen->scrollWidget->core.width;
351 	int ypos = -ScrollBarBorder(xw);
352 #ifdef SCROLLBAR_RIGHT
353 	int xpos = ((xw->misc.useRight)
354 		    ? (screen->fullVwin.fullwidth -
355 		       screen->scrollWidget->core.width -
356 		       BorderWidth(screen->scrollWidget))
357 		    : -ScrollBarBorder(xw));
358 #else
359 	int xpos = -ScrollBarBorder(xw);
360 #endif
361 
362 	TRACE(("ResizeScrollBar at %d,%d %dx%d\n", ypos, xpos, height, width));
363 
364 	XtConfigureWidget(
365 			     screen->scrollWidget,
366 			     (Position) xpos,
367 			     (Position) ypos,
368 			     (Dimension) width,
369 			     (Dimension) height,
370 			     BorderWidth(screen->scrollWidget));
371 	ScrollBarDrawThumb(xw, 1);
372     }
373 }
374 
375 void
WindowScroll(XtermWidget xw,int top,Bool always)376 WindowScroll(XtermWidget xw, int top, Bool always)
377 {
378     TScreen *screen = TScreenOf(xw);
379 
380     (void) always;
381 #if OPT_SCROLL_LOCK
382     if (((screen->allowScrollLock && screen->scroll_lock)
383 	 || (screen->autoScrollLock && top < 0))
384 	&& !always) {
385 	if (screen->scroll_dirty) {
386 	    screen->scroll_dirty = False;
387 	    ScrnRefresh(xw, 0, 0, MaxRows(screen), MaxCols(screen), False);
388 	}
389     } else
390 #endif
391     {
392 	int i;
393 
394 	if (top < -screen->savedlines) {
395 	    top = -screen->savedlines;
396 	} else if (top > 0) {
397 	    top = 0;
398 	}
399 
400 	if ((i = screen->topline - top) != 0) {
401 	    int lines;
402 	    int scrolltop, scrollheight, refreshtop;
403 
404 	    if (screen->cursor_state)
405 		HideCursor(xw);
406 	    lines = i > 0 ? i : -i;
407 	    if (lines > MaxRows(screen))
408 		lines = MaxRows(screen);
409 	    scrollheight = screen->max_row - lines + 1;
410 	    if (i > 0)
411 		refreshtop = scrolltop = 0;
412 	    else {
413 		scrolltop = lines;
414 		refreshtop = scrollheight;
415 	    }
416 	    scrolling_copy_area(xw, scrolltop, scrollheight, -i);
417 	    screen->topline = top;
418 
419 	    ScrollSelection(screen, i, True);
420 
421 	    xtermClear2(xw,
422 			OriginX(screen),
423 			OriginY(screen) + refreshtop * FontHeight(screen),
424 			(unsigned) Width(screen),
425 			(unsigned) (lines * FontHeight(screen)));
426 	    ScrnRefresh(xw, refreshtop, 0, lines, MaxCols(screen), False);
427 
428 #if OPT_BLINK_CURS || OPT_BLINK_TEXT
429 	    RestartBlinking(xw);
430 #endif
431 	}
432     }
433     ScrollBarDrawThumb(xw, 1);
434 }
435 
436 #ifdef SCROLLBAR_RIGHT
437 /*
438  * Adjust the scrollbar position if we're asked to turn on scrollbars for the
439  * first time (or after resizing) after the xterm is already running.  That
440  * makes the window grow after we've initially configured the scrollbar's
441  * position.  (There must be a better way).
442  */
443 void
updateRightScrollbar(XtermWidget xw)444 updateRightScrollbar(XtermWidget xw)
445 {
446     TScreen *screen = TScreenOf(xw);
447 
448     if (xw->misc.useRight
449 	&& screen->fullVwin.fullwidth < xw->core.width)
450 	XtVaSetValues(screen->scrollWidget,
451 		      XtNx, screen->fullVwin.fullwidth - BorderWidth(screen->scrollWidget),
452 		      (XtPointer) 0);
453 }
454 #endif
455 
456 void
ScrollBarOn(XtermWidget xw,Bool init)457 ScrollBarOn(XtermWidget xw, Bool init)
458 {
459     TScreen *screen = TScreenOf(xw);
460 
461     if (screen->fullVwin.sb_info.width || IsIcon(screen))
462 	return;
463 
464     TRACE(("ScrollBarOn(init %s)\n", BtoS(init)));
465     if (init) {			/* then create it only */
466 	if (screen->scrollWidget == 0) {
467 	    /* make it a dummy size and resize later */
468 	    screen->scrollWidget = CreateScrollBar(xw,
469 						   -ScrollBarBorder(xw),
470 						   -ScrollBarBorder(xw),
471 						   5);
472 	    if (screen->scrollWidget == NULL) {
473 		Bell(xw, XkbBI_MinorError, 0);
474 	    }
475 	}
476     } else if (!screen->scrollWidget || !XtIsRealized((Widget) xw)) {
477 	Bell(xw, XkbBI_MinorError, 0);
478 	Bell(xw, XkbBI_MinorError, 0);
479     } else {
480 
481 	ResizeScrollBar(xw);
482 	xtermAddInput(screen->scrollWidget);
483 	XtRealizeWidget(screen->scrollWidget);
484 	TRACE_TRANS("scrollbar", screen->scrollWidget);
485 
486 	screen->fullVwin.sb_info.rv_cached = False;
487 
488 	screen->fullVwin.sb_info.width = (screen->scrollWidget->core.width
489 					  + BorderWidth(screen->scrollWidget));
490 
491 	TRACE(("setting scrollbar width %d = %d + %d\n",
492 	       screen->fullVwin.sb_info.width,
493 	       screen->scrollWidget->core.width,
494 	       BorderWidth(screen->scrollWidget)));
495 
496 	ScrollBarDrawThumb(xw, 1);
497 	DoResizeScreen(xw);
498 
499 #ifdef SCROLLBAR_RIGHT
500 	updateRightScrollbar(xw);
501 #endif
502 
503 	XtMapWidget(screen->scrollWidget);
504 	update_scrollbar();
505 	if (screen->visbuf) {
506 	    xtermClear(xw);
507 	    Redraw();
508 	}
509     }
510 }
511 
512 void
ScrollBarOff(XtermWidget xw)513 ScrollBarOff(XtermWidget xw)
514 {
515     TScreen *screen = TScreenOf(xw);
516 
517     if (!screen->fullVwin.sb_info.width || IsIcon(screen))
518 	return;
519 
520     TRACE(("ScrollBarOff\n"));
521     if (XtIsRealized((Widget) xw)) {
522 	XtUnmapWidget(screen->scrollWidget);
523 	screen->fullVwin.sb_info.width = 0;
524 	DoResizeScreen(xw);
525 	update_scrollbar();
526 	if (screen->visbuf) {
527 	    xtermClear(xw);
528 	    Redraw();
529 	}
530     } else {
531 	Bell(xw, XkbBI_MinorError, 0);
532     }
533 }
534 
535 /*
536  * Toggle the visibility of the scrollbars.
537  */
538 void
ToggleScrollBar(XtermWidget xw)539 ToggleScrollBar(XtermWidget xw)
540 {
541     TScreen *screen = TScreenOf(xw);
542 
543     if (IsIcon(screen)) {
544 	Bell(xw, XkbBI_MinorError, 0);
545     } else {
546 	TRACE(("ToggleScrollBar" TRACE_L "\n"));
547 	if (screen->fullVwin.sb_info.width) {
548 	    ScrollBarOff(xw);
549 	} else {
550 	    ScrollBarOn(xw, False);
551 	}
552 	update_scrollbar();
553 	TRACE((TRACE_R " ToggleScrollBar\n"));
554     }
555 }
556 
557 /*ARGSUSED*/
558 static void
ScrollTextTo(Widget scrollbarWidget,XtPointer client_data GCC_UNUSED,XtPointer call_data)559 ScrollTextTo(
560 		Widget scrollbarWidget,
561 		XtPointer client_data GCC_UNUSED,
562 		XtPointer call_data)
563 {
564     XtermWidget xw = getXtermWidget(scrollbarWidget);
565 
566     if (xw != 0) {
567 	float *topPercent = (float *) call_data;
568 	TScreen *screen = TScreenOf(xw);
569 	int thumbTop;		/* relative to first saved line */
570 	int newTopLine;
571 
572 	/*
573 	 * screen->savedlines : Number of offscreen text lines,
574 	 * MaxRows(screen)    : Number of onscreen  text lines,
575 	 */
576 	thumbTop = (int) (*topPercent
577 			  * (float) (screen->savedlines + MaxRows(screen)));
578 	newTopLine = thumbTop - screen->savedlines;
579 	WindowScroll(xw, newTopLine, True);
580     }
581 }
582 
583 /*ARGSUSED*/
584 static void
ScrollTextUpDownBy(Widget scrollbarWidget,XtPointer client_data GCC_UNUSED,XtPointer call_data)585 ScrollTextUpDownBy(
586 		      Widget scrollbarWidget,
587 		      XtPointer client_data GCC_UNUSED,
588 		      XtPointer call_data)
589 {
590     XtermWidget xw = getXtermWidget(scrollbarWidget);
591 
592     if (xw != 0) {
593 	long pixels = (long) call_data;
594 
595 	TScreen *screen = TScreenOf(xw);
596 	int rowOnScreen, newTopLine;
597 
598 	rowOnScreen = (int) (pixels / FontHeight(screen));
599 	if (rowOnScreen == 0) {
600 	    if (pixels < 0)
601 		rowOnScreen = -1;
602 	    else if (pixels > 0)
603 		rowOnScreen = 1;
604 	}
605 	newTopLine = ROW2INX(screen, rowOnScreen);
606 	WindowScroll(xw, newTopLine, True);
607     }
608 }
609 
610 /*
611  * assume that b is alphabetic and allow plural
612  */
613 static int
CompareWidths(const char * a,const char * b,int * modifier)614 CompareWidths(const char *a, const char *b, int *modifier)
615 {
616     int result;
617     char ca, cb;
618 
619     *modifier = 0;
620     if (!a || !b)
621 	return 0;
622 
623     for (;;) {
624 	ca = x_toupper(*a);
625 	cb = x_toupper(*b);
626 	if (ca != cb || ca == '\0')
627 	    break;		/* if not eq else both nul */
628 	a++;
629 	b++;
630     }
631     if (cb != '\0')
632 	return 0;
633 
634     if (ca == 'S')
635 	ca = *++a;
636 
637     switch (ca) {
638     case '+':
639     case '-':
640 	*modifier = (ca == '-' ? -1 : 1) * atoi(a + 1);
641 	result = 1;
642 	break;
643 
644     case '\0':
645 	result = 1;
646 	break;
647 
648     default:
649 	result = 0;
650 	break;
651     }
652     return result;
653 }
654 
655 static long
params_to_pixels(TScreen * screen,String * params,Cardinal n)656 params_to_pixels(TScreen *screen, String *params, Cardinal n)
657 {
658     int mult = 1;
659     const char *s;
660     int modifier;
661 
662     switch (n > 2 ? 2 : n) {
663     case 2:
664 	s = params[1];
665 	if (CompareWidths(s, "PAGE", &modifier)) {
666 	    mult = (MaxRows(screen) + modifier) * FontHeight(screen);
667 	} else if (CompareWidths(s, "HALFPAGE", &modifier)) {
668 	    mult = ((MaxRows(screen) + modifier) * FontHeight(screen)) / 2;
669 	} else if (CompareWidths(s, "PIXEL", &modifier)) {
670 	    mult = 1;
671 	} else {
672 	    /* else assume that it is Line */
673 	    mult = FontHeight(screen);
674 	}
675 	mult *= atoi(params[0]);
676 	TRACE(("params_to_pixels(%s,%s) = %d\n", params[0], params[1], mult));
677 	break;
678     case 1:
679 	mult = atoi(params[0]) * FontHeight(screen);	/* lines */
680 	TRACE(("params_to_pixels(%s) = %d\n", params[0], mult));
681 	break;
682     default:
683 	mult = screen->scrolllines * FontHeight(screen);
684 	TRACE(("params_to_pixels() = %d\n", mult));
685 	break;
686     }
687     return mult;
688 }
689 
690 static long
AmountToScroll(Widget w,String * params,Cardinal nparams)691 AmountToScroll(Widget w, String *params, Cardinal nparams)
692 {
693     long result = 0;
694     XtermWidget xw;
695 
696     if ((xw = getXtermWidget(w)) != 0) {
697 	TScreen *screen = TScreenOf(xw);
698 	if (nparams <= 2
699 	    || screen->send_mouse_pos == MOUSE_OFF) {
700 	    result = params_to_pixels(screen, params, nparams);
701 	}
702     }
703     return result;
704 }
705 
706 static void
AlternateScroll(Widget w,long amount)707 AlternateScroll(Widget w, long amount)
708 {
709     XtermWidget xw;
710     TScreen *screen;
711 
712     if ((xw = getXtermWidget(w)) != 0 &&
713 	(screen = TScreenOf(xw)) != 0 &&
714 	screen->alternateScroll && screen->whichBuf) {
715 	ANSI reply;
716 
717 	amount /= FontHeight(screen);
718 	memset(&reply, 0, sizeof(reply));
719 	reply.a_type = ((xw->keyboard.flags & MODE_DECCKM)
720 			? ANSI_SS3
721 			: ANSI_CSI);
722 	if (amount > 0) {
723 	    reply.a_final = 'B';
724 	} else {
725 	    amount = -amount;
726 	    reply.a_final = 'A';
727 	}
728 	while (amount-- > 0) {
729 	    unparseseq(xw, &reply);
730 	}
731     } else {
732 	ScrollTextUpDownBy(w, (XtPointer) 0, (XtPointer) amount);
733     }
734 }
735 
736 /*ARGSUSED*/
737 void
HandleScrollTo(Widget w,XEvent * event GCC_UNUSED,String * params,Cardinal * nparams)738 HandleScrollTo(
739 		  Widget w,
740 		  XEvent *event GCC_UNUSED,
741 		  String *params,
742 		  Cardinal *nparams)
743 {
744     XtermWidget xw;
745     TScreen *screen;
746 
747     if ((xw = getXtermWidget(w)) != 0 &&
748 	(screen = TScreenOf(xw)) != 0 &&
749 	*nparams > 0) {
750 	long amount;
751 	int value;
752 	int to_top = (screen->topline - screen->savedlines);
753 	if (!x_strcasecmp(params[0], "begin")) {
754 	    amount = to_top * FontHeight(screen);
755 	} else if (!x_strcasecmp(params[0], "end")) {
756 	    amount = -to_top * FontHeight(screen);
757 	} else if ((value = atoi(params[0])) >= 0) {
758 	    amount = (value + to_top) * FontHeight(screen);
759 	} else {
760 	    amount = 0;
761 	}
762 	AlternateScroll(w, amount);
763     }
764 }
765 
766 /*ARGSUSED*/
767 void
HandleScrollForward(Widget xw,XEvent * event GCC_UNUSED,String * params,Cardinal * nparams)768 HandleScrollForward(
769 		       Widget xw,
770 		       XEvent *event GCC_UNUSED,
771 		       String *params,
772 		       Cardinal *nparams)
773 {
774     long amount;
775 
776     if ((amount = AmountToScroll(xw, params, *nparams)) != 0) {
777 	AlternateScroll(xw, amount);
778     }
779 }
780 
781 /*ARGSUSED*/
782 void
HandleScrollBack(Widget xw,XEvent * event GCC_UNUSED,String * params,Cardinal * nparams)783 HandleScrollBack(
784 		    Widget xw,
785 		    XEvent *event GCC_UNUSED,
786 		    String *params,
787 		    Cardinal *nparams)
788 {
789     long amount;
790 
791     if ((amount = -AmountToScroll(xw, params, *nparams)) != 0) {
792 	AlternateScroll(xw, amount);
793     }
794 }
795 
796 #if OPT_SCROLL_LOCK
797 #define SCROLL_LOCK_LED 3
798 
799 #ifdef HAVE_XKBQUERYEXTENSION
800 /*
801  * Check for Xkb on client and server.
802  */
803 static int
have_xkb(Display * dpy)804 have_xkb(Display *dpy)
805 {
806     static int initialized = -1;
807 
808     if (initialized < 0) {
809 	int xkbmajor = XkbMajorVersion;
810 	int xkbminor = XkbMinorVersion;
811 	int xkbopcode, xkbevent, xkberror;
812 
813 	initialized = 0;
814 	if (XkbLibraryVersion(&xkbmajor, &xkbminor)
815 	    && XkbQueryExtension(dpy,
816 				 &xkbopcode,
817 				 &xkbevent,
818 				 &xkberror,
819 				 &xkbmajor,
820 				 &xkbminor)) {
821 	    TRACE(("we have Xkb\n"));
822 	    initialized = 1;
823 #if OPT_TRACE
824 	    {
825 		XkbDescPtr xkb;
826 		unsigned int mask;
827 
828 		xkb = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
829 		if (xkb != NULL) {
830 		    int n;
831 
832 		    TRACE(("XkbGetKeyboard ok\n"));
833 		    for (n = 0; n < XkbNumVirtualMods; ++n) {
834 			if (xkb->names->vmods[n] != 0) {
835 			    char *modStr = XGetAtomName(xkb->dpy,
836 							xkb->names->vmods[n]);
837 			    if (modStr != 0) {
838 				XkbVirtualModsToReal(xkb,
839 						     (unsigned) (1 << n),
840 						     &mask);
841 				TRACE(("  name[%d] %s (%#x)\n", n, modStr, mask));
842 			    }
843 			}
844 		    }
845 		    XkbFreeKeyboard(xkb, 0, True);
846 		}
847 	    }
848 #endif
849 	}
850     }
851     return initialized;
852 }
853 
854 static Boolean
getXkbLED(Display * dpy,const char * name,Boolean * result)855 getXkbLED(Display *dpy, const char *name, Boolean *result)
856 {
857     Atom my_atom;
858     Boolean success = False;
859     Bool state;
860 
861     if (have_xkb(dpy)) {
862 	my_atom = XInternAtom(dpy, name, False);
863 	if ((my_atom != None) &&
864 	    XkbGetNamedIndicator(dpy, my_atom, NULL, &state, NULL, NULL)) {
865 	    *result = (Boolean) state;
866 	    success = True;
867 	}
868     }
869 
870     return success;
871 }
872 
873 /*
874  * Use Xkb if we have it (still unreliable, but slightly better than hardcoded).
875  */
876 static Boolean
showXkbLED(Display * dpy,const char * name,Bool enable)877 showXkbLED(Display *dpy, const char *name, Bool enable)
878 {
879     Atom my_atom;
880     Boolean result = False;
881 
882     if (have_xkb(dpy)) {
883 	my_atom = XInternAtom(dpy, name, False);
884 	if ((my_atom != None) &&
885 	    XkbGetNamedIndicator(dpy, my_atom, NULL, NULL, NULL, NULL) &&
886 	    XkbSetNamedIndicator(dpy, my_atom, True, enable, False, NULL)) {
887 	    result = True;
888 	}
889     }
890 
891     return result;
892 }
893 #endif
894 
895 /*
896  * xlsatoms agrees with this list.  However Num/Caps lock are generally
897  * unusable due to special treatment in X.  They are used here for
898  * completeness.
899  */
900 static const char *led_table[] =
901 {
902     "Num Lock",
903     "Caps Lock",
904     "Scroll Lock"
905 };
906 
907 static Boolean
xtermGetLED(TScreen * screen,Cardinal led_number)908 xtermGetLED(TScreen *screen, Cardinal led_number)
909 {
910     Display *dpy = screen->display;
911     Boolean result = False;
912 
913 #ifdef HAVE_XKBQUERYEXTENSION
914     if (!getXkbLED(dpy, led_table[led_number - 1], &result))
915 #endif
916     {
917 	XKeyboardState state;
918 	unsigned long my_bit = (unsigned long) (1 << (led_number - 1));
919 
920 	XGetKeyboardControl(dpy, &state);
921 
922 	result = (Boolean) ((state.led_mask & my_bit) != 0);
923     }
924 
925     TRACE(("xtermGetLED %d:%s\n", led_number, BtoS(result)));
926     return result;
927 }
928 
929 /*
930  * Display the given LED, preferably independent of keyboard state.
931  */
932 void
xtermShowLED(TScreen * screen,Cardinal led_number,Bool enable)933 xtermShowLED(TScreen *screen, Cardinal led_number, Bool enable)
934 {
935     TRACE(("xtermShowLED %d:%s\n", led_number, BtoS(enable)));
936     if ((led_number >= 1) && (led_number <= XtNumber(led_table))) {
937 	Display *dpy = screen->display;
938 
939 #ifdef HAVE_XKBQUERYEXTENSION
940 	if (!showXkbLED(dpy, led_table[led_number - 1], enable))
941 #endif
942 	{
943 	    XKeyboardState state;
944 	    XKeyboardControl values;
945 	    unsigned long use_mask;
946 	    unsigned long my_bit = (unsigned long) (1 << (led_number - 1));
947 
948 	    XGetKeyboardControl(dpy, &state);
949 	    use_mask = state.led_mask;
950 	    if (enable) {
951 		use_mask |= my_bit;
952 	    } else {
953 		use_mask &= ~my_bit;
954 	    }
955 
956 	    if (state.led_mask != use_mask) {
957 		values.led = (int) led_number;
958 		values.led_mode = enable;
959 		XChangeKeyboardControl(dpy, KBLed | KBLedMode, &values);
960 	    }
961 	}
962     }
963 }
964 
965 void
xtermClearLEDs(TScreen * screen)966 xtermClearLEDs(TScreen *screen)
967 {
968     Display *dpy = screen->display;
969     XKeyboardControl values;
970 
971     TRACE(("xtermClearLEDs\n"));
972 #ifdef HAVE_XKBQUERYEXTENSION
973     ShowScrollLock(screen, False);
974 #endif
975     memset(&values, 0, sizeof(values));
976     XChangeKeyboardControl(dpy, KBLedMode, &values);
977 }
978 
979 void
ShowScrollLock(TScreen * screen,Bool enable)980 ShowScrollLock(TScreen *screen, Bool enable)
981 {
982     xtermShowLED(screen, SCROLL_LOCK_LED, enable);
983 }
984 
985 void
GetScrollLock(TScreen * screen)986 GetScrollLock(TScreen *screen)
987 {
988     if (screen->allowScrollLock)
989 	screen->scroll_lock = xtermGetLED(screen, SCROLL_LOCK_LED);
990 }
991 
992 void
SetScrollLock(TScreen * screen,Bool enable)993 SetScrollLock(TScreen *screen, Bool enable)
994 {
995     if (screen->allowScrollLock) {
996 	if (screen->scroll_lock != enable) {
997 	    TRACE(("SetScrollLock %s\n", BtoS(enable)));
998 	    screen->scroll_lock = (Boolean) enable;
999 	    ShowScrollLock(screen, enable);
1000 	}
1001     }
1002 }
1003 
1004 /* ARGSUSED */
1005 void
HandleScrollLock(Widget w,XEvent * event GCC_UNUSED,String * params,Cardinal * param_count)1006 HandleScrollLock(Widget w,
1007 		 XEvent *event GCC_UNUSED,
1008 		 String *params,
1009 		 Cardinal *param_count)
1010 {
1011     XtermWidget xw;
1012 
1013     if ((xw = getXtermWidget(w)) != 0) {
1014 	TScreen *screen = TScreenOf(xw);
1015 
1016 	if (screen->allowScrollLock) {
1017 
1018 	    switch (decodeToggle(xw, params, *param_count)) {
1019 	    case toggleOff:
1020 		SetScrollLock(screen, False);
1021 		break;
1022 	    case toggleOn:
1023 		SetScrollLock(screen, True);
1024 		break;
1025 	    case toggleAll:
1026 		SetScrollLock(screen, !screen->scroll_lock);
1027 		break;
1028 	    }
1029 	}
1030     }
1031 }
1032 #endif
1033