diff options
Diffstat (limited to 'scrollbar.c')
-rw-r--r-- | scrollbar.c | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/scrollbar.c b/scrollbar.c new file mode 100644 index 0000000..12e8a4a --- /dev/null +++ b/scrollbar.c @@ -0,0 +1,955 @@ +/* $XTermId: scrollbar.c,v 1.182 2011/09/03 12:13:42 tom Exp $ */ + +/* + * Copyright 2000-2010,2011 by Thomas E. Dickey + * + * All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name(s) of the above copyright + * holders shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization. + * + * + * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + * + * All Rights Reserved + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation, and that the name of Digital Equipment + * Corporation not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior permission. + * + * + * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL + * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#include <xterm.h> + +#include <X11/Xatom.h> + +#if defined(HAVE_LIB_XAW) +#include <X11/Xaw/Scrollbar.h> +#elif defined(HAVE_LIB_XAW3D) +#include <X11/Xaw3d/Scrollbar.h> +#elif defined(HAVE_LIB_NEXTAW) +#include <X11/neXtaw/Scrollbar.h> +#elif defined(HAVE_LIB_XAWPLUS) +#include <X11/XawPlus/Scrollbar.h> +#endif + +#if defined(HAVE_XKBQUERYEXTENSION) && defined(HAVE_X11_XKBLIB_H) && defined(HAVE_X11_EXTENSIONS_XKB_H) +#include <X11/extensions/XKB.h> +#include <X11/XKBlib.h> +#else +#undef HAVE_XKBQUERYEXTENSION +#endif + +#include <data.h> +#include <error.h> +#include <menu.h> +#include <xcharmouse.h> +#include <xstrings.h> + +/* + * The scrollbar's border overlaps the border of the vt100 window. If there + * is no border for the vt100, there can be no border for the scrollbar. + */ +#define SCROLLBAR_BORDER(xw) (TScreenOf(xw)->scrollBarBorder) +#if OPT_TOOLBAR +#define ScrollBarBorder(xw) (BorderWidth(xw) ? SCROLLBAR_BORDER(xw) : 0) +#else +#define ScrollBarBorder(xw) SCROLLBAR_BORDER(xw) +#endif + +/* Event handlers */ + +static void ScrollTextTo PROTO_XT_CALLBACK_ARGS; +static void ScrollTextUpDownBy PROTO_XT_CALLBACK_ARGS; + +/* Resize the text window for a terminal screen, modifying the + * appropriate WM_SIZE_HINTS and taking advantage of bit gravity. + */ +void +DoResizeScreen(XtermWidget xw) +{ + TScreen *screen = TScreenOf(xw); + + int border = 2 * screen->border; + int min_wide = border + screen->fullVwin.sb_info.width; + int min_high = border; + XtGeometryResult geomreqresult; + Dimension reqWidth, reqHeight, repWidth, repHeight; +#ifndef NO_ACTIVE_ICON + VTwin *saveWin = WhichVWin(screen); + + /* all units here want to be in the normal font units */ + WhichVWin(screen) = &screen->fullVwin; +#endif /* NO_ACTIVE_ICON */ + + /* + * I'm going to try to explain, as I understand it, why we + * have to do XGetWMNormalHints and XSetWMNormalHints here, + * although I can't guarantee that I've got it right. + * + * In a correctly written toolkit program, the Shell widget + * parses the user supplied geometry argument. However, + * because of the way xterm does things, the VT100 widget does + * the parsing of the geometry option, not the Shell widget. + * The result of this is that the Shell widget doesn't set the + * correct window manager hints, and doesn't know that the + * user has specified a geometry. + * + * The XtVaSetValues call below tells the Shell widget to + * change its hints. However, since it's confused about the + * hints to begin with, it doesn't get them all right when it + * does the SetValues -- it undoes some of what the VT100 + * widget did when it originally set the hints. + * + * To fix this, we do the following: + * + * 1. Get the sizehints directly from the window, going around + * the (confused) shell widget. + * 2. Call XtVaSetValues to let the shell widget know which + * hints have changed. Note that this may not even be + * necessary, since we're going to right ahead after that + * and set the hints ourselves, but it's good to put it + * here anyway, so that when we finally do fix the code so + * that the Shell does the right thing with hints, we + * already have the XtVaSetValues in place. + * 3. We set the sizehints directly, this fixing up whatever + * damage was done by the Shell widget during the + * XtVaSetValues. + * + * Gross, huh? + * + * The correct fix is to redo VTRealize, VTInitialize and + * VTSetValues so that font processing happens early enough to + * give back responsibility for the size hints to the Shell. + * + * Someday, we hope to have time to do this. Someday, we hope + * to have time to completely rewrite xterm. + */ + + TRACE(("DoResizeScreen\n")); + +#if 1 /* ndef nothack */ + /* + * NOTE: the hints and the XtVaSetValues() must match. + */ + TRACE(("%s@%d -- ", __FILE__, __LINE__)); + TRACE_WM_HINTS(xw); + getXtermSizeHints(xw); + + xtermSizeHints(xw, ScrollbarWidth(screen)); + + /* These are obsolete, but old clients may use them */ + xw->hints.width = MaxCols(screen) * FontWidth(screen) + xw->hints.min_width; + xw->hints.height = MaxRows(screen) * FontHeight(screen) + xw->hints.min_height; +#if OPT_MAXIMIZE + /* assure single-increment resize for fullscreen */ + if (screen->fullscreen) { + xw->hints.width_inc = 1; + xw->hints.height_inc = 1; + } +#endif /* OPT_MAXIMIZE */ +#endif + + XSetWMNormalHints(screen->display, VShellWindow(xw), &xw->hints); + + reqWidth = (Dimension) (MaxCols(screen) * FontWidth(screen) + min_wide); + reqHeight = (Dimension) (MaxRows(screen) * FontHeight(screen) + min_high); + +#if OPT_MAXIMIZE + /* compensate for fullscreen mode */ + if (screen->fullscreen) { + Screen *xscreen = DefaultScreenOfDisplay(xw->screen.display); + reqWidth = (Dimension) WidthOfScreen(xscreen); + reqHeight = (Dimension) HeightOfScreen(xscreen); + ScreenResize(xw, reqWidth, reqHeight, &xw->flags); + } +#endif /* OPT_MAXIMIZE */ + + TRACE(("...requesting screensize chars %dx%d, pixels %dx%d\n", + MaxRows(screen), + MaxCols(screen), + reqHeight, reqWidth)); + + geomreqresult = REQ_RESIZE((Widget) xw, reqWidth, reqHeight, + &repWidth, &repHeight); + + if (geomreqresult == XtGeometryAlmost) { + TRACE(("...almost, retry screensize %dx%d\n", repHeight, repWidth)); + geomreqresult = REQ_RESIZE((Widget) xw, repWidth, + repHeight, NULL, NULL); + } + + if (geomreqresult != XtGeometryYes) { + /* The resize wasn't successful, so we might need to adjust + our idea of how large the screen is. */ + TRACE(("...still no (%d) - resize the core-class\n", geomreqresult)); + xw->core.widget_class->core_class.resize((Widget) xw); + } +#if 1 /* ndef nothack */ + /* + * XtMakeResizeRequest() has the undesirable side-effect of clearing + * the window manager's hints, even on a failed request. This would + * presumably be fixed if the shell did its own work. + */ + if (xw->hints.flags + && repHeight + && repWidth) { + xw->hints.height = repHeight; + xw->hints.width = repWidth; + TRACE_HINTS(&xw->hints); + XSetWMNormalHints(screen->display, VShellWindow(xw), &xw->hints); + } +#endif + XSync(screen->display, False); /* synchronize */ + if (xtermAppPending()) + xevents(); + +#ifndef NO_ACTIVE_ICON + WhichVWin(screen) = saveWin; +#endif /* NO_ACTIVE_ICON */ +} + +static Widget +CreateScrollBar(XtermWidget xw, int x, int y, int height) +{ + Widget result; + Arg args[6]; + + XtSetArg(args[0], XtNx, x); + XtSetArg(args[1], XtNy, y); + XtSetArg(args[2], XtNheight, height); + XtSetArg(args[3], XtNreverseVideo, xw->misc.re_verse); + XtSetArg(args[4], XtNorientation, XtorientVertical); + XtSetArg(args[5], XtNborderWidth, ScrollBarBorder(xw)); + + result = XtCreateWidget("scrollbar", scrollbarWidgetClass, + (Widget) xw, args, XtNumber(args)); + XtAddCallback(result, XtNscrollProc, ScrollTextUpDownBy, 0); + XtAddCallback(result, XtNjumpProc, ScrollTextTo, 0); + return (result); +} + +void +ScrollBarReverseVideo(Widget scrollWidget) +{ + XtermWidget xw = getXtermWidget(scrollWidget); + + if (xw != 0) { + SbInfo *sb = &(TScreenOf(xw)->fullVwin.sb_info); + Arg args[4]; + Cardinal nargs = XtNumber(args); + + /* + * Remember the scrollbar's original colors. + */ + if (sb->rv_cached == False) { + XtSetArg(args[0], XtNbackground, &(sb->bg)); + XtSetArg(args[1], XtNforeground, &(sb->fg)); + XtSetArg(args[2], XtNborderColor, &(sb->bdr)); + XtSetArg(args[3], XtNborderPixmap, &(sb->bdpix)); + XtGetValues(scrollWidget, args, nargs); + sb->rv_cached = True; + sb->rv_active = 0; + } + + sb->rv_active = !(sb->rv_active); + if (sb->rv_active) { + XtSetArg(args[0], XtNbackground, sb->fg); + XtSetArg(args[1], XtNforeground, sb->bg); + } else { + XtSetArg(args[0], XtNbackground, sb->bg); + XtSetArg(args[1], XtNforeground, sb->fg); + } + nargs = 2; /* don't set border_pixmap */ + if (sb->bdpix == XtUnspecifiedPixmap) { + /* if not pixmap then pixel */ + if (sb->rv_active) { + /* keep border visible */ + XtSetArg(args[2], XtNborderColor, args[1].value); + } else { + XtSetArg(args[2], XtNborderColor, sb->bdr); + } + nargs = 3; + } + XtSetValues(scrollWidget, args, nargs); + } +} + +void +ScrollBarDrawThumb(Widget scrollWidget) +{ + XtermWidget xw = getXtermWidget(scrollWidget); + + if (xw != 0) { + TScreen *screen = TScreenOf(xw); + int thumbTop, thumbHeight, totalHeight; + + thumbTop = ROW2INX(screen, screen->savedlines); + thumbHeight = MaxRows(screen); + totalHeight = thumbHeight + screen->savedlines; + + XawScrollbarSetThumb(scrollWidget, + ((float) thumbTop) / (float) totalHeight, + ((float) thumbHeight) / (float) totalHeight); + } +} + +void +ResizeScrollBar(XtermWidget xw) +{ + TScreen *screen = TScreenOf(xw); + + if (screen->scrollWidget != 0) { + int height = screen->fullVwin.height + screen->border * 2; + int width = screen->scrollWidget->core.width; + int ypos = -ScrollBarBorder(xw); +#ifdef SCROLLBAR_RIGHT + int xpos = ((xw->misc.useRight) + ? (screen->fullVwin.fullwidth - + screen->scrollWidget->core.width - + BorderWidth(screen->scrollWidget)) + : -ScrollBarBorder(xw)); +#else + int xpos = -ScrollBarBorder(xw); +#endif + + TRACE(("ResizeScrollBar at %d,%d %dx%d\n", ypos, xpos, height, width)); + + XtConfigureWidget( + screen->scrollWidget, + (Position) xpos, + (Position) ypos, + (Dimension) width, + (Dimension) height, + BorderWidth(screen->scrollWidget)); + ScrollBarDrawThumb(screen->scrollWidget); + } +} + +void +WindowScroll(XtermWidget xw, int top, Bool always GCC_UNUSED) +{ + TScreen *screen = TScreenOf(xw); + int i, lines; + int scrolltop, scrollheight, refreshtop; + +#if OPT_SCROLL_LOCK + if (screen->allowScrollLock && (screen->scroll_lock && !always)) { + if (screen->scroll_dirty) { + screen->scroll_dirty = False; + ScrnRefresh(xw, 0, 0, MaxRows(screen), MaxCols(screen), False); + } + } else +#endif + { + if (top < -screen->savedlines) { + top = -screen->savedlines; + } else if (top > 0) { + top = 0; + } + + if ((i = screen->topline - top) != 0) { + + if (screen->cursor_state) + HideCursor(); + lines = i > 0 ? i : -i; + if (lines > MaxRows(screen)) + lines = MaxRows(screen); + scrollheight = screen->max_row - lines + 1; + if (i > 0) + refreshtop = scrolltop = 0; + else { + scrolltop = lines; + refreshtop = scrollheight; + } + scrolling_copy_area(xw, scrolltop, scrollheight, -i); + screen->topline = top; + + ScrollSelection(screen, i, True); + + XClearArea( + screen->display, + VWindow(screen), + OriginX(screen), + OriginY(screen) + refreshtop * FontHeight(screen), + (unsigned) Width(screen), + (unsigned) (lines * FontHeight(screen)), + False); + ScrnRefresh(xw, refreshtop, 0, lines, MaxCols(screen), False); + +#if OPT_BLINK_CURS || OPT_BLINK_TEXT + RestartBlinking(screen); +#endif + } + } + ScrollBarDrawThumb(screen->scrollWidget); +} + +#ifdef SCROLLBAR_RIGHT +/* + * Adjust the scrollbar position if we're asked to turn on scrollbars for the + * first time (or after resizing) after the xterm is already running. That + * makes the window grow after we've initially configured the scrollbar's + * position. (There must be a better way). + */ +void +updateRightScrollbar(XtermWidget xw) +{ + TScreen *screen = TScreenOf(xw); + + if (xw->misc.useRight + && screen->fullVwin.fullwidth < xw->core.width) + XtVaSetValues(screen->scrollWidget, + XtNx, screen->fullVwin.fullwidth - BorderWidth(screen->scrollWidget), + (XtPointer) 0); +} +#endif + +void +ScrollBarOn(XtermWidget xw, Bool init) +{ + TScreen *screen = TScreenOf(xw); + + if (screen->fullVwin.sb_info.width || IsIcon(screen)) + return; + + TRACE(("ScrollBarOn(init %s)\n", BtoS(init))); + if (init) { /* then create it only */ + if (screen->scrollWidget == 0) { + /* make it a dummy size and resize later */ + screen->scrollWidget = CreateScrollBar(xw, + -ScrollBarBorder(xw), + -ScrollBarBorder(xw), + 5); + if (screen->scrollWidget == NULL) { + Bell(xw, XkbBI_MinorError, 0); + } + } + } else if (!screen->scrollWidget || !XtIsRealized((Widget) xw)) { + Bell(xw, XkbBI_MinorError, 0); + Bell(xw, XkbBI_MinorError, 0); + } else { + + ResizeScrollBar(xw); + xtermAddInput(screen->scrollWidget); + XtRealizeWidget(screen->scrollWidget); + TRACE_TRANS("scrollbar", screen->scrollWidget); + + screen->fullVwin.sb_info.rv_cached = False; + + screen->fullVwin.sb_info.width = (screen->scrollWidget->core.width + + BorderWidth(screen->scrollWidget)); + + TRACE(("setting scrollbar width %d = %d + %d\n", + screen->fullVwin.sb_info.width, + screen->scrollWidget->core.width, + BorderWidth(screen->scrollWidget))); + + ScrollBarDrawThumb(screen->scrollWidget); + DoResizeScreen(xw); + +#ifdef SCROLLBAR_RIGHT + updateRightScrollbar(xw); +#endif + + XtMapWidget(screen->scrollWidget); + update_scrollbar(); + if (screen->visbuf) { + xtermClear(xw); + Redraw(); + } + } +} + +void +ScrollBarOff(XtermWidget xw) +{ + TScreen *screen = TScreenOf(xw); + + if (!screen->fullVwin.sb_info.width || IsIcon(screen)) + return; + + TRACE(("ScrollBarOff\n")); + if (XtIsRealized((Widget) xw)) { + XtUnmapWidget(screen->scrollWidget); + screen->fullVwin.sb_info.width = 0; + DoResizeScreen(xw); + update_scrollbar(); + if (screen->visbuf) { + xtermClear(xw); + Redraw(); + } + } else { + Bell(xw, XkbBI_MinorError, 0); + } +} + +/* + * Toggle the visibility of the scrollbars. + */ +void +ToggleScrollBar(XtermWidget xw) +{ + TScreen *screen = TScreenOf(xw); + + if (IsIcon(screen)) { + Bell(xw, XkbBI_MinorError, 0); + } else { + TRACE(("ToggleScrollBar{{\n")); + if (screen->fullVwin.sb_info.width) { + ScrollBarOff(xw); + } else { + ScrollBarOn(xw, False); + } + update_scrollbar(); + TRACE(("...ToggleScrollBar}}\n")); + } +} + +/*ARGSUSED*/ +static void +ScrollTextTo( + Widget scrollbarWidget, + XtPointer client_data GCC_UNUSED, + XtPointer call_data) +{ + XtermWidget xw = getXtermWidget(scrollbarWidget); + + if (xw != 0) { + float *topPercent = (float *) call_data; + TScreen *screen = TScreenOf(xw); + int thumbTop; /* relative to first saved line */ + int newTopLine; + + /* + * screen->savedlines : Number of offscreen text lines, + * MaxRows(screen) : Number of onscreen text lines, + */ + thumbTop = (int) (*topPercent + * (float) (screen->savedlines + MaxRows(screen))); + newTopLine = thumbTop - screen->savedlines; + WindowScroll(xw, newTopLine, True); + } +} + +/*ARGSUSED*/ +static void +ScrollTextUpDownBy( + Widget scrollbarWidget, + XtPointer client_data GCC_UNUSED, + XtPointer call_data) +{ + XtermWidget xw = getXtermWidget(scrollbarWidget); + + if (xw != 0) { + long pixels = (long) call_data; + + TScreen *screen = TScreenOf(xw); + int rowOnScreen, newTopLine; + + rowOnScreen = (int) (pixels / FontHeight(screen)); + if (rowOnScreen == 0) { + if (pixels < 0) + rowOnScreen = -1; + else if (pixels > 0) + rowOnScreen = 1; + } + newTopLine = ROW2INX(screen, rowOnScreen); + WindowScroll(xw, newTopLine, True); + } +} + +/* + * assume that b is alphabetic and allow plural + */ +static int +CompareWidths(const char *a, const char *b, int *modifier) +{ + int result; + char ca, cb; + + *modifier = 0; + if (!a || !b) + return 0; + + for (;;) { + ca = x_toupper(*a); + cb = x_toupper(*b); + if (ca != cb || ca == '\0') + break; /* if not eq else both nul */ + a++, b++; + } + if (cb != '\0') + return 0; + + if (ca == 'S') + ca = *++a; + + switch (ca) { + case '+': + case '-': + *modifier = (ca == '-' ? -1 : 1) * atoi(a + 1); + result = 1; + break; + + case '\0': + result = 1; + break; + + default: + result = 0; + break; + } + return result; +} + +static long +params_to_pixels(TScreen * screen, String * params, Cardinal n) +{ + int mult = 1; + const char *s; + int modifier; + + switch (n > 2 ? 2 : n) { + case 2: + s = params[1]; + if (CompareWidths(s, "PAGE", &modifier)) { + mult = (MaxRows(screen) + modifier) * FontHeight(screen); + } else if (CompareWidths(s, "HALFPAGE", &modifier)) { + mult = ((MaxRows(screen) + modifier) * FontHeight(screen)) / 2; + } else if (CompareWidths(s, "PIXEL", &modifier)) { + mult = 1; + } else { + /* else assume that it is Line */ + mult = FontHeight(screen); + } + mult *= atoi(params[0]); + break; + case 1: + mult = atoi(params[0]) * FontHeight(screen); /* lines */ + break; + default: + mult = screen->scrolllines * FontHeight(screen); + break; + } + return mult; +} + +static long +AmountToScroll(Widget w, String * params, Cardinal nparams) +{ + long result = 0; + XtermWidget xw; + + if ((xw = getXtermWidget(w)) != 0) { + TScreen *screen = TScreenOf(xw); + if (nparams <= 2 + || screen->send_mouse_pos == MOUSE_OFF) { + result = params_to_pixels(screen, params, nparams); + } + } + return result; +} + +/*ARGSUSED*/ +void +HandleScrollForward( + Widget xw, + XEvent * event GCC_UNUSED, + String * params, + Cardinal *nparams) +{ + long amount; + + if ((amount = AmountToScroll(xw, params, *nparams)) != 0) { + ScrollTextUpDownBy(xw, (XtPointer) 0, (XtPointer) amount); + } +} + +/*ARGSUSED*/ +void +HandleScrollBack( + Widget xw, + XEvent * event GCC_UNUSED, + String * params, + Cardinal *nparams) +{ + long amount; + + if ((amount = -AmountToScroll(xw, params, *nparams)) != 0) { + ScrollTextUpDownBy(xw, (XtPointer) 0, (XtPointer) amount); + } +} + +#if OPT_SCROLL_LOCK +#define SCROLL_LOCK_LED 3 + +#ifdef HAVE_XKBQUERYEXTENSION +/* + * Check for Xkb on client and server. + */ +static int +have_xkb(Display * dpy) +{ + static int initialized = -1; + + if (initialized < 0) { + int xkbmajor = XkbMajorVersion; + int xkbminor = XkbMinorVersion; + int xkbopcode, xkbevent, xkberror; + + initialized = 0; + if (XkbLibraryVersion(&xkbmajor, &xkbminor) + && XkbQueryExtension(dpy, + &xkbopcode, + &xkbevent, + &xkberror, + &xkbmajor, + &xkbminor)) { + TRACE(("we have Xkb\n")); + initialized = 1; +#if OPT_TRACE + { + XkbDescPtr xkb; + unsigned int mask; + int n; + char *modStr; + + xkb = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd); + if (xkb != NULL) { + + TRACE(("XkbGetKeyboard ok\n")); + for (n = 0; n < XkbNumVirtualMods; ++n) { + if (xkb->names->vmods[n] != 0) { + modStr = XGetAtomName(xkb->dpy, + xkb->names->vmods[n]); + if (modStr != 0) { + XkbVirtualModsToReal(xkb, + (unsigned) (1 << n), + &mask); + TRACE((" name[%d] %s (%#x)\n", n, modStr, mask)); + } + } + } + XkbFreeKeyboard(xkb, 0, True); + } + } +#endif + } + } + return initialized; +} + +static Boolean +getXkbLED(Display * dpy, const char *name, Boolean * result) +{ + Atom my_atom; + Boolean success = False; + Bool state; + + if (have_xkb(dpy)) { + my_atom = XInternAtom(dpy, name, False); + if ((my_atom != None) && + XkbGetNamedIndicator(dpy, my_atom, NULL, &state, NULL, NULL)) { + *result = (Boolean) state; + success = True; + } + } + + return success; +} + +/* + * Use Xkb if we have it (still unreliable, but slightly better than hardcoded). + */ +static Boolean +showXkbLED(Display * dpy, const char *name, Bool enable) +{ + Atom my_atom; + Boolean result = False; + + if (have_xkb(dpy)) { + my_atom = XInternAtom(dpy, name, False); + if ((my_atom != None) && + XkbGetNamedIndicator(dpy, my_atom, NULL, NULL, NULL, NULL) && + XkbSetNamedIndicator(dpy, my_atom, True, enable, False, NULL)) { + result = True; + } + } + + return result; +} +#endif + +/* + * xlsatoms agrees with this list. However Num/Caps lock are generally + * unusable due to special treatment in X. They are used here for + * completeness. + */ +static const char *led_table[] = +{ + "Num Lock", + "Caps Lock", + "Scroll Lock" +}; + +static Boolean +xtermGetLED(TScreen * screen, Cardinal led_number) +{ + Display *dpy = screen->display; + Boolean result = False; + +#ifdef HAVE_XKBQUERYEXTENSION + if (!getXkbLED(dpy, led_table[led_number - 1], &result)) +#endif + { + XKeyboardState state; + unsigned long my_bit = (unsigned long) (1 << (led_number - 1)); + + XGetKeyboardControl(dpy, &state); + + result = (Boolean) ((state.led_mask & my_bit) != 0); + } + + TRACE(("xtermGetLED %d:%s\n", led_number, BtoS(result))); + return result; +} + +/* + * Display the given LED, preferably independent of keyboard state. + */ +void +xtermShowLED(TScreen * screen, Cardinal led_number, Bool enable) +{ + TRACE(("xtermShowLED %d:%s\n", led_number, BtoS(enable))); + if ((led_number >= 1) && (led_number <= XtNumber(led_table))) { + Display *dpy = screen->display; + +#ifdef HAVE_XKBQUERYEXTENSION + if (!showXkbLED(dpy, led_table[led_number - 1], enable)) +#endif + { + XKeyboardState state; + XKeyboardControl values; + unsigned long use_mask; + unsigned long my_bit = (unsigned long) (1 << (led_number - 1)); + + XGetKeyboardControl(dpy, &state); + use_mask = state.led_mask; + if (enable) { + use_mask |= my_bit; + } else { + use_mask &= ~my_bit; + } + + if (state.led_mask != use_mask) { + values.led = (int) led_number; + values.led_mode = enable; + XChangeKeyboardControl(dpy, KBLed | KBLedMode, &values); + } + } + } +} + +void +xtermClearLEDs(TScreen * screen) +{ + Display *dpy = screen->display; + XKeyboardControl values; + + TRACE(("xtermClearLEDs\n")); +#ifdef HAVE_XKBQUERYEXTENSION + ShowScrollLock(screen, False); +#endif + memset(&values, 0, sizeof(values)); + XChangeKeyboardControl(dpy, KBLedMode, &values); +} + +void +ShowScrollLock(TScreen * screen, Bool enable) +{ + xtermShowLED(screen, SCROLL_LOCK_LED, enable); +} + +void +GetScrollLock(TScreen * screen) +{ + if (screen->allowScrollLock) + screen->scroll_lock = xtermGetLED(screen, SCROLL_LOCK_LED); +} + +void +SetScrollLock(TScreen * screen, Bool enable) +{ + if (screen->allowScrollLock) { + if (screen->scroll_lock != enable) { + screen->scroll_lock = (Boolean) enable; + ShowScrollLock(screen, enable); + } + } +} + +/* ARGSUSED */ +void +HandleScrollLock(Widget w, + XEvent * event GCC_UNUSED, + String * params, + Cardinal *param_count) +{ + XtermWidget xw; + + if ((xw = getXtermWidget(w)) != 0) { + TScreen *screen = TScreenOf(xw); + + if (screen->allowScrollLock) { + /* + * The default action (used with KeyRelease event) is to cycle the + * state on/off. + */ + if (*param_count == 0) { + SetScrollLock(screen, !screen->scroll_lock); + TRACE(("HandleScrollLock ->%d\n", + screen->scroll_lock)); + } else { + SetScrollLock(screen, atoi(params[0])); + TRACE(("HandleScrollLock(%s) ->%d\n", + params[0], + screen->scroll_lock)); + } + } + } +} +#endif |