/* | |
* $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-2018, 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. | |
*/ | |
#ifndef USE_XKB | |
#define USE_XKB 1 | |
#endif | |
#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> | |
#ifdef USE_XKB | |
#include <X11/XKBlib.h> | |
#endif | |
#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; | |
} | |
/* | |
* map keyboard XEvent to 8 bit char or -1 | |
*/ | |
static int | |
mapkey(e) | |
XEvent *e; | |
{ | |
int shift = (ShiftMask & e->xkey.state) != 0; | |
KeySym key; | |
#ifdef USE_XKB | |
/* | |
* X Keyboard Extension | |
* described in | |
* https://www.x.org/releases/X11R7.7/doc/libX11/XKB/xkblib.html#Xkb_Keyboard_Extension_Support_for_Keyboards | |
* copyright 1995, 1996 | |
* | |
* use XkbLibraryVersion and/or XkbQueryExtension | |
* to determine if available??? | |
*/ | |
key = XkbKeycodeToKeysym(dpy, e->xkey.keycode, 0, shift); | |
#elif 1 | |
/* | |
* documented in | |
* Xlib: C Language X Interface (X Version 11, Release 4) | |
* copyright 1989 | |
*/ | |
int keysyms_per_keycode_return; | |
KeySym *keysyms = XGetKeyboardMapping(dpy, | |
e->xkey.keycode, | |
1, | |
&keysyms_per_keycode_return); | |
key = keysyms[0]; | |
XFree(keysyms); | |
#else /* just in case... */ | |
/* XKeycodeToKeysym deprecated */ | |
key = XKeycodeToKeysym(dpy, e->xkey.keycode, shift); | |
#endif | |
if ((key & 0xff00) == 0) | |
return key & 0xff; | |
switch (key) { | |
case XK_Return: return '\r'; | |
} | |
/* printf("ignoring keycode %#x\r\n", key); /**/ | |
return -1; | |
} | |
static void | |
handle_key_press(w, d, e, b) | |
Widget w; | |
XtPointer d; | |
XEvent *e; | |
Boolean *b; | |
{ | |
int k = mapkey(e); | |
if (k >= 0) | |
display_keydown(k); | |
if (b) | |
*b = TRUE; | |
} | |
static void | |
handle_key_release(w, d, e, b) | |
Widget w; | |
XtPointer d; | |
XEvent *e; | |
Boolean *b; | |
{ | |
int k = mapkey(e); | |
if (k >= 0) | |
display_keyup(k); | |
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(const char *crtname, /* crt type name */ | |
int xp, int yp, /* screen size in pixels */ | |
int colors, /* colors to support (not used) */ | |
void *dptr) | |
{ | |
Arg arg[25]; | |
XGCValues gcvalues; | |
unsigned int n; | |
int argc; | |
char *argv[1]; | |
int height, width; | |
#ifdef USE_XKB | |
Bool supported; | |
#endif | |
xpixels = xp; /* save screen size */ | |
ypixels = yp; | |
if (getenv ("DISPLAY") == NULL) { | |
return 0; | |
} | |
XtToolkitInitialize(); | |
app_context = XtCreateApplicationContext(); | |
argc = 0; | |
argv[0] = NULL; | |
dpy = XtOpenDisplay( app_context, NULL, NULL, crtname, NULL, 0, | |
&argc, argv); | |
#ifdef USE_XKB | |
/* | |
* suppress synthetic key up events from autorepeat | |
* (will still see repeated down events) | |
* see keymap function for XKb history | |
*/ | |
supported = False; | |
(void) XkbSetDetectableAutoRepeat(dpy, True, &supported); | |
#endif | |
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_shutdown (void) | |
{ | |
} | |
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; | |
} |