Home | History | Annotate | Line # | Download | only in dist
      1 /* $XTermId: scrollbar.c,v 1.216 2024/12/01 20:27:00 tom Exp $ */
      2 
      3 /*
      4  * Copyright 2000-2023,2024 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
    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
    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, NULL);
    260     XtAddCallback(result, XtNjumpProc, ScrollTextTo, NULL);
    261     return (result);
    262 }
    263 
    264 void
    265 ScrollBarReverseVideo(Widget scrollWidget)
    266 {
    267     XtermWidget xw = getXtermWidget(scrollWidget);
    268 
    269     if (xw != NULL) {
    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
    311 ScrollBarDrawThumb(XtermWidget xw, int mode)
    312 {
    313     TScreen *screen = TScreenOf(xw);
    314 
    315     if (screen->scrollWidget != NULL) {
    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
    344 ResizeScrollBar(XtermWidget xw)
    345 {
    346     TScreen *screen = TScreenOf(xw);
    347 
    348     if (screen->scrollWidget != NULL) {
    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
    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,
    388 			LastRowNumber(screen) + 1,
    389 			MaxCols(screen), False);
    390 	}
    391     } else
    392 #endif
    393     {
    394 	int i;
    395 
    396 	if (top < -screen->savedlines) {
    397 	    top = -screen->savedlines;
    398 	} else if (top > 0) {
    399 	    top = 0;
    400 	}
    401 
    402 	if ((i = screen->topline - top) != 0) {
    403 	    int lines;
    404 	    int scrolltop, scrollheight, refreshtop;
    405 
    406 	    if (screen->cursor_state)
    407 		HideCursor(xw);
    408 	    lines = i > 0 ? i : -i;
    409 	    if (lines > MaxRows(screen))
    410 		lines = MaxRows(screen);
    411 	    scrollheight = screen->max_row - lines + 1;
    412 	    if (i > 0)
    413 		refreshtop = scrolltop = 0;
    414 	    else {
    415 		scrolltop = lines;
    416 		refreshtop = scrollheight;
    417 	    }
    418 	    scrolling_copy_area(xw, scrolltop, scrollheight, -i);
    419 	    screen->topline = top;
    420 
    421 	    ScrollSelection(screen, i, True);
    422 
    423 	    xtermClear2(xw,
    424 			OriginX(screen),
    425 			OriginY(screen) + refreshtop * FontHeight(screen),
    426 			(unsigned) Width(screen),
    427 			(unsigned) (lines * FontHeight(screen)));
    428 	    ScrnRefresh(xw, refreshtop, 0, lines, MaxCols(screen), False);
    429 
    430 #if OPT_BLINK_CURS || OPT_BLINK_TEXT
    431 	    RestartBlinking(xw);
    432 #endif
    433 	}
    434     }
    435     ScrollBarDrawThumb(xw, 1);
    436 }
    437 
    438 #ifdef SCROLLBAR_RIGHT
    439 /*
    440  * Adjust the scrollbar position if we're asked to turn on scrollbars for the
    441  * first time (or after resizing) after the xterm is already running.  That
    442  * makes the window grow after we've initially configured the scrollbar's
    443  * position.  (There must be a better way).
    444  */
    445 void
    446 updateRightScrollbar(XtermWidget xw)
    447 {
    448     TScreen *screen = TScreenOf(xw);
    449 
    450     if (xw->misc.useRight
    451 	&& screen->fullVwin.fullwidth < xw->core.width)
    452 	XtVaSetValues(screen->scrollWidget,
    453 		      XtNx, screen->fullVwin.fullwidth - BorderWidth(screen->scrollWidget),
    454 		      (XtPointer) 0);
    455 }
    456 #endif
    457 
    458 void
    459 ScrollBarOn(XtermWidget xw, Bool init)
    460 {
    461     TScreen *screen = TScreenOf(xw);
    462 
    463     if (screen->fullVwin.sb_info.width || IsIcon(screen))
    464 	return;
    465 
    466     TRACE(("ScrollBarOn(init %s)\n", BtoS(init)));
    467     if (init) {			/* then create it only */
    468 	if (screen->scrollWidget == NULL) {
    469 	    /* make it a dummy size and resize later */
    470 	    screen->scrollWidget = CreateScrollBar(xw,
    471 						   -ScrollBarBorder(xw),
    472 						   -ScrollBarBorder(xw),
    473 						   5);
    474 	    if (screen->scrollWidget == NULL) {
    475 		Bell(xw, XkbBI_MinorError, 0);
    476 	    }
    477 	}
    478     } else if (!screen->scrollWidget || !XtIsRealized((Widget) xw)) {
    479 	Bell(xw, XkbBI_MinorError, 0);
    480 	Bell(xw, XkbBI_MinorError, 0);
    481     } else {
    482 
    483 	ResizeScrollBar(xw);
    484 	xtermAddInput(screen->scrollWidget);
    485 	XtRealizeWidget(screen->scrollWidget);
    486 	TRACE_TRANS("scrollbar", screen->scrollWidget);
    487 
    488 	screen->fullVwin.sb_info.rv_cached = False;
    489 
    490 	screen->fullVwin.sb_info.width = (screen->scrollWidget->core.width
    491 					  + BorderWidth(screen->scrollWidget));
    492 
    493 	TRACE(("setting scrollbar width %d = %d + %d\n",
    494 	       screen->fullVwin.sb_info.width,
    495 	       screen->scrollWidget->core.width,
    496 	       BorderWidth(screen->scrollWidget)));
    497 
    498 	ScrollBarDrawThumb(xw, 1);
    499 	DoResizeScreen(xw);
    500 
    501 #ifdef SCROLLBAR_RIGHT
    502 	updateRightScrollbar(xw);
    503 #endif
    504 
    505 	XtMapWidget(screen->scrollWidget);
    506 	update_scrollbar();
    507 	if (screen->visbuf) {
    508 	    xtermClear(xw);
    509 	    Redraw();
    510 	}
    511     }
    512 }
    513 
    514 void
    515 ScrollBarOff(XtermWidget xw)
    516 {
    517     TScreen *screen = TScreenOf(xw);
    518 
    519     if (!screen->fullVwin.sb_info.width || IsIcon(screen))
    520 	return;
    521 
    522     TRACE(("ScrollBarOff\n"));
    523     if (XtIsRealized((Widget) xw)) {
    524 	XtUnmapWidget(screen->scrollWidget);
    525 	screen->fullVwin.sb_info.width = 0;
    526 	DoResizeScreen(xw);
    527 	update_scrollbar();
    528 	if (screen->visbuf) {
    529 	    xtermClear(xw);
    530 	    Redraw();
    531 	}
    532     } else {
    533 	Bell(xw, XkbBI_MinorError, 0);
    534     }
    535 }
    536 
    537 /*
    538  * Toggle the visibility of the scrollbars.
    539  */
    540 void
    541 ToggleScrollBar(XtermWidget xw)
    542 {
    543     TScreen *screen = TScreenOf(xw);
    544 
    545     if (IsIcon(screen)) {
    546 	Bell(xw, XkbBI_MinorError, 0);
    547     } else {
    548 	TRACE(("ToggleScrollBar" TRACE_L "\n"));
    549 	if (screen->fullVwin.sb_info.width) {
    550 	    ScrollBarOff(xw);
    551 	} else {
    552 	    ScrollBarOn(xw, False);
    553 	}
    554 	update_scrollbar();
    555 	TRACE((TRACE_R " ToggleScrollBar\n"));
    556     }
    557 }
    558 
    559 /*ARGSUSED*/
    560 static void
    561 ScrollTextTo(
    562 		Widget scrollbarWidget,
    563 		XtPointer client_data GCC_UNUSED,
    564 		XtPointer call_data)
    565 {
    566     XtermWidget xw = getXtermWidget(scrollbarWidget);
    567 
    568     if (xw != NULL) {
    569 	float *topPercent = (float *) call_data;
    570 	TScreen *screen = TScreenOf(xw);
    571 	int thumbTop;		/* relative to first saved line */
    572 	int newTopLine;
    573 
    574 	/*
    575 	 * screen->savedlines : Number of offscreen text lines,
    576 	 * MaxRows(screen)    : Number of onscreen  text lines,
    577 	 */
    578 	thumbTop = (int) (*topPercent
    579 			  * (float) (screen->savedlines + MaxRows(screen)));
    580 	newTopLine = thumbTop - screen->savedlines;
    581 	WindowScroll(xw, newTopLine, True);
    582     }
    583 }
    584 
    585 /*ARGSUSED*/
    586 static void
    587 ScrollTextUpDownBy(
    588 		      Widget scrollbarWidget,
    589 		      XtPointer client_data GCC_UNUSED,
    590 		      XtPointer call_data)
    591 {
    592     XtermWidget xw = getXtermWidget(scrollbarWidget);
    593 
    594     if (xw != NULL) {
    595 	long pixels = (long) call_data;
    596 
    597 	TScreen *screen = TScreenOf(xw);
    598 	int rowOnScreen, newTopLine;
    599 
    600 	rowOnScreen = (int) (pixels / FontHeight(screen));
    601 	if (rowOnScreen == 0) {
    602 	    if (pixels < 0)
    603 		rowOnScreen = -1;
    604 	    else if (pixels > 0)
    605 		rowOnScreen = 1;
    606 	}
    607 	newTopLine = ROW2INX(screen, rowOnScreen);
    608 	WindowScroll(xw, newTopLine, True);
    609     }
    610 }
    611 
    612 /*
    613  * assume that b is alphabetic and allow plural
    614  */
    615 static int
    616 CompareWidths(const char *a, const char *b, int *modifier)
    617 {
    618     int result;
    619     char ca, cb;
    620 
    621     *modifier = 0;
    622     if (!a || !b)
    623 	return 0;
    624 
    625     for (;;) {
    626 	ca = x_toupper(*a);
    627 	cb = x_toupper(*b);
    628 	if (ca != cb || ca == '\0')
    629 	    break;		/* if not eq else both nul */
    630 	a++;
    631 	b++;
    632     }
    633     if (cb != '\0')
    634 	return 0;
    635 
    636     if (ca == 'S')
    637 	ca = *++a;
    638 
    639     switch (ca) {
    640     case '+':
    641     case '-':
    642 	*modifier = (ca == '-' ? -1 : 1) * atoi(a + 1);
    643 	result = 1;
    644 	break;
    645 
    646     case '\0':
    647 	result = 1;
    648 	break;
    649 
    650     default:
    651 	result = 0;
    652 	break;
    653     }
    654     return result;
    655 }
    656 
    657 static long
    658 params_to_pixels(TScreen *screen, String *params, Cardinal n)
    659 {
    660     int mult = 1;
    661     const char *s;
    662     int modifier;
    663 
    664     switch (n > 2 ? 2 : n) {
    665     case 2:
    666 	s = params[1];
    667 	if (CompareWidths(s, "PAGE", &modifier)) {
    668 	    mult = (MaxRows(screen) + modifier) * FontHeight(screen);
    669 	} else if (CompareWidths(s, "HALFPAGE", &modifier)) {
    670 	    mult = ((MaxRows(screen) + modifier) * FontHeight(screen)) / 2;
    671 	} else if (CompareWidths(s, "PIXEL", &modifier)) {
    672 	    mult = 1;
    673 	} else {
    674 	    /* else assume that it is Line */
    675 	    mult = FontHeight(screen);
    676 	}
    677 	mult *= atoi(params[0]);
    678 	TRACE(("params_to_pixels(%s,%s) = %d\n", params[0], params[1], mult));
    679 	break;
    680     case 1:
    681 	mult = atoi(params[0]) * FontHeight(screen);	/* lines */
    682 	TRACE(("params_to_pixels(%s) = %d\n", params[0], mult));
    683 	break;
    684     default:
    685 	mult = screen->scrolllines * FontHeight(screen);
    686 	TRACE(("params_to_pixels() = %d\n", mult));
    687 	break;
    688     }
    689     return mult;
    690 }
    691 
    692 static long
    693 AmountToScroll(Widget w, String *params, Cardinal nparams)
    694 {
    695     long result = 0;
    696     XtermWidget xw;
    697 
    698     if ((xw = getXtermWidget(w)) != NULL) {
    699 	TScreen *screen = TScreenOf(xw);
    700 	if (nparams <= 2
    701 	    || screen->send_mouse_pos == MOUSE_OFF) {
    702 	    result = params_to_pixels(screen, params, nparams);
    703 	}
    704     }
    705     return result;
    706 }
    707 
    708 static void
    709 AlternateScroll(Widget w, long amount)
    710 {
    711     XtermWidget xw;
    712     TScreen *screen;
    713 
    714     if ((xw = getXtermWidget(w)) != NULL &&
    715 	(screen = TScreenOf(xw)) != NULL &&
    716 	screen->alternateScroll && screen->whichBuf) {
    717 	ANSI reply;
    718 
    719 	amount /= FontHeight(screen);
    720 	memset(&reply, 0, sizeof(reply));
    721 	reply.a_type = ((xw->keyboard.flags & MODE_DECCKM)
    722 			? ANSI_SS3
    723 			: ANSI_CSI);
    724 	if (amount > 0) {
    725 	    reply.a_final = 'B';
    726 	} else {
    727 	    amount = -amount;
    728 	    reply.a_final = 'A';
    729 	}
    730 	while (amount-- > 0) {
    731 	    unparseseq(xw, &reply);
    732 	}
    733     } else {
    734 	ScrollTextUpDownBy(w, (XtPointer) 0, (XtPointer) amount);
    735     }
    736 }
    737 
    738 /*ARGSUSED*/
    739 void
    740 HandleScrollTo(
    741 		  Widget w,
    742 		  XEvent *event GCC_UNUSED,
    743 		  String *params,
    744 		  Cardinal *nparams)
    745 {
    746     XtermWidget xw;
    747     TScreen *screen;
    748 
    749     if ((xw = getXtermWidget(w)) != NULL &&
    750 	(screen = TScreenOf(xw)) != NULL &&
    751 	*nparams > 0) {
    752 	long amount;
    753 	int value;
    754 	int to_top = (screen->topline - screen->savedlines);
    755 	if (!x_strcasecmp(params[0], "begin")) {
    756 	    amount = to_top * FontHeight(screen);
    757 	} else if (!x_strcasecmp(params[0], "end")) {
    758 	    amount = -to_top * FontHeight(screen);
    759 	} else if ((value = atoi(params[0])) >= 0) {
    760 	    amount = (value + to_top) * FontHeight(screen);
    761 	} else {
    762 	    amount = 0;
    763 	}
    764 	AlternateScroll(w, amount);
    765     }
    766 }
    767 
    768 /*ARGSUSED*/
    769 void
    770 HandleScrollForward(
    771 		       Widget xw,
    772 		       XEvent *event GCC_UNUSED,
    773 		       String *params,
    774 		       Cardinal *nparams)
    775 {
    776     long amount;
    777 
    778     if ((amount = AmountToScroll(xw, params, *nparams)) != 0) {
    779 	AlternateScroll(xw, amount);
    780     }
    781 }
    782 
    783 /*ARGSUSED*/
    784 void
    785 HandleScrollBack(
    786 		    Widget xw,
    787 		    XEvent *event GCC_UNUSED,
    788 		    String *params,
    789 		    Cardinal *nparams)
    790 {
    791     long amount;
    792 
    793     if ((amount = -AmountToScroll(xw, params, *nparams)) != 0) {
    794 	AlternateScroll(xw, amount);
    795     }
    796 }
    797 
    798 #if OPT_SCROLL_LOCK
    799 #define SCROLL_LOCK_LED 3
    800 
    801 #ifdef HAVE_XKBQUERYEXTENSION
    802 /*
    803  * Check for Xkb on client and server.
    804  */
    805 static int
    806 have_xkb(Display *dpy)
    807 {
    808     static int initialized = -1;
    809 
    810     if (initialized < 0) {
    811 	int xkbmajor = XkbMajorVersion;
    812 	int xkbminor = XkbMinorVersion;
    813 	int xkbopcode, xkbevent, xkberror;
    814 
    815 	initialized = 0;
    816 	if (XkbLibraryVersion(&xkbmajor, &xkbminor)
    817 	    && XkbQueryExtension(dpy,
    818 				 &xkbopcode,
    819 				 &xkbevent,
    820 				 &xkberror,
    821 				 &xkbmajor,
    822 				 &xkbminor)) {
    823 	    TRACE(("we have Xkb\n"));
    824 	    initialized = 1;
    825 #if OPT_TRACE
    826 	    {
    827 		XkbDescPtr xkb;
    828 		unsigned int mask;
    829 
    830 		xkb = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
    831 		if (xkb != NULL) {
    832 		    int n;
    833 
    834 		    TRACE(("XkbGetKeyboard ok\n"));
    835 		    for (n = 0; n < XkbNumVirtualMods; ++n) {
    836 			if (xkb->names->vmods[n] != 0) {
    837 			    char *modStr = XGetAtomName(xkb->dpy,
    838 							xkb->names->vmods[n]);
    839 			    if (modStr != NULL) {
    840 				XkbVirtualModsToReal(xkb,
    841 						     (unsigned) (1 << n),
    842 						     &mask);
    843 				TRACE(("  name[%d] %s (%#x)\n", n, modStr, mask));
    844 				XFree(modStr);
    845 			    }
    846 			}
    847 		    }
    848 		    XkbFreeKeyboard(xkb, 0, True);
    849 		}
    850 	    }
    851 #endif
    852 	}
    853     }
    854     return initialized;
    855 }
    856 
    857 static Boolean
    858 getXkbLED(Display *dpy, const char *name, Boolean *result)
    859 {
    860     Atom my_atom;
    861     Boolean success = False;
    862     Bool state;
    863 
    864     if (have_xkb(dpy)) {
    865 	my_atom = CachedInternAtom(dpy, name);
    866 	if ((my_atom != None) &&
    867 	    XkbGetNamedIndicator(dpy, my_atom, NULL, &state, NULL, NULL)) {
    868 	    *result = (Boolean) state;
    869 	    success = True;
    870 	}
    871     }
    872 
    873     return success;
    874 }
    875 
    876 /*
    877  * Use Xkb if we have it (still unreliable, but slightly better than hardcoded).
    878  */
    879 static Boolean
    880 showXkbLED(Display *dpy, const char *name, Bool enable)
    881 {
    882     Atom my_atom;
    883     Boolean result = False;
    884 
    885     if (have_xkb(dpy)) {
    886 	my_atom = CachedInternAtom(dpy, name);
    887 	if ((my_atom != None) &&
    888 	    XkbGetNamedIndicator(dpy, my_atom, NULL, NULL, NULL, NULL) &&
    889 	    XkbSetNamedIndicator(dpy, my_atom, True, enable, False, NULL)) {
    890 	    result = True;
    891 	}
    892     }
    893 
    894     return result;
    895 }
    896 #endif
    897 
    898 /*
    899  * xlsatoms agrees with this list.  However Num/Caps lock are generally
    900  * unusable due to special treatment in X.  They are used here for
    901  * completeness.
    902  */
    903 static const char *led_table[] =
    904 {
    905     "Num Lock",
    906     "Caps Lock",
    907     "Scroll Lock"
    908 };
    909 
    910 static Boolean
    911 xtermGetLED(TScreen *screen, Cardinal led_number)
    912 {
    913     Display *dpy = screen->display;
    914     Boolean result = False;
    915 
    916 #ifdef HAVE_XKBQUERYEXTENSION
    917     if (!getXkbLED(dpy, led_table[led_number - 1], &result))
    918 #endif
    919     {
    920 	XKeyboardState state;
    921 	unsigned long my_bit = (unsigned long) (1 << (led_number - 1));
    922 
    923 	XGetKeyboardControl(dpy, &state);
    924 
    925 	result = (Boolean) ((state.led_mask & my_bit) != 0);
    926     }
    927 
    928     TRACE(("xtermGetLED %d:%s\n", led_number, BtoS(result)));
    929     return result;
    930 }
    931 
    932 /*
    933  * Display the given LED, preferably independent of keyboard state.
    934  */
    935 void
    936 xtermShowLED(TScreen *screen, Cardinal led_number, Bool enable)
    937 {
    938     TRACE(("xtermShowLED %d:%s\n", led_number, BtoS(enable)));
    939     if ((led_number >= 1) && (led_number <= XtNumber(led_table))) {
    940 	Display *dpy = screen->display;
    941 
    942 #ifdef HAVE_XKBQUERYEXTENSION
    943 	if (!showXkbLED(dpy, led_table[led_number - 1], enable))
    944 #endif
    945 	{
    946 	    XKeyboardState state;
    947 	    XKeyboardControl values;
    948 	    unsigned long use_mask;
    949 	    unsigned long my_bit = (unsigned long) (1 << (led_number - 1));
    950 
    951 	    XGetKeyboardControl(dpy, &state);
    952 	    use_mask = state.led_mask;
    953 	    if (enable) {
    954 		use_mask |= my_bit;
    955 	    } else {
    956 		use_mask &= ~my_bit;
    957 	    }
    958 
    959 	    if (state.led_mask != use_mask) {
    960 		values.led = (int) led_number;
    961 		values.led_mode = enable;
    962 		XChangeKeyboardControl(dpy, KBLed | KBLedMode, &values);
    963 	    }
    964 	}
    965     }
    966 }
    967 
    968 void
    969 xtermClearLEDs(TScreen *screen)
    970 {
    971     Display *dpy = screen->display;
    972     XKeyboardControl values;
    973 
    974     TRACE(("xtermClearLEDs\n"));
    975 #ifdef HAVE_XKBQUERYEXTENSION
    976     ShowScrollLock(screen, False);
    977 #endif
    978     memset(&values, 0, sizeof(values));
    979     XChangeKeyboardControl(dpy, KBLedMode, &values);
    980 }
    981 
    982 void
    983 ShowScrollLock(TScreen *screen, Bool enable)
    984 {
    985     xtermShowLED(screen, SCROLL_LOCK_LED, enable);
    986 }
    987 
    988 void
    989 GetScrollLock(TScreen *screen)
    990 {
    991     if (screen->allowScrollLock)
    992 	screen->scroll_lock = xtermGetLED(screen, SCROLL_LOCK_LED);
    993 }
    994 
    995 void
    996 SetScrollLock(TScreen *screen, Bool enable)
    997 {
    998     if (screen->allowScrollLock) {
    999 	if (screen->scroll_lock != enable) {
   1000 	    TRACE(("SetScrollLock %s\n", BtoS(enable)));
   1001 	    screen->scroll_lock = (Boolean) enable;
   1002 	    ShowScrollLock(screen, enable);
   1003 	}
   1004     }
   1005 }
   1006 
   1007 /* ARGSUSED */
   1008 void
   1009 HandleScrollLock(Widget w,
   1010 		 XEvent *event GCC_UNUSED,
   1011 		 String *params,
   1012 		 Cardinal *param_count)
   1013 {
   1014     XtermWidget xw;
   1015 
   1016     if ((xw = getXtermWidget(w)) != NULL) {
   1017 	TScreen *screen = TScreenOf(xw);
   1018 
   1019 	if (screen->allowScrollLock) {
   1020 
   1021 	    switch (decodeToggle(xw, params, *param_count)) {
   1022 	    case toggleOff:
   1023 		SetScrollLock(screen, False);
   1024 		break;
   1025 	    case toggleOn:
   1026 		SetScrollLock(screen, True);
   1027 		break;
   1028 	    case toggleAll:
   1029 		SetScrollLock(screen, !screen->scroll_lock);
   1030 		break;
   1031 	    }
   1032 	}
   1033     }
   1034 }
   1035 #endif
   1036