GNU bug report logs - #80993
[PATCH] Add support for tty scroll-bars

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: emacs; Reported by: Michael Grant <mgrant@HIDDEN>; Keywords: patch; dated Fri, 8 May 2026 15:02:01 UTC; Maintainer for emacs is bug-gnu-emacs@HIDDEN.

Message received at 80993 <at> debbugs.gnu.org:


Received: (at 80993) by debbugs.gnu.org; 6 Jun 2026 09:33:39 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Jun 06 05:33:39 2026
Received: from localhost ([127.0.0.1]:36123 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1wVnPO-0008KF-MB
	for submit <at> debbugs.gnu.org; Sat, 06 Jun 2026 05:33:39 -0400
Received: from eggs.gnu.org ([2001:470:142:3::10]:33924)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1wVnPM-0008Jr-4r
 for 80993 <at> debbugs.gnu.org; Sat, 06 Jun 2026 05:33:37 -0400
Received: from fencepost.gnu.org ([2001:470:142:3::e])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <eliz@HIDDEN>)
 id 1wVnPE-0006Q4-Sg; Sat, 06 Jun 2026 05:33:28 -0400
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org;
 s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From:
 Date; bh=CrXiC+0VQ4sfcDpp+Rquk4jkvEQ3CRYtc4H+F3fEhPg=; b=iSbosotT0kI7XcTuFAgP
 I6OSKjqjhGLNtQlbo6wLUxfPbXYoGFh/SHQj8xHZi3RLUmjyU3UhQdHwCOpZJERiQs3oXgELBl5dr
 2HqlpPksUAs2k6CeBa8I146LTuPOFuG1jJL+F2LcVu49jb0LxzRwz6+Pg+522YiNzx9XgH2rVyp/5
 ICZDR9nVzFRDMUiG5Rfh2GfC4shCgM2YHNhly1RDqTdTcZyzjHZRJqcfcjxbWUF297R4xVl8wKvkg
 H9rGs4CoerMCDAK7LrbphoHlC9UK94S/gwJujKI9mcfcO6Z+pk3Tt0nLjIi8Ujz/SAAs0ZSsgDwKw
 v1RBmahIwFTAmw==;
Date: Sat, 06 Jun 2026 12:33:25 +0300
Message-Id: <86ldcsko7u.fsf@HIDDEN>
From: Eli Zaretskii <eliz@HIDDEN>
To: mgrant@HIDDEN, Jared Finder <jared@HIDDEN>
In-Reply-To: <fe8b6d3d49ec4eb84acd8b17f164d199@HIDDEN> (message from Jared
 Finder on Sun, 17 May 2026 18:56:29 -0700)
Subject: Re: bug#80993: [PATCH] Add support for tty scroll-bars
References: <af36i-emhv7cCESf@HIDDEN> <86cxyv660l.fsf@HIDDEN>
 <fe8b6d3d49ec4eb84acd8b17f164d199@HIDDEN>
MIME-version: 1.0
Content-type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -2.3 (--)
X-Debbugs-Envelope-To: 80993
Cc: 80993 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -3.3 (---)

Ping!  Micheal, can you please chime in and respond to the comments, so
we could make progress in preparing this feature for installation?

> Date: Sun, 17 May 2026 18:56:29 -0700
> From: Jared Finder <jared@HIDDEN>
> Cc: Michael Grant <mgrant@HIDDEN>, 80993 <at> debbugs.gnu.org
> 
> On 2026-05-16 02:46, Eli Zaretskii wrote:
> >> Date: Fri, 8 May 2026 11:00:27 -0400
> >> From:  Michael Grant via "Bug reports for GNU Emacs,
> >>  the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN>
> >> 
> >> This is a feature patch which implements tty based scroll-bars which
> >> mimick the X-windows toolkit scrollbars.  It reserves a column on the
> >> left or right (configurable) for the scroll-bar.  The scroll-bar thumb
> >> is composed of inverse video white spaces and the scroll-bar gutter 
> >> dark
> >> spaces.  The scroll-bar thumb is draggable and clicking above or below
> >> the scroll-bar scrolls the buffer.
> > 
> > Thanks, a few comments below.
> 
> Some additional comments.
> 
> +(defface scroll-bar-thumb
> +  '((((type tty)) :inverse-video t)
> +    (t nil))
> +  "Basic face for the scroll bar thumb (draggable portion) in TTY 
> frames.
> +On character-based terminals, the thumb is rendered as inverse-video 
> spaces."
> +  :version "30.1"
> +  :group 'frames
> +  :group 'basic-faces)
> 
> I think it would be better to have the face definition have the same 
> meaning as under X, where the scroll-bar face's foreground color is the 
> color of the thumb and the background is the color of the track.  This 
> additional face is unnecessary.
> 
> >> --- a/lisp/mouse.el
> >> +++ b/lisp/mouse.el
> >> @@ -865,7 +865,16 @@ mouse-position-for-drag-line
> >>  relative to the selected frame, unless the current drag operation was
> >>  produced from a touch screen event, in which event, return the 
> >> position
> >>  of the active touch-screen tool relative to the same."
> >> -  (if tty (mouse-position-in-root-frame)
> >> +  (if tty
> >> +      ;; mouse-position-in-root-frame reads from the GPM mouse hook,
> >> +      ;; which is not updated by xterm mouse events.  When xterm 
> >> mouse
> >> +      ;; mode is active the coordinates are stored as terminal 
> >> parameters
> >> +      ;; by xterm-mouse-event; use those when available.
> >> +      (let ((xt-x (terminal-parameter nil 'xterm-mouse-x))
> >> +            (xt-y (terminal-parameter nil 'xterm-mouse-y)))
> >> +        (if (and xt-x xt-y)
> >> +            (cons xt-x xt-y)
> >> +          (mouse-position-in-root-frame)))
> > 
> > Jared, any comments?  Would it be cleaner to update xtmouse such that
> > mouse_position_hook works for it?  Or some other way of supporting
> > mouse-position-in-root-frame on xtmouse?
> 
> I don't think this change is needed. Local experimentation shows that 
> mouse-position-in-root-frame properly reports mouse coordinates when 
> xterm-mouse-mode is enabled. This is because xterm-mouse-mode sets 
> mouse-position-function to xterm-mouse-position-function which does 
> exactly this transformation.
> 
> What am I missing here?
> 
> +  (let* ((start-pos    (event-start event))
> +         (window       (nth 0 start-pos))
> +         (click-sb-row (car (nth 2 start-pos)))
> 
> Here, and in other places throughout this patch nth is used instead of 
> the posn utility functions like posn-window, posn-x-y, etc.  I believe 
> the posn utility functions are preferred stylistically.
> 
> +(defun xterm-mouse--tty-scroll-bar-window (x y frame)
> +  "Return the window whose TTY scroll bar column is at frame column X, 
> row Y.
> +Returns nil if no TTY scroll bar occupies position (X, Y)."
> +  (let ((sb-side (frame-parameter frame 'vertical-scroll-bars))
> +        result)
> +    (when sb-side
> +      (walk-windows
> ... and a bunch of code ...
> 
> Can this just use window-at instead to get the specific window? And it 
> would be nice if the function coordinates_in_window properly detected 
> the scroll bar, in which case you could add a parameter to 
> coordinates-in-window-p that allows it to return scroll bar areas.  For 
> backward compat it currently returns nil when passed scrollbar 
> coordinates.
> 
> +(defun xterm-mouse--tty-vertical-border-window (x y frame)
> +  "Return the window whose TTY `|' border column is at frame column X, 
> row Y.
> +Returns nil unless (X, Y) is on the `|' border of a non-rightmost TTY
> +window with a right scroll bar.  In that layout, [content][SB][|], the
> +`|' occupies the last column of the window's total-width allocation."
> ... and a bunch of code ...
> 
> Could this be replaced with:
> 
> (eq (coordinates-in-window-p (cons x y) (window-at x y frame)
>      'vertical-line)
> 
> +(defun xterm-mouse--tty-scroll-bar-part (window y)
> +  "Return the scroll bar part clicked at terminal row Y for WINDOW.
> +Returns one of the symbols `above-handle', `handle', or 
> `below-handle'."
> 
> This appears to be a copy of code added in 
> tty_apply_scroll_bar_glyphs_for_window.  I would prefer that be 
> extracted out to a utility function that xt-mouse.el can call instead so 
> we do not need to duplicate code in both xt-mouse.el and in C.
> 
> @@ -341,6 +431,59 @@ xterm-mouse-event
>   				  frame)
>   				item)
>   			  (nthcdr 2 (posn-at-x-y x y (selected-frame)))))))
> +             ;; Check for a click in a TTY vertical scroll bar column.
> +             ;; When detected, replace the position with a scroll bar
> +             ;; position so that [vertical-scroll-bar mouse-N] bindings 
> fire.
> +             (sb-window (and (not (display-graphic-p))
> +                             frame
> +                             (not (eq type 'mouse-movement))
> +                             (xterm-mouse--tty-scroll-bar-window x y 
> frame)))
> 
> I think the changes above could dramatically simplify this case.
> 
> +             (posn (if sb-window
> 
> posn was just bound above. It would be better to put this code into the 
> logic for constructing the posn.  There's already significant logic 
> looking at the x/y coordinate an calculating the window part from there.
> 
> +                       (let* ((win-ht  (window-body-height sb-window))
> +                              (win-top (window-top-line sb-window))
> +                              (sb-row  (max 0 (min (1- win-ht) (- y 
> win-top))))
> +                              (part    
> (xterm-mouse--tty-scroll-bar-part
> +                                        sb-window y))
> +                              ;; For button-up events: if the drag 
> started on
> +                              ;; the scroll-bar handle, keep 
> part='handle' so
> +                              ;; scroll-bar-drag-1 proportionally 
> scrolls to
> +                              ;; the release position rather than 
> paging.
> +                              (down-ev  (terminal-parameter
> +                                         nil 'xterm-mouse-last-down))
> +                              (down-part (and down-ev
> +                                             (nth 4 (nth 1 down-ev))))
> +                              (part     (if (and (not (string-prefix-p
> +                                                       "down-"
> +                                                       (symbol-name 
> type)))
> +                                                 (eq down-part 
> 'handle))
> +                                            'handle
> 
> I do not think it is a good idea to make tty mouse return different 
> events than the graphical mouse.  I would instead suggest you figure out 
> what part of the scroll bar is clicked inside scroll-bar-toolkit-scroll.
> 
> +                                          part)))
> +                         ;; Build a scroll-bar position in the same 
> format as
> +                         ;; make_scroll_bar_position in keyboard.c:
> +                         ;;   (window AREA (pos . size) timestamp part)
> +                         ;; AREA must be the bare symbol 
> `vertical-scroll-bar'
> +                         ;; (not a cons) so that read_key_sequence's 
> SYMBOLP
> +                         ;; check expands the event to 
> [vertical-scroll-bar
> +                         ;; mouse-1], which fires 
> `scroll-bar-toolkit-scroll'.
> +                         (list sb-window
> +                               'vertical-scroll-bar
> +                               (cons sb-row win-ht)
> +                               timestamp
> +                               part))
> +                     posn))
> +             ;; Check for a click on the TTY '|' window border.
> +             ;; For a right scroll bar on a non-rightmost TTY window 
> the '|'
> +             ;; glyph occupies the last column of the window 
> allocation; a
> +             ;; click there should generate a vertical-line position so 
> that
> +             ;; [vertical-line down-mouse-1] → mouse-drag-vertical-line 
> fires.
> +             (border-window (and (not sb-window)
> +                                 frame
> +                                 (not (eq type 'mouse-movement))
> +                                 
> (xterm-mouse--tty-vertical-border-window
> +                                  x y frame)))
> +             (posn (if border-window
> +                       (list border-window 'vertical-line (cons x y) 
> timestamp)
> +                     posn))
>                (event (list type posn)))
>           (setcar (nthcdr 3 posn) timestamp)
> 
> Overall, I suspect the above code can be made much simpler if it used 
> the above suggestions, especially moving to the existing functions that 
> already determine what part of a window a coordinate is in.
> 
>    -- MJF
> 




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#80993; Package emacs. Full text available.

Message received at 80993 <at> debbugs.gnu.org:


Received: (at 80993) by debbugs.gnu.org; 18 May 2026 01:56:42 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun May 17 21:56:41 2026
Received: from localhost ([127.0.0.1]:51076 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1wOnDh-0001Hw-Id
	for submit <at> debbugs.gnu.org; Sun, 17 May 2026 21:56:41 -0400
Received: from greenhill.hpalace.com ([192.155.80.58]:53064)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <jared@HIDDEN>) id 1wOnDa-0001Gq-O2
 for 80993 <at> debbugs.gnu.org; Sun, 17 May 2026 21:56:34 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=finder.org; s=2018;
 t=1779069389; bh=DJUMTHz8e0RPwBSpTUn+bsfyHgzz/WVK1nHlBgHUgig=;
 h=Date:From:To:Cc:Subject:In-Reply-To:References:From;
 b=T7gzfT85MHSBqMBpnIyJjoeWdJUXV2LP8GwEKXbGFawa1bSq6pGrXgv8s9i8ShkGl
 MkyLvo1Z02K/trX1TapffTcM5GhVmJwgljaCwYBFv/DPSJKQedloHgNVhHp3hdo3Jr
 4rl56/RYaxle9VIPFT0De5syKuDfmTUHb/v7kMbqnY5P40bVZowbCJGYCb1gpRKsUn
 BzHHsKr3roabu60pmy8ImnUmLWnbwbwrPtlahdBB6s0kW+EWoyEh6Q7Z62IuvypFFl
 uAwEiaS151qEl9VphdaZa+Uuoislgr2LWpKHXTwygsJwbj9WJvW8DCTmP2f3DxU773
 YJjH/yHvz7xUw==
Received: from mail.finder.org (unknown [192.155.80.58])
 by greenhill.hpalace.com (Postfix) with ESMTPSA id 5321470A;
 Mon, 18 May 2026 01:56:29 +0000 (UTC)
MIME-Version: 1.0
Date: Sun, 17 May 2026 18:56:29 -0700
From: Jared Finder <jared@HIDDEN>
To: Eli Zaretskii <eliz@HIDDEN>
Subject: Re: bug#80993: [PATCH] Add support for tty scroll-bars
In-Reply-To: <86cxyv660l.fsf@HIDDEN>
References: <af36i-emhv7cCESf@HIDDEN> <86cxyv660l.fsf@HIDDEN>
Message-ID: <fe8b6d3d49ec4eb84acd8b17f164d199@HIDDEN>
X-Sender: jared@HIDDEN
Content-Type: text/plain; charset=UTF-8;
 format=flowed
Content-Transfer-Encoding: 8bit
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 80993
Cc: 80993 <at> debbugs.gnu.org, Michael Grant <mgrant@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

On 2026-05-16 02:46, Eli Zaretskii wrote:
>> Date: Fri, 8 May 2026 11:00:27 -0400
>> From:  Michael Grant via "Bug reports for GNU Emacs,
>>  the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN>
>> 
>> This is a feature patch which implements tty based scroll-bars which
>> mimick the X-windows toolkit scrollbars.  It reserves a column on the
>> left or right (configurable) for the scroll-bar.  The scroll-bar thumb
>> is composed of inverse video white spaces and the scroll-bar gutter 
>> dark
>> spaces.  The scroll-bar thumb is draggable and clicking above or below
>> the scroll-bar scrolls the buffer.
> 
> Thanks, a few comments below.

Some additional comments.

+(defface scroll-bar-thumb
+  '((((type tty)) :inverse-video t)
+    (t nil))
+  "Basic face for the scroll bar thumb (draggable portion) in TTY 
frames.
+On character-based terminals, the thumb is rendered as inverse-video 
spaces."
+  :version "30.1"
+  :group 'frames
+  :group 'basic-faces)

I think it would be better to have the face definition have the same 
meaning as under X, where the scroll-bar face's foreground color is the 
color of the thumb and the background is the color of the track.  This 
additional face is unnecessary.

>> --- a/lisp/mouse.el
>> +++ b/lisp/mouse.el
>> @@ -865,7 +865,16 @@ mouse-position-for-drag-line
>>  relative to the selected frame, unless the current drag operation was
>>  produced from a touch screen event, in which event, return the 
>> position
>>  of the active touch-screen tool relative to the same."
>> -  (if tty (mouse-position-in-root-frame)
>> +  (if tty
>> +      ;; mouse-position-in-root-frame reads from the GPM mouse hook,
>> +      ;; which is not updated by xterm mouse events.  When xterm 
>> mouse
>> +      ;; mode is active the coordinates are stored as terminal 
>> parameters
>> +      ;; by xterm-mouse-event; use those when available.
>> +      (let ((xt-x (terminal-parameter nil 'xterm-mouse-x))
>> +            (xt-y (terminal-parameter nil 'xterm-mouse-y)))
>> +        (if (and xt-x xt-y)
>> +            (cons xt-x xt-y)
>> +          (mouse-position-in-root-frame)))
> 
> Jared, any comments?  Would it be cleaner to update xtmouse such that
> mouse_position_hook works for it?  Or some other way of supporting
> mouse-position-in-root-frame on xtmouse?

I don't think this change is needed. Local experimentation shows that 
mouse-position-in-root-frame properly reports mouse coordinates when 
xterm-mouse-mode is enabled. This is because xterm-mouse-mode sets 
mouse-position-function to xterm-mouse-position-function which does 
exactly this transformation.

What am I missing here?

+  (let* ((start-pos    (event-start event))
+         (window       (nth 0 start-pos))
+         (click-sb-row (car (nth 2 start-pos)))

Here, and in other places throughout this patch nth is used instead of 
the posn utility functions like posn-window, posn-x-y, etc.  I believe 
the posn utility functions are preferred stylistically.

+(defun xterm-mouse--tty-scroll-bar-window (x y frame)
+  "Return the window whose TTY scroll bar column is at frame column X, 
row Y.
+Returns nil if no TTY scroll bar occupies position (X, Y)."
+  (let ((sb-side (frame-parameter frame 'vertical-scroll-bars))
+        result)
+    (when sb-side
+      (walk-windows
... and a bunch of code ...

Can this just use window-at instead to get the specific window? And it 
would be nice if the function coordinates_in_window properly detected 
the scroll bar, in which case you could add a parameter to 
coordinates-in-window-p that allows it to return scroll bar areas.  For 
backward compat it currently returns nil when passed scrollbar 
coordinates.

+(defun xterm-mouse--tty-vertical-border-window (x y frame)
+  "Return the window whose TTY `|' border column is at frame column X, 
row Y.
+Returns nil unless (X, Y) is on the `|' border of a non-rightmost TTY
+window with a right scroll bar.  In that layout, [content][SB][|], the
+`|' occupies the last column of the window's total-width allocation."
... and a bunch of code ...

Could this be replaced with:

(eq (coordinates-in-window-p (cons x y) (window-at x y frame)
     'vertical-line)

+(defun xterm-mouse--tty-scroll-bar-part (window y)
+  "Return the scroll bar part clicked at terminal row Y for WINDOW.
+Returns one of the symbols `above-handle', `handle', or 
`below-handle'."

This appears to be a copy of code added in 
tty_apply_scroll_bar_glyphs_for_window.  I would prefer that be 
extracted out to a utility function that xt-mouse.el can call instead so 
we do not need to duplicate code in both xt-mouse.el and in C.

@@ -341,6 +431,59 @@ xterm-mouse-event
  				  frame)
  				item)
  			  (nthcdr 2 (posn-at-x-y x y (selected-frame)))))))
+             ;; Check for a click in a TTY vertical scroll bar column.
+             ;; When detected, replace the position with a scroll bar
+             ;; position so that [vertical-scroll-bar mouse-N] bindings 
fire.
+             (sb-window (and (not (display-graphic-p))
+                             frame
+                             (not (eq type 'mouse-movement))
+                             (xterm-mouse--tty-scroll-bar-window x y 
frame)))

I think the changes above could dramatically simplify this case.

+             (posn (if sb-window

posn was just bound above. It would be better to put this code into the 
logic for constructing the posn.  There's already significant logic 
looking at the x/y coordinate an calculating the window part from there.

+                       (let* ((win-ht  (window-body-height sb-window))
+                              (win-top (window-top-line sb-window))
+                              (sb-row  (max 0 (min (1- win-ht) (- y 
win-top))))
+                              (part    
(xterm-mouse--tty-scroll-bar-part
+                                        sb-window y))
+                              ;; For button-up events: if the drag 
started on
+                              ;; the scroll-bar handle, keep 
part='handle' so
+                              ;; scroll-bar-drag-1 proportionally 
scrolls to
+                              ;; the release position rather than 
paging.
+                              (down-ev  (terminal-parameter
+                                         nil 'xterm-mouse-last-down))
+                              (down-part (and down-ev
+                                             (nth 4 (nth 1 down-ev))))
+                              (part     (if (and (not (string-prefix-p
+                                                       "down-"
+                                                       (symbol-name 
type)))
+                                                 (eq down-part 
'handle))
+                                            'handle

I do not think it is a good idea to make tty mouse return different 
events than the graphical mouse.  I would instead suggest you figure out 
what part of the scroll bar is clicked inside scroll-bar-toolkit-scroll.

+                                          part)))
+                         ;; Build a scroll-bar position in the same 
format as
+                         ;; make_scroll_bar_position in keyboard.c:
+                         ;;   (window AREA (pos . size) timestamp part)
+                         ;; AREA must be the bare symbol 
`vertical-scroll-bar'
+                         ;; (not a cons) so that read_key_sequence's 
SYMBOLP
+                         ;; check expands the event to 
[vertical-scroll-bar
+                         ;; mouse-1], which fires 
`scroll-bar-toolkit-scroll'.
+                         (list sb-window
+                               'vertical-scroll-bar
+                               (cons sb-row win-ht)
+                               timestamp
+                               part))
+                     posn))
+             ;; Check for a click on the TTY '|' window border.
+             ;; For a right scroll bar on a non-rightmost TTY window 
the '|'
+             ;; glyph occupies the last column of the window 
allocation; a
+             ;; click there should generate a vertical-line position so 
that
+             ;; [vertical-line down-mouse-1] → mouse-drag-vertical-line 
fires.
+             (border-window (and (not sb-window)
+                                 frame
+                                 (not (eq type 'mouse-movement))
+                                 
(xterm-mouse--tty-vertical-border-window
+                                  x y frame)))
+             (posn (if border-window
+                       (list border-window 'vertical-line (cons x y) 
timestamp)
+                     posn))
               (event (list type posn)))
          (setcar (nthcdr 3 posn) timestamp)

Overall, I suspect the above code can be made much simpler if it used 
the above suggestions, especially moving to the existing functions that 
already determine what part of a window a coordinate is in.

   -- MJF




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#80993; Package emacs. Full text available.

Message received at 80993 <at> debbugs.gnu.org:


Received: (at 80993) by debbugs.gnu.org; 16 May 2026 09:47:09 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat May 16 05:47:09 2026
Received: from localhost ([127.0.0.1]:56528 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1wOBbw-00048u-Vi
	for submit <at> debbugs.gnu.org; Sat, 16 May 2026 05:47:09 -0400
Received: from eggs.gnu.org ([2001:470:142:3::10]:54274)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1wOBbu-00048M-JO
 for 80993 <at> debbugs.gnu.org; Sat, 16 May 2026 05:47:07 -0400
Received: from fencepost.gnu.org ([2001:470:142:3::e])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <eliz@HIDDEN>)
 id 1wOBbn-0000oA-EN; Sat, 16 May 2026 05:46:59 -0400
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org;
 s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date:
 mime-version; bh=vELxjSR+O5eGuDvvpigw0HSl56QoMoOoGcorxbthjsw=; b=pzuTBiLQf13H
 EkOrhHTDW9txMJ8RykDn/+cWJlOm6vcS9gVGE5nM0/JxfQruQ6g37sEhm1e/W8d1DPchbDGBRgxi2
 SpkyJsaJZOAhTCGqrIErP8R5ip9TaBFFrEjmF9QQZ3QZBVryqMjAQBQribmoFqjDEmhLtCIR4mzEX
 /K3aC4HJo+mCLVWxL1zH3c2TtSzcsywzNrpB2raLYmd22N2teKotTCOetK4iBE1RbccprS6FFHCGt
 IjTdlZ66Nm7P9bc75UEeqMRtlVHhOCJRU8KctIt22I0AULQSg/TUE9eWm8zxuI3plqf6qaMXtCswP
 o/Dt1V1PIR93ijItDrRjzw==;
Date: Sat, 16 May 2026 12:46:50 +0300
Message-Id: <86cxyv660l.fsf@HIDDEN>
From: Eli Zaretskii <eliz@HIDDEN>
To: Michael Grant <mgrant@HIDDEN>, Jared Finder <jared@HIDDEN>
In-Reply-To: <af36i-emhv7cCESf@HIDDEN> (bug-gnu-emacs@HIDDEN)
Subject: Re: bug#80993: [PATCH] Add support for tty scroll-bars
References: <af36i-emhv7cCESf@HIDDEN>
X-Spam-Score: -2.3 (--)
X-Debbugs-Envelope-To: 80993
Cc: 80993 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -3.3 (---)

> Date: Fri, 8 May 2026 11:00:27 -0400
> From:  Michael Grant via "Bug reports for GNU Emacs,
>  the Swiss army knife of text editors" <bug-gnu-emacs@HIDDEN>
> 
> This is a feature patch which implements tty based scroll-bars which
> mimick the X-windows toolkit scrollbars.  It reserves a column on the
> left or right (configurable) for the scroll-bar.  The scroll-bar thumb
> is composed of inverse video white spaces and the scroll-bar gutter dark
> spaces.  The scroll-bar thumb is draggable and clicking above or below
> the scroll-bar scrolls the buffer.

Thanks, a few comments below.

> --- a/lisp/mouse.el
> +++ b/lisp/mouse.el
> @@ -865,7 +865,16 @@ mouse-position-for-drag-line
>  relative to the selected frame, unless the current drag operation was
>  produced from a touch screen event, in which event, return the position
>  of the active touch-screen tool relative to the same."
> -  (if tty (mouse-position-in-root-frame)
> +  (if tty
> +      ;; mouse-position-in-root-frame reads from the GPM mouse hook,
> +      ;; which is not updated by xterm mouse events.  When xterm mouse
> +      ;; mode is active the coordinates are stored as terminal parameters
> +      ;; by xterm-mouse-event; use those when available.
> +      (let ((xt-x (terminal-parameter nil 'xterm-mouse-x))
> +            (xt-y (terminal-parameter nil 'xterm-mouse-y)))
> +        (if (and xt-x xt-y)
> +            (cons xt-x xt-y)
> +          (mouse-position-in-root-frame)))

Jared, any comments?  Would it be cleaner to update xtmouse such that
mouse_position_hook works for it?  Or some other way of supporting
mouse-position-in-root-frame on xtmouse?

> diff --git a/lisp/xt-mouse.el b/lisp/xt-mouse.el
> index b93d914380f..b9e85e22c40 100644
> --- a/lisp/xt-mouse.el
> +++ b/lisp/xt-mouse.el

Jared, any comments on the xt-mouse parts (or stuff related to
xt-mouse elsewhere in the patch)?

> +                     ;; On TTY frames a right scroll bar on a non-rightmost
> +                     ;; window has its indicator one column left of the last
> +                     ;; column, which holds the '|' border glyph.  Layout:
> +                     ;; [content][SB][|].  On the rightmost window there is
> +                     ;; no '|', so the SB occupies the last column as usual.
> +                     ;;
> +                     ;; NOTE: (frame-width) returns the TEXT-area column
> +                     ;; count (excluding the SB column), so the rightmost
> +                     ;; window has right-edge = (1+ (frame-width frame)).
> +                     (if (and (not (display-graphic-p))
> +                              (/= right-edge
> +                                  (1+ (frame-width (window-frame w)))))
> +                         (- right-edge 2)
> +                       (1- right-edge)))))

Couldn't this be simplified using what window-right returns?

> +    (let (result)
> +      (walk-windows
> +       (lambda (w)
> +         (unless result
> +           (let* ((right-edge (+ (window-left-column w)
> +                                 (window-total-width w)))
> +                  (win-top (window-top-line w))
> +                  (win-bot (+ win-top (window-total-height w))))
> +             (when (and (/= right-edge (1+ (frame-width (window-frame w))))
> +                        (= x (1- right-edge))
> +                        (<= win-top y)
> +                        (< y win-bot))
> +               (setq result w)))))

Same here?

> +	      /* For TTY frames with character-based scroll bars, reserve
> +		 columns at the left or right edge of each window row.
> +		 The scroll bar columns are at glyphs[LEFT_MARGIN_AREA][0..sbl-1]
> +		 (left scroll bar) or glyphs[LAST_AREA][0..sbr-1] (right).
> +		 TEXT_AREA and RIGHT_MARGIN_AREA are shifted accordingly so
> +		 the text iterator doesn't overwrite scroll bar columns.  */
> +	      int sbl = 0, sbr = 0;
> +	      if (w && !FRAME_WINDOW_P (XFRAME (w->frame))
> +		  && !MINI_WINDOW_P (w))
> +		{
> +		  sbl = (WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_LEFT (w)
> +			 ? WINDOW_SCROLL_BAR_COLS (w) : 0);
> +		  sbr = (WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_RIGHT (w)
> +			 ? WINDOW_SCROLL_BAR_COLS (w) : 0);
> +		}
>  	      row->glyphs[TEXT_AREA]
> -		= row->glyphs[LEFT_MARGIN_AREA] + left;
> +		= row->glyphs[LEFT_MARGIN_AREA] + sbl + left;
>  	      row->glyphs[RIGHT_MARGIN_AREA]
> -		= row->glyphs[TEXT_AREA] + dim.width - left - right;
> -	      /* Leave room for a border glyph.  */
> +		= row->glyphs[TEXT_AREA] + dim.width - sbl - sbr - left - right;
> +	      /* Leave room for a border glyph unless the window has a right
> +		 scroll bar (which serves as the visual separator).  */
>  	      if (!FRAME_WINDOW_P (XFRAME (w->frame))
>  		  && !WINDOW_RIGHTMOST_P (w)
> -		  && right > 0)
> +		  && right > 0
> +		  && sbr == 0)
>  		row->glyphs[RIGHT_MARGIN_AREA] -= 1;
> +	      /* LAST_AREA ends before the right scroll bar columns.  */
>  	      row->glyphs[LAST_AREA]
> -		= row->glyphs[LEFT_MARGIN_AREA] + dim.width;
> +		= row->glyphs[LEFT_MARGIN_AREA] + dim.width - sbr;
>  	    }

I believe the code here was modified lately, so please rebase.

> @@ -976,7 +983,7 @@ tty_write_glyphs (struct frame *f, struct glyph *string, int len)
>       since that would scroll the whole frame on some terminals.  */
>    if (AutoWrap (tty)
>        && curY (tty) + 1 == FRAME_TOTAL_LINES (f)
> -      && curX (tty) + len == FRAME_COLS (f)
> +      && curX (tty) + len == FrameCols (tty)

Why was this needed?

And I have several more questions:

  . where are the glyphs defined that are used to draw the TTY
    scroll-bar and its thumb? it sounds like they are always SPC
    characters with a special face, is that right?
  . since the scroll bar takes up another column of each window's
    screen line, shouldn't we make last_visible_x set up in
    init_iterator by one? otherwise, a ling enough line could
    overwrite the scroll-bar glyphs, no?
  . similarly, shouldn't append_glyph avoid adding glyphs past the
    scroll-bar? e.g., did you try displaying a buffer with wide
    characters, those which take more than 1 column, when the line is
    long enough to reach the right edge of the window?

And finally, would people please try this patch and report back any
issues they find with the code?

Thanks.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#80993; Package emacs. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 8 May 2026 15:01:12 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri May 08 11:01:12 2026
Received: from localhost ([127.0.0.1]:39209 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1wLMhM-0005on-Ck
	for submit <at> debbugs.gnu.org; Fri, 08 May 2026 11:01:12 -0400
Received: from lists1p.gnu.org ([2001:470:142::17]:57722)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <mgrant@HIDDEN>) id 1wLMhE-0005ll-Jc
 for submit <at> debbugs.gnu.org; Fri, 08 May 2026 11:01:01 -0400
Received: from eggs.gnu.org ([2001:470:142:3::10])
 by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <mgrant@HIDDEN>) id 1wLMgz-00081y-Kg
 for bug-gnu-emacs@HIDDEN; Fri, 08 May 2026 11:00:42 -0400
Received: from strange.networkguild.org ([2600:3c02:e000:dd::1])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <mgrant@HIDDEN>) id 1wLMgo-0005ou-D9
 for bug-gnu-emacs@HIDDEN; Fri, 08 May 2026 11:00:36 -0400
Received: from strange.networkguild.org (localhost [127.0.0.1])
 by strange.networkguild.org (8.18.1/8.18.1/Debian-6) with ESMTPS id
 648F0RjD399370
 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT)
 for <bug-gnu-emacs@HIDDEN>; Fri, 8 May 2026 11:00:27 -0400
Received: (from root@localhost)
 by strange.networkguild.org (8.18.1/8.18.1/Submit) id 648F0RJ5399369
 for bug-gnu-emacs@HIDDEN; Fri, 8 May 2026 11:00:27 -0400
Date: Fri, 8 May 2026 11:00:27 -0400
From: Michael Grant <mgrant@HIDDEN>
To: bug-gnu-emacs@HIDDEN
Subject: [PATCH] Add support for tty scroll-bars
Message-ID: <af36i-emhv7cCESf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/signed; micalg=pgp-sha512;
 protocol="application/pgp-signature"; boundary="JZGPDlq/4kg2H8cZ"
Content-Disposition: inline
X-Virus-Scanned: clamav-milter 1.4.3 at strange.networkguild.org
X-Virus-Status: Clean
Received-SPF: pass client-ip=2600:3c02:e000:dd::1;
 envelope-from=mgrant@HIDDEN; helo=strange.networkguild.org
X-Spam_score_int: -18
X-Spam_score: -1.9
X-Spam_bar: -
X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_PASS=-0.001,
 SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Spam-Score: 0.9 (/)
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -0.1 (/)


--JZGPDlq/4kg2H8cZ
Content-Type: multipart/mixed; boundary="36I/gurMzNR42vlK"
Content-Disposition: inline


--36I/gurMzNR42vlK
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

This bug report will be sent to the Bug-GNU-Emacs mailing list
and the GNU bug tracker at debbugs.gnu.org.  Please check that
the From: line contains a valid email address.  After a delay of up
to one day, you should receive an acknowledgment at that address.

Please write in English if possible, as the Emacs maintainers
usually do not have translators for other languages.

Please describe exactly what actions triggered the bug, and
the precise symptoms of the bug.  If you can, give a recipe
starting from 'emacs -Q':

This is a feature patch which implements tty based scroll-bars which
mimick the X-windows toolkit scrollbars.  It reserves a column on the
left or right (configurable) for the scroll-bar.  The scroll-bar thumb
is composed of inverse video white spaces and the scroll-bar gutter dark
spaces.  The scroll-bar thumb is draggable and clicking above or below
the scroll-bar scrolls the buffer.

I have created this bug-report from within an emacs compiled with these
scroll-bars, hence the version and configuration information below.

If Emacs crashed, and you have the Emacs process in the gdb debugger,
please include the output from the following gdb commands:
    'bt full' and 'xbacktrace'.
For information about debugging Emacs, please read the file
/home/mgrant/emacs-master/etc/DEBUG.

In GNU Emacs 32.0.50 (build 2, x86_64-pc-linux-gnu, GTK+ Version
 3.24.41, cairo version 1.18.0) of 2026-05-08 built on Slablet
Repository revision: 1019ae4e29f24232ea40668a7622b9bd01bc1e02
Repository branch: tty_scrollbars
System Description: Ubuntu 24.04.4 LTS
Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG
LCMS2 LIBOTF LIBSYSTEMD LIBXML2 M17N_FLT MODULES NATIVE_COMP NOTIFY
INOTIFY PDUMPER PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF
TOOLKIT_SCROLL_BARS TREE_SITTER WEBP X11 XDBE XIM XINERAMA XINPUT2 XPM
XRANDR GTK3 ZLIB

Important settings:
  value of $LANG: C.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Lisp Interaction

Minor modes in effect:
  recentf-mode: t
  desktop-save-mode: t
  save-place-mode: t
  xterm-mouse-mode: t
  winner-mode: t
  delete-selection-mode: t
  cua-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  window-divider-mode: t
  minibuffer-nonselected-mode: t
  minibuffer-regexp-mode: t
  column-number-mode: t
  line-number-mode: t
  indent-tabs-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t

Load-path shadows:
/home/mgrant/.emacs.d/lisp/completion hides /home/mgrant/emacs-master/lisp/completion
/home/mgrant/.emacs.d/lisp/ispell hides /home/mgrant/emacs-master/lisp/textmodes/ispell
/home/mgrant/.emacs.d/lisp/sendmail hides /home/mgrant/emacs-master/lisp/mail/sendmail
/home/mgrant/.emacs.d/lisp/vt-control hides /home/mgrant/emacs-master/lisp/obsolete/vt-control

Features:
(shadow sort mail-extr emacsbug lisp-mnt message yank-media puny dired
dired-loaddefs rfc822 mml mml-sec epa derived epg rfc6068 epg-config
gnus-util mm-decode mm-bodies mm-encode mail-parse rfc2231 rfc2047
rfc2045 mm-util ietf-drums mail-prsvr mailabbrev mail-utils gmm-utils
mailheader sendmail cl-extra help-mode warnings time-date term/xterm
xterm compile text-property-search comp-run bytecomp byte-compile
comp-common rx wombat-theme recentf tree-widget desktop frameset
saveplace server comint ansi-osc ansi-color xt-mouse winner cus-edit pp
cus-start cus-load wid-edit ring delsel cua-base finder-inf package
browse-url xdg url url-proxy url-privacy url-expand url-methods
url-history url-cookie generate-lisp-file url-domsuf url-util mailcap
url-handlers url-parse auth-source cl-seq eieio eieio-core cl-macs gv
icons password-cache json subr-x mule-util map url-vars cl-loaddefs
cl-lib package-activate rmc iso-transl tooltip cconv eldoc paren
electric uniquify ediff-hook vc-hooks lisp-float-type elisp-mode mwheel
term/x-win x-win term/common-win x-dnd touch-screen tool-bar dnd fontset
image regexp-opt fringe tabulated-list replace newcomment text-mode
lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow isearch
easymenu timer select scroll-bar mouse jit-lock font-lock syntax
font-core term/tty-colors frame minibuffer nadvice seq simple cl-generic
indonesian philippine cham georgian utf-8-lang misc-lang vietnamese
tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek
romanian slovak czech european ethiopic indian cyrillic chinese
composite emoji-zwj charscript charprop case-table epa-hook
jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs
theme-loaddefs faces cus-face macroexp files window text-properties
overlay sha1 md5 base64 format env code-pages mule custom widget keymap
hashtable-print-readable backquote threads dbusbind inotify lcms2
dynamic-setting system-font-setting font-render-setting cairo gtk
x-toolkit xinput2 x multi-tty move-toolbar make-network-process
tty-child-frames native-compile emacs)

Memory information:
((conses 16 147245 20124) (symbols 48 11511 0) (strings 32 37115 2565)
 (string-bytes 1 1236747) (vectors 16 22745)
 (vector-slots 8 617206 41990) (floats 8 83 13435)
 (intervals 56 319 0) (buffers 1064 12))

--36I/gurMzNR42vlK
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="tty-scrollbars.patch"
Content-Transfer-Encoding: quoted-printable

diff --git a/lisp/faces.el b/lisp/faces.el
index 94da4ba0290..07a5da8522b 100644
--- a/lisp/faces.el
+++ b/lisp/faces.el
@@ -2932,12 +2932,25 @@ fringe
   :group 'frames
   :group 'basic-faces)
=20
-(defface scroll-bar '((t nil))
-  "Basic face for the scroll bar colors under X."
+(defface scroll-bar
+  '((((type tty)) :background "black")
+    (t nil))
+  "Basic face for the scroll bar colors under X.
+On TTY frames, this face determines the appearance of the scroll bar
+gutter (track).  The default TTY appearance is a black background."
   :version "21.1"
   :group 'frames
   :group 'basic-faces)
=20
+(defface scroll-bar-thumb
+  '((((type tty)) :inverse-video t)
+    (t nil))
+  "Basic face for the scroll bar thumb (draggable portion) in TTY frames.
+On character-based terminals, the thumb is rendered as inverse-video space=
s."
+  :version "30.1"
+  :group 'frames
+  :group 'basic-faces)
+
 (defface border '((t nil))
   "Basic face for the frame border under X."
   :version "21.1"
diff --git a/lisp/mouse.el b/lisp/mouse.el
index 20c819ee0a8..5ff9e1ab87a 100644
--- a/lisp/mouse.el
+++ b/lisp/mouse.el
@@ -865,7 +865,16 @@ mouse-position-for-drag-line
 relative to the selected frame, unless the current drag operation was
 produced from a touch screen event, in which event, return the position
 of the active touch-screen tool relative to the same."
-  (if tty (mouse-position-in-root-frame)
+  (if tty
+      ;; mouse-position-in-root-frame reads from the GPM mouse hook,
+      ;; which is not updated by xterm mouse events.  When xterm mouse
+      ;; mode is active the coordinates are stored as terminal parameters
+      ;; by xterm-mouse-event; use those when available.
+      (let ((xt-x (terminal-parameter nil 'xterm-mouse-x))
+            (xt-y (terminal-parameter nil 'xterm-mouse-y)))
+        (if (and xt-x xt-y)
+            (cons xt-x xt-y)
+          (mouse-position-in-root-frame)))
     (or (touch-screen-last-drag-position)
         (mouse-absolute-pixel-position))))
=20
diff --git a/lisp/scroll-bar.el b/lisp/scroll-bar.el
index b9d1d3a441e..7430ee82a3b 100644
--- a/lisp/scroll-bar.el
+++ b/lisp/scroll-bar.el
@@ -92,6 +92,7 @@ scroll-bar-mode-explicit
   "Non-nil means `set-scroll-bar-mode' should really do something.
 This is nil while loading `scroll-bar.el', and t afterward.")
=20
+;;;###autoload
 (defun set-scroll-bar-mode (value)
   "Set the scroll bar mode to VALUE and put the new value into effect.
 See the `scroll-bar-mode' variable for possible values to use."
@@ -104,6 +105,7 @@ set-scroll-bar-mode
     (modify-all-frames-parameters (list (cons 'vertical-scroll-bars
 					      scroll-bar-mode)))))
=20
+(custom-autoload 'scroll-bar-mode "scroll-bar" t)
 (defcustom scroll-bar-mode default-frame-scroll-bars
   "Specify whether to have vertical scroll bars, and on which side.
 Possible values are nil (no scroll bars), `left' (scroll bars on left)
@@ -124,6 +126,16 @@ scroll-bar-mode
 ;; If it is set again, that is for real.
 (setq scroll-bar-mode-explicit t)
=20
+(defun scroll-bar--tty-frame-setup ()
+  "Apply `scroll-bar-mode' to TTY frames at startup.
+TTY frames don't go through the GUI frame-parameter initialization
+that would apply `scroll-bar-mode' during frame creation, so we
+apply it explicitly here via `window-setup-hook'."
+  (when scroll-bar-mode
+    (set-scroll-bar-mode scroll-bar-mode)))
+
+(add-hook 'window-setup-hook #'scroll-bar--tty-frame-setup)
+
 (defun get-scroll-bar-mode ()
   (declare (gv-setter set-scroll-bar-mode))
   scroll-bar-mode)
@@ -301,6 +313,106 @@ scroll-bar-drag
     (with-current-buffer (window-buffer window)
       (setq point-before-scroll before-scroll))))
=20
+(defun tty-scroll-bar--thumb-geometry (window)
+  "Return (START . END) thumb geometry for WINDOW's TTY scroll bar.
+START and END are 0-indexed row numbers; the thumb occupies rows
+\[START, END) (exclusive END).  Mirrors the formula in C function
+`tty_apply_scroll_bar_glyphs_for_window'."
+  (let* ((buf     (window-buffer window))
+         (win-ht  (window-body-height window))
+         (whole   (buffer-size buf))
+         (pos     (with-current-buffer buf
+                    (- (window-start window) (point-min))))
+         (portion (- (window-end window t) (window-start window))))
+    (if (or (<=3D whole 0) (>=3D portion whole))
+        (cons 0 win-ht)
+      (let* ((ta    (floor (* (/ (float pos) whole) win-ht)))
+             (below (- whole pos portion))
+             (tb    (if (> below 0)
+                        (floor (* (/ (float below) whole) win-ht))
+                      0)))
+        (when (and (> pos 0) (=3D ta 0)) (setq ta 1))
+        (when (and (> below 0) (=3D tb 0)) (setq tb 1))
+        (let ((ts ta)
+              (te (- win-ht tb)))
+          (when (<=3D te ts) (setq te (1+ ts)))
+          (setq ts (min ts (1- win-ht)))
+          (setq te (min te win-ht))
+          (when (>=3D ts te) (setq te (1+ ts)))
+          (cons ts te))))))
+
+(defun tty-scroll-bar--thumb-start (window)
+  "Return the 0-indexed thumb-start row for WINDOW's TTY scroll bar."
+  (car (tty-scroll-bar--thumb-geometry window)))
+
+(defun tty-scroll-bar-drag (event)
+  "Scroll a TTY scroll bar window live as the mouse is dragged.
+EVENT is the down-mouse-1 event on the scroll bar handle.
+Uses `read-key' (not `read-event') so that xterm mouse escape sequences
+are decoded through `input-decode-map' during the drag loop.
+
+Handles both `mouse-movement' events (when the mouse is over the text
+area) and `scroll-bar-movement' events (when the mouse is over the
+scroll bar itself); only the y coordinate is used to scroll, so moving
+horizontally into the text area while dragging still scrolls correctly.
+
+The grab point =E2=80=94 the row within the thumb where the drag was initi=
ated
+=E2=80=94 is preserved throughout the drag, so the thumb follows the cursor
+rather than jumping to align its top edge with the cursor."
+  (interactive "e")
+  (let* ((start-pos    (event-start event))
+         (window       (nth 0 start-pos))
+         (click-sb-row (car (nth 2 start-pos)))
+         (grab-offset  (max 0 (- click-sb-row
+                                 (tty-scroll-bar--thumb-start window)))))
+    (track-mouse
+      (let (done)
+        (while (not done)
+          (let ((ev (read-key)))
+            (cond
+             ((eq (car-safe ev) 'scroll-bar-movement)
+              ;; Mouse moved within the scroll bar: extract sb-row from
+              ;; the event's (PORTION . WHOLE) field directly.
+              (let* ((posn   (nth 1 ev))
+                     (ratio  (nth 2 posn))
+                     (win-ht (window-body-height window))
+                     (sb-row (max 0 (min (1- win-ht)
+                                         (- (car ratio) grab-offset)))))
+                (when (> win-ht 0)
+                  (with-current-buffer (window-buffer window)
+                    (goto-char (+ (point-min)
+                                  (/ (* sb-row (- (point-max) (point-min)))
+                                     win-ht)))
+                    (vertical-motion 0 window)
+                    (set-window-start window (point))))))
+             ((mouse-movement-p ev)
+              ;; Mouse moved outside the scroll bar (e.g. into the text
+              ;; area): use the y coordinate stored by xterm-mouse.
+              (let* ((y       (terminal-parameter nil 'xterm-mouse-y))
+                     (win-ht  (window-body-height window))
+                     (win-top (window-top-line window))
+                     (sb-row  (max 0 (min (1- win-ht)
+                                          (- (- y win-top) grab-offset)))))
+                (when (> win-ht 0)
+                  (with-current-buffer (window-buffer window)
+                    (goto-char (+ (point-min)
+                                  (/ (* sb-row (- (point-max) (point-min)))
+                                     win-ht)))
+                    (vertical-motion 0 window)
+                    (set-window-start window (point))))))
+             (t
+              (setq done t)))))))))
+
+(defun tty-scroll-bar-down-mouse-1 (event)
+  "Handle down-mouse-1 on a TTY vertical scroll bar.
+When clicked on the thumb handle, initiate live drag scrolling via
+`tty-scroll-bar-drag'.  When clicked above or below the handle, do
+nothing so that the subsequent button-up event fires
+`scroll-bar-toolkit-scroll' to page up or down."
+  (interactive "e")
+  (when (eq (nth 4 (event-start event)) 'handle)
+    (tty-scroll-bar-drag event)))
+
 ;; Scroll the window to the proper position for EVENT.
 (defun scroll-bar-horizontal-drag-1 (event)
   (let* ((start-position (event-start event))
@@ -494,12 +606,34 @@ scroll-bar-toolkit-horizontal-scroll
        (global-set-key [vertical-scroll-bar mouse-1]
 		       'scroll-bar-toolkit-scroll)
        (global-set-key [horizontal-scroll-bar mouse-1]
-		       'scroll-bar-toolkit-horizontal-scroll))
+		       'scroll-bar-toolkit-horizontal-scroll)
+       ;; Also bind the TTY live-drag key so that `src/emacs -nw' in a
+       ;; toolkit build uses the same drag handler as non-toolkit builds.
+       ;; In graphical frames the toolkit delivers drags as mouse-1
+       ;; (not down-mouse-1), so this binding does not conflict.
+       (global-set-key [vertical-scroll-bar down-mouse-1]
+		       'tty-scroll-bar-down-mouse-1)
+       ;; When a TTY drag ends outside the scroll bar column, the release
+       ;; event becomes drag-mouse-1; handle it like a click on the end
+       ;; position so the buffer still scrolls proportionally.
+       (global-set-key [vertical-scroll-bar drag-mouse-1]
+		       'scroll-bar-toolkit-scroll))
       (t
+       ;; TTY (character-based) scroll bars include part information
+       ;; (above-handle, handle, below-handle).
+       ;;
+       ;; down-mouse-1 is bound to `tty-scroll-bar-down-mouse-1': when
+       ;; the thumb handle is clicked it enters a live-drag loop using
+       ;; `read-key' (which decodes xterm escape sequences); clicking
+       ;; above/below the handle is a no-op so the button-up event falls
+       ;; through to `scroll-bar-toolkit-scroll' for page up/down.
+       (global-set-key [vertical-scroll-bar down-mouse-1]
+		       'tty-scroll-bar-down-mouse-1)
        (global-set-key [vertical-scroll-bar mouse-1]
-		       'scroll-bar-scroll-up)
+		       'scroll-bar-toolkit-scroll)
+       ;; Dragging the thumb: treat it as a click on the final position.
        (global-set-key [vertical-scroll-bar drag-mouse-1]
-		       'scroll-bar-scroll-up)
+		       'scroll-bar-toolkit-scroll)
        (global-set-key [vertical-scroll-bar down-mouse-2]
 		       'scroll-bar-drag)
        (global-set-key [vertical-scroll-bar mouse-3]
diff --git a/lisp/xt-mouse.el b/lisp/xt-mouse.el
index b93d914380f..b9e85e22c40 100644
--- a/lisp/xt-mouse.el
+++ b/lisp/xt-mouse.el
@@ -131,6 +131,96 @@ xterm-mouse-translate-1
                 (xterm-mouse--handle-mouse-movement)
 		(vector (list 'mouse-movement ev-data))))))))))))
=20
+(defun xterm-mouse--tty-scroll-bar-window (x y frame)
+  "Return the window whose TTY scroll bar column is at frame column X, row=
 Y.
+Returns nil if no TTY scroll bar occupies position (X, Y)."
+  (let ((sb-side (frame-parameter frame 'vertical-scroll-bars))
+        result)
+    (when sb-side
+      (walk-windows
+       (lambda (w)
+         (unless result
+           (let* ((right-edge (+ (window-left-column w)
+                                 (window-total-width w)))
+                  (sb-col
+                   (cond
+                    ((eq sb-side 'left) (window-left-column w))
+                    ((eq sb-side 'right)
+                     ;; On TTY frames a right scroll bar on a non-rightmost
+                     ;; window has its indicator one column left of the la=
st
+                     ;; column, which holds the '|' border glyph.  Layout:
+                     ;; [content][SB][|].  On the rightmost window there is
+                     ;; no '|', so the SB occupies the last column as usua=
l.
+                     ;;
+                     ;; NOTE: (frame-width) returns the TEXT-area column
+                     ;; count (excluding the SB column), so the rightmost
+                     ;; window has right-edge =3D (1+ (frame-width frame)).
+                     (if (and (not (display-graphic-p))
+                              (/=3D right-edge
+                                  (1+ (frame-width (window-frame w)))))
+                         (- right-edge 2)
+                       (1- right-edge)))))
+                  (win-top (window-top-line w))
+                  (win-bot (+ win-top (window-total-height w))))
+             (when (and sb-col
+                        (=3D x sb-col)
+                        (<=3D win-top y)
+                        (< y win-bot))
+               (setq result w)))))
+       nil frame))
+    result))
+
+(defun xterm-mouse--tty-vertical-border-window (x y frame)
+  "Return the window whose TTY `|' border column is at frame column X, row=
 Y.
+Returns nil unless (X, Y) is on the `|' border of a non-rightmost TTY
+window with a right scroll bar.  In that layout, [content][SB][|], the
+`|' occupies the last column of the window's total-width allocation."
+  (when (and (not (display-graphic-p))
+             (eq (frame-parameter frame 'vertical-scroll-bars) 'right))
+    (let (result)
+      (walk-windows
+       (lambda (w)
+         (unless result
+           (let* ((right-edge (+ (window-left-column w)
+                                 (window-total-width w)))
+                  (win-top (window-top-line w))
+                  (win-bot (+ win-top (window-total-height w))))
+             (when (and (/=3D right-edge (1+ (frame-width (window-frame w)=
)))
+                        (=3D x (1- right-edge))
+                        (<=3D win-top y)
+                        (< y win-bot))
+               (setq result w)))))
+       nil frame)
+      result)))
+
+(defun xterm-mouse--tty-scroll-bar-part (window y)
+  "Return the scroll bar part clicked at terminal row Y for WINDOW.
+Returns one of the symbols `above-handle', `handle', or `below-handle'."
+  (let* ((buf         (window-buffer window))
+         (win-height  (window-body-height window))
+         (win-top     (window-top-line window))
+         (sb-row      (max 0 (min (1- win-height) (- y win-top))))
+         (buf-size    (buffer-size buf))
+         (win-start   (window-start window))
+         (win-end     (window-end window t))
+         (portion     (max 1 (- win-end win-start)))
+         (whole       (max portion buf-size))
+         ;; Mirror the C formula: compute track above and below separately.
+         (track-above (if (> whole 0)
+                          (floor (* win-start (/ (float win-height) whole)=
))
+                        0))
+         (below-chars (max 0 (- whole win-start portion)))
+         (track-below (if (> whole 0)
+                          (floor (* below-chars (/ (float win-height) whol=
e)))
+                        0))
+         (thumb-start track-above)
+         (thumb-end   (- win-height track-below)))
+    (when (>=3D thumb-end win-height) (setq thumb-end (1- win-height)))
+    (cond
+     ((< sb-row thumb-start)  'above-handle)
+     ((>=3D sb-row thumb-end)   'below-handle)
+     (t                       'handle))))
+
 (defun xterm-mouse--handle-mouse-movement ()
   "Handle mouse motion that was just generated for XTerm mouse."
   (when-let* ((frame (terminal-parameter nil 'xterm-mouse-frame)))
@@ -341,6 +431,59 @@ xterm-mouse-event
 				  frame)
 				item)
 			  (nthcdr 2 (posn-at-x-y x y (selected-frame)))))))
+             ;; Check for a click in a TTY vertical scroll bar column.
+             ;; When detected, replace the position with a scroll bar
+             ;; position so that [vertical-scroll-bar mouse-N] bindings fi=
re.
+             (sb-window (and (not (display-graphic-p))
+                             frame
+                             (not (eq type 'mouse-movement))
+                             (xterm-mouse--tty-scroll-bar-window x y frame=
)))
+             (posn (if sb-window
+                       (let* ((win-ht  (window-body-height sb-window))
+                              (win-top (window-top-line sb-window))
+                              (sb-row  (max 0 (min (1- win-ht) (- y win-to=
p))))
+                              (part    (xterm-mouse--tty-scroll-bar-part
+                                        sb-window y))
+                              ;; For button-up events: if the drag started=
 on
+                              ;; the scroll-bar handle, keep part=3D'handl=
e' so
+                              ;; scroll-bar-drag-1 proportionally scrolls =
to
+                              ;; the release position rather than paging.
+                              (down-ev  (terminal-parameter
+                                         nil 'xterm-mouse-last-down))
+                              (down-part (and down-ev
+                                             (nth 4 (nth 1 down-ev))))
+                              (part     (if (and (not (string-prefix-p
+                                                       "down-"
+                                                       (symbol-name type)))
+                                                 (eq down-part 'handle))
+                                            'handle
+                                          part)))
+                         ;; Build a scroll-bar position in the same format=
 as
+                         ;; make_scroll_bar_position in keyboard.c:
+                         ;;   (window AREA (pos . size) timestamp part)
+                         ;; AREA must be the bare symbol `vertical-scroll-=
bar'
+                         ;; (not a cons) so that read_key_sequence's SYMBO=
LP
+                         ;; check expands the event to [vertical-scroll-bar
+                         ;; mouse-1], which fires `scroll-bar-toolkit-scro=
ll'.
+                         (list sb-window
+                               'vertical-scroll-bar
+                               (cons sb-row win-ht)
+                               timestamp
+                               part))
+                     posn))
+             ;; Check for a click on the TTY '|' window border.
+             ;; For a right scroll bar on a non-rightmost TTY window the '=
|'
+             ;; glyph occupies the last column of the window allocation; a
+             ;; click there should generate a vertical-line position so th=
at
+             ;; [vertical-line down-mouse-1] =E2=86=92 mouse-drag-vertical=
-line fires.
+             (border-window (and (not sb-window)
+                                 frame
+                                 (not (eq type 'mouse-movement))
+                                 (xterm-mouse--tty-vertical-border-window
+                                  x y frame)))
+             (posn (if border-window
+                       (list border-window 'vertical-line (cons x y) times=
tamp)
+                     posn))
              (event (list type posn)))
         (setcar (nthcdr 3 posn) timestamp)
=20
diff --git a/src/dispextern.h b/src/dispextern.h
index d08bd7ee7a9..f0d5be70f13 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -1967,6 +1967,7 @@ #define FACE_UNIBYTE_P(FACE) ((FACE)->charset < 0)
   TAB_LINE_ACTIVE_FACE_ID,
   TAB_LINE_INACTIVE_FACE_ID,
   MARGIN_FACE_ID,
+  SCROLL_BAR_THUMB_FACE_ID,
   BASIC_FACE_ID_SENTINEL
 };
=20
diff --git a/src/dispnew.c b/src/dispnew.c
index 284a0eb175f..001d7c29298 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -467,17 +467,35 @@ adjust_glyph_matrix (struct window *w, struct glyph_m=
atrix *matrix, int x, int y
 	    }
 	  else
 	    {
+	      /* For TTY frames with character-based scroll bars, reserve
+		 columns at the left or right edge of each window row.
+		 The scroll bar columns are at glyphs[LEFT_MARGIN_AREA][0..sbl-1]
+		 (left scroll bar) or glyphs[LAST_AREA][0..sbr-1] (right).
+		 TEXT_AREA and RIGHT_MARGIN_AREA are shifted accordingly so
+		 the text iterator doesn't overwrite scroll bar columns.  */
+	      int sbl =3D 0, sbr =3D 0;
+	      if (w && !FRAME_WINDOW_P (XFRAME (w->frame))
+		  && !MINI_WINDOW_P (w))
+		{
+		  sbl =3D (WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_LEFT (w)
+			 ? WINDOW_SCROLL_BAR_COLS (w) : 0);
+		  sbr =3D (WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_RIGHT (w)
+			 ? WINDOW_SCROLL_BAR_COLS (w) : 0);
+		}
 	      row->glyphs[TEXT_AREA]
-		=3D row->glyphs[LEFT_MARGIN_AREA] + left;
+		=3D row->glyphs[LEFT_MARGIN_AREA] + sbl + left;
 	      row->glyphs[RIGHT_MARGIN_AREA]
-		=3D row->glyphs[TEXT_AREA] + dim.width - left - right;
-	      /* Leave room for a border glyph.  */
+		=3D row->glyphs[TEXT_AREA] + dim.width - sbl - sbr - left - right;
+	      /* Leave room for a border glyph unless the window has a right
+		 scroll bar (which serves as the visual separator).  */
 	      if (!FRAME_WINDOW_P (XFRAME (w->frame))
 		  && !WINDOW_RIGHTMOST_P (w)
-		  && right > 0)
+		  && right > 0
+		  && sbr =3D=3D 0)
 		row->glyphs[RIGHT_MARGIN_AREA] -=3D 1;
+	      /* LAST_AREA ends before the right scroll bar columns.  */
 	      row->glyphs[LAST_AREA]
-		=3D row->glyphs[LEFT_MARGIN_AREA] + dim.width;
+		=3D row->glyphs[LEFT_MARGIN_AREA] + dim.width - sbr;
 	    }
 	}
=20
@@ -2093,6 +2111,24 @@ adjust_frame_glyphs_for_frame_redisplay (struct fram=
e *f)
   pool_changed_p =3D realloc_glyph_pool (f->desired_pool, matrix_dim);
   realloc_glyph_pool (f->current_pool, matrix_dim);
=20
+  /* When the TTY scroll bar type changes, sbl/sbr (computed in
+     adjust_glyph_matrix) change too, so the TEXT_AREA glyph pointer
+     offsets within each pool row must be updated.  Because neither the
+     pool size nor the window sizes change in that case, the normal
+     pool_changed_p / window_change_flags detection misses it.
+     Use config_scroll_bar_height (unused by TTY frames) to track the
+     last-seen scroll bar type and force re-adjustment only on change.  */
+  if (!FRAME_WINDOW_P (f))
+    {
+      enum vertical_scroll_bar_type cur_sb
+	=3D FRAME_VERTICAL_SCROLL_BAR_TYPE (f);
+      if ((int) cur_sb !=3D f->config_scroll_bar_height)
+	{
+	  f->config_scroll_bar_height =3D (int) cur_sb;
+	  pool_changed_p =3D true;
+	}
+    }
+
   /* Set up glyph pointers within window matrices.  Do this only if
      absolutely necessary since it requires a frame redraw.  */
   if (pool_changed_p || window_change_flags)
@@ -2582,8 +2618,14 @@ build_frame_matrix_from_leaf_window (struct glyph_ma=
trix *frame_matrix, struct w
     {
       window_matrix =3D w->desired_matrix;
=20
-      /* Decide whether we want to add a vertical border glyph.  */
-      if (!WINDOW_RIGHTMOST_P (w))
+      /* Decide whether we want to add a vertical border glyph between
+	 horizontally adjacent windows.  For TTY frames we always insert
+	 '|' at LAST_AREA-1: with a right scroll bar, window_body_width
+	 now reserves that column explicitly so the border and the scroll
+	 bar indicator (one column further right) are visually distinct.  */
+      if (!WINDOW_RIGHTMOST_P (w)
+	  && (!WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_RIGHT (w)
+	      || !FRAME_WINDOW_P (f)))
 	{
 	  struct Lisp_Char_Table *dp =3D window_display_table (w);
 	  Lisp_Object gc;
@@ -2665,7 +2707,23 @@ build_frame_matrix_from_leaf_window (struct glyph_ma=
trix *frame_matrix, struct w
 	     windows.  */
 	  if (GLYPH_CHAR (right_border_glyph) !=3D 0)
 	    {
-	      struct glyph *border =3D window_row->glyphs[LAST_AREA] - 1;
+	      struct glyph *border;
+	      /* For TTY frames with a right scroll bar, the normal
+		 border position (LAST_AREA - 1) is used for the scroll
+		 bar indicator; tty_apply_scroll_bar_glyphs shifts the SB
+		 one column left to make room.  Place '|' at LAST_AREA[0]
+		 (the SB slot) so the layout is [content][SB][|].  */
+	      if (!FRAME_WINDOW_P (f)
+		  && WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_RIGHT (w))
+		{
+		  border =3D window_row->glyphs[LAST_AREA];
+		  /* Extend the used count to cover this extra column.  */
+		  int need =3D (int)(border - frame_row->glyphs[TEXT_AREA]) + 1;
+		  if (frame_row->used[TEXT_AREA] < need)
+		    frame_row->used[TEXT_AREA] =3D need;
+		}
+	      else
+		border =3D window_row->glyphs[LAST_AREA] - 1;
 	      /* It's a subtle bug if we are overwriting some non-char
 		 glyph with the vertical border glyph.  */
 	      eassert (border->type =3D=3D CHAR_GLYPH);
@@ -3913,10 +3971,171 @@ flush_terminal (struct frame *f)
   fflush (FRAME_TTY (f)->output);
 }
=20
+/* Apply TTY character-based scroll bar glyphs for window W into the
+   desired matrix of its frame.  Called recursively for the window tree.  =
*/
+static void
+tty_apply_scroll_bar_glyphs_for_window (struct frame *f, struct window *w)
+{
+  if (!WINDOW_LEAF_P (w))
+    {
+      /* Internal window: recurse into children.  */
+      struct window *child =3D XWINDOW (w->contents);
+      while (child)
+	{
+	  tty_apply_scroll_bar_glyphs_for_window (f, child);
+	  child =3D NILP (child->next) ? NULL : XWINDOW (child->next);
+	}
+      return;
+    }
+
+  /* Leaf window: draw scroll bar if active.  Skip minibuffer windows;
+     they occupy the last terminal row and writing 80 glyphs there would
+     trigger cmcheckmagic.  Minibuffer windows don't show scroll bars on
+     GUI either, so this is consistent behaviour.  */
+  if (MINI_WINDOW_P (w))
+    return;
+  if (!WINDOW_HAS_VERTICAL_SCROLL_BAR (w))
+    return;
+
+  /* Retrieve stored scroll bar parameters set by tty_set_vertical_scroll_=
bar.  */
+  Lisp_Object sb_data =3D w->vertical_scroll_bar;
+  if (!VECTORP (sb_data) || ASIZE (sb_data) < 3)
+    return;
+
+  int portion  =3D XFIXNUM (AREF (sb_data, 0));
+  int whole    =3D XFIXNUM (AREF (sb_data, 1));
+  int position =3D XFIXNUM (AREF (sb_data, 2));
+
+  struct glyph_matrix *matrix =3D f->desired_matrix;
+  if (!matrix)
+    return;
+
+  /* Window rows in the frame matrix.  */
+  int frame_y_top =3D w->desired_matrix->matrix_y;
+  int nrows =3D WINDOW_TOTAL_LINES (w);
+  /* Exclude mode line.  */
+  if (window_wants_mode_line (w))
+    nrows--;
+  /* Skip header/tab line rows at top.  */
+  int top_skip =3D 0;
+  if (window_wants_tab_line (w))
+    top_skip++;
+  if (window_wants_header_line (w))
+    top_skip++;
+
+  if (nrows <=3D top_skip)
+    return;
+  int sb_rows =3D nrows - top_skip;  /* usable rows for scroll bar */
+
+  /* Compute the scroll bar column in frame coordinates.  */
+  int sb_cols =3D WINDOW_SCROLL_BAR_COLS (w);
+  int frame_col;
+  if (WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_LEFT (w))
+    frame_col =3D WINDOW_LEFT_EDGE_COL (w);
+  else
+    frame_col =3D WINDOW_RIGHT_EDGE_COL (w) - sb_cols;
+
+  /* For a right scroll bar on a non-rightmost TTY window, the last
+     column of the window allocation holds the '|' border glyph
+     (placed there by build_frame_matrix_from_leaf_window).  Shift the
+     SB indicator one column left so the layout becomes
+     [content][SB][|] rather than [content][|][SB].  */
+  bool right_border
+    =3D (!FRAME_WINDOW_P (f)
+       && WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_RIGHT (w)
+       && !WINDOW_RIGHTMOST_P (w));
+  if (right_border)
+    frame_col -=3D 1;
+
+  /* Compute thumb position within sb_rows.
+     The hook passes: portion =3D visible chars, whole =3D total chars,
+     position =3D char position of first visible line.
+     When the whole buffer fits in the window, portion >=3D whole and the
+     thumb spans the full bar.
+
+     We compute the track regions above and below the thumb by rounding
+     each down separately.  This keeps the thumb height constant as the
+     user scrolls: the thumb always abuts the top rail when position=3D=3D0
+     and the bottom rail when position+portion=3D=3Dwhole.  */
+  int thumb_start =3D 0, thumb_end =3D sb_rows;
+  if (whole > 0 && portion < whole)
+    {
+      int track_above =3D (int) ((double) position / whole * sb_rows);
+      ptrdiff_t below_chars =3D (ptrdiff_t) whole - position - portion;
+      int track_below =3D (below_chars > 0)
+	? (int) ((double) below_chars / whole * sb_rows) : 0;
+      /* When content exists beyond the visible area but the proportional
+	 track size rounds to zero, ensure at least 1 track row so the
+	 thumb never appears full-height when the buffer is not fully
+	 visible.  */
+      if (position > 0 && track_above =3D=3D 0)
+	track_above =3D 1;
+      if (below_chars > 0 && track_below =3D=3D 0)
+	track_below =3D 1;
+      thumb_start =3D track_above;
+      thumb_end   =3D sb_rows - track_below;
+      /* Guarantee at least a 1-row thumb.  */
+      if (thumb_end <=3D thumb_start)
+	thumb_end =3D thumb_start + 1;
+      thumb_start =3D min (thumb_start, sb_rows - 1);
+      thumb_end   =3D min (thumb_end,   sb_rows);
+      if (thumb_start >=3D thumb_end)
+	thumb_end =3D thumb_start + 1;
+    }
+
+  /* Write scroll bar glyphs into the frame desired matrix.  */
+  for (int r =3D 0; r < sb_rows; r++)
+    {
+      int frame_row =3D frame_y_top + top_skip + r;
+      if (frame_row >=3D matrix->nrows)
+	break;
+
+      struct glyph_row *frow =3D matrix->rows + frame_row;
+
+      /* Determine face: thumb (inverse video) or track (dark background).=
  */
+      bool is_thumb =3D (r >=3D thumb_start && r < thumb_end);
+      int face_id =3D is_thumb ? SCROLL_BAR_THUMB_FACE_ID : SCROLL_BAR_FAC=
E_ID;
+
+      /* Write sb_cols scroll bar glyphs into the frame row.  */
+      struct glyph *g =3D frow->glyphs[TEXT_AREA] + frame_col;
+      for (int c =3D 0; c < sb_cols; c++, g++)
+	{
+	  g->type =3D CHAR_GLYPH;
+	  g->u.ch =3D ' ';
+	  g->face_id =3D face_id;
+	  g->padding_p =3D false;
+	  g->charpos =3D -1;
+	  g->frame =3D f;
+	}
+      /* Ensure the used count covers the scroll bar column, and for the
+	 right-border case also the '|' glyph one column to its right.  */
+      int need =3D frame_col + sb_cols + (right_border ? 1 : 0);
+      if (frow->used[TEXT_AREA] < need)
+	frow->used[TEXT_AREA] =3D need;
+      /* Mark the row enabled so it gets written to the terminal.  */
+      frow->enabled_p =3D true;
+    }
+}
+
+/* Apply character-based scroll bar glyphs to frame F's desired matrix.
+   Called after build_frame_matrix so fill_up_glyph_row_with_spaces has
+   run, and we can safely overwrite the scroll bar columns.  */
+static void
+tty_apply_scroll_bar_glyphs (struct frame *f)
+{
+  if (!f->desired_matrix)
+    return;
+  tty_apply_scroll_bar_glyphs_for_window (f, XWINDOW (f->root_window));
+}
+
 static void
 update_tty_frame (struct frame *f)
 {
   build_frame_matrix (f);
+  /* Apply character-based scroll bar glyphs after fill_up_glyph_row_with_=
spaces
+     has run (which is called from build_frame_matrix).  */
+  if (FRAME_TERMCAP_P (f))
+    tty_apply_scroll_bar_glyphs (f);
 }
=20
 #ifndef HAVE_ANDROID
@@ -3942,6 +4161,11 @@ abs_cursor_pos (struct frame *f, int *x, int *y)
       int wx =3D window_to_frame_hpos (w, w->cursor.hpos);
       int wy =3D window_to_frame_vpos (w, w->cursor.vpos);
=20
+      /* cursor.hpos is TEXT_AREA-relative; account for the left scroll
+	 bar width so the absolute cursor position is in the text area.
+	 Mini-windows have no scroll bar even when the frame does.  */
+      if (!MINI_WINDOW_P (w))
+	wx +=3D WINDOW_LEFT_SCROLL_BAR_COLS (w);
       wx +=3D max (0, w->left_margin_cols);
=20
       root_xy (f, wx, wy, x, y);
@@ -5746,6 +5970,12 @@ tty_set_cursor (struct frame *f)
 	  int x =3D window_to_frame_hpos (w, w->cursor.hpos);
 	  int y =3D window_to_frame_vpos (w, w->cursor.vpos);
=20
+	  /* cursor.hpos is TEXT_AREA-relative; add the left scroll bar
+	     width so the terminal cursor lands in the text area, not in
+	     the scroll bar column.  Mini-windows have no scroll bar even
+	     when the frame does, so skip the offset for them.  */
+	  if (!MINI_WINDOW_P (w))
+	    x +=3D WINDOW_LEFT_SCROLL_BAR_COLS (w);
 	  x +=3D max (0, w->left_margin_cols);
 	  cursor_to (f, y, x);
 	}
diff --git a/src/frame.c b/src/frame.c
index 2c0a27cf47c..ffbb234d6a1 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -204,7 +204,9 @@ frame_inhibit_resize (struct frame *f, bool horizontal,=
 Lisp_Object parameter)
   return (EQ (frame_inhibit_implied_resize, Qforce)
 	  || (f->after_make_frame
 #ifdef USE_GTK
-	      && f->tool_bar_resized
+	      /* TTY and MSDOS frames have no tool bar and tool_bar_resized
+		 is never set for them; handle them like non-GTK builds.  */
+	      && (f->tool_bar_resized || FRAME_TERMCAP_P (f) || FRAME_MSDOS_P (f))
 #endif
 	      && (EQ (frame_inhibit_implied_resize, Qt)
 		  || (CONSP (frame_inhibit_implied_resize)
@@ -1184,8 +1186,8 @@ make_frame (bool mini_p)
   f->new_width =3D -1;
   f->new_height =3D -1;
   f->no_special_glyphs =3D false;
-#ifdef HAVE_WINDOW_SYSTEM
   f->vertical_scroll_bar_type =3D vertical_scroll_bar_none;
+#ifdef HAVE_WINDOW_SYSTEM
   f->horizontal_scroll_bars =3D false;
   f->want_fullscreen =3D FULLSCREEN_NONE;
   f->undecorated =3D false;
@@ -1448,11 +1450,7 @@ make_initial_frame (void)
=20
   FRAME_FOREGROUND_PIXEL (f) =3D FACE_TTY_DEFAULT_FG_COLOR;
   FRAME_BACKGROUND_PIXEL (f) =3D FACE_TTY_DEFAULT_BG_COLOR;
-
-#ifdef HAVE_WINDOW_SYSTEM
   f->vertical_scroll_bar_type =3D vertical_scroll_bar_none;
-  f->horizontal_scroll_bars =3D false;
-#endif
=20
   /* The default value of menu-bar-mode is t.  */
   set_menu_bar_lines (f, make_fixnum (1), Qnil);
@@ -4186,13 +4184,13 @@ DEFUN ("frame-parameter", Fframe_parameter, Sframe_=
parameter, 2, 2, 0,
       /* Avoid consing in frequent cases.  */
       if (EQ (parameter, Qname))
 	value =3D f->name;
-#ifdef HAVE_WINDOW_SYSTEM
       /* These are used by vertical motion commands.  */
       else if (EQ (parameter, Qvertical_scroll_bars))
 	value =3D (f->vertical_scroll_bar_type =3D=3D vertical_scroll_bar_none
 		 ? Qnil
 		 : (f->vertical_scroll_bar_type =3D=3D vertical_scroll_bar_left
 		    ? Qleft : Qright));
+#ifdef HAVE_WINDOW_SYSTEM
       else if (EQ (parameter, Qhorizontal_scroll_bars))
 	value =3D f->horizontal_scroll_bars ? Qt : Qnil;
       else if (EQ (parameter, Qline_spacing) && f->extra_line_spacing =3D=
=3D 0)
@@ -4301,6 +4299,8 @@ DEFUN ("modify-frame-parameters", Fmodify_frame_param=
eters,
 	  if (EQ (prop, Qforeground_color)
 	      || EQ (prop, Qbackground_color))
 	    update_face_from_frame_parameter (f, prop, val);
+	  else if (EQ (prop, Qvertical_scroll_bars))
+	    tty_set_vertical_scroll_bars (f, val);
 	}
=20
       if (is_tty_child_frame (f))
diff --git a/src/frame.h b/src/frame.h
index 091b112e8b9..1e2775c6a54 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -452,6 +452,11 @@ #define EMACS_FRAME_H
      via mouse clicks or by moving the mouse into it.  */
   bool_bf no_accept_focus : 1;
=20
+  /* If not vertical_scroll_bar_none, we should actually display the
+     scroll bars of this type on this frame.  Available on all frame
+     types, including TTY frames for character-based scroll bars.  */
+  ENUM_BF (vertical_scroll_bar_type) vertical_scroll_bar_type : 2;
+
 #ifdef HAVE_WINDOW_SYSTEM
   /* True if this frame is a tooltip frame.  */
   bool_bf tooltip : 1;
@@ -459,10 +464,6 @@ #define EMACS_FRAME_H
   /* See FULLSCREEN_ enum on top.  */
   ENUM_BF (fullscreen_type) want_fullscreen : 4;
=20
-  /* If not vertical_scroll_bar_none, we should actually
-     display the scroll bars of this type on this frame.  */
-  ENUM_BF (vertical_scroll_bar_type) vertical_scroll_bar_type : 2;
-
   /* Nonzero if we should actually display horizontal scroll bars on this =
frame.  */
   bool_bf horizontal_scroll_bars : 1;
=20
@@ -1211,9 +1212,8 @@ #define FRAME_INSERTN_COST(f) (f)->insert_n_lines_cost
 #define FRAME_DELETEN_COST(f) (f)->delete_n_lines_cost
 #define FRAME_FOCUS_FRAME(f) f->focus_frame
=20
-#ifdef HAVE_WINDOW_SYSTEM
 /* This frame slot says whether scroll bars are currently enabled for fram=
e F,
-   and which side they are on.  */
+   and which side they are on.  Works for all frame types including TTY.  =
*/
 #define FRAME_VERTICAL_SCROLL_BAR_TYPE(f) ((f)->vertical_scroll_bar_type)
 #define FRAME_HAS_VERTICAL_SCROLL_BARS(f) \
   ((f)->vertical_scroll_bar_type !=3D vertical_scroll_bar_none)
@@ -1221,14 +1221,6 @@ #define FRAME_HAS_VERTICAL_SCROLL_BARS_ON_LEFT(f) \
   ((f)->vertical_scroll_bar_type =3D=3D vertical_scroll_bar_left)
 #define FRAME_HAS_VERTICAL_SCROLL_BARS_ON_RIGHT(f) \
   ((f)->vertical_scroll_bar_type =3D=3D vertical_scroll_bar_right)
-#else /* not HAVE_WINDOW_SYSTEM */
-/* If there is no window system, there are no scroll bars.  */
-#define FRAME_VERTICAL_SCROLL_BAR_TYPE(f) \
-  ((void) (f), vertical_scroll_bar_none)
-#define FRAME_HAS_VERTICAL_SCROLL_BARS(f) ((void) (f), 0)
-#define FRAME_HAS_VERTICAL_SCROLL_BARS_ON_LEFT(f) ((void) (f), 0)
-#define FRAME_HAS_VERTICAL_SCROLL_BARS_ON_RIGHT(f) ((void) (f), 0)
-#endif /* HAVE_WINDOW_SYSTEM */
=20
 INLINE struct frame *
 FRAME_PARENT_FRAME (struct frame *f)
@@ -1503,6 +1495,7 @@ window_system_available (struct frame *f)
=20
 extern WINDOW_SYSTEM_RETURN void check_window_system (struct frame *);
 struct frame *decode_tty_frame (Lisp_Object frame);
+extern void tty_set_vertical_scroll_bars (struct frame *, Lisp_Object);
 extern void frame_make_pointer_invisible (struct frame *);
 extern void frame_make_pointer_visible (struct frame *);
 extern struct frame *root_frame (struct frame *f);
diff --git a/src/term.c b/src/term.c
index 354375085e2..e35eda745dd 100644
--- a/src/term.c
+++ b/src/term.c
@@ -62,6 +62,13 @@
 #include "w32term.h"
 #endif
=20
+static void tty_set_vertical_scroll_bar (struct window *w,
+					 int portion, int whole, int position);
+static void tty_condemn_scroll_bars (struct frame *f);
+static void tty_redeem_scroll_bar (struct window *w);
+static void tty_judge_scroll_bars (struct frame *f);
+static void tty_set_scroll_bar_default_width (struct frame *f);
+
 #ifndef HAVE_ANDROID
=20
 static void tty_set_scroll_region (struct frame *f, int start, int stop);
@@ -976,7 +983,7 @@ tty_write_glyphs (struct frame *f, struct glyph *string=
, int len)
      since that would scroll the whole frame on some terminals.  */
   if (AutoWrap (tty)
       && curY (tty) + 1 =3D=3D FRAME_TOTAL_LINES (f)
-      && curX (tty) + len =3D=3D FRAME_COLS (f)
+      && curX (tty) + len =3D=3D FrameCols (tty)
       && len > 0)
     {
       /* If writing only one glyph in the last column, make that two so
@@ -4335,7 +4342,15 @@ set_tty_hooks (struct terminal *terminal)
   terminal->delete_terminal_hook =3D &delete_tty;
=20
   terminal->frame_raise_lower_hook =3D tty_raise_lower_frame;
-  /* Other hooks are NULL by default.  */
+
+  /* Scroll Bar Hooks */
+  terminal->set_vertical_scroll_bar_hook =3D tty_set_vertical_scroll_bar;
+  terminal->condemn_scroll_bars_hook =3D tty_condemn_scroll_bars;
+  terminal->redeem_scroll_bar_hook =3D tty_redeem_scroll_bar;
+  terminal->judge_scroll_bars_hook =3D tty_judge_scroll_bars;
+  terminal->set_scroll_bar_default_width_hook =3D tty_set_scroll_bar_defau=
lt_width;
+
+   /* Other hooks are NULL by default.  */
 }
=20
 /* If FD is the controlling terminal, drop it.  */
@@ -4378,6 +4393,96 @@ dissociate_if_controlling_tty (int fd)
 _Noreturn
 #endif
=20
+/* Set the vertical scroll bar for window W to show PORTION/WHOLE of
+   the buffer, with scroll position POSITION.  Stores the info in the
+   window's vertical_scroll_bar slot as [portion whole position] for
+   later rendering by tty_apply_scroll_bar_glyphs.  */
+static void
+tty_set_vertical_scroll_bar (struct window *w,
+			     int portion, int whole, int position)
+{
+  struct frame *f =3D XFRAME (WINDOW_FRAME (w));
+
+  if (! FRAME_TERMCAP_P (f))
+    return;
+
+  /* Store scroll bar parameters for later rendering.  We can't render
+     here because build_frame_matrix hasn't been called yet and would
+     overwrite our glyphs.  Instead, tty_apply_scroll_bar_glyphs (called
+     from update_tty_frame after build_frame_matrix) will do the rendering=
=2E  */
+  w->vertical_scroll_bar =3D make_vector (3, make_fixnum (0));
+  ASET (w->vertical_scroll_bar, 0, make_fixnum (portion));
+  ASET (w->vertical_scroll_bar, 1, make_fixnum (whole));
+  ASET (w->vertical_scroll_bar, 2, make_fixnum (position));
+}
+
+static void
+tty_condemn_scroll_bars (struct frame *f)
+{
+}
+
+static void
+tty_redeem_scroll_bar (struct window *w)
+{
+}
+
+static void
+tty_judge_scroll_bars (struct frame *f)
+{
+}
+
+/* Set scroll bar default width for TTY frames: 1 character column.  */
+static void
+tty_set_scroll_bar_default_width (struct frame *f)
+{
+  /* For TTY, scroll bars are 1 character wide.  */
+  FRAME_CONFIG_SCROLL_BAR_WIDTH (f) =3D 1;
+  FRAME_CONFIG_SCROLL_BAR_COLS (f) =3D 1;
+}
+
+/* Called from frame.c when the vertical-scroll-bars parameter is changed
+   for a TTY frame.  */
+void
+tty_set_vertical_scroll_bars (struct frame *f, Lisp_Object arg)
+{
+  if (! FRAME_TERMCAP_P (f))
+    return;
+
+  enum vertical_scroll_bar_type new_type
+    =3D (NILP (arg)
+       ? vertical_scroll_bar_none
+       : EQ (Qleft, arg)
+       ? vertical_scroll_bar_left
+       : EQ (Qright, arg)
+       ? vertical_scroll_bar_right
+       : EQ (Qleft, Vdefault_frame_scroll_bars)
+       ? vertical_scroll_bar_left
+       : EQ (Qright, Vdefault_frame_scroll_bars)
+       ? vertical_scroll_bar_right
+       : vertical_scroll_bar_none);
+
+  if (FRAME_VERTICAL_SCROLL_BAR_TYPE (f) =3D=3D new_type)
+    return;
+
+  FRAME_VERTICAL_SCROLL_BAR_TYPE (f) =3D new_type;
+
+  /* Set the scroll bar width if enabling scroll bars.  */
+  if (new_type !=3D vertical_scroll_bar_none
+      && FRAME_TERMINAL (f)->set_scroll_bar_default_width_hook)
+    (*FRAME_TERMINAL (f)->set_scroll_bar_default_width_hook) (f);
+
+  /* Trigger a full redisplay.  adjust_frame_size recalculates window
+     text areas to account for the newly-reserved scroll bar columns.
+     When only the scroll bar side changes (e.g. right=E2=86=92left) all f=
rame
+     dimensions stay identical and adjust_frame_size returns early
+     without calling adjust_frame_glyphs.  Call it explicitly so that
+     the glyph matrix TEXT_AREA pointers (shifted by sbl/sbr) are
+     always updated to match the new scroll bar layout.  */
+  adjust_frame_size (f, -1, -1, 3, 0, Qvertical_scroll_bars);
+  adjust_frame_glyphs (f);
+  SET_FRAME_GARBAGED (f);
+}
+
 struct terminal *
 init_tty (const char *name, const char *terminal_type, bool must_succeed)
 {
diff --git a/src/window.c b/src/window.c
index 3dbf1530d78..066c7872c84 100644
--- a/src/window.c
+++ b/src/window.c
@@ -1100,7 +1100,19 @@ window_body_width (struct window *w, enum window_bod=
y_unit pixelwise)
   int width =3D (w->pixel_width
 	       - WINDOW_RIGHT_DIVIDER_WIDTH (w)
 	       - (WINDOW_HAS_VERTICAL_SCROLL_BAR (w)
-		  ? WINDOW_SCROLL_BAR_AREA_WIDTH (w)
+		  ? (WINDOW_SCROLL_BAR_AREA_WIDTH (w)
+		     /* On TTY frames a right scroll bar acts as the window
+			separator, so we reserve one extra character column
+			for the '|' border glyph that
+			build_frame_matrix_from_leaf_window inserts.  This
+			keeps the border visually separate from the scroll bar
+			indicator (thumb/track) even when the buffer is short
+			enough to fill the bar with a full-height thumb.
+			Left scroll bars already have a dedicated border column
+			(the condition below), so this adjustment is right-only.  */
+		     + (!FRAME_WINDOW_P (f)
+			&& !WINDOW_RIGHTMOST_P (w)
+			&& WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_RIGHT (w)))
 		  : (/* A vertical bar is either 1 or 0.  */
 		     !FRAME_WINDOW_P (f)
 		     && !WINDOW_RIGHTMOST_P (w)
diff --git a/src/xfaces.c b/src/xfaces.c
index 010b0e1847e..2f4d9f7e65e 100644
--- a/src/xfaces.c
+++ b/src/xfaces.c
@@ -5212,6 +5212,7 @@ lookup_basic_face (struct window *w, struct frame *f,=
 int face_id)
     case INTERNAL_BORDER_FACE_ID:	name =3D Qinternal_border; 	break;
     case CHILD_FRAME_BORDER_FACE_ID:	name =3D Qchild_frame_border; 	break;
     case MARGIN_FACE_ID:		name =3D Qmargin;			break;
+    case SCROLL_BAR_THUMB_FACE_ID:	name =3D Qscroll_bar_thumb;	break;
=20
     default:
       emacs_abort (); /* the caller is supposed to pass us a basic face id=
 */
@@ -5980,6 +5981,7 @@ realize_basic_faces (struct frame *f)
       realize_named_face (f, Qtab_line_active, TAB_LINE_ACTIVE_FACE_ID);
       realize_named_face (f, Qtab_line_inactive, TAB_LINE_INACTIVE_FACE_ID=
);
       realize_named_face (f, Qmargin, MARGIN_FACE_ID);
+      realize_named_face (f, Qscroll_bar_thumb, SCROLL_BAR_THUMB_FACE_ID);
       unbind_to (count, Qnil);
=20
       /* Reflect changes in the `menu' face in menu bars.  */
@@ -7544,6 +7546,7 @@ syms_of_xfaces (void)
   DEFSYM (Qheader_line_inactive, "header-line-inactive");
   DEFSYM (Qheader_line_active, "header-line-active");
   DEFSYM (Qscroll_bar, "scroll-bar");
+  DEFSYM (Qscroll_bar_thumb, "scroll-bar-thumb");
   DEFSYM (Qmenu, "menu");
   DEFSYM (Qcursor, "cursor");
   DEFSYM (Qborder, "border");

--36I/gurMzNR42vlK--

--JZGPDlq/4kg2H8cZ
Content-Type: application/pgp-signature; name="signature.asc"

-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEEDXVee01gWrPIdWPRwgRAue7sYQMFAmn9+oIACgkQwgRAue7s
YQO7uRAAolB4AHspvZNAyJ2Nr/ymqW3IfSdSaF8H0NvJhLVOojJuUNIidy2f1DVo
ugo56tEHXqEGykM0KsdoObXeuA0zbP+kuYTXMRfqfjNk4Js4lIBrGcI6eGaVgq4R
xvObIlCwjLnT+NozlTYM/nhTLUoT4mHg3NlYirsFAVM1Z4JeNuvfMYLmUOL90WkU
k/tgGVorLf2c0lNmkVa5vfrpGrpmwPAm4qCRxxd1FC7ILvWP26jB05EvwA1RQuRh
BPWNpMcU/4QAKg+Cee8pa6ff4ZDt/OWRPZN9SczPEHG35eY20inbesOlQtYmyVNj
rs+znhmhGrgoj4hZ6Hn4KdLUjhiTsB0pA4pSQX+NGaW2D/Mkz9wsMDkUBdfqw3I6
fOHN6nn2NBSsal3SGmj5LiyzX0wMbiCPNrJsIlDcf6OTt+eBrlu+vCbmhUn/YrJt
Mwgwuk9diKWrjyaMmwmZx0Uga0Ieb4GIw5zVJNxAaIeS6r0fwS3STN+ZIYNsrNTN
1Ps2sy0lidGGv7sp8iPBoDnBngZI6YNYfCzZ2VU6EExT8kSUVIUE9BBW0ydCLmE9
v6gqT5NdQkSDK5pVCHmjoig2mL5oc3msMWNOkT1vQ00E+JISp24jH8xNCwzVkZUE
CfiUiEhz8egqIBX8jvjUnydWbJ+clk8s9HSIijaSo8H3EkYbmew=
=f+hp
-----END PGP SIGNATURE-----

--JZGPDlq/4kg2H8cZ--




Acknowledgement sent to Michael Grant <mgrant@HIDDEN>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs@HIDDEN. Full text available.
Report forwarded to bug-gnu-emacs@HIDDEN:
bug#80993; Package emacs. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Sat, 6 Jun 2026 09:45:02 UTC

GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997 nCipher Corporation Ltd, 1994-97 Ian Jackson.