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