| /* |
| * $Id: x11.c,v 1.29 2004/02/03 21:23:51 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 "display.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 |
| |
| //#define FULL_SCREEN 1 |
| #define NO_CURSOR 1 |
| #define NO_BORDER 1 |
| |
| /* |
| * 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 |
| x -= xoffset; |
| y -= yoffset; |
| #endif |
| #if PIX_SIZE > 1 |
| x *= PIX_SIZE; |
| y *= PIX_SIZE; |
| #endif |
| |
| #ifndef NO_CURSUR |
| if (!display_tablet) |
| /* crosshair cursor to indicate tip of active pen */ |
| XDefineCursor(dpy, XtWindow(crt), |
| (Cursor) XCreateFontCursor(dpy, XC_crosshair)); |
| #endif |
| |
| 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 */ |
| #ifndef NO_CURSOR |
| if (!display_tablet) |
| /* pencil cursor (close to a pen!) to indicate inactive pen posn */ |
| XDefineCursor(dpy, XtWindow(crt), |
| (Cursor) XCreateFontCursor(dpy, XC_pencil)); |
| #endif |
| |
| /* 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 */ |
| #ifdef NO_BORDER |
| overrideShellWidgetClass, |
| #else |
| applicationShellWidgetClass, /* wclass */ |
| #endif |
| 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); |
| |
| /* |
| * 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); |
| |
| #ifndef NO_CURSOR |
| if (!display_tablet) { |
| /* pencil cursor */ |
| XDefineCursor(dpy, XtWindow(crt), |
| (Cursor) XCreateFontCursor(dpy, XC_pencil)); |
| } |
| #endif |
| |
| /* |
| * 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 */ |
| |
| |
| /* Added 2006-07-19 SAI */ |
| |
| void ws_close(void) |
| { |
| |
| XtCloseDisplay(dpy); |
| |
| } |
| |
| |
| 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; |
| } |