xref: /plan9/sys/src/9/pc/mouse.c (revision 8cde348a8db0bd4f8cfcb77e5206af1e21fb4b80)
1 #include "u.h"
2 #include "../port/lib.h"
3 #include "mem.h"
4 #include "dat.h"
5 #include "fns.h"
6 #include "../port/error.h"
7 #include "io.h"
8 
9 #define	Image	IMAGE
10 #include <draw.h>
11 #include <memdraw.h>
12 #include <cursor.h>
13 #include "screen.h"
14 
15 /*
16  *  mouse types
17  */
18 enum
19 {
20 	Mouseother=	0,
21 	Mouseserial=	1,
22 	MousePS2=	2,
23 };
24 
25 extern int mouseshifted;
26 
27 static QLock mousectlqlock;
28 static int mousetype;
29 static int intellimouse;
30 static int packetsize;
31 static int resolution;
32 static int accelerated;
33 static int mousehwaccel;
34 static char mouseport[5];
35 
36 enum
37 {
38 	CMaccelerated,
39 	CMhwaccel,
40 	CMintellimouse,
41 	CMlinear,
42 	CMps2,
43 	CMps2intellimouse,
44 	CMres,
45 	CMreset,
46 	CMserial,
47 };
48 
49 static Cmdtab mousectlmsg[] =
50 {
51 	CMaccelerated,		"accelerated",		0,
52 	CMhwaccel,		"hwaccel",		2,
53 	CMintellimouse,		"intellimouse",		1,
54 	CMlinear,		"linear",		1,
55 	CMps2,			"ps2",			1,
56 	CMps2intellimouse,	"ps2intellimouse",	1,
57 	CMres,			"res",			0,
58 	CMreset,		"reset",		1,
59 	CMserial,		"serial",		0,
60 };
61 
62 /*
63  *  ps/2 mouse message is three bytes
64  *
65  *	byte 0 -	0 0 SDY SDX 1 M R L
66  *	byte 1 -	DX
67  *	byte 2 -	DY
68  *
69  *  shift & right button is the same as middle button
70  *
71  * Intellimouse and AccuPoint with extra buttons deliver
72  *	byte 3 -	00 or 01 or FF according to extra button state.
73  * extra buttons are mapped in this code to buttons 4 and 5.
74  * AccuPoint generates repeated events for these buttons;
75 *  it and Intellimouse generate 'down' events only, so
76  * user-level code is required to generate button 'up' events
77  * if they are needed by the application.
78  * Also on laptops with AccuPoint AND external mouse, the
79  * controller may deliver 3 or 4 bytes according to the type
80  * of the external mouse; code must adapt.
81  *
82  * On the NEC Versa series (and perhaps others?) we seem to
83  * lose a byte from the packet every once in a while, which
84  * means we lose where we are in the instruction stream.
85  * To resynchronize, if we get a byte more than two seconds
86  * after the previous byte, we assume it's the first in a packet.
87  */
88 static void
ps2mouseputc(int c,int shift)89 ps2mouseputc(int c, int shift)
90 {
91 	static short msg[4];
92 	static int nb;
93 	static uchar b[] = {0, 1, 4, 5, 2, 3, 6, 7, 0, 1, 2, 3, 2, 3, 6, 7 };
94 	static ulong lasttick;
95 	ulong m;
96 	int buttons, dx, dy;
97 
98 	/*
99 	 * non-ps2 keyboards might not set shift
100 	 * but still set mouseshifted.
101 	 */
102 	shift |= mouseshifted;
103 	/*
104 	 * Resynchronize in stream with timing; see comment above.
105 	 */
106 	m = MACHP(0)->ticks;
107 	if(TK2SEC(m - lasttick) > 2)
108 		nb = 0;
109 	lasttick = m;
110 
111 	/*
112 	 *  check byte 0 for consistency
113 	 */
114 	if(nb==0 && (c&0xc8)!=0x08)
115 		if(intellimouse && (c==0x00 || c==0x01 || c==0xFF)){
116 			/* last byte of 4-byte packet */
117 			packetsize = 4;
118 			return;
119 		}
120 
121 	msg[nb] = c;
122 	if(++nb == packetsize){
123 		nb = 0;
124 		if(msg[0] & 0x10)
125 			msg[1] |= 0xFF00;
126 		if(msg[0] & 0x20)
127 			msg[2] |= 0xFF00;
128 
129 		buttons = b[(msg[0]&7) | (shift ? 8 : 0)];
130 		if(intellimouse && packetsize==4){
131 			if((msg[3]&0xc8) == 0x08){
132 				/* first byte of 3-byte packet */
133 				packetsize = 3;
134 				msg[0] = msg[3];
135 				nb = 1;
136 				/* fall through to emit previous packet */
137 			}else{
138 				/* The AccuPoint on the Toshiba 34[48]0CT
139 				 * encodes extra buttons as 4 and 5. They repeat
140 				 * and don't release, however, so user-level
141 				 * timing code is required. Furthermore,
142 				 * intellimice with 3buttons + scroll give a
143 				 * two's complement number in the lower 4 bits
144 				 * (bit 4 is sign extension) that describes
145 				 * the amount the scroll wheel has moved during
146 				 * the last sample. Here we use only the sign to
147 				 * decide whether the wheel is moving up or down
148 				 * and generate a single button 4 or 5 click
149 				 * accordingly.
150 				 */
151 				if((msg[3] >> 3) & 1)
152 					buttons |= 1<<3;
153 				else if(msg[3] & 0x7)
154 					buttons |= 1<<4;
155 			}
156 		}
157 		dx = msg[1];
158 		dy = -msg[2];
159 		mousetrack(dx, dy, buttons, TK2MS(MACHP(0)->ticks));
160 	}
161 	return;
162 }
163 
164 /*
165  *  set up a ps2 mouse
166  */
167 static void
ps2mouse(void)168 ps2mouse(void)
169 {
170 	if(mousetype == MousePS2)
171 		return;
172 
173 	i8042auxenable(ps2mouseputc);
174 	/* make mouse streaming, enabled */
175 	i8042auxcmd(0xEA);
176 	i8042auxcmd(0xF4);
177 
178 	mousetype = MousePS2;
179 	packetsize = 3;
180 	mousehwaccel = 1;
181 }
182 
183 /*
184  * The PS/2 Trackpoint multiplexor on the IBM Thinkpad T23 ignores
185  * acceleration commands.  It is supposed to pass them on
186  * to the attached device, but my Logitech mouse is simply
187  * not behaving any differently.  For such devices, we allow
188  * the user to use "hwaccel off" to tell us to back off to
189  * software acceleration even if we're using the PS/2 port.
190  * (Serial mice are always software accelerated.)
191  * For more information on the Thinkpad multiplexor, see
192  * http://wwwcssrv.almaden.ibm.com/trackpoint/
193  */
194 static void
setaccelerated(int x)195 setaccelerated(int x)
196 {
197 	accelerated = x;
198 	if(mousehwaccel){
199 		switch(mousetype){
200 		case MousePS2:
201 			i8042auxcmd(0xE7);
202 			return;
203 		}
204 	}
205 	mouseaccelerate(x);
206 }
207 
208 static void
setlinear(void)209 setlinear(void)
210 {
211 	accelerated = 0;
212 	if(mousehwaccel){
213 		switch(mousetype){
214 		case MousePS2:
215 			i8042auxcmd(0xE6);
216 			return;
217 		}
218 	}
219 	mouseaccelerate(0);
220 }
221 
222 static void
setres(int n)223 setres(int n)
224 {
225 	resolution = n;
226 	switch(mousetype){
227 	case MousePS2:
228 		i8042auxcmd(0xE8);
229 		i8042auxcmd(n);
230 		break;
231 	}
232 }
233 
234 static void
setintellimouse(void)235 setintellimouse(void)
236 {
237 	intellimouse = 1;
238 	packetsize = 4;
239 	switch(mousetype){
240 	case MousePS2:
241 		i8042auxcmd(0xF3);	/* set sample */
242 		i8042auxcmd(0xC8);
243 		i8042auxcmd(0xF3);	/* set sample */
244 		i8042auxcmd(0x64);
245 		i8042auxcmd(0xF3);	/* set sample */
246 		i8042auxcmd(0x50);
247 		break;
248 	case Mouseserial:
249 		i8250setmouseputc(mouseport, m5mouseputc);
250 		break;
251 	}
252 }
253 
254 static void
resetmouse(void)255 resetmouse(void)
256 {
257 	packetsize = 3;
258 	switch(mousetype){
259 	case MousePS2:
260 		i8042auxcmd(0xF6);
261 		i8042auxcmd(0xEA);	/* streaming */
262 		i8042auxcmd(0xE8);	/* set resolution */
263 		i8042auxcmd(3);
264 		i8042auxcmd(0xF4);	/* enabled */
265 		break;
266 	}
267 }
268 
269 void
mousectl(Cmdbuf * cb)270 mousectl(Cmdbuf *cb)
271 {
272 	Cmdtab *ct;
273 
274 	qlock(&mousectlqlock);
275 	if(waserror()){
276 		qunlock(&mousectlqlock);
277 		nexterror();
278 	}
279 
280 	ct = lookupcmd(cb, mousectlmsg, nelem(mousectlmsg));
281 	switch(ct->index){
282 	case CMaccelerated:
283 		setaccelerated(cb->nf == 1 ? 1 : atoi(cb->f[1]));
284 		break;
285 	case CMintellimouse:
286 		setintellimouse();
287 		break;
288 	case CMlinear:
289 		setlinear();
290 		break;
291 	case CMps2:
292 		intellimouse = 0;
293 		ps2mouse();
294 		break;
295 	case CMps2intellimouse:
296 		ps2mouse();
297 		setintellimouse();
298 		break;
299 	case CMres:
300 		if(cb->nf >= 2)
301 			setres(atoi(cb->f[1]));
302 		else
303 			setres(1);
304 		break;
305 	case CMreset:
306 		resetmouse();
307 		if(accelerated)
308 			setaccelerated(accelerated);
309 		if(resolution)
310 			setres(resolution);
311 		if(intellimouse)
312 			setintellimouse();
313 		break;
314 	case CMserial:
315 		if(mousetype == Mouseserial)
316 			error(Emouseset);
317 
318 		if(cb->nf > 2){
319 			if(strcmp(cb->f[2], "M") == 0)
320 				i8250mouse(cb->f[1], m3mouseputc, 0);
321 			else if(strcmp(cb->f[2], "MI") == 0)
322 				i8250mouse(cb->f[1], m5mouseputc, 0);
323 			else
324 				i8250mouse(cb->f[1], mouseputc, cb->nf == 1);
325 		} else
326 			i8250mouse(cb->f[1], mouseputc, cb->nf == 1);
327 
328 		mousetype = Mouseserial;
329 		strncpy(mouseport, cb->f[1], sizeof(mouseport)-1);
330 		packetsize = 3;
331 		break;
332 	case CMhwaccel:
333 		if(strcmp(cb->f[1], "on")==0)
334 			mousehwaccel = 1;
335 		else if(strcmp(cb->f[1], "off")==0)
336 			mousehwaccel = 0;
337 		else
338 			cmderror(cb, "bad mouse control message");
339 	}
340 
341 	qunlock(&mousectlqlock);
342 	poperror();
343 }
344