9front - general discussion about 9front
 help / color / mirror / Atom feed
* [9front] Mouse clipping patch
@ 2021-07-12 19:50 José Miguel Sánchez García
  2021-07-13  2:36 ` ori
  2021-07-14 22:57 ` ori
  0 siblings, 2 replies; 42+ messages in thread
From: José Miguel Sánchez García @ 2021-07-12 19:50 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 2822 bytes --]

Hello everyone,

After talking with some people about the need of proper mouse grabbing
for some programs (think about vncv, qwx's quake ports, screenlock),
I've implemented it. The devmouse and libdraw patches modify
/dev/mousectl to accept two new commands: "clip x0 y0 x1 y1" and
"release".

The "clip" command confines the mouse inside the given rect. It must
be a subrect of the current screen, otherwise the operation will fail.
Release undoes that operation (and it's a no-op if there is no grab
active). Subsequent clip and release commands override each other, and
a grab is automatically released if the fd that made it is closed (in
order to prevent a grab to trap the mouse after a program crash).

The rio patch modifies rio so that it multiplexes the new grabbing
capabilities among its windows. This is transparent to the underlying
program: they can clip and release the mouse at will (provided that
the clipping rect lies inside their window). Mouse grab in rio follows
this rules:

- The grab is released after a Reshape event. It's up to the running
program to recover that grab in its resize handler. That guarantees
the active grab to be always correct (and gives the program a chance
to fix it once it isn't anymore)
- Only the current window's grab is active at any moment. If some
other window is grabbing the mouse, it doesn't have any effect. This
keeps rio transparent to the underlying programs grabbing the mouse:
they all think they have the global grab at the same time, but focus
alone is what ultimately decides which one is active, like it already
does for keyboard/mouse input. This proves to be useful when windows
are opened while a grab is held (after a plumb, or after spawning a
new window).
- Rio menus temporarily release the mouse, so options outside of the
grabbing rect can be accessed. The grab is restored if needed after
the menu is closed.
- Windows holding the mouse have a different border color, so they can
be clearly identified. Currently it overrides the blue hold color,
although I believe that the event of having a text window grabbing the
mouse and also holding the input is unlikely.

In order to make these changes possible, I had to alter the behavior
of rio when handling a reshape done using the window borders: right
now it tops the window, and then handles the reshape. I changed that
behavior to be the opposite: first handle the resize and then top the
window. This allows resizing unfocused windows which would grab the
mouse when topped. As far as I know, and after some testing, it looks
like I didn't break anything.

I'm open to feedback, as there has been interesting alternatives
proposed during the development of this patch. I hope that, by
bringing my work to the public, any improvements can be shared and
reviewed by everyone.

Cheers,
jmi2k

[-- Attachment #2: libdraw-mousegrab.diff --]
[-- Type: application/octet-stream, Size: 1845 bytes --]

--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/include/mouse.h
+++ /sys/include/mouse.h
@@ -22,6 +22,7 @@
 
 	char		*file;
 	int		mfd;		/* to mouse file */
+	int		mctlfd;		/* to mousectl file */
 	int		cfd;		/* to cursor file */
 	int		pid;		/* of slave proc */
 	Image*	image;	/* of associated window/display */
@@ -39,6 +40,8 @@
  */
 extern Mousectl*	initmouse(char*, Image*);
 extern void		moveto(Mousectl*, Point);
+extern int		clipmouse(Mousectl*, Rectangle);
+extern void		releasemouse(Mousectl*);
 extern int			readmouse(Mousectl*);
 extern void		closemouse(Mousectl*);
 extern void		setcursor(Mousectl*, Cursor*);
--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree/sys/src/libdraw/mouse.c
+++ sys/src/libdraw/mouse.c
@@ -12,14 +12,29 @@
 	m->xy = pt;
 }
 
+int
+clipmouse(Mousectl *m, Rectangle clipr)
+{
+	return fprint(m->mctlfd, "clip %d %d %d %d",
+		clipr.min.x, clipr.min.y,
+		clipr.max.x, clipr.max.y);
+}
+
 void
+releasemouse(Mousectl *m)
+{
+	fprint(m->mctlfd, "release");
+}
+
+void
 closemouse(Mousectl *mc)
 {
 	if(mc == nil)
 		return;
 	close(mc->mfd);
+	close(mc->mctlfd);
 	close(mc->cfd);
-	mc->mfd = mc->cfd = -1;
+	mc->mfd = mc->mctlfd = mc->cfd = -1;
 	threadint(mc->pid);
 }
 
@@ -106,18 +121,24 @@
 		free(mc);
 		return nil;
 	}
-	t = malloc(strlen(file)+16);
+	n = strlen(file)+16;
+	t = malloc(n);
 	if (t == nil) {
 		close(mc->mfd);
 		free(mc);
 		return nil;
 	}
-	strcpy(t, file);
+	seprint(t, t+n, "%s", file);
 	sl = utfrrune(t, '/');
 	if(sl)
-		strcpy(sl, "/cursor");
+		seprint(sl, t+n, "/mousectl");
 	else
-		strcpy(t, "/dev/cursor");
+		seprint(t, t+n, "/dev/mousectl");
+	mc->mctlfd = open(t, ORDWR|OCEXEC);
+	if(sl)
+		seprint(sl, t+n, "/cursor");
+	else
+		seprint(t, t+n, "/dev/cursor");
 	mc->cfd = open(t, ORDWR|OCEXEC);
 	free(t);
 	mc->image = i;

[-- Attachment #3: rio-mousegrab.diff --]
[-- Type: application/octet-stream, Size: 7944 bytes --]

--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/cmd/rio/dat.h
+++ /sys/src/cmd/rio/dat.h
@@ -10,6 +10,7 @@
 	Qlabel,
 	Qkbd,
 	Qmouse,
+	Qmousectl,
 	Qnew,
 	Qscreen,
 	Qsnarf,
@@ -154,6 +155,9 @@
 	 * Now they're always the same but the code doesn't assume so.
 	*/
 	Rectangle		screenr;	/* screen coordinates of window */
+	Rectangle	grabr;
+	Fid			*grabfid;
+	int			grab;
 	int			resized;
 	int			wctlready;
 	Rectangle		lastsr;
@@ -304,11 +308,14 @@
 Image	*lightholdcol;
 Image	*paleholdcol;
 Image	*paletextcol;
+Image	*grabcol;
+Image	*lightgrabcol;
 Image	*sizecol;
 int	reverse;	/* there are no pastel paints in the dungeons and dragons world -- rob pike */
 
 Window	**window;
 Window	*wkeyboard;	/* window of simulated keyboard */
+Window	*wgrab;
 int		nwindow;
 int		snarffd;
 int		gotscreen;
--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/cmd/rio/data.c
+++ /sys/src/cmd/rio/data.c
@@ -195,6 +195,8 @@
 	lightholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreyblue);
 	paleholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreyblue);
 	paletextcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x666666FF^reverse);
+	grabcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xB22222FF);
+	lightgrabcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCD5C5CFF);
 	sizecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DRed);
 
 	if(reverse == 0)
--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/cmd/rio/fns.h
+++ /sys/src/cmd/rio/fns.h
@@ -5,6 +5,7 @@
 int	writewctl(Xfid*, char*);
 Window *new(Image*, int, int, int, char*, char*, char**);
 void	riosetcursor(Cursor*);
+void	updategrab(void);
 int	min(int, int);
 int	max(int, int);
 Rune*	strrune(Rune*, Rune);
--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/cmd/rio/fsys.c
+++ /sys/src/cmd/rio/fsys.c
@@ -30,6 +30,7 @@
 	{ "label",		QTFILE,	Qlabel,		0600 },
 	{ "kbd",	QTFILE,	Qkbd,		0600 },
 	{ "mouse",	QTFILE,	Qmouse,		0600 },
+	{ "mousectl",	QTFILE,	Qmousectl,		0600 },
 	{ "screen",		QTFILE,	Qscreen,		0400 },
 	{ "snarf",		QTFILE,	Qsnarf,		0600 },
 	{ "text",		QTFILE,	Qtext,		0600 },
--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/cmd/rio/rio.c
+++ /sys/src/cmd/rio/rio.c
@@ -327,6 +327,27 @@
 }
 
 void
+updategrab(void)
+{
+	Window *w;
+
+	if(input == nil || !input->grab || menuing || sweeping)
+		w = nil;
+	else
+		w = input;
+	if(w == wgrab)
+		return;
+	if(w != nil){
+		clipmouse(mousectl, w->grabr);
+		wsendctlmesg(w, Repaint, ZR, nil);
+	}else
+		releasemouse(mousectl);
+	if(wgrab)
+		wsendctlmesg(wgrab, Repaint, ZR, nil);
+	wgrab = w;
+}
+
+void
 killprocs(void)
 {
 	int i;
@@ -489,6 +510,9 @@
 				wtopme(wkeyboard);
 				winput = wkeyboard;
 			}
+			w = wpointto(mouse->xy);
+			if(sending == FALSE && !scrolling && w!=nil && !w->deleted && inborder(w->screenr, mouse->xy))
+				moving = TRUE;
 			if(winput!=nil && !winput->deleted && winput->i!=nil){
 				/* convert to logical coordinates */
 				xy.x = mouse->xy.x + (winput->i->r.min.x-winput->screenr.min.x);
@@ -505,10 +529,7 @@
 					scrolling = mouse->buttons;
 				else
 					scrolling = mouse->buttons && ptinrect(xy, winput->scrollr);
-				/* topped will be zero or less if window has been bottomed */
-				if(sending == FALSE && !scrolling && inborder(winput->screenr, mouse->xy) && winput->topped>0)
-					moving = TRUE;
-				else if(inside && (scrolling || winput->mouseopen || (mouse->buttons&1)))
+				if(inside && (scrolling || winput->mouseopen || (mouse->buttons&1)))
 					sending = TRUE;
 			}else
 				sending = FALSE;
@@ -523,21 +544,20 @@
 				continue;
 			}
 			if(moving && (mouse->buttons&7)){
-				incref(winput);
+				incref(w);
 				sweeping = TRUE;
 				if(mouse->buttons & 3)
-					i = bandsize(winput);
+					i = bandsize(w);
 				else
-					i = drag(winput);
+					i = drag(w);
 				sweeping = FALSE;
 				if(i != nil){
-					wcurrent(winput);
-					wsendctlmesg(winput, Reshaped, i->r, i);
+					wcurrent(w);
+					wsendctlmesg(w, Reshaped, i->r, i);
 				}
-				wclose(winput);
+				wclose(w);
 				continue;
 			}
-			w = wpointto(mouse->xy);
 			if(w!=nil && inborder(w->screenr, mouse->xy))
 				riosetcursor(corners[whichcorner(w->screenr, mouse->xy)]);
 			else
@@ -715,6 +735,7 @@
 		menu3str[i] = nil;
 	}
 	sweeping = TRUE;
+	updategrab();
 	switch(i = menuhit(3, mousectl, &menu3, wscreen)){
 	case -1:
 		break;
@@ -744,6 +765,7 @@
 		break;
 	}
 	sweeping = FALSE;
+	updategrab();
 }
 
 void
@@ -753,6 +775,8 @@
 		menu2str[Scroll] = "noscroll";
 	else
 		menu2str[Scroll] = "scroll";
+	sweeping = TRUE;
+	updategrab();
 	switch(menuhit(2, mousectl, &menu2, wscreen)){
 	case Cut:
 		wsnarf(w);
@@ -787,6 +811,8 @@
 			wshow(w, w->nr);
 		break;
 	}
+	sweeping = FALSE;
+	updategrab();
 	flushimage(display, 1);
 	wsendctlmesg(w, Wakeup, ZR, nil);
 }
--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/cmd/rio/wind.c
+++ /sys/src/cmd/rio/wind.c
@@ -302,6 +302,11 @@
 			col = holdcol;
 		else
 			col = paleholdcol;
+	}else if(w->grab){
+		if(type == Selborder)
+			col = grabcol;
+		else
+			col = lightgrabcol;
 	}else{
 		if(type == Selborder)
 			col = titlecol;
@@ -358,6 +363,8 @@
 	w->scrollr.max.x = r.min.x+Scrollwid;
 	w->lastsr = ZR;
 	r.min.x += Scrollwid+Scrollgap;
+	w->grab = FALSE;
+	updategrab();
 	frclear(w, FALSE);
 	frinit(w, r, w->font, w->i, cols);
 	wsetcols(w, w == input);
@@ -1264,6 +1271,7 @@
 	w->gone = chancreate(sizeof(char*), 0);
 	w->scrollr = r;
 	w->scrollr.max.x = r.min.x+Scrollwid;
+	w->grab = FALSE;
 	w->lastsr = ZR;
 	r.min.x += Scrollwid+Scrollgap;
 	frinit(w, r, font, i, cols);
@@ -1306,6 +1314,7 @@
 		input = nil;
 		riosetcursor(nil);
 	}
+	updategrab();
 	if(w == wkeyboard)
 		wkeyboard = nil;
 	for(i=0; i<nhidden; i++)
@@ -1390,6 +1399,7 @@
 
 			sendp(c, w);
 		}
+		updategrab();
 		if(w->i==nil || Dx(w->screenr)<=0)
 			break;
 		wrepaint(w);
--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/cmd/rio/xfid.c
+++ /sys/src/cmd/rio/xfid.c
@@ -18,6 +18,7 @@
 char Elong[] = 		"snarf buffer too long";
 char Eunkid[] = 	"unknown id in attach";
 char Ebadrect[] = 	"bad rectangle in attach";
+char Egrab[] =		"invalid grab rectangle";
 char Ewindow[] = 	"cannot make window";
 char Enowindow[] = 	"window has no image";
 char Ebadmouse[] = 	"bad format on /dev/mouse";
@@ -342,6 +343,14 @@
 	case Qkbd:
 		w->kbdopen = FALSE;
 		break;
+	case Qmousectl:
+		if(w->grab && w->grabfid == x->f){
+			w->grab = FALSE;
+			w->grabfid = nil;
+			updategrab();
+			wsendctlmesg(w, Repaint, ZR, nil);
+		}
+		break;
 	case Qmouse:
 		w->resized = FALSE;
 		w->mouseopen = FALSE;
@@ -372,6 +381,7 @@
 	Fcall fc;
 	int cnt, qid, nb, off, nr;
 	char err[ERRMAX], *p;
+	Rectangle grabr;
 	Point pt;
 	Window *w;
 	Rune *r;
@@ -500,6 +510,46 @@
 		w->label = p;
 		w->label[cnt] = 0;
 		memmove(w->label, x->data, cnt);
+		break;
+
+	case Qmousectl:
+		if(strncmp(x->data, "clip", 4)==0){
+			grabr.min.x = strtol(x->data+4, &p, 0);
+			if(p == nil){
+				filsysrespond(x->fs, x, &fc, Egrab);
+				return;
+			}
+			grabr.min.y = strtol(p, &p, 0);
+			if(p == nil){
+				filsysrespond(x->fs, x, &fc, Egrab);
+				return;
+			}
+			grabr.max.x = strtol(p, &p, 0);
+			if(p == nil){
+				filsysrespond(x->fs, x, &fc, Egrab);
+				return;
+			}
+			grabr.max.y = strtol(p, &p, 0);
+			if(p == nil){
+				filsysrespond(x->fs, x, &fc, Egrab);
+				return;
+			}
+			if(!rectinrect(grabr, w->screenr)){
+				filsysrespond(x->fs, x, &fc, Egrab);
+				return;
+			}
+			w->grabr = grabr;
+			w->grabfid = x->f;
+			w->grab = TRUE;
+			updategrab();
+			wsendctlmesg(w, Repaint, ZR, nil);
+		}else if(strncmp(x->data, "release", 7)==0){
+			w->grab = FALSE;
+			w->grabfid = nil;
+			updategrab();
+			wsendctlmesg(w, Repaint, ZR, nil);
+		}else
+			write(mousectl->mctlfd, x->data, cnt);
 		break;
 
 	case Qmouse:

[-- Attachment #4: devmouse-grab.diff --]
[-- Type: application/octet-stream, Size: 2411 bytes --]

--- //.git/fs/object/2f8a59f4b5bfe028c022855acc19666d69eed909/tree//sys/src/9/port/devmouse.c
+++ /sys/src/9/port/devmouse.c
@@ -18,6 +18,8 @@
 	ScrollRight = 0x40,
 };
 
+char Egrab[] = "invalid grab rectangle";
+
 typedef struct Mouseinfo	Mouseinfo;
 typedef struct Mousestate	Mousestate;
 
@@ -53,6 +55,8 @@
 	CMbuttonmap,
 	CMscrollswap,
 	CMswap,
+	CMclip,
+	CMrelease,
 	CMblank,
 	CMblanktime,
 	CMtwitch,
@@ -64,6 +68,8 @@
 	CMbuttonmap,	"buttonmap",	0,
 	CMscrollswap,	"scrollswap",	0,
 	CMswap,		"swap",		1,
+	CMclip,		"clip",		0,
+	CMrelease,	"release",	1,
 	CMblank,	"blank",	1,
 	CMblanktime,	"blanktime",	2,
 	CMtwitch,	"twitch",	1,
@@ -95,6 +101,9 @@
 static uchar buttonmap[8] = {
 	0, 1, 2, 3, 4, 5, 6, 7,
 };
+static Rectangle grabr;
+static Chan *grabchan;
+static int grab;
 static int mouseswap;
 static int scrollswap;
 static ulong mousetime;
@@ -206,6 +215,10 @@
 	if((c->qid.type&QTDIR)!=0 || (c->flag&COPEN)==0)
 		return;
 	switch((ulong)c->qid.path){
+	case Qmousectl:
+		if(c == grabchan)
+			grab = 0;
+		break;
 	case Qmousein:
 		mouse.inbuttons &= ~((Mousestate*)c->aux)->buttons;
 		free(c->aux);	/* Mousestate */
@@ -344,6 +357,7 @@
 mousewrite(Chan *c, void *va, long n, vlong)
 {
 	char *p;
+	Rectangle r;
 	Point pt;
 	Cmdbuf *cb;
 	Cmdtab *ct;
@@ -403,6 +417,37 @@
 			mouseblankscreen(1);
 			break;
 
+		case CMclip:
+			if(cb->nf < 5)
+				error(Eshort);
+			if(!gscreen)
+				break;
+			r.min.x = strtol(cb->f[1], &p, 0);
+			if(p == nil)
+				error(Egrab);
+			r.min.y = strtol(cb->f[2], &p, 0);
+			if(p == nil)
+				error(Egrab);
+			r.max.x = strtol(cb->f[3], &p, 0);
+			if(p == nil)
+				error(Egrab);
+			r.max.y = strtol(cb->f[4], &p, 0);
+			if(p == nil)
+				error(Egrab);
+			if(!rectinrect(r, gscreen->clipr))
+				error(Egrab);
+			grabr = r;
+			grabchan = c;
+			grab = 1;
+			if(!ptinrect(mouse.xy, grabr))
+				absmousetrack(mouse.xy.x, mouse.xy.y, 0, 0);
+			break;
+
+		case CMrelease:
+			grab = 0;
+			grabchan = nil;
+			break;
+
 		case CMblanktime:
 			blanktime = strtoul(cb->f[1], 0, 0);
 			/* wet floor */
@@ -609,6 +654,17 @@
 		y = gscreen->clipr.min.y;
 	if(y >= gscreen->clipr.max.y)
 		y = gscreen->clipr.max.y-1;
+
+	if(grab){
+		if(x < grabr.min.x)
+			x = grabr.min.x;
+		if(x >= grabr.max.x)
+			x = grabr.max.x-1;
+		if(y < grabr.min.y)
+			y = grabr.min.y;
+		if(y >= grabr.max.y)
+			y = grabr.max.y-1;
+	}
 
 
 	ilock(&mouse);

^ permalink raw reply	[flat|nested] 42+ messages in thread

end of thread, other threads:[~2021-08-11  2:36 UTC | newest]

Thread overview: 42+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-12 19:50 [9front] Mouse clipping patch José Miguel Sánchez García
2021-07-13  2:36 ` ori
2021-07-13  2:45   ` ori
2021-07-13 14:03     ` Stuart Morrow
2021-08-11  0:56       ` Stuart Morrow
2021-07-13  3:14   ` José Miguel Sánchez García
2021-07-13  8:25     ` hiro
2021-07-13 10:27       ` José Miguel Sánchez García
2021-07-13 14:05         ` Stuart Morrow
     [not found]           ` <CAFSF3XPhDeKiKXdsL0Abnderm45Uc2GCPYsi6ygSaBkf7gDBmA@mail.gmail.com>
2021-07-13 15:09             ` José Miguel Sánchez García
2021-07-13 15:34               ` José Miguel Sánchez García
2021-07-14 12:04                 ` Stuart Morrow
2021-07-15  2:06                   ` Xiao-Yong Jin
2021-07-13 15:11             ` Stuart Morrow
2021-07-13 16:16               ` José Miguel Sánchez García
2021-07-13 16:27                 ` Stuart Morrow
2021-07-13 17:42                   ` José Miguel Sánchez García
2021-07-13 21:20                     ` hiro
2021-07-13 21:57                     ` ori
2021-07-14 11:55                       ` Stuart Morrow
2021-07-13 17:26                 ` Xiao-Yong Jin
2021-07-13 17:45                   ` Stuart Morrow
2021-07-13 18:29                   ` José Miguel Sánchez García
2021-07-13 21:32                     ` hiro
2021-07-13 21:22                   ` Benjamin Purcell
2021-07-13 16:21               ` hiro
2021-07-13 16:29             ` ori
2021-07-14  8:42               ` hiro
2021-07-14 11:52                 ` Stuart Morrow
2021-07-14 12:17                   ` hiro
2021-07-15  3:13                     ` ori
2021-07-14 12:53                   ` José Miguel Sánchez García
2021-07-15  7:36                     ` hiro
2021-07-14 14:26                 ` ori
2021-07-13 15:27           ` kvik
2021-07-13 16:28             ` ori
2021-07-13 12:00     ` José Miguel Sánchez García
2021-07-13 12:43   ` kvik
2021-07-13 13:36     ` hiro
2021-07-13 13:40       ` hiro
2021-07-14 22:57 ` ori
2021-07-15  7:40   ` hiro

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).