| /* | |
| * $Id: x11.c,v 1.32 2005/01/14 18:58:03 phil Exp $ | |
| * X11 support for XY display simulator | |
| * Phil Budne <phil@ultimate.com> | |
| * September 2003 | |
| * | |
| * Changes from Douglas A. Gwyn, Jan 8, 2004 | |
| * | |
| * started from PDP-8/E simulator (vc8e.c & kc8e.c); | |
| * This PDP8 Emulator was written by Douglas W. Jones at the | |
| * University of Iowa. It is distributed as freeware, of | |
| * uncertain function and uncertain utility. | |
| */ | |
| /* | |
| * Copyright (c) 2003-2004, Philip L. Budne | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a | |
| * copy of this software and associated documentation files (the "Software"), | |
| * to deal in the Software without restriction, including without limitation | |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| * and/or sell copies of the Software, and to permit persons to whom the | |
| * Software is furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| * | |
| * Except as contained in this notice, the names of the authors shall | |
| * not be used in advertising or otherwise to promote the sale, use or | |
| * other dealings in this Software without prior written authorization | |
| * from the authors. | |
| */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include "ws.h" | |
| #include "xy.h" | |
| #include <X11/X.h> | |
| #include <X11/Xlib.h> | |
| #include <X11/Intrinsic.h> | |
| #include <X11/StringDefs.h> | |
| #include <X11/Core.h> | |
| #include <X11/Shell.h> | |
| #include <X11/cursorfont.h> | |
| #include <sys/types.h> | |
| #include <sys/time.h> | |
| #ifndef PIX_SIZE | |
| #define PIX_SIZE 1 | |
| #endif | |
| /* | |
| * light pen location | |
| * see ws.h for full description | |
| */ | |
| int ws_lp_x = -1; | |
| int ws_lp_y = -1; | |
| static XtAppContext app_context; /* the topmost context for everything */ | |
| static Display* dpy; /* its display */ | |
| static int scr; /* its screen */ | |
| static Colormap cmap; /* its colormap */ | |
| static Widget crtshell; /* the X window shell */ | |
| static Widget crt; /* the X window in which output will plot */ | |
| static int xpixels, ypixels; | |
| #ifdef FULL_SCREEN | |
| /* occupy entire screen for vintage computer fan Sellam Ismail */ | |
| static int xoffset, yoffset; | |
| #endif | |
| static GC whiteGC; /* gc with white foreground */ | |
| static GC blackGC; /* gc with black foreground */ | |
| static int buttons = 0; /* tracks state of all buttons */ | |
| static int os_pollfd(int, int); /* forward */ | |
| /* here on any mouse button down, AND movement when any button down */ | |
| static void | |
| handle_button_press(w, d, e, b) | |
| Widget w; | |
| XtPointer d; | |
| XEvent *e; | |
| Boolean *b; | |
| { | |
| int x, y; | |
| x = e->xbutton.x; | |
| y = e->xbutton.y; | |
| #ifdef FULL_SCREEN | |
| /* untested! */ | |
| x -= xoffset; | |
| y -= yoffset; | |
| #endif | |
| #if PIX_SIZE > 1 | |
| x *= PIX_SIZE; | |
| y *= PIX_SIZE; | |
| #endif | |
| if (!display_tablet) | |
| /* crosshair cursor to indicate tip of active pen */ | |
| XDefineCursor(dpy, XtWindow(crt), | |
| (Cursor) XCreateFontCursor(dpy, XC_crosshair)); | |
| y = ypixels - y - 1; | |
| /*printf("lightpen at %d,%d\n", x, y); fflush(stdout);*/ | |
| ws_lp_x = x; | |
| ws_lp_y = y; | |
| if (e->type == ButtonPress) { | |
| buttons |= e->xbutton.button; | |
| if (e->xbutton.button == 1) { | |
| display_lp_sw = 1; | |
| /*printf("tip switch activated\n"); fflush(stdout);*/ | |
| } | |
| } | |
| if (b) | |
| *b = TRUE; | |
| } | |
| static void | |
| handle_button_release(w, d, e, b) | |
| Widget w; | |
| XtPointer d; | |
| XEvent *e; | |
| Boolean *b; | |
| { | |
| if ((buttons &= ~e->xbutton.button) == 0) { /* all buttons released */ | |
| if (!display_tablet) | |
| /* pencil cursor (close to a pen!) to indicate inactive pen posn */ | |
| XDefineCursor(dpy, XtWindow(crt), | |
| (Cursor) XCreateFontCursor(dpy, XC_pencil)); | |
| /* XXX change cursor back?? */ | |
| ws_lp_x = ws_lp_y = -1; | |
| } | |
| if (e->xbutton.button == 1) { | |
| display_lp_sw = 0; | |
| /*printf("tip switch deactivated\n"); fflush(stdout);*/ | |
| } | |
| if (b) | |
| *b = TRUE; | |
| } | |
| static void | |
| handle_key_press(w, d, e, b) | |
| Widget w; | |
| XtPointer d; | |
| XEvent *e; | |
| Boolean *b; | |
| { | |
| int shift = (ShiftMask & e->xkey.state) != 0; | |
| KeySym key = XKeycodeToKeysym( dpy, e->xkey.keycode, shift ); | |
| /*printf("key %d down\n", key); fflush(stdout);*/ | |
| if ((key & 0xff00) == 0) | |
| display_keydown(key); | |
| if (b) | |
| *b = TRUE; | |
| } | |
| static void | |
| handle_key_release(w, d, e, b) | |
| Widget w; | |
| XtPointer d; | |
| XEvent *e; | |
| Boolean *b; | |
| { | |
| int shift = (ShiftMask & e->xkey.state) != 0; | |
| KeySym key = XKeycodeToKeysym( dpy, e->xkey.keycode, shift ); | |
| /*printf("key %d up\n", key); fflush(stdout);*/ | |
| if ((key & 0xff00) == 0) | |
| display_keyup(key); | |
| if (b) | |
| *b = TRUE; | |
| } | |
| static void | |
| handle_exposure(w, d, e, b) | |
| Widget w; | |
| XtPointer d; | |
| XEvent *e; | |
| Boolean *b; | |
| { | |
| display_repaint(); | |
| if (b) | |
| *b = TRUE; | |
| } | |
| int | |
| ws_init(char *crtname, /* crt type name */ | |
| int xp, int yp, /* screen size in pixels */ | |
| int colors) /* colors to support (not used) */ | |
| { | |
| Arg arg[25]; | |
| XGCValues gcvalues; | |
| unsigned int n; | |
| int argc; | |
| char *argv[1]; | |
| int height, width; | |
| xpixels = xp; /* save screen size */ | |
| ypixels = yp; | |
| XtToolkitInitialize(); | |
| app_context = XtCreateApplicationContext(); | |
| argc = 0; | |
| argv[0] = NULL; | |
| dpy = XtOpenDisplay( app_context, NULL, NULL, crtname, NULL, 0, | |
| &argc, argv); | |
| scr = DefaultScreen(dpy); | |
| crtshell = XtAppCreateShell( crtname, /* app name */ | |
| crtname, /* app class */ | |
| applicationShellWidgetClass, /* wclass */ | |
| dpy, /* display */ | |
| NULL, /* arglist */ | |
| 0); /* nargs */ | |
| cmap = DefaultColormap(dpy, scr); | |
| /* | |
| * Create a drawing area | |
| */ | |
| n = 0; | |
| #ifdef FULL_SCREEN | |
| /* center raster in full-screen black window */ | |
| width = DisplayWidth(dpy,scr); | |
| height = DisplayHeight(dpy,scr); | |
| xoffset = (width - xpixels*PIX_SIZE)/2; | |
| yoffset = (height - ypixels*PIX_SIZE)/2; | |
| #else | |
| width = xpixels*PIX_SIZE; | |
| height = ypixels*PIX_SIZE; | |
| #endif | |
| XtSetArg(arg[n], XtNwidth, width); n++; | |
| XtSetArg(arg[n], XtNheight, height); n++; | |
| XtSetArg(arg[n], XtNbackground, BlackPixel( dpy, scr )); n++; | |
| crt = XtCreateWidget( crtname, widgetClass, crtshell, arg, n); | |
| XtManageChild(crt); | |
| XtPopup(crtshell, XtGrabNonexclusive); | |
| XtSetKeyboardFocus(crtshell, crt); /* experimental? */ | |
| /* | |
| * Create black and white Graphics Contexts | |
| */ | |
| gcvalues.foreground = BlackPixel( dpy, scr ); | |
| gcvalues.background = BlackPixel( dpy, scr ); | |
| blackGC = XCreateGC(dpy, XtWindow(crt), | |
| GCForeground | GCBackground, &gcvalues); | |
| gcvalues.foreground = WhitePixel( dpy, scr ); | |
| whiteGC = XCreateGC(dpy, XtWindow(crt), | |
| GCForeground | GCBackground, &gcvalues); | |
| if (!display_tablet) { | |
| /* pencil cursor */ | |
| XDefineCursor(dpy, XtWindow(crt), | |
| (Cursor) XCreateFontCursor(dpy, XC_pencil)); | |
| } | |
| /* | |
| * Setup to handle events | |
| */ | |
| XtAddEventHandler(crt, ButtonPressMask|ButtonMotionMask, FALSE, | |
| handle_button_press, NULL); | |
| XtAddEventHandler(crt, ButtonReleaseMask, FALSE, | |
| handle_button_release, NULL); | |
| XtAddEventHandler(crt, KeyPressMask, FALSE, | |
| handle_key_press, NULL); | |
| XtAddEventHandler(crt, KeyReleaseMask, FALSE, | |
| handle_key_release, NULL); | |
| XtAddEventHandler(crt, ExposureMask, FALSE, | |
| handle_exposure, NULL); | |
| return 1; | |
| } /* ws_init */ | |
| void * | |
| ws_color_black(void) | |
| { | |
| return blackGC; | |
| } | |
| void * | |
| ws_color_white(void) | |
| { | |
| return whiteGC; | |
| } | |
| void * | |
| ws_color_rgb(int r, int g, int b) | |
| { | |
| XColor color; | |
| color.red = r; | |
| color.green = g; | |
| color.blue = b; | |
| /* ignores flags */ | |
| if (XAllocColor(dpy, cmap, &color)) { | |
| XGCValues gcvalues; | |
| memset(&gcvalues, 0, sizeof(gcvalues)); | |
| gcvalues.foreground = gcvalues.background = color.pixel; | |
| return XCreateGC(dpy, XtWindow(crt), | |
| GCForeground | GCBackground, | |
| &gcvalues); | |
| } | |
| /* allocation failed */ | |
| return NULL; | |
| } | |
| /* put a point on the screen */ | |
| void | |
| ws_display_point(int x, int y, void *color) | |
| { | |
| GC gc = (GC) color; | |
| if (x > xpixels || y > ypixels) | |
| return; | |
| y = ypixels - y - 1; /* X11 coordinate system */ | |
| #ifdef FULL_SCREEN | |
| x += xoffset; | |
| y += yoffset; | |
| #endif | |
| if (gc == NULL) | |
| gc = blackGC; /* default to off */ | |
| #if PIX_SIZE == 1 | |
| XDrawPoint(dpy, XtWindow(crt), gc, x, y); | |
| #else | |
| XFillRectangle(dpy, XtWindow(crt), gc, | |
| x*PIX_SIZE, y*PIX_SIZE, PIX_SIZE, PIX_SIZE); | |
| #endif | |
| } | |
| void | |
| ws_sync(void) | |
| { | |
| XFlush(dpy); | |
| } | |
| /* | |
| * elapsed wall clock time since last call | |
| * +INF on first call | |
| */ | |
| struct elapsed_state { | |
| struct timeval tvs[2]; | |
| int new; | |
| }; | |
| static unsigned long | |
| elapsed(struct elapsed_state *ep) | |
| { | |
| unsigned long val; | |
| gettimeofday(&ep->tvs[ep->new], NULL); | |
| if (ep->tvs[!ep->new].tv_sec == 0) | |
| val = ~0L; | |
| else | |
| val = ((ep->tvs[ep->new].tv_sec - ep->tvs[!ep->new].tv_sec) * 1000000 + | |
| (ep->tvs[ep->new].tv_usec - ep->tvs[!ep->new].tv_usec)); | |
| ep->new = !ep->new; | |
| return val; | |
| } | |
| /* called periodically */ | |
| int | |
| ws_poll(int *valp, int maxusec) | |
| { | |
| static struct elapsed_state es; /* static to avoid clearing! */ | |
| #ifdef WS_POLL_DEBUG | |
| printf("ws_poll %d\n", maxusec); | |
| fflush(stdout); | |
| #endif | |
| elapsed(&es); /* start clock */ | |
| do { | |
| unsigned long e; | |
| /* tried checking return, but lost on TCP connections? */ | |
| os_pollfd(ConnectionNumber(dpy), maxusec); | |
| while (XtAppPending(app_context)) { | |
| XEvent event; | |
| /* XXX check for connection loss; set *valp? return 0 */ | |
| XtAppNextEvent(app_context, &event ); | |
| XtDispatchEvent( &event ); | |
| } | |
| e = elapsed(&es); | |
| #ifdef WS_POLL_DEBUG | |
| printf(" maxusec %d e %d\r\n", maxusec, e); | |
| fflush(stdout); | |
| #endif | |
| maxusec -= e; | |
| } while (maxusec > 10000); /* 10ms */ | |
| return 1; | |
| } | |
| /* utility: can be called from main program | |
| * which is willing to cede control | |
| */ | |
| int | |
| ws_loop(void (*func)(void *), void *arg) | |
| { | |
| int val; | |
| /* XXX use XtAppAddWorkProc & XtAppMainLoop? */ | |
| while (ws_poll(&val,0)) | |
| (*func)(arg); | |
| return val; | |
| } | |
| void | |
| ws_beep(void) | |
| { | |
| XBell(dpy, 0); /* ring at base volume */ | |
| XFlush(dpy); | |
| } | |
| /**************** | |
| * could move these to unix.c, if VMS versions needed | |
| * (or just (GASP!) ifdef) | |
| */ | |
| /* public version, used by delay code */ | |
| unsigned long | |
| os_elapsed(void) | |
| { | |
| static struct elapsed_state es; | |
| return elapsed(&es); | |
| } | |
| /* | |
| * select/DisplayNumber works on VMS 7.0+? | |
| * could move to "unix.c" | |
| * (I have some nasty VMS code that's supposed to to the job | |
| * for older systems) | |
| */ | |
| /* | |
| * sleep for maxus microseconds, returning TRUE sooner if fd is readable | |
| * used by X11 driver | |
| */ | |
| static int | |
| os_pollfd(int fd, int maxus) | |
| { | |
| /* use trusty old select (most portable) */ | |
| fd_set rfds; | |
| struct timeval tv; | |
| if (maxus >= 1000000) { /* not bloody likely, avoid divide */ | |
| tv.tv_sec = maxus / 1000000; | |
| tv.tv_usec = maxus % 1000000; | |
| } | |
| else { | |
| tv.tv_sec = 0; | |
| tv.tv_usec = maxus; | |
| } | |
| FD_ZERO(&rfds); | |
| FD_SET(fd, &rfds); | |
| return select(fd+1, &rfds, NULL, NULL, &tv) > 0; | |
| } |