Menu.C
#include "Menu.h"
#include "Manager.h"
#include "Client.h"
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <X11/keysym.h>
10
Boolean Menu::m_initialised = False;
GC Menu::m_menuGC;
#if I18N
XFontSet Menu::m_fontset;
#endif
XFontStruct *Menu::m_font;
unsigned long Menu::m_foreground;
unsigned long Menu::m_background;
unsigned long Menu::m_border;
20 Window Menu::m_window;
Menu::Menu(WindowManager *manager, XEvent *e)
: m_items(0), m_nItems(0), m_nHidden(0),
m_windowManager(manager), m_event(e),
m_hasSubmenus(False)
{
if (!m_initialised)
{
30 m_foreground = m_windowManager->allocateColour
(CONFIG_MENU_FOREGROUND, "menu foreground");
m_background = m_windowManager->allocateColour
(CONFIG_MENU_BACKGROUND, "menu background");
m_border = m_windowManager->allocateColour
(CONFIG_MENU_BORDERS, "menu border");
#if I18N
char **ml;
int mc;
40 char *ds;
m_fontset = XCreateFontSet(display(), CONFIG_NICE_MENU_FONT,
&ml, &mc, &ds);
if (!m_fontset)
m_fontset = XCreateFontSet(display(), CONFIG_NASTY_FONT,
&ml, &mc, &ds);
if (m_fontset) {
XFontStruct **fs_list;
XFontsOfFontSet(m_fontset, &fs_list, &ml);
50 m_font = fs_list[0];
} else {
m_font = NULL;
}
#define XTextWidth(x,y,z) XmbTextEscapement(m_fontset,y,z)
#define XDrawString(t,u,v,w,x,y,z) XmbDrawString(t,u,m_fontset,v,w,x,y,z)
#else
m_font = XLoadQueryFont(display(), CONFIG_NICE_MENU_FONT);
if (!m_font) m_font = XLoadQueryFont(display(), CONFIG_NASTY_FONT);
#endif
60 if (!m_font) m_windowManager->fatal("couldn't load menu font\n");
XGCValues values;
values.background = m_background;
values.foreground = m_foreground ^ m_background;
values.function = GXxor;
values.line_width = 0;
values.subwindow_mode = IncludeInferiors;
values.font = m_font->fid;
70 m_menuGC = XCreateGC
(display(), root(),
GCForeground | GCBackground | GCFunction |
GCLineWidth | GCSubwindowMode, &values);
XChangeGC(display(), Border::drawGC(m_windowManager), GCFont, &values);
m_window = XCreateSimpleWindow
(display(), root(), 0, 0, 1, 1, 1,
m_border, m_background);
80
XSetWindowAttributes attr;
#if ( CONFIG_USE_PIXMAPS != False ) && ( CONFIG_USE_PIXMAP_MENUS != False )
attr.background_pixmap = Border::backgroundPixmap(manager);
#endif
attr.save_under =
(DoesSaveUnders(ScreenOfDisplay(display(), screen())) ?
True : False);
90 #if ( CONFIG_USE_PIXMAPS != False ) && ( CONFIG_USE_PIXMAP_MENUS != False )
XChangeWindowAttributes
(display(), m_window, CWBackPixmap, &attr);
#endif
XChangeWindowAttributes
(display(), m_window, CWSaveUnder, &attr);
m_initialised = True;
}
}
100
Menu::~Menu()
{
XUnmapWindow(display(), m_window);
}
void Menu::cleanup(WindowManager *const wm)
{
if (m_initialised) { // fix due to Eric Marsden
#if I18N
110 XFreeFontSet(wm->display(), m_fontset);
#else
XFreeFont(wm->display(), m_font);
#endif
XFreeGC(wm->display(), m_menuGC);
}
}
int nobuttons(XButtonEvent *e) // straight outta 9wm
{
120 int state;
state = (e->state & AllButtonMask);
return (e->type == ButtonRelease) && (state & (state - 1)) == 0;
}
int Menu::getSelection()
{
m_items = getItems(&m_nItems, &m_nHidden);
XButtonEvent *xbev = (XButtonEvent *)m_event; // KeyEvent is similar enough
130 if (xbev->window == m_window || m_nItems == 0) return -1;
int width, maxWidth = 10;
for(int i = 0; i < m_nItems; i++) {
width = XTextWidth(m_font, m_items[i], strlen(m_items[i]));
if (width > maxWidth) maxWidth = width;
}
maxWidth += 32;
Boolean isKeyboardMenu =
140 (CONFIG_WANT_KEYBOARD_MENU && (xbev->type == KeyPress));
int selecting = isKeyboardMenu ? 0 : -1, prev = -1;
int entryHeight = m_font->ascent + m_font->descent + 4;
int totalHeight = entryHeight * m_nItems + 13;
int mx = DisplayWidth (display(), screen()) - 1;
int my = DisplayHeight(display(), screen()) - 1;
int x, y;
150 if (isKeyboardMenu) {
x = mx / 2 - maxWidth / 2;
y = my / 2 - totalHeight / 2;
} else {
x = xbev->x - maxWidth/2;
y = xbev->y - 2;
Boolean warp = False;
160 if (x < 0) {
xbev->x -= x;
x = 0;
warp = True;
} else if (x + maxWidth >= mx) {
xbev->x -= x + maxWidth - mx;
x = mx - maxWidth;
warp = True;
}
170 if (y < 0) {
xbev->y -= y;
y = 0;
warp = True;
} else if (y + totalHeight >= my) {
xbev->y -= y + totalHeight - my;
y = my - totalHeight;
warp = True;
}
180 if (warp) XWarpPointer(display(), None, root(), None, None,
None, None, xbev->x, xbev->y);
}
XMoveResizeWindow(display(), m_window, x, y, maxWidth, totalHeight);
XSelectInput(display(), m_window, MenuMask);
XMapRaised(display(), m_window);
if (m_windowManager->attemptGrab(m_window, None,
MenuGrabMask, xbev->time)
190 != GrabSuccess) {
XUnmapWindow(display(), m_window);
return -1;
}
if (isKeyboardMenu) {
if (m_windowManager->attemptGrabKey(m_window, xbev->time)
!= GrabSuccess) {
XUnmapWindow(display(), m_window);
return -1;
200 }
}
Boolean done = False;
Boolean drawn = False;
XEvent event;
struct timeval sleepval;
unsigned long tdiff = 0L;
Boolean speculating = False;
Boolean foundEvent;
210
while (!done)
{
int i;
foundEvent = False;
if (CONFIG_FEEDBACK_DELAY >= 0 &&
tdiff > CONFIG_FEEDBACK_DELAY &&
!isKeyboardMenu && // removeFeedback didn't seem to work for it
!speculating) {
220
if (selecting >= 0 && selecting < m_nItems) {
raiseFeedbackLevel(selecting);
XRaiseWindow(display(), m_window);
}
speculating = True;
}
//!!! MenuMask | ??? suggests MenuMask is wrong
230 while (XCheckMaskEvent
(display(), MenuMask | StructureNotifyMask |
KeyPressMask | KeyReleaseMask, &event)) {
foundEvent = True;
if (event.type != MotionNotify) break;
}
if (!foundEvent) {
sleepval.tv_sec = 0;
sleepval.tv_usec = 10000;
240 select(0, 0, 0, 0, &sleepval);
tdiff += 10;
continue;
}
switch (event.type)
{
case ButtonPress:
break;
250 case ButtonRelease:
if (isKeyboardMenu) break;
if (drawn) {
if (event.xbutton.button != xbev->button) break;
x = event.xbutton.x;
y = event.xbutton.y - 11;
i = y / entryHeight;
260 if (selecting >= 0 && y >= selecting * entryHeight - 3 &&
y <= (selecting + 1) * entryHeight - 3) i = selecting;
if (m_hasSubmenus && (i >= 0 && i < m_nHidden)) i = -i;
if (x < 0 || x > maxWidth || y < -3) i = -1;
else if (i < 0 || i >= m_nItems) i = -1;
} else {
i = -1;
270 }
if (!nobuttons(&event.xbutton)) i = -1;
m_windowManager->releaseGrab(&event.xbutton);
XUnmapWindow(display(), m_window);
selecting = i;
done = True;
break;
case MotionNotify:
280 if (!drawn || isKeyboardMenu) break;
x = event.xbutton.x;
y = event.xbutton.y - 11;
prev = selecting;
selecting = y / entryHeight;
if (prev >= 0 && y >= prev * entryHeight - 3 &&
y <= (prev+1) * entryHeight - 3) selecting = prev;
290 if (m_hasSubmenus && (selecting >= 0 && selecting < m_nHidden) &&
x >= maxWidth-32 && x < maxWidth)
{
xbev->x += event.xbutton.x - 32;
xbev->y += event.xbutton.y;
createSubmenu ((XEvent *)xbev, selecting);
done = True;
break;
}
300
if (x < 0 || x > maxWidth || y < -3) selecting = -1;
else if (selecting < 0 || selecting > m_nItems) selecting = -1;
if (selecting == prev) break;
tdiff = 0; speculating = False;
if (prev >= 0 && prev < m_nItems) {
removeFeedback(prev, speculating);
XFillRectangle(display(), m_window, m_menuGC,
310 4, prev * entryHeight + 9,
maxWidth - 8, entryHeight);
}
if (selecting >= 0 && selecting < m_nItems) {
showFeedback(selecting);
XRaiseWindow(display(), m_window);
XFillRectangle(display(), m_window, m_menuGC,
4, selecting * entryHeight + 9,
maxWidth - 8, entryHeight);
320 }
break;
case Expose:
if (CONFIG_MAD_FEEDBACK && event.xexpose.window != m_window) {
m_windowManager->dispatchEvent(&event);
break;
}
330
XClearWindow(display(), m_window);
XDrawRectangle(display(), m_window, m_menuGC, 2, 7,
maxWidth - 5, totalHeight - 10);
for (i = 0; i < m_nItems; i++) {
int dx = XTextWidth(m_font, m_items[i], strlen(m_items[i]));
int dy = i * entryHeight + m_font->ascent + 10;
340
if (i >= m_nHidden) {
XDrawString(display(), m_window,
Border::drawGC(m_windowManager),
maxWidth - 8 - dx, dy,
m_items[i], strlen(m_items[i]));
} else {
XDrawString(display(), m_window,
Border::drawGC(m_windowManager),
8, dy, m_items[i], strlen(m_items[i]));
350 }
}
if (selecting >= 0 && selecting < m_nItems) {
XFillRectangle(display(), m_window, m_menuGC,
4, selecting * entryHeight + 9,
maxWidth - 8, entryHeight);
}
drawn = True;
360 break;
case KeyPress:
{
if (!isKeyboardMenu) break;
KeySym key = XKeycodeToKeysym(display(), event.xkey.keycode, 0);
if (key == CONFIG_MENU_SELECT_KEY) {
370 if (!drawn) selecting = -1;
if (m_hasSubmenus && selecting >= 0 && selecting < m_nHidden)
{
createSubmenu((XEvent *)xbev, selecting);
selecting = -1;
}
m_windowManager->releaseGrabKeyMode(&event.xkey);
XUnmapWindow(display(), m_window);
done = True;
380 break;
} else if (key == CONFIG_MENU_CANCEL_KEY) {
m_windowManager->releaseGrabKeyMode(&event.xkey);
XUnmapWindow(display(), m_window);
if (selecting >= 0) removeFeedback(selecting, speculating);
selecting = -1;
done = True;
break;
390
} else if (key != CONFIG_MENU_UP_KEY &&
key != CONFIG_MENU_DOWN_KEY) {
break;
}
if (!drawn) break;
prev = selecting;
if (key == CONFIG_MENU_UP_KEY) {
400
if (prev <= 0) selecting = m_nItems - 1;
else selecting = prev - 1;
} else {
if (prev == m_nItems - 1 || prev < 0) selecting = 0;
else selecting = prev + 1;
}
410 tdiff = 0; speculating = False;
if (prev >= 0 && prev < m_nItems) {
removeFeedback(prev, speculating);
XFillRectangle(display(), m_window, m_menuGC,
4, prev * entryHeight + 9,
maxWidth - 8, entryHeight);
}
if (selecting >= 0 && selecting < m_nItems) {
420 showFeedback(selecting);
XRaiseWindow(display(), m_window);
XFillRectangle(display(), m_window, m_menuGC,
4, selecting * entryHeight + 9,
maxWidth - 8, entryHeight);
}
break;
}
430 case KeyRelease:
break;
default:
if (event.xmap.window == m_window) break;
m_windowManager->dispatchEvent(&event);
}
}
if (selecting >= 0) removeFeedback(selecting, speculating);
440 return selecting;
}
ClientMenu::ClientMenu(WindowManager *manager, XEvent *e)
: Menu(manager, e), m_allowExit(False)
{
int selecting = getSelection();
if (selecting == m_nItems-1 && m_allowExit) { // getItems sets m_allowExit
450 m_windowManager->setSignalled();
return;
}
if (CONFIG_DISABLE_NEW_WINDOW_COMMAND && selecting >= 0) {
++selecting; // ha ha ha!
++m_nHidden;
}
if (selecting == 0) {
460 m_windowManager->spawn(CONFIG_NEW_WINDOW_COMMAND, NULL);
return;
}
if (selecting > 0) {
Client *cl = m_clients.item(selecting - 1);
if (selecting < m_nHidden) cl->unhide(True);
else if (selecting < m_nItems) {
470
if (CONFIG_CLICK_TO_FOCUS) cl->activate();
else cl->mapRaised();
cl->ensureVisible();
}
if (CONFIG_WANT_KEYBOARD_MENU && e->type == KeyPress)
cl->activateAndWarp();
}
480 --m_nHidden; // just in case
}
ClientMenu::~ClientMenu()
{
m_clients.remove_all();
free(m_items);
}
char **ClientMenu::getItems(int *niR, int *nhR)
490 {
int i;
XButtonEvent *xbev = (XButtonEvent *)m_event; // KeyEvent is similar enough
for (i = 0; i < m_windowManager->hiddenClients().count(); ++i) {
if (m_windowManager->hiddenClients().item(i)->channel() ==
m_windowManager->channel()) {
m_clients.append(m_windowManager->hiddenClients().item(i));
}
}
500
int nh = m_clients.count();
if (!CONFIG_DISABLE_NEW_WINDOW_COMMAND) ++nh;
if (CONFIG_EVERYTHING_ON_ROOT_MENU) {
for (i = 0; i < m_windowManager->clients().count(); ++i) {
if (m_windowManager->clients().item(i)->isNormal() &&
m_windowManager->clients().item(i)->channel() ==
m_windowManager->channel()) {
m_clients.append(m_windowManager->clients().item(i));
510 }
}
}
int n = m_clients.count();
if (!CONFIG_DISABLE_NEW_WINDOW_COMMAND) ++n;
int mx = DisplayWidth (display(), screen()) - 1;
int my = DisplayHeight(display(), screen()) - 1;
520 m_allowExit =
((CONFIG_EXIT_CLICK_SIZE_X != 0 ?
(CONFIG_EXIT_CLICK_SIZE_X > 0 ?
(xbev->x < CONFIG_EXIT_CLICK_SIZE_X) :
(xbev->x > mx + CONFIG_EXIT_CLICK_SIZE_X)) : 1) &&
(CONFIG_EXIT_CLICK_SIZE_Y != 0 ?
(CONFIG_EXIT_CLICK_SIZE_Y > 0 ?
(xbev->y < CONFIG_EXIT_CLICK_SIZE_Y) :
(xbev->y > my + CONFIG_EXIT_CLICK_SIZE_Y)) : 1));
530 if (m_allowExit) ++n;
const char **items = (const char **)malloc(n * sizeof(char *));
for (i = 0; i < n; ++i) {
if (CONFIG_DISABLE_NEW_WINDOW_COMMAND == False) {
if (i == 0) items[i] = CONFIG_NEW_WINDOW_LABEL;
else if (m_allowExit && i > m_clients.count()) items[i] = "[Exit wmx]";
540 else items[i] = m_clients.item(i-1)->label();
} else {
if (m_allowExit && i >= m_clients.count()) items[i] = "[Exit wmx]";
else items[i] = m_clients.item(i)->label();
}
}
*niR = n;
550 *nhR = nh;
return (char **)items;
}
Client *ClientMenu::checkFeedback(int item)
{
if (CONFIG_MAD_FEEDBACK == False) return NULL;
560 if (CONFIG_DISABLE_NEW_WINDOW_COMMAND == False) {
if (item <= 0 || item > m_clients.count()+1) return NULL;
if (m_allowExit && item == m_clients.count() + 1) return NULL;
return m_clients.item(item - 1);
} else {
if (item < 0 || item > m_clients.count()) return NULL;
if (m_allowExit && item == m_clients.count()) return NULL;
570 return m_clients.item(item);
}
}
void ClientMenu::showFeedback(int item)
{
if (Client *c = checkFeedback(item)) c->showFeedback();
}
void ClientMenu::removeFeedback(int item, Boolean mapped)
580 {
if (Client *c = checkFeedback(item)) c->removeFeedback(mapped);
}
void ClientMenu::raiseFeedbackLevel(int item)
{
if (Client *c = checkFeedback(item)) c->raiseFeedbackLevel();
}
590 CommandMenu::CommandMenu(WindowManager *manager, XEvent *e,
char* otherdir = NULL)
: Menu(manager, e)
{
const char *home = getenv("HOME");
const char *wmxdir = getenv("WMXDIR");
m_commandDir = NULL;
m_hasSubmenus = True;
600 if (otherdir == NULL)
{
if (wmxdir == NULL)
{
if (home == NULL) return;
m_commandDir =
(char *)malloc(strlen(home) + strlen(CONFIG_COMMAND_MENU) + 2);
sprintf(m_commandDir, "%s/%s", home, CONFIG_COMMAND_MENU);
}
else
610 {
if(wmxdir[0] == '/')
{
m_commandDir =
(char *)malloc(strlen(wmxdir) + 1);
strcpy(m_commandDir, wmxdir);
}
else
{
m_commandDir =
620 (char *)malloc(strlen(home) + strlen(wmxdir) + 2);
sprintf(m_commandDir, "%s/%s", home, wmxdir);
}
}
}
else
{
m_commandDir = (char *)malloc(strlen(otherdir)+1);
strcpy(m_commandDir, otherdir);
}
630
int i = getSelection();
if (i >= 0 && i < m_nHidden) {
// This should never happen.
}
if (i >= m_nHidden && i < m_nItems)
{
char *commandFile =
640 (char *)malloc(strlen(m_commandDir) + strlen(m_items[i]) + 2);
sprintf(commandFile, "%s/%s", m_commandDir, m_items[i]);
m_windowManager->spawn(m_items[i], commandFile);
free(commandFile);
}
}
CommandMenu::~CommandMenu()
{
650 free(m_commandDir);
for (int i = 0; i < m_nItems; i++) {
free(m_items[i]);
}
free(m_items);
}
static int sortstrs(const void *va, const void *vb)
{
char **a = (char **)va;
660 char **b = (char **)vb;
return strcmp(*a, *b);
}
void CommandMenu::createSubmenu (XEvent *e, int i)
{
char *new_directory;
int dirlen = strlen (m_commandDir);
new_directory = (char *)malloc (dirlen + strlen(m_items[i]) + 2);
670 strcpy (new_directory, m_commandDir);
new_directory[dirlen] = '/';
strcpy (new_directory + dirlen + 1, m_items[i]);
CommandMenu menu (m_windowManager, e, new_directory);
free(new_directory);
}
char **CommandMenu::getItems(int *niR, int *nhR)
{
680 *niR = *nhR = 0;
const char *home;
if ((home = getenv("HOME")) == NULL) return NULL;
int dirlen = strlen(m_commandDir);
char *dirpath = (char *)malloc(dirlen + 1024 + 2); // NAME_MAX guess
strcpy(dirpath, m_commandDir);
DIR *dir = opendir(m_commandDir);
690
if (dir == NULL) {
free(dirpath);
free(m_commandDir);
m_commandDir =
(char *)malloc(strlen(CONFIG_SYSTEM_COMMAND_MENU) + 1);
sprintf(m_commandDir, CONFIG_SYSTEM_COMMAND_MENU);
dirlen = strlen(m_commandDir);
700 dirpath = (char *)malloc(dirlen + 1024 + 2); // NAME_MAX guess
strcpy(dirpath, m_commandDir);
dir = opendir(m_commandDir);
if (dir == NULL) {
free(dirpath);
return NULL;
}
}
710
int count = 0;
struct dirent *ent;
// We already assume efficient realloc() in Border.C, so we may as
// well assume it here too.
char **items = 0;
while ((ent = readdir(dir)) != NULL) {
720
struct stat st;
sprintf(dirpath + dirlen, "/%s", ent->d_name);
if (stat(dirpath, &st) == -1) continue;
if (!S_ISDIR(st.st_mode) || !(st.st_mode & 0444)) continue;
if (ent->d_name[0] == '.') continue;
items = (!items ? (char **)malloc(sizeof(char *)) :
(char **)realloc(items, (count + 1) * sizeof(char *)));
730
items[count++] = NewString(ent->d_name);
}
qsort(items, count, sizeof(char *), sortstrs);
*nhR = count;
rewinddir(dir);
while ((ent = readdir(dir)) != NULL) {
740
struct stat st;
sprintf(dirpath + dirlen, "/%s", ent->d_name);
if (stat(dirpath, &st) == -1) continue;
if (!S_ISREG(st.st_mode) || !(st.st_mode & 0111)) continue;
items = (!items ? (char **)malloc(sizeof(char *)) :
(char **)realloc(items, (count + 1) * sizeof(char *)));
750 items[count++] = NewString(ent->d_name);
}
*niR = count;
qsort(items + *nhR, *niR - *nhR, sizeof(char *), sortstrs);
free(dirpath);
closedir(dir);
return items;
760 }
// Fake geometry as a "menu" to save on code
ShowGeometry::ShowGeometry(WindowManager *manager, XEvent *e)
: Menu(manager, e)
{
// empty
}
770
void ShowGeometry::update(int x, int y)
{
char string[20];
sprintf(string, "%d %d", x, y);
int width = XTextWidth(m_font, string, strlen(string)) + 8;
int height = m_font->ascent + m_font->descent + 8;
int mx = DisplayWidth (display(), screen()) - 1;
int my = DisplayHeight(display(), screen()) - 1;
780
XMoveResizeWindow(display(), m_window,
(mx - width) / 2, (my - height) / 2, width, height);
XClearWindow(display(), m_window);
XMapRaised(display(), m_window);
XDrawString(display(), m_window, Border::drawGC(m_windowManager),
4, 4 + m_font->ascent, string, strlen(string));
}
790 void ShowGeometry::remove()
{
XUnmapWindow(display(), m_window);
}
ShowGeometry::~ShowGeometry()
{
// empty
}
800 char **ShowGeometry::getItems(int *niR, int *nhR)
{
niR = nhR = 0;
return NULL;
}