xref: /plan9/sys/src/9/pc/screen.c (revision 58db92f49907d8fad4b51ea9739d73b48e4ebd9d)
17dd7cddfSDavid du Colombier #include "u.h"
27dd7cddfSDavid du Colombier #include "../port/lib.h"
37dd7cddfSDavid du Colombier #include "mem.h"
47dd7cddfSDavid du Colombier #include "dat.h"
57dd7cddfSDavid du Colombier #include "fns.h"
67dd7cddfSDavid du Colombier #include "io.h"
77dd7cddfSDavid du Colombier #include "ureg.h"
87dd7cddfSDavid du Colombier #include "../port/error.h"
97dd7cddfSDavid du Colombier 
107dd7cddfSDavid du Colombier #define	Image	IMAGE
117dd7cddfSDavid du Colombier #include <draw.h>
127dd7cddfSDavid du Colombier #include <memdraw.h>
137dd7cddfSDavid du Colombier #include <cursor.h>
147dd7cddfSDavid du Colombier #include "screen.h"
157dd7cddfSDavid du Colombier 
167dd7cddfSDavid du Colombier #define RGB2K(r,g,b)	((156763*(r)+307758*(g)+59769*(b))>>19)
177dd7cddfSDavid du Colombier 
187dd7cddfSDavid du Colombier Point ZP = {0, 0};
197dd7cddfSDavid du Colombier 
207dd7cddfSDavid du Colombier Rectangle physgscreenr;
217dd7cddfSDavid du Colombier 
227dd7cddfSDavid du Colombier Memdata gscreendata;
237dd7cddfSDavid du Colombier Memimage *gscreen;
247dd7cddfSDavid du Colombier 
257dd7cddfSDavid du Colombier VGAscr vgascreen[1];
267dd7cddfSDavid du Colombier 
277dd7cddfSDavid du Colombier Cursor	arrow = {
287dd7cddfSDavid du Colombier 	{ -1, -1 },
297dd7cddfSDavid du Colombier 	{ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
307dd7cddfSDavid du Colombier 	  0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
317dd7cddfSDavid du Colombier 	  0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
327dd7cddfSDavid du Colombier 	  0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
337dd7cddfSDavid du Colombier 	},
347dd7cddfSDavid du Colombier 	{ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
357dd7cddfSDavid du Colombier 	  0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
367dd7cddfSDavid du Colombier 	  0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
377dd7cddfSDavid du Colombier 	  0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
387dd7cddfSDavid du Colombier 	},
397dd7cddfSDavid du Colombier };
407dd7cddfSDavid du Colombier 
414de34a7eSDavid du Colombier int didswcursorinit;
424de34a7eSDavid du Colombier 
437dd7cddfSDavid du Colombier int
447dd7cddfSDavid du Colombier screensize(int x, int y, int z, ulong chan)
457dd7cddfSDavid du Colombier {
467dd7cddfSDavid du Colombier 	VGAscr *scr;
477dd7cddfSDavid du Colombier 
484de34a7eSDavid du Colombier 	lock(&vgascreenlock);
497dd7cddfSDavid du Colombier 	memimageinit();
507dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
517dd7cddfSDavid du Colombier 
527dd7cddfSDavid du Colombier 	/*
537dd7cddfSDavid du Colombier 	 * BUG: need to check if any xalloc'ed memory needs to
547dd7cddfSDavid du Colombier 	 * be given back if aperture is set.
557dd7cddfSDavid du Colombier 	 */
564de34a7eSDavid du Colombier 	if(scr->paddr == 0){
577dd7cddfSDavid du Colombier 		int width = (x*z)/BI2WD;
587dd7cddfSDavid du Colombier 
597dd7cddfSDavid du Colombier 		gscreendata.bdata = xalloc(width*BY2WD*y);
607dd7cddfSDavid du Colombier 		if(gscreendata.bdata == 0)
617dd7cddfSDavid du Colombier 			error("screensize: vga soft memory");
627dd7cddfSDavid du Colombier /*		memset(gscreendata.bdata, 0x72, width*BY2WD*y);	/* not really black */
637dd7cddfSDavid du Colombier 		scr->useflush = 1;
644de34a7eSDavid du Colombier 		scr->paddr = VGAMEM();
654de34a7eSDavid du Colombier 		scr->vaddr = KADDR(scr->paddr);
667dd7cddfSDavid du Colombier 		scr->apsize = 1<<16;
677dd7cddfSDavid du Colombier 	}
687dd7cddfSDavid du Colombier 	else
694de34a7eSDavid du Colombier 		gscreendata.bdata = scr->vaddr;
707dd7cddfSDavid du Colombier 
717dd7cddfSDavid du Colombier 	if(gscreen)
727dd7cddfSDavid du Colombier 		freememimage(gscreen);
734de34a7eSDavid du Colombier 	scr->gscreen = nil;
747dd7cddfSDavid du Colombier 
757dd7cddfSDavid du Colombier 	gscreen = allocmemimaged(Rect(0,0,x,y), chan, &gscreendata);
767dd7cddfSDavid du Colombier 	vgaimageinit(chan);
774de34a7eSDavid du Colombier 	if(gscreen == nil){
784de34a7eSDavid du Colombier 		unlock(&vgascreenlock);
797dd7cddfSDavid du Colombier 		return -1;
804de34a7eSDavid du Colombier 	}
817dd7cddfSDavid du Colombier 
829a747e4fSDavid du Colombier 	if(scr->dev && scr->dev->flush)
839a747e4fSDavid du Colombier 		scr->useflush = 1;
847dd7cddfSDavid du Colombier 
857dd7cddfSDavid du Colombier 	scr->palettedepth = 6;	/* default */
867dd7cddfSDavid du Colombier 	scr->gscreendata = &gscreendata;
877dd7cddfSDavid du Colombier 	scr->memdefont = getmemdefont();
887dd7cddfSDavid du Colombier 	scr->gscreen = gscreen;
897dd7cddfSDavid du Colombier 
907dd7cddfSDavid du Colombier 	physgscreenr = gscreen->r;
914de34a7eSDavid du Colombier 	unlock(&vgascreenlock);
927dd7cddfSDavid du Colombier 
934de34a7eSDavid du Colombier 	if(didswcursorinit)
944de34a7eSDavid du Colombier 		swcursorinit();
957dd7cddfSDavid du Colombier 	drawcmap();
967dd7cddfSDavid du Colombier 	return 0;
977dd7cddfSDavid du Colombier }
987dd7cddfSDavid du Colombier 
997dd7cddfSDavid du Colombier int
1007dd7cddfSDavid du Colombier screenaperture(int size, int align)
1017dd7cddfSDavid du Colombier {
1027dd7cddfSDavid du Colombier 	VGAscr *scr;
1037dd7cddfSDavid du Colombier 
1047dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
1057dd7cddfSDavid du Colombier 
1064de34a7eSDavid du Colombier 	if(scr->paddr)	/* set up during enable */
1074de34a7eSDavid du Colombier 		return 0;
1084de34a7eSDavid du Colombier 
1094de34a7eSDavid du Colombier 	if(size == 0)
1104de34a7eSDavid du Colombier 		return 0;
1114de34a7eSDavid du Colombier 
1124de34a7eSDavid du Colombier 	if(scr->dev && scr->dev->linear){
1134de34a7eSDavid du Colombier 		scr->dev->linear(scr, size, align);
1147dd7cddfSDavid du Colombier 		return 0;
1157dd7cddfSDavid du Colombier 	}
1167dd7cddfSDavid du Colombier 
1174de34a7eSDavid du Colombier 	/*
1184de34a7eSDavid du Colombier 	 * Need to allocate some physical address space.
1194de34a7eSDavid du Colombier 	 * The driver will tell the card to use it.
1204de34a7eSDavid du Colombier 	 */
1214de34a7eSDavid du Colombier 	size = PGROUND(size);
1224de34a7eSDavid du Colombier 	scr->paddr = upaalloc(size, align);
1234de34a7eSDavid du Colombier 	if(scr->paddr == 0)
1244de34a7eSDavid du Colombier 		return -1;
1254de34a7eSDavid du Colombier 	scr->vaddr = vmap(scr->paddr, size);
1264de34a7eSDavid du Colombier 	if(scr->vaddr == nil)
1274de34a7eSDavid du Colombier 		return -1;
1287dd7cddfSDavid du Colombier 	scr->apsize = size;
1297dd7cddfSDavid du Colombier 
1307dd7cddfSDavid du Colombier 	return 0;
1317dd7cddfSDavid du Colombier }
1327dd7cddfSDavid du Colombier 
1337dd7cddfSDavid du Colombier uchar*
1347dd7cddfSDavid du Colombier attachscreen(Rectangle* r, ulong* chan, int* d, int* width, int *softscreen)
1357dd7cddfSDavid du Colombier {
1367dd7cddfSDavid du Colombier 	VGAscr *scr;
1377dd7cddfSDavid du Colombier 
1387dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
1397dd7cddfSDavid du Colombier 	if(scr->gscreen == nil || scr->gscreendata == nil)
1407dd7cddfSDavid du Colombier 		return nil;
1417dd7cddfSDavid du Colombier 
1429a747e4fSDavid du Colombier 	*r = scr->gscreen->clipr;
1437dd7cddfSDavid du Colombier 	*chan = scr->gscreen->chan;
1447dd7cddfSDavid du Colombier 	*d = scr->gscreen->depth;
1457dd7cddfSDavid du Colombier 	*width = scr->gscreen->width;
1467dd7cddfSDavid du Colombier 	*softscreen = scr->useflush;
1477dd7cddfSDavid du Colombier 
1487dd7cddfSDavid du Colombier 	return scr->gscreendata->bdata;
1497dd7cddfSDavid du Colombier }
1507dd7cddfSDavid du Colombier 
1517dd7cddfSDavid du Colombier /*
1527dd7cddfSDavid du Colombier  * It would be fair to say that this doesn't work for >8-bit screens.
1537dd7cddfSDavid du Colombier  */
1547dd7cddfSDavid du Colombier void
1557dd7cddfSDavid du Colombier flushmemscreen(Rectangle r)
1567dd7cddfSDavid du Colombier {
1577dd7cddfSDavid du Colombier 	VGAscr *scr;
1587dd7cddfSDavid du Colombier 	uchar *sp, *disp, *sdisp, *edisp;
1597dd7cddfSDavid du Colombier 	int y, len, incs, off, page;
1607dd7cddfSDavid du Colombier 
1617dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
1629a747e4fSDavid du Colombier 	if(scr->dev && scr->dev->flush){
1639a747e4fSDavid du Colombier 		scr->dev->flush(scr, r);
1649a747e4fSDavid du Colombier 		return;
1659a747e4fSDavid du Colombier 	}
1667dd7cddfSDavid du Colombier 	if(scr->gscreen == nil || scr->useflush == 0)
1677dd7cddfSDavid du Colombier 		return;
1687dd7cddfSDavid du Colombier 	if(scr->dev == nil || scr->dev->page == nil)
1697dd7cddfSDavid du Colombier 		return;
1707dd7cddfSDavid du Colombier 
1717dd7cddfSDavid du Colombier 	if(rectclip(&r, scr->gscreen->r) == 0)
1727dd7cddfSDavid du Colombier 		return;
1737dd7cddfSDavid du Colombier 
1747dd7cddfSDavid du Colombier 	incs = scr->gscreen->width * BY2WD;
1757dd7cddfSDavid du Colombier 
1767dd7cddfSDavid du Colombier 	switch(scr->gscreen->depth){
1777dd7cddfSDavid du Colombier 	default:
1787dd7cddfSDavid du Colombier 		len = 0;
1797dd7cddfSDavid du Colombier 		panic("flushmemscreen: depth\n");
1807dd7cddfSDavid du Colombier 		break;
1817dd7cddfSDavid du Colombier 	case 8:
1827dd7cddfSDavid du Colombier 		len = Dx(r);
1837dd7cddfSDavid du Colombier 		break;
1847dd7cddfSDavid du Colombier 	}
1857dd7cddfSDavid du Colombier 	if(len < 1)
1867dd7cddfSDavid du Colombier 		return;
1877dd7cddfSDavid du Colombier 
1887dd7cddfSDavid du Colombier 	off = r.min.y*scr->gscreen->width*BY2WD+(r.min.x*scr->gscreen->depth)/8;
1897dd7cddfSDavid du Colombier 	page = off/scr->apsize;
1907dd7cddfSDavid du Colombier 	off %= scr->apsize;
1914de34a7eSDavid du Colombier 	disp = scr->vaddr;
1927dd7cddfSDavid du Colombier 	sdisp = disp+off;
1937dd7cddfSDavid du Colombier 	edisp = disp+scr->apsize;
1947dd7cddfSDavid du Colombier 
1957dd7cddfSDavid du Colombier 	off = r.min.y*scr->gscreen->width*BY2WD+(r.min.x*scr->gscreen->depth)/8;
1967dd7cddfSDavid du Colombier 
1977dd7cddfSDavid du Colombier 	sp = scr->gscreendata->bdata + off;
1987dd7cddfSDavid du Colombier 
1997dd7cddfSDavid du Colombier 	scr->dev->page(scr, page);
2007dd7cddfSDavid du Colombier 	for(y = r.min.y; y < r.max.y; y++) {
2017dd7cddfSDavid du Colombier 		if(sdisp + incs < edisp) {
2027dd7cddfSDavid du Colombier 			memmove(sdisp, sp, len);
2037dd7cddfSDavid du Colombier 			sp += incs;
2047dd7cddfSDavid du Colombier 			sdisp += incs;
2057dd7cddfSDavid du Colombier 		}
2067dd7cddfSDavid du Colombier 		else {
2077dd7cddfSDavid du Colombier 			off = edisp - sdisp;
2087dd7cddfSDavid du Colombier 			page++;
2097dd7cddfSDavid du Colombier 			if(off <= len){
2107dd7cddfSDavid du Colombier 				if(off > 0)
2117dd7cddfSDavid du Colombier 					memmove(sdisp, sp, off);
2127dd7cddfSDavid du Colombier 				scr->dev->page(scr, page);
2137dd7cddfSDavid du Colombier 				if(len - off > 0)
2147dd7cddfSDavid du Colombier 					memmove(disp, sp+off, len - off);
2157dd7cddfSDavid du Colombier 			}
2167dd7cddfSDavid du Colombier 			else {
2177dd7cddfSDavid du Colombier 				memmove(sdisp, sp, len);
2187dd7cddfSDavid du Colombier 				scr->dev->page(scr, page);
2197dd7cddfSDavid du Colombier 			}
2207dd7cddfSDavid du Colombier 			sp += incs;
2217dd7cddfSDavid du Colombier 			sdisp += incs - scr->apsize;
2227dd7cddfSDavid du Colombier 		}
2237dd7cddfSDavid du Colombier 	}
2247dd7cddfSDavid du Colombier }
2257dd7cddfSDavid du Colombier 
2267dd7cddfSDavid du Colombier void
2277dd7cddfSDavid du Colombier getcolor(ulong p, ulong* pr, ulong* pg, ulong* pb)
2287dd7cddfSDavid du Colombier {
2297dd7cddfSDavid du Colombier 	VGAscr *scr;
2307dd7cddfSDavid du Colombier 	ulong x;
2317dd7cddfSDavid du Colombier 
2327dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
2337dd7cddfSDavid du Colombier 	if(scr->gscreen == nil)
2347dd7cddfSDavid du Colombier 		return;
2357dd7cddfSDavid du Colombier 
2367dd7cddfSDavid du Colombier 	switch(scr->gscreen->depth){
2377dd7cddfSDavid du Colombier 	default:
2387dd7cddfSDavid du Colombier 		x = 0x0F;
2397dd7cddfSDavid du Colombier 		break;
2407dd7cddfSDavid du Colombier 	case 8:
2417dd7cddfSDavid du Colombier 		x = 0xFF;
2427dd7cddfSDavid du Colombier 		break;
2437dd7cddfSDavid du Colombier 	}
2447dd7cddfSDavid du Colombier 	p &= x;
2457dd7cddfSDavid du Colombier 
2467dd7cddfSDavid du Colombier 	lock(&cursor);
2477dd7cddfSDavid du Colombier 	*pr = scr->colormap[p][0];
2487dd7cddfSDavid du Colombier 	*pg = scr->colormap[p][1];
2497dd7cddfSDavid du Colombier 	*pb = scr->colormap[p][2];
2507dd7cddfSDavid du Colombier 	unlock(&cursor);
2517dd7cddfSDavid du Colombier }
2527dd7cddfSDavid du Colombier 
2537dd7cddfSDavid du Colombier int
2547dd7cddfSDavid du Colombier setpalette(ulong p, ulong r, ulong g, ulong b)
2557dd7cddfSDavid du Colombier {
2567dd7cddfSDavid du Colombier 	VGAscr *scr;
2577dd7cddfSDavid du Colombier 	int d;
2587dd7cddfSDavid du Colombier 
2597dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
2607dd7cddfSDavid du Colombier 	d = scr->palettedepth;
2617dd7cddfSDavid du Colombier 
2627dd7cddfSDavid du Colombier 	lock(&cursor);
2637dd7cddfSDavid du Colombier 	scr->colormap[p][0] = r;
2647dd7cddfSDavid du Colombier 	scr->colormap[p][1] = g;
2657dd7cddfSDavid du Colombier 	scr->colormap[p][2] = b;
2667dd7cddfSDavid du Colombier 	vgao(PaddrW, p);
2677dd7cddfSDavid du Colombier 	vgao(Pdata, r>>(32-d));
2687dd7cddfSDavid du Colombier 	vgao(Pdata, g>>(32-d));
2697dd7cddfSDavid du Colombier 	vgao(Pdata, b>>(32-d));
2707dd7cddfSDavid du Colombier 	unlock(&cursor);
2717dd7cddfSDavid du Colombier 
2727dd7cddfSDavid du Colombier 	return ~0;
2737dd7cddfSDavid du Colombier }
2747dd7cddfSDavid du Colombier 
2757dd7cddfSDavid du Colombier /*
2767dd7cddfSDavid du Colombier  * On some video cards (e.g. Mach64), the palette is used as the
2777dd7cddfSDavid du Colombier  * DAC registers for >8-bit modes.  We don't want to set them when the user
2787dd7cddfSDavid du Colombier  * is trying to set a colormap and the card is in one of these modes.
2797dd7cddfSDavid du Colombier  */
2807dd7cddfSDavid du Colombier int
2817dd7cddfSDavid du Colombier setcolor(ulong p, ulong r, ulong g, ulong b)
2827dd7cddfSDavid du Colombier {
2837dd7cddfSDavid du Colombier 	VGAscr *scr;
2847dd7cddfSDavid du Colombier 	int x;
2857dd7cddfSDavid du Colombier 
2867dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
2877dd7cddfSDavid du Colombier 	if(scr->gscreen == nil)
2887dd7cddfSDavid du Colombier 		return 0;
2897dd7cddfSDavid du Colombier 
2907dd7cddfSDavid du Colombier 	switch(scr->gscreen->depth){
2917dd7cddfSDavid du Colombier 	case 1:
2927dd7cddfSDavid du Colombier 	case 2:
2937dd7cddfSDavid du Colombier 	case 4:
2947dd7cddfSDavid du Colombier 		x = 0x0F;
2957dd7cddfSDavid du Colombier 		break;
2967dd7cddfSDavid du Colombier 	case 8:
2977dd7cddfSDavid du Colombier 		x = 0xFF;
2987dd7cddfSDavid du Colombier 		break;
2997dd7cddfSDavid du Colombier 	default:
3007dd7cddfSDavid du Colombier 		return 0;
3017dd7cddfSDavid du Colombier 	}
3027dd7cddfSDavid du Colombier 	p &= x;
3037dd7cddfSDavid du Colombier 
3047dd7cddfSDavid du Colombier 	return setpalette(p, r, g, b);
3057dd7cddfSDavid du Colombier }
3067dd7cddfSDavid du Colombier 
3077dd7cddfSDavid du Colombier int
3087dd7cddfSDavid du Colombier cursoron(int dolock)
3097dd7cddfSDavid du Colombier {
3107dd7cddfSDavid du Colombier 	VGAscr *scr;
3117dd7cddfSDavid du Colombier 	int v;
3127dd7cddfSDavid du Colombier 
3137dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
3147dd7cddfSDavid du Colombier 	if(scr->cur == nil || scr->cur->move == nil)
3157dd7cddfSDavid du Colombier 		return 0;
3167dd7cddfSDavid du Colombier 
3177dd7cddfSDavid du Colombier 	if(dolock)
3187dd7cddfSDavid du Colombier 		lock(&cursor);
3197dd7cddfSDavid du Colombier 	v = scr->cur->move(scr, mousexy());
3207dd7cddfSDavid du Colombier 	if(dolock)
3217dd7cddfSDavid du Colombier 		unlock(&cursor);
3227dd7cddfSDavid du Colombier 
3237dd7cddfSDavid du Colombier 	return v;
3247dd7cddfSDavid du Colombier }
3257dd7cddfSDavid du Colombier 
3267dd7cddfSDavid du Colombier void
3277dd7cddfSDavid du Colombier cursoroff(int)
3287dd7cddfSDavid du Colombier {
3297dd7cddfSDavid du Colombier }
3307dd7cddfSDavid du Colombier 
3317dd7cddfSDavid du Colombier void
3327dd7cddfSDavid du Colombier setcursor(Cursor* curs)
3337dd7cddfSDavid du Colombier {
3347dd7cddfSDavid du Colombier 	VGAscr *scr;
3357dd7cddfSDavid du Colombier 
3367dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
3377dd7cddfSDavid du Colombier 	if(scr->cur == nil || scr->cur->load == nil)
3387dd7cddfSDavid du Colombier 		return;
3397dd7cddfSDavid du Colombier 
3407dd7cddfSDavid du Colombier 	scr->cur->load(scr, curs);
3417dd7cddfSDavid du Colombier }
3427dd7cddfSDavid du Colombier 
3437dd7cddfSDavid du Colombier int hwaccel = 1;
3449a747e4fSDavid du Colombier int hwblank = 0;	/* turned on by drivers that are known good */
3459a747e4fSDavid du Colombier int panning = 0;
3467dd7cddfSDavid du Colombier 
3477dd7cddfSDavid du Colombier int
3487dd7cddfSDavid du Colombier hwdraw(Memdrawparam *par)
3497dd7cddfSDavid du Colombier {
3507dd7cddfSDavid du Colombier 	VGAscr *scr;
3514de34a7eSDavid du Colombier 	Memimage *dst, *src, *mask;
35259cc4ca5SDavid du Colombier 	int m;
3537dd7cddfSDavid du Colombier 
3547dd7cddfSDavid du Colombier 	if(hwaccel == 0)
3557dd7cddfSDavid du Colombier 		return 0;
3567dd7cddfSDavid du Colombier 
3577dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
3584de34a7eSDavid du Colombier 	if((dst=par->dst) == nil || dst->data == nil)
3597dd7cddfSDavid du Colombier 		return 0;
3604de34a7eSDavid du Colombier 	if((src=par->src) == nil || src->data == nil)
3614de34a7eSDavid du Colombier 		return 0;
3624de34a7eSDavid du Colombier 	if((mask=par->mask) == nil || mask->data == nil)
3634de34a7eSDavid du Colombier 		return 0;
3644de34a7eSDavid du Colombier 
3654de34a7eSDavid du Colombier 	if(scr->cur == &swcursor){
366*58db92f4SDavid du Colombier 		/*
367*58db92f4SDavid du Colombier 		 * always calling swcursorhide here doesn't cure
368*58db92f4SDavid du Colombier 		 * leaving cursor tracks nor failing to refresh menus
369*58db92f4SDavid du Colombier 		 * with the latest libmemdraw/draw.c.
370*58db92f4SDavid du Colombier 		 */
3714de34a7eSDavid du Colombier 		if(dst->data->bdata == gscreendata.bdata)
3724de34a7eSDavid du Colombier 			swcursoravoid(par->r);
3734de34a7eSDavid du Colombier 		if(src->data->bdata == gscreendata.bdata)
3744de34a7eSDavid du Colombier 			swcursoravoid(par->sr);
3754de34a7eSDavid du Colombier 		if(mask->data->bdata == gscreendata.bdata)
3764de34a7eSDavid du Colombier 			swcursoravoid(par->mr);
3774de34a7eSDavid du Colombier 	}
3787dd7cddfSDavid du Colombier 
3797dd7cddfSDavid du Colombier 	if(dst->data->bdata != gscreendata.bdata)
3807dd7cddfSDavid du Colombier 		return 0;
3817dd7cddfSDavid du Colombier 
3827dd7cddfSDavid du Colombier 	if(scr->fill==nil && scr->scroll==nil)
3837dd7cddfSDavid du Colombier 		return 0;
3847dd7cddfSDavid du Colombier 
3857dd7cddfSDavid du Colombier 	/*
3867dd7cddfSDavid du Colombier 	 * If we have an opaque mask and source is one opaque
3877dd7cddfSDavid du Colombier 	 * pixel we can convert to the destination format and just
3887dd7cddfSDavid du Colombier 	 * replicate with memset.
3897dd7cddfSDavid du Colombier 	 */
39059cc4ca5SDavid du Colombier 	m = Simplesrc|Simplemask|Fullmask;
3916a9fc400SDavid du Colombier 	if(scr->fill
3926a9fc400SDavid du Colombier 	&& (par->state&m)==m
3936a9fc400SDavid du Colombier 	&& ((par->srgba&0xFF) == 0xFF)
3946a9fc400SDavid du Colombier 	&& (par->op&S) == S)
3957dd7cddfSDavid du Colombier 		return scr->fill(scr, par->r, par->sdval);
3967dd7cddfSDavid du Colombier 
3977dd7cddfSDavid du Colombier 	/*
3987dd7cddfSDavid du Colombier 	 * If no source alpha, an opaque mask, we can just copy the
3997dd7cddfSDavid du Colombier 	 * source onto the destination.  If the channels are the same and
4007dd7cddfSDavid du Colombier 	 * the source is not replicated, memmove suffices.
4017dd7cddfSDavid du Colombier 	 */
4026a9fc400SDavid du Colombier 	m = Simplemask|Fullmask;
4036a9fc400SDavid du Colombier 	if(scr->scroll
4046a9fc400SDavid du Colombier 	&& src->data->bdata==dst->data->bdata
4056a9fc400SDavid du Colombier 	&& !(src->flags&Falpha)
4066a9fc400SDavid du Colombier 	&& (par->state&m)==m
4076a9fc400SDavid du Colombier 	&& (par->op&S) == S)
4087dd7cddfSDavid du Colombier 		return scr->scroll(scr, par->r, par->sr);
4097dd7cddfSDavid du Colombier 
4107dd7cddfSDavid du Colombier 	return 0;
4117dd7cddfSDavid du Colombier }
4127dd7cddfSDavid du Colombier 
4137dd7cddfSDavid du Colombier void
4147dd7cddfSDavid du Colombier blankscreen(int blank)
4157dd7cddfSDavid du Colombier {
4167dd7cddfSDavid du Colombier 	VGAscr *scr;
4177dd7cddfSDavid du Colombier 
4187dd7cddfSDavid du Colombier 	scr = &vgascreen[0];
4199a747e4fSDavid du Colombier 	if(hwblank){
4209a747e4fSDavid du Colombier 		if(scr->blank)
4217dd7cddfSDavid du Colombier 			scr->blank(scr, blank);
4229a747e4fSDavid du Colombier 		else
4239a747e4fSDavid du Colombier 			vgablank(scr, blank);
4249a747e4fSDavid du Colombier 	}
4257dd7cddfSDavid du Colombier }
4264de34a7eSDavid du Colombier 
4274de34a7eSDavid du Colombier void
4284de34a7eSDavid du Colombier vgalinearpciid(VGAscr *scr, int vid, int did)
4294de34a7eSDavid du Colombier {
4304de34a7eSDavid du Colombier 	Pcidev *p;
4314de34a7eSDavid du Colombier 
4324de34a7eSDavid du Colombier 	p = nil;
4334de34a7eSDavid du Colombier 	while((p = pcimatch(p, vid, 0)) != nil){
4344de34a7eSDavid du Colombier 		if(p->ccrb != 3)	/* video card */
4354de34a7eSDavid du Colombier 			continue;
4364de34a7eSDavid du Colombier 		if(did != 0 && p->did != did)
4374de34a7eSDavid du Colombier 			continue;
4384de34a7eSDavid du Colombier 		break;
4394de34a7eSDavid du Colombier 	}
4404de34a7eSDavid du Colombier 	if(p == nil)
4414de34a7eSDavid du Colombier 		error("pci video card not found");
4424de34a7eSDavid du Colombier 
4434de34a7eSDavid du Colombier 	scr->pci = p;
4444de34a7eSDavid du Colombier 	vgalinearpci(scr);
4454de34a7eSDavid du Colombier }
4464de34a7eSDavid du Colombier 
4474de34a7eSDavid du Colombier void
4484de34a7eSDavid du Colombier vgalinearpci(VGAscr *scr)
4494de34a7eSDavid du Colombier {
4504de34a7eSDavid du Colombier 	ulong paddr;
4514de34a7eSDavid du Colombier 	int i, size, best;
4524de34a7eSDavid du Colombier 	Pcidev *p;
4534de34a7eSDavid du Colombier 
4544de34a7eSDavid du Colombier 	p = scr->pci;
4554de34a7eSDavid du Colombier 	if(p == nil)
4564de34a7eSDavid du Colombier 		return;
4574de34a7eSDavid du Colombier 
4584de34a7eSDavid du Colombier 	/*
4594de34a7eSDavid du Colombier 	 * Scan for largest memory region on card.
4604de34a7eSDavid du Colombier 	 * Some S3 cards (e.g. Savage) have enormous
4614de34a7eSDavid du Colombier 	 * mmio regions (but even larger frame buffers).
4629c06fe1dSDavid du Colombier 	 * Some 3dfx cards (e.g., Voodoo3) have mmio
4639c06fe1dSDavid du Colombier 	 * buffers the same size as the frame buffer,
4649c06fe1dSDavid du Colombier 	 * but only the frame buffer is marked as
4659c06fe1dSDavid du Colombier 	 * prefetchable (bar&8).  If a card doesn't fit
4669c06fe1dSDavid du Colombier 	 * into these heuristics, its driver will have to
4679c06fe1dSDavid du Colombier 	 * call vgalinearaddr directly.
4684de34a7eSDavid du Colombier 	 */
4694de34a7eSDavid du Colombier 	best = -1;
4704de34a7eSDavid du Colombier 	for(i=0; i<nelem(p->mem); i++){
4714de34a7eSDavid du Colombier 		if(p->mem[i].bar&1)	/* not memory */
4724de34a7eSDavid du Colombier 			continue;
4734de34a7eSDavid du Colombier 		if(p->mem[i].size < 640*480)	/* not big enough */
4744de34a7eSDavid du Colombier 			continue;
4759c06fe1dSDavid du Colombier 		if(best==-1
4769c06fe1dSDavid du Colombier 		|| p->mem[i].size > p->mem[best].size
4774baec263SDavid du Colombier 		|| (p->mem[i].size == p->mem[best].size
4784baec263SDavid du Colombier 		  && (p->mem[i].bar&8)
4794baec263SDavid du Colombier 		  && !(p->mem[best].bar&8)))
4804de34a7eSDavid du Colombier 			best = i;
4814de34a7eSDavid du Colombier 	}
4824de34a7eSDavid du Colombier 	if(best >= 0){
4834de34a7eSDavid du Colombier 		paddr = p->mem[best].bar & ~0x0F;
4844de34a7eSDavid du Colombier 		size = p->mem[best].size;
4854de34a7eSDavid du Colombier 		vgalinearaddr(scr, paddr, size);
4864de34a7eSDavid du Colombier 		return;
4874de34a7eSDavid du Colombier 	}
4884de34a7eSDavid du Colombier 	error("no video memory found on pci card");
4894de34a7eSDavid du Colombier }
4904de34a7eSDavid du Colombier 
4914de34a7eSDavid du Colombier void
4924de34a7eSDavid du Colombier vgalinearaddr(VGAscr *scr, ulong paddr, int size)
4934de34a7eSDavid du Colombier {
4944de34a7eSDavid du Colombier 	int x, nsize;
4954de34a7eSDavid du Colombier 	ulong npaddr;
4964de34a7eSDavid du Colombier 
4974de34a7eSDavid du Colombier 	/*
4984de34a7eSDavid du Colombier 	 * new approach.  instead of trying to resize this
4994de34a7eSDavid du Colombier 	 * later, let's assume that we can just allocate the
5004de34a7eSDavid du Colombier 	 * entire window to start with.
5014de34a7eSDavid du Colombier 	 */
5024de34a7eSDavid du Colombier 
5034de34a7eSDavid du Colombier 	if(scr->paddr == paddr && size <= scr->apsize)
5044de34a7eSDavid du Colombier 		return;
5054de34a7eSDavid du Colombier 
5064de34a7eSDavid du Colombier 	if(scr->paddr){
5074de34a7eSDavid du Colombier 		/*
5084de34a7eSDavid du Colombier 		 * could call vunmap and vmap,
5094de34a7eSDavid du Colombier 		 * but worried about dangling pointers in devdraw
5104de34a7eSDavid du Colombier 		 */
5114de34a7eSDavid du Colombier 		error("cannot grow vga frame buffer");
5124de34a7eSDavid du Colombier 	}
5134de34a7eSDavid du Colombier 
5144de34a7eSDavid du Colombier 	/* round to page boundary, just in case */
5154de34a7eSDavid du Colombier 	x = paddr&(BY2PG-1);
5164de34a7eSDavid du Colombier 	npaddr = paddr-x;
5174de34a7eSDavid du Colombier 	nsize = PGROUND(size+x);
5184de34a7eSDavid du Colombier 
5191bd28109SDavid du Colombier 	/*
5201bd28109SDavid du Colombier 	 * Don't bother trying to map more than 4000x4000x32 = 64MB.
5211bd28109SDavid du Colombier 	 * We only have a 256MB window.
5221bd28109SDavid du Colombier 	 */
5231bd28109SDavid du Colombier 	if(nsize > 64*MB)
5241bd28109SDavid du Colombier 		nsize = 64*MB;
5254de34a7eSDavid du Colombier 	scr->vaddr = vmap(npaddr, nsize);
5264de34a7eSDavid du Colombier 	if(scr->vaddr == 0)
5274de34a7eSDavid du Colombier 		error("cannot allocate vga frame buffer");
5284de34a7eSDavid du Colombier 	scr->vaddr = (char*)scr->vaddr+x;
5294de34a7eSDavid du Colombier 	scr->paddr = paddr;
5304de34a7eSDavid du Colombier 	scr->apsize = nsize;
5314de34a7eSDavid du Colombier }
5324de34a7eSDavid du Colombier 
5334de34a7eSDavid du Colombier 
5344de34a7eSDavid du Colombier /*
5354de34a7eSDavid du Colombier  * Software cursor.
5364de34a7eSDavid du Colombier  */
5374de34a7eSDavid du Colombier int	swvisible;	/* is the cursor visible? */
5384de34a7eSDavid du Colombier int	swenabled;	/* is the cursor supposed to be on the screen? */
5394de34a7eSDavid du Colombier Memimage*	swback;	/* screen under cursor */
5404de34a7eSDavid du Colombier Memimage*	swimg;	/* cursor image */
5414de34a7eSDavid du Colombier Memimage*	swmask;	/* cursor mask */
5424de34a7eSDavid du Colombier Memimage*	swimg1;
5434de34a7eSDavid du Colombier Memimage*	swmask1;
5444de34a7eSDavid du Colombier 
5454de34a7eSDavid du Colombier Point	swoffset;
5464de34a7eSDavid du Colombier Rectangle	swrect;	/* screen rectangle in swback */
5474de34a7eSDavid du Colombier Point	swpt;	/* desired cursor location */
5484de34a7eSDavid du Colombier Point	swvispt;	/* actual cursor location */
5494de34a7eSDavid du Colombier int	swvers;	/* incremented each time cursor image changes */
5504de34a7eSDavid du Colombier int	swvisvers;	/* the version on the screen */
5514de34a7eSDavid du Colombier 
5524de34a7eSDavid du Colombier /*
5534de34a7eSDavid du Colombier  * called with drawlock locked for us, most of the time.
5544de34a7eSDavid du Colombier  * kernel prints at inopportune times might mean we don't
5554de34a7eSDavid du Colombier  * hold the lock, but memimagedraw is now reentrant so
5564de34a7eSDavid du Colombier  * that should be okay: worst case we get cursor droppings.
5574de34a7eSDavid du Colombier  */
5584de34a7eSDavid du Colombier void
5594de34a7eSDavid du Colombier swcursorhide(void)
5604de34a7eSDavid du Colombier {
5614de34a7eSDavid du Colombier 	if(swvisible == 0)
5624de34a7eSDavid du Colombier 		return;
5634de34a7eSDavid du Colombier 	if(swback == nil)
5644de34a7eSDavid du Colombier 		return;
5654de34a7eSDavid du Colombier 	swvisible = 0;
5664de34a7eSDavid du Colombier 	memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S);
56726d1d1dfSDavid du Colombier 	flushmemscreen(swrect);
5684de34a7eSDavid du Colombier }
5694de34a7eSDavid du Colombier 
5704de34a7eSDavid du Colombier void
5714de34a7eSDavid du Colombier swcursoravoid(Rectangle r)
5724de34a7eSDavid du Colombier {
5734de34a7eSDavid du Colombier 	if(swvisible && rectXrect(r, swrect))
5744de34a7eSDavid du Colombier 		swcursorhide();
5754de34a7eSDavid du Colombier }
5764de34a7eSDavid du Colombier 
5774de34a7eSDavid du Colombier void
5784de34a7eSDavid du Colombier swcursordraw(void)
5794de34a7eSDavid du Colombier {
5804de34a7eSDavid du Colombier 	if(swvisible)
5814de34a7eSDavid du Colombier 		return;
5824de34a7eSDavid du Colombier 	if(swenabled == 0)
5834de34a7eSDavid du Colombier 		return;
5844de34a7eSDavid du Colombier 	if(swback == nil || swimg1 == nil || swmask1 == nil)
5854de34a7eSDavid du Colombier 		return;
5864de34a7eSDavid du Colombier 	assert(!canqlock(&drawlock));
5874de34a7eSDavid du Colombier 	swvispt = swpt;
5884de34a7eSDavid du Colombier 	swvisvers = swvers;
5894de34a7eSDavid du Colombier 	swrect = rectaddpt(Rect(0,0,16,16), swvispt);
5904de34a7eSDavid du Colombier 	memimagedraw(swback, swback->r, gscreen, swpt, memopaque, ZP, S);
5914de34a7eSDavid du Colombier 	memimagedraw(gscreen, swrect, swimg1, ZP, swmask1, ZP, SoverD);
59226d1d1dfSDavid du Colombier 	flushmemscreen(swrect);
5934de34a7eSDavid du Colombier 	swvisible = 1;
5944de34a7eSDavid du Colombier }
5954de34a7eSDavid du Colombier 
5964de34a7eSDavid du Colombier /*
5974de34a7eSDavid du Colombier  * Need to lock drawlock for ourselves.
5984de34a7eSDavid du Colombier  */
5994de34a7eSDavid du Colombier void
6004de34a7eSDavid du Colombier swenable(VGAscr*)
6014de34a7eSDavid du Colombier {
6024de34a7eSDavid du Colombier 	swenabled = 1;
6034de34a7eSDavid du Colombier 	if(canqlock(&drawlock)){
6044de34a7eSDavid du Colombier 		swcursordraw();
6054de34a7eSDavid du Colombier 		qunlock(&drawlock);
6064de34a7eSDavid du Colombier 	}
6074de34a7eSDavid du Colombier }
6084de34a7eSDavid du Colombier 
6094de34a7eSDavid du Colombier void
6104de34a7eSDavid du Colombier swdisable(VGAscr*)
6114de34a7eSDavid du Colombier {
6124de34a7eSDavid du Colombier 	swenabled = 0;
6134de34a7eSDavid du Colombier 	if(canqlock(&drawlock)){
6144de34a7eSDavid du Colombier 		swcursorhide();
6154de34a7eSDavid du Colombier 		qunlock(&drawlock);
6164de34a7eSDavid du Colombier 	}
6174de34a7eSDavid du Colombier }
6184de34a7eSDavid du Colombier 
6194de34a7eSDavid du Colombier void
6204de34a7eSDavid du Colombier swload(VGAscr*, Cursor *curs)
6214de34a7eSDavid du Colombier {
6224de34a7eSDavid du Colombier 	uchar *ip, *mp;
6234de34a7eSDavid du Colombier 	int i, j, set, clr;
6244de34a7eSDavid du Colombier 
6254de34a7eSDavid du Colombier 	if(!swimg || !swmask || !swimg1 || !swmask1)
6264de34a7eSDavid du Colombier 		return;
6274de34a7eSDavid du Colombier 	/*
6284de34a7eSDavid du Colombier 	 * Build cursor image and mask.
6294de34a7eSDavid du Colombier 	 * Image is just the usual cursor image
6304de34a7eSDavid du Colombier 	 * but mask is a transparent alpha mask.
6314de34a7eSDavid du Colombier 	 *
6324de34a7eSDavid du Colombier 	 * The 16x16x8 memimages do not have
6334de34a7eSDavid du Colombier 	 * padding at the end of their scan lines.
6344de34a7eSDavid du Colombier 	 */
6354de34a7eSDavid du Colombier 	ip = byteaddr(swimg, ZP);
6364de34a7eSDavid du Colombier 	mp = byteaddr(swmask, ZP);
6374de34a7eSDavid du Colombier 	for(i=0; i<32; i++){
6384de34a7eSDavid du Colombier 		set = curs->set[i];
6394de34a7eSDavid du Colombier 		clr = curs->clr[i];
6404de34a7eSDavid du Colombier 		for(j=0x80; j; j>>=1){
6414de34a7eSDavid du Colombier 			*ip++ = set&j ? 0x00 : 0xFF;
6424de34a7eSDavid du Colombier 			*mp++ = (clr|set)&j ? 0xFF : 0x00;
6434de34a7eSDavid du Colombier 		}
6444de34a7eSDavid du Colombier 	}
6454de34a7eSDavid du Colombier 	swoffset = curs->offset;
6464de34a7eSDavid du Colombier 	swvers++;
6474de34a7eSDavid du Colombier 	memimagedraw(swimg1, swimg1->r, swimg, ZP, memopaque, ZP, S);
6484de34a7eSDavid du Colombier 	memimagedraw(swmask1, swmask1->r, swmask, ZP, memopaque, ZP, S);
6494de34a7eSDavid du Colombier }
6504de34a7eSDavid du Colombier 
6514de34a7eSDavid du Colombier int
6524de34a7eSDavid du Colombier swmove(VGAscr*, Point p)
6534de34a7eSDavid du Colombier {
6544de34a7eSDavid du Colombier 	swpt = addpt(p, swoffset);
6554de34a7eSDavid du Colombier 	return 0;
6564de34a7eSDavid du Colombier }
6574de34a7eSDavid du Colombier 
6584de34a7eSDavid du Colombier void
6594de34a7eSDavid du Colombier swcursorclock(void)
6604de34a7eSDavid du Colombier {
6614de34a7eSDavid du Colombier 	int x;
6624de34a7eSDavid du Colombier 
6634de34a7eSDavid du Colombier 	if(!swenabled)
6644de34a7eSDavid du Colombier 		return;
6654de34a7eSDavid du Colombier 	if(swvisible && eqpt(swpt, swvispt) && swvers==swvisvers)
6664de34a7eSDavid du Colombier 		return;
6674de34a7eSDavid du Colombier 
6684de34a7eSDavid du Colombier 	x = splhi();
6694de34a7eSDavid du Colombier 	if(swenabled)
6704de34a7eSDavid du Colombier 	if(!swvisible || !eqpt(swpt, swvispt) || swvers!=swvisvers)
6714de34a7eSDavid du Colombier 	if(canqlock(&drawlock)){
6724de34a7eSDavid du Colombier 		swcursorhide();
6734de34a7eSDavid du Colombier 		swcursordraw();
6744de34a7eSDavid du Colombier 		qunlock(&drawlock);
6754de34a7eSDavid du Colombier 	}
6764de34a7eSDavid du Colombier 	splx(x);
6774de34a7eSDavid du Colombier }
6784de34a7eSDavid du Colombier 
6794de34a7eSDavid du Colombier void
6804de34a7eSDavid du Colombier swcursorinit(void)
6814de34a7eSDavid du Colombier {
6824de34a7eSDavid du Colombier 	static int init, warned;
6834de34a7eSDavid du Colombier 	VGAscr *scr;
6844de34a7eSDavid du Colombier 
6854de34a7eSDavid du Colombier 	didswcursorinit = 1;
6864de34a7eSDavid du Colombier 	if(!init){
6874de34a7eSDavid du Colombier 		init = 1;
68826ad7229SDavid du Colombier 		addclock0link(swcursorclock, 10);
6894de34a7eSDavid du Colombier 	}
6904de34a7eSDavid du Colombier 	scr = &vgascreen[0];
6914de34a7eSDavid du Colombier 	if(scr==nil || scr->gscreen==nil)
6924de34a7eSDavid du Colombier 		return;
6934de34a7eSDavid du Colombier 
6944de34a7eSDavid du Colombier 	if(scr->dev == nil || scr->dev->linear == nil){
6954de34a7eSDavid du Colombier 		if(!warned){
6964de34a7eSDavid du Colombier 			print("cannot use software cursor on non-linear vga screen\n");
6974de34a7eSDavid du Colombier 			warned = 1;
6984de34a7eSDavid du Colombier 		}
6994de34a7eSDavid du Colombier 		return;
7004de34a7eSDavid du Colombier 	}
7014de34a7eSDavid du Colombier 
7024de34a7eSDavid du Colombier 	if(swback){
7034de34a7eSDavid du Colombier 		freememimage(swback);
7044de34a7eSDavid du Colombier 		freememimage(swmask);
7054de34a7eSDavid du Colombier 		freememimage(swmask1);
7064de34a7eSDavid du Colombier 		freememimage(swimg);
7074de34a7eSDavid du Colombier 		freememimage(swimg1);
7084de34a7eSDavid du Colombier 	}
7094de34a7eSDavid du Colombier 
7104de34a7eSDavid du Colombier 	swback = allocmemimage(Rect(0,0,32,32), gscreen->chan);
7114de34a7eSDavid du Colombier 	swmask = allocmemimage(Rect(0,0,16,16), GREY8);
7124de34a7eSDavid du Colombier 	swmask1 = allocmemimage(Rect(0,0,16,16), GREY1);
7134de34a7eSDavid du Colombier 	swimg = allocmemimage(Rect(0,0,16,16), GREY8);
7144de34a7eSDavid du Colombier 	swimg1 = allocmemimage(Rect(0,0,16,16), GREY1);
7154de34a7eSDavid du Colombier 	if(swback==nil || swmask==nil || swmask1==nil || swimg==nil || swimg1 == nil){
716dc4ee190SDavid du Colombier 		print("software cursor: allocmemimage fails");
7174de34a7eSDavid du Colombier 		return;
7184de34a7eSDavid du Colombier 	}
7194de34a7eSDavid du Colombier 
7204de34a7eSDavid du Colombier 	memfillcolor(swmask, DOpaque);
7214de34a7eSDavid du Colombier 	memfillcolor(swmask1, DOpaque);
7224de34a7eSDavid du Colombier 	memfillcolor(swimg, DBlack);
7234de34a7eSDavid du Colombier 	memfillcolor(swimg1, DBlack);
7244de34a7eSDavid du Colombier }
7254de34a7eSDavid du Colombier 
7264de34a7eSDavid du Colombier VGAcur swcursor =
7274de34a7eSDavid du Colombier {
7284de34a7eSDavid du Colombier 	"soft",
7294de34a7eSDavid du Colombier 	swenable,
7304de34a7eSDavid du Colombier 	swdisable,
7314de34a7eSDavid du Colombier 	swload,
7324de34a7eSDavid du Colombier 	swmove,
7334de34a7eSDavid du Colombier };
7344de34a7eSDavid du Colombier 
735