Package: libncursesw6
Version: 6.4+20231121-1
Severity: normal

Dear Maintainer,

I am attaching a repro.c program that when built with
  cc -O3 -g -Wall -Wextra -DNCURSES_WIDECHAR -D_GNU_SOURCE  repro.c
  -lncursesw -ltinfo -o repro
presents a UI in the form of
  first line
  -> L1
     L2
     L3
     L4
     L5
  last line

Mousing is enabled, and if you click on a line the cursor on the left
moves to select it.

The first line and last line are on stdscr, the rest of the screen is
urlswin = newpad(LINES, COLS), rendered via
  pnoutrefresh(urlswin, /**/ url[first_on_page].cursor_y + fudge, 0, /**/ 1 
/*title line*/, 0, LINES - 2, COLS);

On KEY_MOUSE and BUTTON1_CLICKED:
  getmouse(&ev);
  if(!wmouse_trafo(urlswin, &ev.y, &ev.x, false))
    break;
and the line targeted is found and selected.

Clicking on first line correctly hits the break.
Clicking on last line incorrectly continues on.

This shows as scrolling to the next screen
(since the line selected is L6, which would be under last line).

This /only happens/ if the lines go all the way to the line above last.
If you click on any line below a non-full screen (s/1000/10/ or scroll down),
nothing happens.
Is this a weird thing with the edge of the screen?
Or with how pnoutrefresh accounts for the undrawn parts of the pad?

Best,
наб

-- System Information:
Debian Release: 12.4
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable-security'), (500, 
'stable-debug'), (500, 'stable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 6.1.0-9-amd64 (SMP w/24 CPU threads; PREEMPT)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_FIRMWARE_WORKAROUND, 
TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE
Locale: LANG=en_GB.UTF-8, LC_CTYPE=en_GB.UTF-8 (charmap=UTF-8), 
LANGUAGE=en_GB:en
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages libncursesw6 depends on:
ii  libc6      2.36-9+deb12u3
ii  libtinfo6  6.4+20231121-1

Versions of packages libncursesw6 recommends:
ii  libgpm2  1.20.7-10+b1

libncursesw6 suggests no packages.

-- no debconf information
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <ncurses.h>
#include <regex.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


struct url {
	char * url;
	size_t urllen;
	int cursor_y, cursor_x;
	int last_y;
};


int main() {
	setlocale(LC_ALL, "");

	size_t urlcount  = 1000;
	struct url * url = reallocarray(NULL, urlcount, sizeof(*url));
	for(size_t i = 0; i < urlcount; ++i)
		url[i].urllen = asprintf(&url[i].url, "L%zu", i);

	size_t current = 0, oldcurrent = 0;

	initscr();

	cbreak();
	noecho();
	curs_set(1);
	keypad(stdscr, TRUE);
	mousemask(BUTTON1_CLICKED | BUTTON5_PRESSED | BUTTON4_PRESSED, NULL);
	mouseinterval(5);

//                                      title line
#define urlswin_logical_height (LINES - 1 - 1)
	//                                        status line
	WINDOW * urlswin = newpad(LINES, COLS);
	if(!urlswin)
		return endwin(), 1;

	int page_of_current  = 0;
	size_t first_on_page = 0;
	int fudge            = 0;
	for(bool done = false; !done;) {
		{
			clear();
			mvwaddstr(stdscr, 0, 0, "first line");
			mvwaddstr(stdscr, LINES - 1, 0, "last line");
			wnoutrefresh(stdscr);

			wclear(urlswin);
			wmove(urlswin, 0, 0);
			for(size_t i = 0; i < urlcount; ++i) {
#define EXPAND()                                  \
	{                                               \
		int sizey, sizex;                             \
		getmaxyx(urlswin, sizey, sizex);              \
		if(wresize(urlswin, sizey * 2, sizex) == ERR) \
			break;                                      \
	}
#define EXPANDONSCROLL(...)         \
	if(__VA_ARGS__ != OK) {           \
		wmove(urlswin, starty, startx); \
		EXPAND();                       \
		goto again;                     \
	}
				int starty, startx;
				getyx(urlswin, starty, startx);

			again:
				EXPANDONSCROLL(waddstr(urlswin, "   "));
				getyx(urlswin, url[i].cursor_y, url[i].cursor_x);
				--url[i].cursor_x;
				EXPANDONSCROLL(wprintw(urlswin, "%4zu", i + 1));
				EXPANDONSCROLL(waddch(urlswin, ' '));
				EXPANDONSCROLL(waddnstr(urlswin, url[i].url, url[i].urllen));
				url[i].last_y = getcury(urlswin);

				if(i != urlcount - 1)
					if(waddch(urlswin, '\n') != OK) {
						EXPAND();
						waddch(urlswin, '\n');
					}
			}

			mvwaddstr(urlswin, url[oldcurrent].cursor_y, url[oldcurrent].cursor_x - 2, "  ");
			wstandout(urlswin);
			mvwaddstr(urlswin, url[current].cursor_y, url[current].cursor_x - 2, "->");
			wstandend(urlswin);

			page_of_current = url[current].cursor_y / urlswin_logical_height;
			first_on_page   = 0;
			while(url[first_on_page].cursor_y / urlswin_logical_height != page_of_current)
				++first_on_page;
			fudge = 0;  // try to get the last-URL-on-the-page to be included when it's selected
			if(url[current].last_y - url[first_on_page].cursor_y > urlswin_logical_height)
				// unclear why +1 needed here; this means we overscroll (sometimes)
				// compare the first screen on a rows 54; cols 172; teletype of
				//   ./urlview text.uv <(for i in A B C D E F H I J K L M; do tr Q $i < text.uv; done)
				// and
				//   ./urlview text.uv <(for i in A B C D E F H I J K L M; do tr Q $i < text.uv; done | fold -w 300)
				// the last link on the first screen appear to have a completely identical last_y, but the former is taller by a line?
				fudge = (url[current].last_y - url[first_on_page].cursor_y) - urlswin_logical_height + 1;
			pnoutrefresh(urlswin, /**/ url[first_on_page].cursor_y + fudge, 0, /**/ 1 /*title line*/, 0, urlswin_logical_height, COLS);
			doupdate();

			oldcurrent = current;

			// same projection as pnoutrefresh() above
			move(url[current].cursor_y - (url[first_on_page].cursor_y + fudge) + 1 /*title line*/, url[current].cursor_x - 0 + 0);
		}

		errno = 0;
		switch(getch()) {
			case KEY_MOUSE: {
				MEVENT ev;
				getmouse(&ev);

				if(ev.bstate & (BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED)) {
					if(!wmouse_trafo(urlswin, &ev.y, &ev.x, false))
						break;
					ev.y += url[first_on_page].cursor_y + fudge;

					for(size_t hit = first_on_page; hit != urlcount; ++hit)
						if(ev.y >= url[hit].cursor_y && ev.y <= url[hit].last_y) {
							current = hit;
							break;
						}
				}
			} break;
			case ERR:
				if(errno == EINTR)
					continue;
				else if(errno)
					err(1, "getch()");
				__attribute__((fallthrough));
			case 'q':
				done = true;
				break;
		}
	}

	endwin();
}

Attachment: signature.asc
Description: PGP signature

Reply via email to