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