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
94static void ScrollTextTo PROTO_XT_CALLBACK_ARGS;
95static 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 */
100void
101DoResizeScreen(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
244static Widget
245CreateScrollBar(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
264void
265ScrollBarReverseVideo(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
310void
311ScrollBarDrawThumb(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
343void
344ResizeScrollBar(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
375void
376WindowScroll(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 */
445void
446updateRightScrollbar(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
458void
459ScrollBarOn(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
514void
515ScrollBarOff(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 */
540void
541ToggleScrollBar(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*/
560static void
561ScrollTextTo(
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*/
586static void
587ScrollTextUpDownBy(
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 */
615static int
616CompareWidths(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
657static long
658params_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
692static long
693AmountToScroll(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
708static void
709AlternateScroll(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*/
739void
740HandleScrollTo(
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*/
769void
770HandleScrollForward(
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*/
784void
785HandleScrollBack(
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 */
805static int
806have_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
857static Boolean
858getXkbLED(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 */
879static Boolean
880showXkbLED(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 */
903static const char *led_table[] =
904{
905    "Num Lock",
906    "Caps Lock",
907    "Scroll Lock"
908};
909
910static Boolean
911xtermGetLED(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 */
935void
936xtermShowLED(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
968void
969xtermClearLEDs(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
982void
983ShowScrollLock(TScreen *screen, Bool enable)
984{
985    xtermShowLED(screen, SCROLL_LOCK_LED, enable);
986}
987
988void
989GetScrollLock(TScreen *screen)
990{
991    if (screen->allowScrollLock)
992	screen->scroll_lock = xtermGetLED(screen, SCROLL_LOCK_LED);
993}
994
995void
996SetScrollLock(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 */
1008void
1009HandleScrollLock(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