1142 lines
27 KiB
Bash
Executable File
1142 lines
27 KiB
Bash
Executable File
#! /bin/sh
|
|
echo - 'README'
|
|
cat << 'EOF-README' > 'README'
|
|
Thu Mar 19 23:51:38 IST 2015
|
|
|
|
This is 9menu, a simple program that allows you to create X menus from the
|
|
shell, where each menu item will run a command. 9menu is intended for use
|
|
with 9wm, but can be used with any other window manager.
|
|
|
|
The idea of a command line menu generator is from xmenu, but xmenu was
|
|
exclusively a pop-up menu, not what I wanted.
|
|
|
|
The files are:
|
|
|
|
README --- this file
|
|
9menu.1 --- man page for 9menu
|
|
9menu.c --- source code
|
|
Imakefile --- a contributed Imakefile,
|
|
from Bengt Kleberg <bengt@softwell.se>
|
|
Makefile.noimake --- a simple makefile.
|
|
|
|
Licence
|
|
=======
|
|
|
|
9menu is free software, and is Copyright (c) 1994 by David Hogan and
|
|
Arnold Robbins. Permission is granted to all sentient beings to use
|
|
this software, to make copies of it, and to distribute those copies,
|
|
provided that:
|
|
|
|
(1) the copyright and licence notices are left intact
|
|
(2) the recipients are aware that it is free software
|
|
(3) any unapproved changes in functionality are either
|
|
(i) only distributed as patches
|
|
or (ii) distributed as a new program which is not called 9menu
|
|
and whose documentation gives credit where it is due
|
|
(4) the authors are not held responsible for any defects
|
|
or shortcomings in the software, or damages caused by it.
|
|
|
|
There is no warranty for this software. Have a nice day.
|
|
|
|
--
|
|
Arnold Robbins
|
|
arnold@skeeve.com
|
|
EOF-README
|
|
echo - '9menu.1'
|
|
cat << 'EOF-9menu.1' > '9menu.1'
|
|
.TH 9MENU 1 "Apr 16 2020"
|
|
.SH NAME
|
|
9menu \- create a menu to run commands
|
|
.SH SYNOPSIS
|
|
.B 9menu
|
|
[
|
|
.BI \-bg " background-color"
|
|
] [
|
|
.BI \-display " displayname"
|
|
] [
|
|
.BI \-file " name"
|
|
] [
|
|
.BI \-fg " foreground-color"
|
|
] [
|
|
.BI \-font " fname"
|
|
] [
|
|
.BI \-geometry " geom"
|
|
] [
|
|
.B \-iconic
|
|
] [
|
|
.BI \-label " name"
|
|
] [
|
|
.B \-path
|
|
] [
|
|
.B \-popdown
|
|
] [
|
|
.B \-popup
|
|
] [
|
|
.BI \-shell " prog"
|
|
] [
|
|
.B \-teleport
|
|
] [
|
|
.B \-version
|
|
] [
|
|
.B \-warp
|
|
]
|
|
.IR menuitem [: command ]
|
|
\&...
|
|
.SH DESCRIPTION
|
|
.I 9menu
|
|
is a simple program that accepts a list of menu item and command
|
|
pairs on the command line.
|
|
It creates a window that consists of nothing but a menu.
|
|
When a particular item is selected, the corresponding command is executed.
|
|
.PP
|
|
Either
|
|
.B Button1
|
|
or
|
|
.B Button3
|
|
may be used to select an item.
|
|
Alternatively, the UP-ARROW and DOWN-ARROW cursor keys may be used to
|
|
highlight different items, with ENTER used to select the
|
|
highlighted item.
|
|
.PP
|
|
Menu items and commands are separated by a colon. The colon and command
|
|
are optional. If they are missing, then the menu item is assumed to be
|
|
a command that can be executed directly.
|
|
.PP
|
|
A menu item consisting of the word
|
|
.B exit
|
|
causes
|
|
.I 9menu
|
|
to exit when it is selected.
|
|
Otherwise, to stop
|
|
.I 9menu ,
|
|
delete it using the window manager.
|
|
The
|
|
.B exit
|
|
menu item can be anywhere in the list, although by convention it is last.
|
|
If a command is supplied along with the
|
|
.B exit
|
|
item, that command is executed before
|
|
.I 9menu
|
|
exits.
|
|
.PP
|
|
If a menu item's command starts with the word
|
|
.BR exec ,
|
|
.I 9menu
|
|
ceases operating after launching it.
|
|
.PP
|
|
.I 9menu
|
|
accepts the following command line options,
|
|
listed alphabetically:
|
|
.TP
|
|
.BI \-bg " background-color"
|
|
Set the background color to
|
|
.IR background-color .
|
|
By default, the background color is white.
|
|
.TP
|
|
.BI \-display " displayname"
|
|
Use the X display
|
|
.IR displayname ,
|
|
instead of the default display.
|
|
.TP
|
|
.BI \-file " filename"
|
|
Read items to display from
|
|
.IR filename ,
|
|
in addition to any other command line arguments. This is intended for use
|
|
with
|
|
.B #!
|
|
in scripts.
|
|
A
|
|
.I filename
|
|
of
|
|
.B -
|
|
causes
|
|
.I 9menu
|
|
to read items from standard input.
|
|
.TP
|
|
.BI \-fg " foreground-color"
|
|
Set the foreground color to
|
|
.IR foreground-color .
|
|
By default, the foreground color is black.
|
|
.TP
|
|
.BI \-font " fname"
|
|
Use the font
|
|
.IR fname ,
|
|
instead of one of the default fonts built into
|
|
.IR 9wm .
|
|
.TP
|
|
.BI \-geometry " geom"
|
|
Use
|
|
.I geom
|
|
(a geometry in standard X format) as the geometry of the menu.
|
|
This is most useful for specifying the initial location of the menu.
|
|
Note that
|
|
.I 9menu
|
|
overrides the size part of the geometry specification. The window is
|
|
always just large enough to hold the menu.
|
|
.TP
|
|
.B \-iconic
|
|
Start up in the iconified state.
|
|
.TP
|
|
.BI \-label " name"
|
|
Change both the window and icon labels of the window to
|
|
.IR name .
|
|
The default label is the last component of the path used to run
|
|
.IR 9menu ,
|
|
typically,
|
|
.BR 9menu .
|
|
.TP
|
|
.B \-path
|
|
Append the current directory to the command search path.
|
|
.TP
|
|
.B \-popdown
|
|
Once an item is selected, the menu window automatically iconifies itself.
|
|
.TP
|
|
.B \-popup
|
|
Act like a pop-up menu. Once a menu item is selected,
|
|
.I 9menu
|
|
exits.
|
|
This option overrides
|
|
.BR \-popdown .
|
|
.TP
|
|
.BI \-shell " prog"
|
|
Use
|
|
.I prog
|
|
as the shell to run commands, instead of
|
|
.BR /bin/sh .
|
|
A popular alternative shell is
|
|
.IR rc (1).
|
|
If the shell cannot be executed,
|
|
.I 9menu
|
|
then
|
|
.I silently
|
|
falls back to using
|
|
.BR /bin/sh .
|
|
.TP
|
|
.B \-teleport
|
|
Move the menu to where the mouse is when the menu is uniconified.
|
|
This option is particularly useful when combined with
|
|
.BR \-popdown .
|
|
.TP
|
|
.B \-version
|
|
This option prints the version of
|
|
.I 9menu
|
|
on the standard output, and then exits with an exit value of zero.
|
|
.TP
|
|
.B \-warp
|
|
Warp the mouse to the menu when the menu is uniconified.
|
|
After the selection is made, restore the mouse to where it was.
|
|
This option is particularly useful when combined with
|
|
.BR \-popdown .
|
|
.SH EXAMPLES
|
|
.ft B
|
|
.nf
|
|
9menu \-label Remotes xterm 'acme:ssh acme xterm' 'herman:ssh herman 9term' &
|
|
.sp
|
|
9menu \-label 'X progs' gv xdvi xeyes xneko exit &
|
|
.ft
|
|
.fi
|
|
.SH SEE ALSO
|
|
.IR sam (1),
|
|
.IR 9term (1),
|
|
.IR 9wm (1),
|
|
.IR rc (1).
|
|
.PP
|
|
.IR "The Plan 9 Programmer's Manual" .
|
|
.SH VERSION
|
|
This man page documents
|
|
.I 9menu
|
|
version 1.10.
|
|
.PP
|
|
Source code is available from
|
|
.BR ftp://ftp.freefriends.org/arnold/Source/9menu.shar.gz .
|
|
.PP
|
|
The code with full history is also available via Git from
|
|
.BR http://github.com/arnoldrobbins/9menu .
|
|
.SH BUGS
|
|
This program has grown to have too many options.
|
|
.SH AUTHORS
|
|
The initial idea for this program was by Arnold Robbins, after having
|
|
worked with John Mackin's GWM Blit emulation.
|
|
Matty Farrow wrote a version using libXg, from which some ideas were borrowed.
|
|
This code was written by David Hogan and Arnold Robbins.
|
|
Rich Salz motivated the
|
|
.B \-shell
|
|
option.
|
|
Christopher Platt motivated the
|
|
.B \-teleport
|
|
option.
|
|
John O'Donnell supplied the basic code for the
|
|
.B \-fg
|
|
and
|
|
.B \-bg
|
|
options.
|
|
Peter Seebach provided the base code for the
|
|
.B \-file
|
|
and
|
|
.B \-path
|
|
options.
|
|
Matthias Bauer made it work with the keyboard.
|
|
EOF-9menu.1
|
|
echo - '9menu.c'
|
|
cat << 'EOF-9menu.c' > '9menu.c'
|
|
/*
|
|
* 9menu.c
|
|
*
|
|
* This program puts up a window that is just a menu, and executes
|
|
* commands that correspond to the items selected.
|
|
*
|
|
* Initial idea: Arnold Robbins
|
|
* Version using libXg: Matty Farrow (some ideas borrowed)
|
|
* This code by: David Hogan and Arnold Robbins
|
|
*
|
|
* Copyright (c), Arnold Robbins and David Hogan
|
|
*
|
|
* Arnold Robbins
|
|
* arnold@skeeve.com
|
|
* October, 1994
|
|
*
|
|
* Code added to cause pop-up (unIconify) to move menu to mouse.
|
|
* Christopher Platt
|
|
* platt@coos.dartmouth.edu
|
|
* May, 1995
|
|
*
|
|
* Said code moved to -teleport option, and -warp option added.
|
|
* Arnold Robbins
|
|
* June, 1995
|
|
*
|
|
* Code added to allow -fg and -bg colors.
|
|
* John M. O'Donnell
|
|
* odonnell@stpaul.lampf.lanl.gov
|
|
* April, 1997
|
|
*
|
|
* Code added for -file and -path options.
|
|
* Peter Seebach
|
|
* seebs@plethora.net
|
|
* October, 2001
|
|
*
|
|
* Code added to allow up and down arrow keys to go up
|
|
* and down menu and RETURN to select an item.
|
|
* Matthias Bauer
|
|
* bauerm@immd1.informatik.uni-erlangen.de
|
|
* June, 2003
|
|
*
|
|
* spawn() changed to do exec directly if -popup, based on
|
|
* suggestion from
|
|
* Andrew Stribblehill
|
|
* a.d.stribblehill@durham.ac.uk
|
|
* June, 2004
|
|
*
|
|
* Allow "-" to mean read info from stdin.
|
|
* suggestion from
|
|
* Peter Bailey, by way of
|
|
* Andrew Stribblehill
|
|
* a.d.stribblehill@durham.ac.uk
|
|
* August, 2005
|
|
*
|
|
* Fix compile warnings (getcwd and getting a key sym).
|
|
* Arnold Robbins
|
|
* January, 2015.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <X11/X.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xproto.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/keysymdef.h>
|
|
#include <X11/keysym.h>
|
|
#include <X11/XKBlib.h>
|
|
|
|
char version[] = "9menu version 1.10";
|
|
|
|
Display *dpy; /* lovely X stuff */
|
|
int screen;
|
|
Window root;
|
|
Window menuwin;
|
|
GC gc;
|
|
unsigned long black;
|
|
unsigned long white;
|
|
char *fgcname = NULL;
|
|
char *bgcname = NULL;
|
|
Colormap defcmap;
|
|
XColor color;
|
|
XFontStruct *font;
|
|
Atom wm_protocols;
|
|
Atom wm_delete_window;
|
|
int g_argc; /* for XSetWMProperties to use */
|
|
char **g_argv;
|
|
int f_argc; /* for labels read from files */
|
|
char **f_argv;
|
|
char *geometry = "";
|
|
int savex, savey;
|
|
Window savewindow;
|
|
|
|
char *fontlist[] = { /* default font list if no -font */
|
|
"pelm.latin1.9",
|
|
"lucm.latin1.9",
|
|
"blit",
|
|
"9x15bold",
|
|
"9x15",
|
|
"lucidasanstypewriter-12",
|
|
"fixed",
|
|
NULL
|
|
};
|
|
|
|
/* the 9menu icon, for garish window managers */
|
|
#define nine_menu_width 40
|
|
#define nine_menu_height 40
|
|
static unsigned char nine_menu_bits[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
|
|
0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00,
|
|
0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04,
|
|
0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff,
|
|
0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
|
|
0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
|
|
0x80, 0xe0, 0x01, 0x04, 0x00, 0x80, 0xe0, 0x00, 0xfc, 0xff, 0xff, 0xe0,
|
|
0x01, 0xfc, 0xff, 0xff, 0x20, 0x03, 0x04, 0x00, 0x80, 0x00, 0x06, 0x04,
|
|
0x00, 0x80, 0x00, 0x0c, 0xfc, 0xff, 0xff, 0x00, 0x08, 0xfc, 0xff, 0xff,
|
|
0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
|
|
0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
|
|
0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00,
|
|
0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04,
|
|
0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff,
|
|
0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
|
|
0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
/* Modify this to your liking */
|
|
#define CONFIG_MENU_UP_KEY XK_Up
|
|
#define CONFIG_MENU_DOWN_KEY XK_Down
|
|
#define CONFIG_MENU_SELECT_KEY XK_Return
|
|
|
|
char *progname; /* my name */
|
|
char *displayname; /* X display */
|
|
char *fontname; /* font */
|
|
char *labelname; /* window and icon name */
|
|
char *filename; /* file to read options or labels from */
|
|
bool popup = false; /* true if we're a popup window */
|
|
bool popdown = false; /* autohide after running a command */
|
|
bool iconic = false; /* start iconified */
|
|
bool teleport = false; /* teleport the menu */
|
|
bool warp = false; /* warp the mouse */
|
|
|
|
char **labels; /* list of labels and commands */
|
|
char **commands;
|
|
int numitems;
|
|
|
|
char *shell = "/bin/sh"; /* default shell */
|
|
|
|
extern void usage(), run_menu(), spawn(char *com), ask_wm_for_delete();
|
|
extern void reap(int sig), set_wm_hints(int wide, int high);
|
|
extern void redraw(int cur, int high, int wide);
|
|
extern void teleportmenu(int cur, int wide, int high);
|
|
extern void warpmouse(int cur, int wide, int high);
|
|
extern void restoremouse();
|
|
extern void memory(char *msg);
|
|
extern int args(int argc, char **argv);
|
|
|
|
/* memory --- print the out of memory message and die */
|
|
|
|
void
|
|
memory(char *s)
|
|
{
|
|
fprintf(stderr, "%s: couldn't allocate memory for %s: %s\n", progname, s, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
/* args --- go through the argument list, set options */
|
|
|
|
int
|
|
args(int argc, char **argv)
|
|
{
|
|
int i;
|
|
|
|
if (argc == 0 || argv == NULL || argv[0][0] == '\0')
|
|
return -1;
|
|
|
|
for (i = 0; i < argc && argv[i] != NULL; i++) {
|
|
if (strcmp(argv[i], "-display") == 0) {
|
|
displayname = argv[i+1];
|
|
i++;
|
|
} else if (strcmp(argv[i], "-file") == 0) {
|
|
filename = argv[i+1];
|
|
i++;
|
|
} else if (strcmp(argv[i], "-font") == 0) {
|
|
fontname = argv[i+1];
|
|
i++;
|
|
} else if (strcmp(argv[i], "-geometry") == 0) {
|
|
geometry = argv[i+1];
|
|
i++;
|
|
} else if (strcmp(argv[i], "-label") == 0) {
|
|
labelname = argv[i+1];
|
|
i++;
|
|
} else if (strcmp(argv[i], "-shell") == 0) {
|
|
shell = argv[i+1];
|
|
i++;
|
|
} else if (strcmp(argv[i], "-popup") == 0)
|
|
popup = true;
|
|
else if (strcmp(argv[i], "-popdown") == 0)
|
|
popdown = true;
|
|
else if (strcmp(argv[i], "-fg") == 0)
|
|
fgcname = argv[++i];
|
|
else if (strcmp(argv[i], "-bg") == 0)
|
|
bgcname = argv[++i];
|
|
else if (strcmp(argv[i], "-iconic") == 0)
|
|
iconic = true;
|
|
else if (strcmp(argv[i], "-path") == 0) {
|
|
char pathbuf[MAXPATHLEN];
|
|
char *s, *t;
|
|
|
|
s = getenv("PATH");
|
|
if (s != NULL) {
|
|
/* append current dir to PATH */
|
|
char *cp = getcwd(pathbuf, MAXPATHLEN);
|
|
t = malloc(strlen(s) + strlen(pathbuf) + 7);
|
|
sprintf(t, "PATH=%s:%s", pathbuf, s);
|
|
putenv(t);
|
|
}
|
|
} else if (strcmp(argv[i], "-teleport") == 0)
|
|
teleport = true;
|
|
else if (strcmp(argv[i], "-warp") == 0)
|
|
warp = true;
|
|
else if (strcmp(argv[i], "-version") == 0) {
|
|
printf("%s\n", version);
|
|
exit(0);
|
|
} else if (argv[i][0] == '-')
|
|
usage();
|
|
else
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* main --- crack arguments, set up X stuff, run the main menu loop */
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int i, j;
|
|
char *cp;
|
|
XGCValues gv;
|
|
unsigned long mask;
|
|
int nlabels = 0;
|
|
|
|
g_argc = argc;
|
|
g_argv = argv;
|
|
|
|
/* set default label name */
|
|
if ((cp = strrchr(argv[0], '/')) == NULL)
|
|
labelname = argv[0];
|
|
else
|
|
labelname = ++cp;
|
|
|
|
++argv;
|
|
--argc;
|
|
|
|
/* and program name for diagnostics */
|
|
progname = labelname;
|
|
|
|
i = args(argc, argv);
|
|
|
|
numitems = argc - i;
|
|
|
|
if (numitems <= 0 && filename == NULL)
|
|
usage();
|
|
|
|
if (filename) {
|
|
/* Read options and labels from file */
|
|
char fbuf[1024];
|
|
FILE *fp;
|
|
|
|
if (strcmp(filename, "-") == 0) {
|
|
fp = stdin;
|
|
} else {
|
|
fp = fopen(filename, "r");
|
|
}
|
|
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "%s: couldn't open '%s': %s\n", progname,
|
|
filename, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
while (fgets(fbuf, sizeof fbuf, fp)) {
|
|
char *s = fbuf;
|
|
strtok(s, "\n");
|
|
if (s[0] == '-') {
|
|
char *temp[3];
|
|
temp[0] = s;
|
|
temp[1] = strchr(s, ' ');
|
|
if (temp[1]) {
|
|
*(temp[1]++) = '\0';
|
|
s = malloc(strlen(temp[1]) + 1);
|
|
if (s == NULL)
|
|
memory("temporary argument");
|
|
strcpy(s, temp[1]);
|
|
temp[1] = s;
|
|
}
|
|
temp[2] = 0;
|
|
args(temp[1] ? 2 : 1, temp);
|
|
continue;
|
|
}
|
|
if (s[0] == '#')
|
|
continue;
|
|
/* allow - in menu items to be escaped */
|
|
if (s[0] == '\\')
|
|
++s;
|
|
/* allocate space */
|
|
if (f_argc < nlabels + 1) {
|
|
int k;
|
|
char **temp = malloc(sizeof(char *) * (f_argc + 5));
|
|
if (temp == 0)
|
|
memory("temporary item");
|
|
|
|
for (k = 0; k < nlabels; k++)
|
|
temp[k] = f_argv[k];
|
|
|
|
free(f_argv);
|
|
f_argv = temp;
|
|
f_argc += 5;
|
|
}
|
|
f_argv[nlabels] = malloc(strlen(s) + 1);
|
|
if (f_argv[nlabels] == NULL)
|
|
memory("temporary text");
|
|
strcpy(f_argv[nlabels], s);
|
|
++nlabels;
|
|
}
|
|
}
|
|
|
|
labels = (char **) malloc((numitems + nlabels) * sizeof(char *));
|
|
commands = (char **) malloc((numitems + nlabels) * sizeof(char *));
|
|
if (commands == NULL || labels == NULL)
|
|
memory("command and label arrays");
|
|
|
|
for (j = 0; j < numitems; j++) {
|
|
labels[j] = argv[i + j];
|
|
if ((cp = strchr(labels[j], ':')) != NULL) {
|
|
*cp++ = '\0';
|
|
commands[j] = cp;
|
|
} else
|
|
commands[j] = labels[j];
|
|
}
|
|
|
|
/*
|
|
* Now we no longer need i (our offset into argv) so we recycle it,
|
|
* while keeping the old value of j!
|
|
*/
|
|
for (i = 0; i < nlabels; i++) {
|
|
labels[j] = f_argv[i];
|
|
if ((cp = strchr(labels[j], ':')) != NULL) {
|
|
*cp++ = '\0';
|
|
commands[j] = cp;
|
|
} else
|
|
commands[j] = labels[j];
|
|
++j;
|
|
}
|
|
|
|
/* And now we merge the totals */
|
|
numitems += nlabels;
|
|
|
|
dpy = XOpenDisplay(displayname);
|
|
if (dpy == NULL) {
|
|
fprintf(stderr, "%s: cannot open display", progname);
|
|
if (displayname != NULL)
|
|
fprintf(stderr, " %s", displayname);
|
|
fprintf(stderr, "\n");
|
|
exit(1);
|
|
}
|
|
|
|
screen = DefaultScreen(dpy);
|
|
root = RootWindow(dpy, screen);
|
|
|
|
/*
|
|
* This used to be
|
|
* black = BlackPixel(dpy, screen);
|
|
* white = WhitePixel(dpy, screen);
|
|
*/
|
|
defcmap = DefaultColormap(dpy, screen);
|
|
if (fgcname == NULL
|
|
|| XParseColor(dpy, defcmap, fgcname, &color) == 0
|
|
|| XAllocColor(dpy, defcmap, &color) == 0)
|
|
black = BlackPixel(dpy, screen);
|
|
else
|
|
black = color.pixel;
|
|
|
|
if (bgcname == NULL
|
|
|| XParseColor(dpy, defcmap, bgcname, &color) == 0
|
|
|| XAllocColor(dpy, defcmap, &color) == 0)
|
|
white = WhitePixel(dpy, screen);
|
|
else
|
|
white = color.pixel;
|
|
|
|
/* try user's font first */
|
|
if (fontname != NULL) {
|
|
font = XLoadQueryFont(dpy, fontname);
|
|
if (font == NULL)
|
|
fprintf(stderr, "%s: warning: can't load font %s\n",
|
|
progname, fontname);
|
|
}
|
|
|
|
/* if no user font, try one of our default fonts */
|
|
if (font == NULL) {
|
|
for (i = 0; fontlist[i] != NULL; i++) {
|
|
font = XLoadQueryFont(dpy, fontlist[i]);
|
|
if (font != NULL)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (font == NULL) {
|
|
fprintf(stderr, "%s: fatal: cannot load a font\n", progname);
|
|
exit(1);
|
|
}
|
|
|
|
gv.foreground = black^white;
|
|
gv.background = white;
|
|
gv.font = font->fid;
|
|
gv.function = GXxor;
|
|
gv.line_width = 0;
|
|
mask = GCForeground | GCBackground | GCFunction | GCFont | GCLineWidth;
|
|
gc = XCreateGC(dpy, root, mask, &gv);
|
|
|
|
signal(SIGCHLD, reap);
|
|
|
|
run_menu();
|
|
|
|
XCloseDisplay(dpy);
|
|
exit(0);
|
|
}
|
|
|
|
/* spawn --- run a command */
|
|
|
|
void
|
|
spawn(char *com)
|
|
{
|
|
int pid;
|
|
static char *sh_base = NULL;
|
|
|
|
if (sh_base == NULL) {
|
|
sh_base = strrchr(shell, '/');
|
|
if (sh_base != NULL)
|
|
sh_base++;
|
|
else
|
|
sh_base = shell;
|
|
}
|
|
|
|
/*
|
|
* Since -popup means run command and exit, just
|
|
* fall straight into exec code. Thus only fork
|
|
* if not popup.
|
|
*/
|
|
if (! popup) {
|
|
if (strncmp(com, "exec ", 5) != 0) {
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
fprintf(stderr, "%s: can't fork: %s\n", progname, strerror(errno));
|
|
return;
|
|
} else if (pid > 0)
|
|
return;
|
|
} else {
|
|
com += 5;
|
|
}
|
|
}
|
|
|
|
close(ConnectionNumber(dpy));
|
|
execl(shell, sh_base, "-c", com, NULL);
|
|
execl("/bin/sh", "sh", "-c", com, NULL);
|
|
_exit(1);
|
|
}
|
|
|
|
/* reap --- collect dead children */
|
|
|
|
void
|
|
reap(int sig)
|
|
{
|
|
(void) wait((int *) NULL);
|
|
signal(sig, reap);
|
|
}
|
|
|
|
/* usage --- print a usage message and die */
|
|
|
|
void
|
|
usage()
|
|
{
|
|
fprintf(stderr, "usage: %s [-display displayname] [-font fname] ", progname);
|
|
fprintf(stderr, "[-file filename] [-path]");
|
|
fprintf(stderr, "[-geometry geom] [-shell shell] [-label name] ");
|
|
fprintf(stderr, "[-popup] [-popdown] [-iconic] [-teleport] ");
|
|
fprintf(stderr, "[-warp] [-version] menitem:command ...\n");
|
|
exit(0);
|
|
}
|
|
|
|
/* run_menu --- put up the window, execute selected commands */
|
|
|
|
void
|
|
run_menu()
|
|
{
|
|
XEvent ev;
|
|
XClientMessageEvent *cmsg;
|
|
KeySym key;
|
|
int i, cur, old, wide, high, ico, dx, dy;
|
|
|
|
dx = 0;
|
|
for (i = 0; i < numitems; i++) {
|
|
wide = XTextWidth(font, labels[i], strlen(labels[i])) + 4;
|
|
if (wide > dx)
|
|
dx = wide;
|
|
}
|
|
wide = dx;
|
|
|
|
old = cur = -1;
|
|
|
|
high = font->ascent + font->descent + 1;
|
|
dy = numitems * high;
|
|
|
|
set_wm_hints(wide, dy);
|
|
|
|
ask_wm_for_delete();
|
|
|
|
#define MenuMask (ButtonPressMask|ButtonReleaseMask\
|
|
|LeaveWindowMask|PointerMotionMask|ButtonMotionMask\
|
|
|ExposureMask|StructureNotifyMask|KeyPressMask)
|
|
|
|
XSelectInput(dpy, menuwin, MenuMask);
|
|
|
|
XMapWindow(dpy, menuwin);
|
|
|
|
ico = 1; /* warp to first item */
|
|
i = 0; /* save menu Item position */
|
|
|
|
for (;;) {
|
|
XNextEvent(dpy, &ev);
|
|
switch (ev.type) {
|
|
default:
|
|
fprintf(stderr, "%s: unknown ev.type %d\n",
|
|
progname, ev.type);
|
|
break;
|
|
case ButtonRelease:
|
|
/* allow button 1 or button 3 */
|
|
if (ev.xbutton.button == Button2)
|
|
break;
|
|
i = ev.xbutton.y/high;
|
|
if (ev.xbutton.x < 0 || ev.xbutton.x > wide)
|
|
break;
|
|
else if (i < 0 || i >= numitems)
|
|
break;
|
|
if (warp)
|
|
restoremouse();
|
|
if (strcmp(labels[i], "exit") == 0) {
|
|
if (commands[i] != labels[i]) {
|
|
spawn(commands[i]);
|
|
}
|
|
return;
|
|
}
|
|
spawn(commands[i]);
|
|
if (popup)
|
|
return;
|
|
if (cur >= 0 && cur < numitems)
|
|
XFillRectangle(dpy, menuwin, gc, 0, cur*high, wide, high);
|
|
if (popdown)
|
|
XIconifyWindow(dpy, menuwin, screen);
|
|
cur = -1;
|
|
break;
|
|
case ButtonPress:
|
|
case MotionNotify:
|
|
old = cur;
|
|
cur = ev.xbutton.y/high;
|
|
if (ev.xbutton.x < 0 || ev.xbutton.x > wide)
|
|
cur = -1;
|
|
else if (cur < 0 || cur >= numitems)
|
|
cur = -1;
|
|
if (cur == old)
|
|
break;
|
|
if (old >= 0 && old < numitems)
|
|
XFillRectangle(dpy, menuwin, gc, 0, old*high, wide, high);
|
|
if (cur >= 0 && cur < numitems)
|
|
XFillRectangle(dpy, menuwin, gc, 0, cur*high, wide, high);
|
|
break;
|
|
case KeyPress:
|
|
/* http://stackoverflow.com/questions/9838385/replace-of-xkeycodetokeysym */
|
|
key = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0,
|
|
ev.xkey.state & ShiftMask ? 1 : 0);
|
|
if (key != CONFIG_MENU_UP_KEY
|
|
&& key != CONFIG_MENU_DOWN_KEY
|
|
&& key != CONFIG_MENU_SELECT_KEY)
|
|
break;
|
|
|
|
/* adjust i so mapping will work */
|
|
if (key == CONFIG_MENU_UP_KEY) {
|
|
old = cur;
|
|
cur--;
|
|
i--;
|
|
} else if (key == CONFIG_MENU_DOWN_KEY) {
|
|
old = cur;
|
|
cur++;
|
|
i++;
|
|
}
|
|
|
|
while (cur < 0)
|
|
cur += numitems;
|
|
|
|
cur %= numitems;
|
|
|
|
if (key == CONFIG_MENU_UP_KEY || key == CONFIG_MENU_DOWN_KEY) {
|
|
if (cur == old)
|
|
break;
|
|
if (old >= 0 && old < numitems && cur != -1)
|
|
XFillRectangle(dpy, menuwin, gc, 0, old*high, wide, high);
|
|
if (cur >= 0 && cur < numitems && cur != -1)
|
|
XFillRectangle(dpy, menuwin, gc, 0, cur*high, wide, high);
|
|
break;
|
|
}
|
|
|
|
if (warp)
|
|
restoremouse();
|
|
if (key == CONFIG_MENU_SELECT_KEY) {
|
|
if (strcmp(labels[cur], "exit") == 0) {
|
|
if (commands[cur] != labels[cur]) {
|
|
spawn(commands[cur]);
|
|
}
|
|
return;
|
|
}
|
|
spawn(commands[cur]);
|
|
}
|
|
|
|
if (popup)
|
|
return;
|
|
if (popdown)
|
|
XIconifyWindow(dpy, menuwin, screen);
|
|
break;
|
|
case LeaveNotify:
|
|
cur = old = -1;
|
|
XClearWindow(dpy, menuwin);
|
|
redraw(cur, high, wide);
|
|
break;
|
|
case ReparentNotify:
|
|
case ConfigureNotify:
|
|
/*
|
|
* ignore these, they come from XMoveWindow
|
|
* and are enabled by Struct..
|
|
*/
|
|
break;
|
|
case UnmapNotify:
|
|
ico = 1;
|
|
XClearWindow(dpy, menuwin);
|
|
break;
|
|
case MapNotify:
|
|
if (ico) {
|
|
if (teleport)
|
|
teleportmenu(i, wide, high);
|
|
else if (warp)
|
|
warpmouse(i, wide, high);
|
|
}
|
|
XClearWindow(dpy, menuwin);
|
|
redraw(cur = i, high, wide);
|
|
ico = 0;
|
|
break;
|
|
case Expose:
|
|
XClearWindow(dpy, menuwin);
|
|
redraw(cur, high, wide);
|
|
break;
|
|
case ClientMessage:
|
|
cmsg = &ev.xclient;
|
|
if (cmsg->message_type == wm_protocols
|
|
&& cmsg->data.l[0] == wm_delete_window)
|
|
return;
|
|
case MappingNotify: /* why do we get this? */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* set_wm_hints --- set all the window manager hints */
|
|
|
|
void
|
|
set_wm_hints(int wide, int high)
|
|
{
|
|
Pixmap iconpixmap;
|
|
XWMHints *wmhints;
|
|
XSizeHints *sizehints;
|
|
XClassHint *classhints;
|
|
XTextProperty wname, iname;
|
|
|
|
if ((sizehints = XAllocSizeHints()) == NULL)
|
|
memory("size hints");
|
|
|
|
if ((wmhints = XAllocWMHints()) == NULL)
|
|
memory("window manager hints");
|
|
|
|
if ((classhints = XAllocClassHint()) == NULL)
|
|
memory("class hints");
|
|
|
|
/* fill in hints in order to parse geometry spec */
|
|
sizehints->width = sizehints->min_width = sizehints->max_width = wide;
|
|
sizehints->height = sizehints->min_height = sizehints->max_height = high;
|
|
sizehints->flags = USSize|PSize|PMinSize|PMaxSize;
|
|
if (XWMGeometry(dpy, screen, geometry, "", 1, sizehints,
|
|
&sizehints->x, &sizehints->y,
|
|
&sizehints->width, &sizehints->height,
|
|
&sizehints->win_gravity) & (XValue|YValue))
|
|
sizehints->flags |= USPosition;
|
|
|
|
/* override -geometry for size of window */
|
|
sizehints->width = sizehints->min_width = sizehints->max_width = wide;
|
|
sizehints->height = sizehints->min_height = sizehints->max_height = high;
|
|
|
|
if (XStringListToTextProperty(& labelname, 1, & wname) == 0)
|
|
memory("window name structure");
|
|
|
|
if (XStringListToTextProperty(& labelname, 1, & iname) == 0)
|
|
memory("icon name structure");
|
|
|
|
menuwin = XCreateSimpleWindow(dpy, root, sizehints->x, sizehints->y,
|
|
sizehints->width, sizehints->height, 1, black, white);
|
|
|
|
iconpixmap = XCreateBitmapFromData(dpy, menuwin,
|
|
nine_menu_bits,
|
|
nine_menu_width,
|
|
nine_menu_height);
|
|
|
|
wmhints->icon_pixmap = iconpixmap;
|
|
wmhints->input = False; /* no keyboard input */
|
|
if (iconic)
|
|
wmhints->initial_state = IconicState;
|
|
else
|
|
wmhints->initial_state = NormalState;
|
|
|
|
wmhints->flags = IconPixmapHint | StateHint | InputHint;
|
|
|
|
classhints->res_name = progname;
|
|
classhints->res_class = "9menu";
|
|
|
|
#ifdef SET_PROPERTIES_MANUALLY
|
|
/*
|
|
* For some reason, XSetWMProperties (see below) is failing
|
|
* John O'Donnell replaces it with the following commands
|
|
* (this leaves out XSetWMClientMachine,
|
|
* and also environment variable checking from ClassHint)
|
|
*/
|
|
XSetWMName(dpy, menuwin, &wname);
|
|
XSetWMIconName(dpy, menuwin, &iname);
|
|
XSetCommand(dpy, menuwin, g_argv, g_argc);
|
|
XSetWMHints(dpy, menuwin, wmhints);
|
|
XSetClassHint(dpy, menuwin, classhints);
|
|
XSetWMNormalHints(dpy, menuwin, sizehints);
|
|
#else
|
|
XSetWMProperties(dpy, menuwin, & wname, & iname,
|
|
g_argv, g_argc, sizehints, wmhints, classhints);
|
|
#endif
|
|
}
|
|
|
|
/* ask_wm_for_delete --- jump through hoops to ask WM to delete us */
|
|
|
|
void
|
|
ask_wm_for_delete()
|
|
{
|
|
int status;
|
|
|
|
wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
|
|
wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
|
|
status = XSetWMProtocols(dpy, menuwin, & wm_delete_window, 1);
|
|
|
|
if (status != True)
|
|
fprintf(stderr, "%s: could not ask for clean delete\n",
|
|
progname);
|
|
}
|
|
|
|
/* redraw --- actually redraw the menu */
|
|
|
|
void
|
|
redraw(int cur, int high, int wide)
|
|
{
|
|
int tx, ty, i;
|
|
|
|
for (i = 0; i < numitems; i++) {
|
|
tx = (wide - XTextWidth(font, labels[i], strlen(labels[i]))) / 2;
|
|
ty = i*high + font->ascent + 1;
|
|
XDrawString(dpy, menuwin, gc, tx, ty, labels[i], strlen(labels[i]));
|
|
}
|
|
if (cur >= 0 && cur < numitems)
|
|
XFillRectangle(dpy, menuwin, gc, 0, cur*high, wide, high);
|
|
}
|
|
|
|
/* teleportmenu --- move the menu to the right place */
|
|
|
|
void
|
|
teleportmenu(int cur, int wide, int high)
|
|
{
|
|
int x, y, dummy;
|
|
Window wdummy;
|
|
|
|
if (XQueryPointer(dpy, menuwin, &wdummy, &wdummy, &x, &y,
|
|
&dummy, &dummy, &dummy))
|
|
XMoveWindow(dpy, menuwin, x-wide/2, y-cur*high-high/2);
|
|
}
|
|
|
|
/* warpmouse --- bring the mouse to the menu */
|
|
|
|
void
|
|
warpmouse(int cur, int wide, int high)
|
|
{
|
|
int dummy;
|
|
Window wdummy;
|
|
int offset;
|
|
|
|
/* move tip of pointer into middle of menu item */
|
|
offset = (font->ascent + font->descent + 1) / 2;
|
|
offset += 6; /* fudge factor */
|
|
|
|
if (XQueryPointer(dpy, menuwin, &wdummy, &wdummy, &savex, &savey,
|
|
&dummy, &dummy, &dummy))
|
|
XWarpPointer(dpy, None, menuwin, 0, 0, 0, 0,
|
|
wide/2, cur*high-high/2+offset);
|
|
}
|
|
|
|
/* restoremouse --- put the mouse back where it was */
|
|
|
|
void
|
|
restoremouse()
|
|
{
|
|
XWarpPointer(dpy, menuwin, root, 0, 0, 0, 0,
|
|
savex, savey);
|
|
}
|
|
EOF-9menu.c
|
|
echo - 'Imakefile'
|
|
cat << 'EOF-Imakefile' > 'Imakefile'
|
|
INCLUDES = -I$(TOP)
|
|
DEPLIBS = $(DEPXLIBONLY)
|
|
LOCAL_LIBRARIES = $(XLIBONLY)
|
|
DEFINES = -DSHAPE #-DDEBUG -DDEBUG_EV
|
|
SRCS = 9menu.c
|
|
OBJS = 9menu.o
|
|
|
|
ComplexProgramTarget(9menu)
|
|
EOF-Imakefile
|
|
echo - 'Makefile.noimake'
|
|
cat << 'EOF-Makefile.noimake' > 'Makefile.noimake'
|
|
# Makefile for 9menu.
|
|
#
|
|
# Edit to taste
|
|
#
|
|
# Arnold Robbins
|
|
# arnold@skeeve.atl.ga.us
|
|
|
|
CC = gcc
|
|
CFLAGS = -g -O
|
|
LIBS = -lX11
|
|
|
|
9menu: 9menu.c
|
|
$(CC) $(CFLAGS) 9menu.c $(LIBS) -o 9menu
|
|
EOF-Makefile.noimake
|