531 lines
13 KiB
C
531 lines
13 KiB
C
/* Copyright (c) 1994-1996 David Hogan, see README for licence details */
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <X11/X.h>
|
|
#include <X11/Xos.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/Xatom.h>
|
|
#ifdef SHAPE
|
|
#include <X11/extensions/shape.h>
|
|
#endif
|
|
#include "config.h"
|
|
#ifdef VIRTNOTIFY
|
|
#include <libnotify/notify.h>
|
|
#endif
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "patchlevel.h"
|
|
|
|
char* version[] = {
|
|
"ryudo version 1.0\nCopyright (c) 1994-1996 David Hogan,\n(c) 2004 Russ "
|
|
"Cox,\n(c) 2019-2021 Derek Stevens",
|
|
0};
|
|
|
|
Display* dpy;
|
|
ScreenInfo* screens;
|
|
int initting;
|
|
XFontStruct* font;
|
|
int nostalgia;
|
|
char** myargv;
|
|
char* termprog;
|
|
char* shell;
|
|
Bool shape;
|
|
int _border = 4;
|
|
int _corner = 25;
|
|
int _inset = 1;
|
|
int curtime;
|
|
int debug;
|
|
int signalled;
|
|
int scrolling;
|
|
int num_screens;
|
|
int solidsweep = 0;
|
|
int numvirtuals = 0;
|
|
int ffm = 0;
|
|
int kbLaunch = 0;
|
|
int zoom = 0;
|
|
|
|
Atom exit_rio;
|
|
Atom restart_rio;
|
|
Atom wm_state;
|
|
Atom wm_change_state;
|
|
Atom wm_protocols;
|
|
Atom wm_delete;
|
|
Atom wm_take_focus;
|
|
Atom wm_lose_focus;
|
|
Atom wm_colormaps;
|
|
Atom _rio_running;
|
|
Atom _rio_hold_mode;
|
|
Atom wm_state_fullscreen;
|
|
Atom wm_state;
|
|
|
|
char* fontlist[] = FONTLIST;
|
|
|
|
void usage(void) {
|
|
fprintf(
|
|
stderr,
|
|
"usage: ryudo [-ffm] [-font fname] [-s] [-term prog] [-version] [-virtuals "
|
|
"num] [exit|restart]\n");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
int i, do_exit, do_restart;
|
|
char* fname;
|
|
int shape_event;
|
|
#ifdef SHAPE
|
|
int dummy;
|
|
#endif
|
|
|
|
shape_event = 0;
|
|
myargv = argv; /* for restart */
|
|
|
|
do_exit = do_restart = 0;
|
|
font = 0;
|
|
fname = 0;
|
|
for (i = 1; i < argc; i++)
|
|
if (strcmp(argv[i], "-nostalgia") == 0)
|
|
nostalgia++;
|
|
else if (strcmp(argv[i], "-debug") == 0)
|
|
debug++;
|
|
|
|
else if (strcmp(argv[i], "-ffm") == 0)
|
|
ffm++;
|
|
|
|
else if (strcmp(argv[i], "-font") == 0 && i + 1 < argc) {
|
|
i++;
|
|
fname = argv[i];
|
|
} else if (strcmp(argv[i], "-term") == 0 && i + 1 < argc)
|
|
termprog = argv[++i];
|
|
else if (strcmp(argv[i], "-virtuals") == 0 && i + 1 < argc) {
|
|
numvirtuals = atoi(argv[++i]);
|
|
if (numvirtuals < 0 || numvirtuals > 12) {
|
|
fprintf(
|
|
stderr,
|
|
"ryudo: wrong number of virtual displays, defaulting to 4\n");
|
|
numvirtuals = 4;
|
|
}
|
|
} else if (strcmp(argv[i], "-version") == 0) {
|
|
fprintf(stderr, "%s", version[0]);
|
|
if (PATCHLEVEL > 0)
|
|
fprintf(stderr, "; patch level %d", PATCHLEVEL);
|
|
fprintf(stderr, "\n");
|
|
exit(0);
|
|
} else if (strcmp(argv[i], "-s") == 0) {
|
|
scrolling = 1;
|
|
} else if (argv[i][0] == '-')
|
|
usage();
|
|
else
|
|
break;
|
|
for (; i < argc; i++)
|
|
if (strcmp(argv[i], "exit") == 0)
|
|
do_exit++;
|
|
else if (strcmp(argv[i], "restart") == 0)
|
|
do_restart++;
|
|
else
|
|
usage();
|
|
|
|
if (do_exit && do_restart)
|
|
usage();
|
|
|
|
shell = (char*)getenv("SHELL");
|
|
if (shell == NULL)
|
|
shell = DEFSHELL;
|
|
|
|
dpy = XOpenDisplay("");
|
|
if (dpy == 0)
|
|
fatal("can't open display");
|
|
|
|
initting = 1;
|
|
XSetErrorHandler(handler);
|
|
if (signal(SIGTERM, sighandler) == SIG_IGN)
|
|
signal(SIGTERM, SIG_IGN);
|
|
if (signal(SIGINT, sighandler) == SIG_IGN)
|
|
signal(SIGINT, SIG_IGN);
|
|
if (signal(SIGHUP, sighandler) == SIG_IGN)
|
|
signal(SIGHUP, SIG_IGN);
|
|
|
|
exit_rio = XInternAtom(dpy, "9WM_EXIT", False);
|
|
restart_rio = XInternAtom(dpy, "9WM_RESTART", False);
|
|
|
|
curtime = -1; /* don't care */
|
|
if (do_exit) {
|
|
sendcmessage(DefaultRootWindow(dpy), exit_rio, 0L, 1, 1);
|
|
XSync(dpy, False);
|
|
exit(0);
|
|
}
|
|
if (do_restart) {
|
|
sendcmessage(DefaultRootWindow(dpy), restart_rio, 0L, 1, 1);
|
|
XSync(dpy, False);
|
|
exit(0);
|
|
}
|
|
|
|
if (0)
|
|
XSynchronize(dpy, True);
|
|
|
|
wm_state = XInternAtom(dpy, "WM_STATE", False);
|
|
wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", False);
|
|
wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
|
|
wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
|
|
wm_take_focus = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
|
|
wm_lose_focus = XInternAtom(dpy, "_9WM_LOSE_FOCUS", False);
|
|
wm_colormaps = XInternAtom(dpy, "WM_COLORMAP_WINDOWS", False);
|
|
_rio_running = XInternAtom(dpy, "_9WM_RUNNING", False);
|
|
_rio_hold_mode = XInternAtom(dpy, "_9WM_HOLD_MODE", False);
|
|
wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
|
|
wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
|
|
|
|
if (fname != 0)
|
|
if ((font = XLoadQueryFont(dpy, fname)) == 0)
|
|
fprintf(stderr, "ryudo: warning: can't load font %s\n", fname);
|
|
|
|
if (font == 0) {
|
|
i = 0;
|
|
for (;;) {
|
|
fname = fontlist[i++];
|
|
if (fname == 0) {
|
|
fprintf(stderr, "ryudo: warning: can't find a font\n");
|
|
break;
|
|
}
|
|
font = XLoadQueryFont(dpy, fname);
|
|
if (font != 0)
|
|
break;
|
|
}
|
|
}
|
|
if (nostalgia) {
|
|
_border--;
|
|
_inset--;
|
|
}
|
|
|
|
#ifdef SHAPE
|
|
shape = XShapeQueryExtension(dpy, &shape_event, &dummy);
|
|
#endif
|
|
|
|
num_screens = ScreenCount(dpy);
|
|
screens = (ScreenInfo*)malloc(sizeof(ScreenInfo) * num_screens);
|
|
|
|
for (i = 0; i < num_screens; i++)
|
|
initscreen(&screens[i], i, 0);
|
|
|
|
initb2menu(numvirtuals);
|
|
|
|
/* set selection so that 9term knows we're running */
|
|
curtime = CurrentTime;
|
|
XSetSelectionOwner(dpy, _rio_running, screens[0].menuwin, timestamp());
|
|
|
|
XSync(dpy, False);
|
|
initting = 0;
|
|
|
|
nofocus();
|
|
|
|
for (i = 0; i < num_screens; i++)
|
|
scanwins(&screens[i]);
|
|
|
|
#ifdef VIRTNOTIFY
|
|
notify_init("ryudo");
|
|
#endif
|
|
keysetup();
|
|
mainloop(shape_event);
|
|
#ifdef VIRTNOTIFY
|
|
notify_uninit();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void initscreen(ScreenInfo* s, int i, int background) {
|
|
char *ds, *colon, *dot1;
|
|
unsigned long mask;
|
|
unsigned long gmask;
|
|
XGCValues gv;
|
|
XSetWindowAttributes attr;
|
|
XVisualInfo xvi;
|
|
XSetWindowAttributes attrs;
|
|
|
|
s->num = i;
|
|
s->root = RootWindow(dpy, i);
|
|
s->def_cmap = DefaultColormap(dpy, i);
|
|
s->min_cmaps = MinCmapsOfScreen(ScreenOfDisplay(dpy, i));
|
|
s->depth = DefaultDepth(dpy, i);
|
|
|
|
/*
|
|
* Figure out underlying screen format.
|
|
*/
|
|
if (
|
|
XMatchVisualInfo(dpy, i, 16, TrueColor, &xvi) ||
|
|
XMatchVisualInfo(dpy, i, 16, DirectColor, &xvi)) {
|
|
s->vis = xvi.visual;
|
|
s->depth = 16;
|
|
} else if (
|
|
XMatchVisualInfo(dpy, i, 15, TrueColor, &xvi) ||
|
|
XMatchVisualInfo(dpy, i, 15, DirectColor, &xvi)) {
|
|
s->vis = xvi.visual;
|
|
s->depth = 15;
|
|
} else if (
|
|
XMatchVisualInfo(dpy, i, 24, TrueColor, &xvi) ||
|
|
XMatchVisualInfo(dpy, i, 24, DirectColor, &xvi)) {
|
|
s->vis = xvi.visual;
|
|
s->depth = 24;
|
|
} else if (
|
|
XMatchVisualInfo(dpy, i, 8, PseudoColor, &xvi) ||
|
|
XMatchVisualInfo(dpy, i, 8, StaticColor, &xvi)) {
|
|
s->vis = xvi.visual;
|
|
s->depth = 8;
|
|
} else {
|
|
s->depth = DefaultDepth(dpy, i);
|
|
if (s->depth != 8) {
|
|
fprintf(stderr, "can't understand depth %d screen", s->depth);
|
|
exit(1);
|
|
}
|
|
s->vis = DefaultVisual(dpy, i);
|
|
}
|
|
if (DefaultDepth(dpy, i) != s->depth) {
|
|
s->def_cmap = XCreateColormap(dpy, s->root, s->vis, AllocNone);
|
|
}
|
|
|
|
ds = DisplayString(dpy);
|
|
colon = rindex(ds, ':');
|
|
if (colon && num_screens > 1) {
|
|
strcpy(s->display, "DISPLAY=");
|
|
strcat(s->display, ds);
|
|
colon = s->display + 8 + (colon - ds); /* use version in buf */
|
|
dot1 = index(colon, '.'); /* first period after colon */
|
|
if (!dot1)
|
|
dot1 = colon + strlen(colon); /* if not there, append */
|
|
sprintf(dot1, ".%d", i);
|
|
} else
|
|
s->display[0] = '\0';
|
|
|
|
s->black = BlackPixel(dpy, i);
|
|
s->white = WhitePixel(dpy, i);
|
|
s->activeholdborder = colorpixel(dpy, s, s->depth, SHOLDCOL, s->white);
|
|
s->inactiveholdborder = colorpixel(dpy, s, s->depth, HOLDCOL, s->black);
|
|
s->activeborder = colorpixel(dpy, s, s->depth, SBORDERCOL, s->black);
|
|
s->inactiveborder = colorpixel(dpy, s, s->depth, BORDERCOL, s->white);
|
|
s->red = colorpixel(dpy, s, s->depth, GHOSTCOL, s->white);
|
|
s->width = WidthOfScreen(ScreenOfDisplay(dpy, i));
|
|
s->height = HeightOfScreen(ScreenOfDisplay(dpy, i));
|
|
s->bkup[0] =
|
|
XCreatePixmap(dpy, s->root, 2 * s->width, BORDER, DefaultDepth(dpy, i));
|
|
s->bkup[1] =
|
|
XCreatePixmap(dpy, s->root, BORDER, 2 * s->height, DefaultDepth(dpy, i));
|
|
|
|
gv.foreground = s->black ^ s->white;
|
|
gv.background = s->white;
|
|
gv.function = GXxor;
|
|
gv.line_width = 0;
|
|
gv.subwindow_mode = IncludeInferiors;
|
|
gmask =
|
|
GCForeground | GCBackground | GCFunction | GCLineWidth | GCSubwindowMode;
|
|
if (font != 0) {
|
|
gv.font = font->fid;
|
|
gmask |= GCFont;
|
|
}
|
|
s->gc = XCreateGC(dpy, s->root, gmask, &gv);
|
|
|
|
gv.function = GXcopy;
|
|
s->gccopy = XCreateGC(dpy, s->root, gmask, &gv);
|
|
|
|
gv.foreground = s->red;
|
|
s->gcred = XCreateGC(dpy, s->root, gmask, &gv);
|
|
|
|
gv.foreground = colorpixel(dpy, s, s->depth, 0xEEEEEE, s->black);
|
|
s->gcsweep = XCreateGC(dpy, s->root, gmask, &gv);
|
|
|
|
initcurs(s);
|
|
|
|
attr.cursor = s->arrow;
|
|
attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask |
|
|
ColormapChangeMask | ButtonPressMask | ButtonReleaseMask |
|
|
PropertyChangeMask | KeyPressMask | EnterWindowMask;
|
|
mask = CWCursor | CWEventMask;
|
|
XChangeWindowAttributes(dpy, s->root, mask, &attr);
|
|
XSync(dpy, False);
|
|
|
|
attrs.border_pixel = colorpixel(dpy, s, s->depth, MBORDERCOL, s->black);
|
|
attrs.background_pixel = colorpixel(dpy, s, s->depth, MENUBGCOL, s->white);
|
|
attrs.colormap = s->def_cmap;
|
|
|
|
s->menuwin = XCreateWindow(
|
|
dpy,
|
|
s->root,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
MENUBORDER,
|
|
s->depth,
|
|
CopyFromParent,
|
|
s->vis,
|
|
CWBackPixel | CWBorderPixel | CWColormap,
|
|
&attrs);
|
|
|
|
gv.foreground = colorpixel(dpy, s, s->depth, MENUBGCOL, s->black);
|
|
s->gcmenubg = XCreateGC(dpy, s->menuwin, gmask, &gv);
|
|
|
|
gv.foreground = colorpixel(dpy, s, s->depth, SMENUBGCOL, s->white);
|
|
s->gcmenubgs = XCreateGC(dpy, s->menuwin, gmask, &gv);
|
|
|
|
gv.foreground = colorpixel(dpy, s, s->depth, MENUFGCOL, s->white);
|
|
gv.background = colorpixel(dpy, s, s->depth, MENUBGCOL, s->black);
|
|
s->gcmenufg = XCreateGC(dpy, s->menuwin, gmask, &gv);
|
|
|
|
gv.foreground = colorpixel(dpy, s, s->depth, SMENUFGCOL, s->white);
|
|
gv.background = colorpixel(dpy, s, s->depth, SMENUBGCOL, s->black);
|
|
s->gcmenufgs = XCreateGC(dpy, s->menuwin, gmask, &gv);
|
|
|
|
attrs.border_pixel = s->red;
|
|
attrs.background_pixel = colorpixel(dpy, s, s->depth, 0xEEEEEE, s->black);
|
|
attrs.colormap = s->def_cmap;
|
|
s->sweepwin = XCreateWindow(
|
|
dpy,
|
|
s->root,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
4,
|
|
s->depth,
|
|
CopyFromParent,
|
|
s->vis,
|
|
CWBackPixel | CWBorderPixel | CWColormap,
|
|
&attrs);
|
|
}
|
|
|
|
ScreenInfo* getscreen(Window w) {
|
|
int i;
|
|
|
|
for (i = 0; i < num_screens; i++)
|
|
if (screens[i].root == w)
|
|
return &screens[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
Time timestamp(void) {
|
|
XEvent ev;
|
|
|
|
if (curtime == CurrentTime) {
|
|
XChangeProperty(
|
|
dpy,
|
|
screens[0].root,
|
|
_rio_running,
|
|
_rio_running,
|
|
8,
|
|
PropModeAppend,
|
|
(unsigned char*)"",
|
|
0);
|
|
XMaskEvent(dpy, PropertyChangeMask, &ev);
|
|
curtime = ev.xproperty.time;
|
|
}
|
|
return curtime;
|
|
}
|
|
|
|
void sendcmessage(Window w, Atom a, long x, int isroot, int usemask) {
|
|
XEvent ev;
|
|
int status;
|
|
long mask;
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.xclient.type = ClientMessage;
|
|
ev.xclient.window = w;
|
|
ev.xclient.message_type = a;
|
|
ev.xclient.format = 32;
|
|
ev.xclient.data.l[0] = x;
|
|
ev.xclient.data.l[1] = timestamp();
|
|
mask = 0;
|
|
if (usemask) {
|
|
mask |= KeyPressMask; /* seems to be necessary */
|
|
if (isroot)
|
|
mask |= SubstructureRedirectMask; /* magic! */
|
|
else
|
|
mask |= ExposureMask; /* not really correct but so be it */
|
|
}
|
|
status = XSendEvent(dpy, w, False, mask, &ev);
|
|
if (status == 0)
|
|
fprintf(stderr, "ryudo: sendcmessage failed\n");
|
|
}
|
|
|
|
void sendconfig(Client* c) {
|
|
XConfigureEvent ce;
|
|
|
|
ce.type = ConfigureNotify;
|
|
ce.event = c->window;
|
|
ce.window = c->window;
|
|
ce.x = c->x;
|
|
ce.y = c->y;
|
|
ce.width = c->dx;
|
|
ce.height = c->dy;
|
|
ce.border_width = c->border;
|
|
ce.above = None;
|
|
ce.override_redirect = 0;
|
|
XSendEvent(dpy, c->window, False, StructureNotifyMask, (XEvent*)&ce);
|
|
}
|
|
|
|
void sighandler(void) { signalled = 1; }
|
|
|
|
void getevent(XEvent* e) {
|
|
int fd;
|
|
fd_set rfds;
|
|
struct timeval t;
|
|
|
|
if (!signalled) {
|
|
if (QLength(dpy) > 0) {
|
|
XNextEvent(dpy, e);
|
|
return;
|
|
}
|
|
fd = ConnectionNumber(dpy);
|
|
FD_ZERO(&rfds);
|
|
FD_SET(fd, &rfds);
|
|
t.tv_sec = t.tv_usec = 0;
|
|
if (select(fd + 1, &rfds, NULL, NULL, &t) == 1) {
|
|
XNextEvent(dpy, e);
|
|
return;
|
|
}
|
|
XFlush(dpy);
|
|
FD_SET(fd, &rfds);
|
|
if (select(fd + 1, &rfds, NULL, NULL, NULL) == 1) {
|
|
XNextEvent(dpy, e);
|
|
return;
|
|
}
|
|
if (errno != EINTR || !signalled) {
|
|
perror("ryudo: select failed");
|
|
exit(1);
|
|
}
|
|
}
|
|
fprintf(stderr, "ryudo: exiting on signal\n");
|
|
cleanup();
|
|
exit(1);
|
|
}
|
|
|
|
void cleanup(void) {
|
|
Client *c, *cc[2], *next;
|
|
XWindowChanges wc;
|
|
int i;
|
|
|
|
/* order of un-reparenting determines final stacking order... */
|
|
cc[0] = cc[1] = 0;
|
|
for (c = clients; c; c = next) {
|
|
next = c->next;
|
|
i = normal(c);
|
|
c->next = cc[i];
|
|
cc[i] = c;
|
|
}
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
for (c = cc[i]; c; c = c->next) {
|
|
if (!withdrawn(c)) {
|
|
XReparentWindow(dpy, c->window, c->screen->root, c->x, c->y);
|
|
}
|
|
wc.border_width = c->border;
|
|
XConfigureWindow(dpy, c->window, CWBorderWidth, &wc);
|
|
}
|
|
}
|
|
|
|
XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, timestamp());
|
|
for (i = 0; i < num_screens; i++)
|
|
cmapnofocus(&screens[i]);
|
|
XCloseDisplay(dpy);
|
|
}
|