commit 64884ba4e717126d7c4bdda521b645a786f4c7cf
parent 5a8b84480aa36a0825c28f0d598d0eb0b001b156
Author: seninha <lucas@seninha.org>
Date: Fri, 11 Feb 2022 19:33:51 -0300
add support for torn off menu windows
Diffstat:
M | shod.1 | | | 22 | ++++++++++++++++++++++ |
M | shod.c | | | 1140 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
2 files changed, 840 insertions(+), 322 deletions(-)
diff --git a/shod.1 b/shod.1
@@ -438,6 +438,28 @@ Dragging the left title\-bar button with the third mouse button moves the row th
.B The Right Title\-Bar Button.
Each title bar has a right button.
Clicking on the right title-bar button with the first mouse button closes the focused client or its top dialog.
+.SS Dialogs
+Windows that are transient for another managed windows (called its leader) are mapped in the center of the leader.
+.PP
+Dialogs are small windows that communicates informate to the user and can prompt for a response.
+.PP
+.B shod
+only changes the position and size of a dialog window when the size of its leader changes.
+.SS Menus
+Windows of type
+.BR _NET_WM_WINDOW_TYPE_MENU ,
+.BR _NET_WM_WINDOW_TYPE_UTILITY ,
+.BR _NET_WM_WINDOW_TYPE_TOOLBAR ,
+or
+.B MWM_TEAROFF_WINDOW
+(called menu windows)
+are windows that cannot be tiled or tabbed into a container and are tied to a leader window.
+They are floating windows that always appear on top of their leader and are not listed on the list of clients.
+.PP
+Menu windows, often called torn off windows, are pinnable menus, utility windows and toolbar windows,
+ie' toolbars and menus "torn off" from the main application.
+.PP
+The user can change the position of a menu window in the same way of changing the position of a container.
.SS Prompt
A window of type
.B _NET_WM_WINDOW_TYPE_PROMPT
diff --git a/shod.c b/shod.c
@@ -30,11 +30,19 @@ enum {
TYPE_NORMAL,
TYPE_DESKTOP,
TYPE_DOCK,
+ TYPE_MENU,
+ TYPE_DIALOG,
TYPE_NOTIFICATION,
TYPE_PROMPT,
TYPE_DOCKAPP
};
+/* floating object type */
+enum {
+ FLOAT_CONTAINER,
+ FLOAT_MENU,
+};
+
/* state action */
enum {
REMOVE = 0,
@@ -99,6 +107,7 @@ enum {
WM_TAKE_FOCUS,
WM_PROTOCOLS,
WM_STATE,
+ WM_CLIENT_LEADER,
/* EWMH atoms */
_NET_SUPPORTED,
@@ -140,6 +149,9 @@ enum {
_NET_REQUEST_FRAME_EXTENTS,
_NET_FRAME_EXTENTS,
+ /* motif atoms */
+ _MOTIF_WM_HINTS,
+
/* shod atoms */
_SHOD_GROUP_TAB,
_SHOD_GROUP_CONTAINER,
@@ -210,6 +222,48 @@ enum {
BORDER_LAST
};
+/* motif constants, mostly unused */
+enum {
+ /*
+ * Constants copied from lib/Xm/MwmUtil.h on motif's source code.
+ */
+
+ PROP_MOTIF_WM_HINTS_ELEMENTS = 5,
+ PROP_MWM_HINTS_ELEMENTS = PROP_MOTIF_WM_HINTS_ELEMENTS,
+
+ /* bit definitions for MwmHints.flags */
+ MWM_HINTS_FUNCTIONS = (1 << 0),
+ MWM_HINTS_DECORATIONS = (1 << 1),
+ MWM_HINTS_INPUT_MODE = (1 << 2),
+ MWM_HINTS_STATUS = (1 << 3),
+
+ /* bit definitions for MwmHints.functions */
+ MWM_FUNC_ALL = (1 << 0),
+ MWM_FUNC_RESIZE = (1 << 1),
+ MWM_FUNC_MOVE = (1 << 2),
+ MWM_FUNC_MINIMIZE = (1 << 3),
+ MWM_FUNC_MAXIMIZE = (1 << 4),
+ MWM_FUNC_CLOSE = (1 << 5),
+
+ /* bit definitions for MwmHints.decorations */
+ MWM_DECOR_ALL = (1 << 0),
+ MWM_DECOR_BORDER = (1 << 1),
+ MWM_DECOR_RESIZEH = (1 << 2),
+ MWM_DECOR_TITLE = (1 << 3),
+ MWM_DECOR_MENU = (1 << 4),
+ MWM_DECOR_MINIMIZE = (1 << 5),
+ MWM_DECOR_MAXIMIZE = (1 << 6),
+
+ /* values for MwmHints.input_mode */
+ MWM_INPUT_MODELESS = 0,
+ MWM_INPUT_PRIMARY_APPLICATION_MODAL = 1,
+ MWM_INPUT_SYSTEM_MODAL = 2,
+ MWM_INPUT_FULL_APPLICATION_MODAL = 3,
+
+ /* bit definitions for MwmHints.status */
+ MWM_TEAROFF_WINDOW = (1 << 0),
+};
+
/* window eight sections (aka octants) */
enum Octant {
C = 0,
@@ -233,6 +287,7 @@ struct Winres {
struct Row *row; /* row (with buttons) of window */
struct Tab *t; /* tab of window */
struct Dialog *d; /* dialog of window */
+ struct Menu *menu; /* menu of window */
};
/* dialog window structure */
@@ -253,9 +308,11 @@ struct Tab {
struct Tab *prev, *next; /* pointer for list of tabs */
struct Row *row; /* pointer to parent row */
struct Dialog *ds; /* pointer to list of dialogs */
+ struct Menu *menus; /* pointer to list of menus */
Window title; /* title bar */
Window win; /* actual client window */
Window frame; /* window to reparent the client window */
+ Window leader; /* the group leader of the window */
Pixmap pix; /* pixmap to draw the background of the frame */
Pixmap pixtitle; /* pixmap to draw the background of the title window */
char *name; /* client name */
@@ -323,6 +380,24 @@ struct Container {
int nx, ny, nw, nh; /* non-maximized geometry */
};
+/* menu structure */
+struct Menu {
+ struct Menu *prev, *next; /* pointers for list of menus */
+ struct Tab *t; /* pointer to parent tab */
+ Window titlebar; /* close button */
+ Window button; /* close button */
+ Window frame; /* frame window */
+ Window win; /* the actual menu window */
+ Pixmap pix; /* pixmap to draw the frame */
+ Pixmap pixbutton; /* pixmap to draw the button */
+ Pixmap pixtitlebar; /* pixmap to draw the titlebar */
+ int x, y, w, h; /* geometry of the menu window + the frame */
+ int pw, ph; /* pixmap size */
+ int tw, th; /* titlebar pixmap size */
+ int ignoreunmap; /* number of unmapnotifys to ignore */
+ char *name; /* client name */
+};
+
/* desktop of a monitor */
struct Desktop {
struct Monitor *mon; /* monitor the desktop is in */
@@ -395,6 +470,15 @@ struct Dock {
int mapped; /* whether dock is mapped */
};
+/* motif window manager (Mwm) hints */
+struct MwmHints {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long inputMode;
+ unsigned long status;
+};
+
/* window manager stuff */
struct WM {
struct Bar *bars; /* list of bars */
@@ -779,6 +863,7 @@ initatoms(void)
[WM_TAKE_FOCUS] = "WM_TAKE_FOCUS",
[WM_PROTOCOLS] = "WM_PROTOCOLS",
[WM_STATE] = "WM_STATE",
+ [WM_CLIENT_LEADER] = "WM_CLIENT_LEADER",
[_NET_SUPPORTED] = "_NET_SUPPORTED",
[_NET_CLIENT_LIST] = "_NET_CLIENT_LIST",
[_NET_CLIENT_LIST_STACKING] = "_NET_CLIENT_LIST_STACKING",
@@ -817,6 +902,7 @@ initatoms(void)
[_NET_WM_STRUT_PARTIAL] = "_NET_WM_STRUT_PARTIAL",
[_NET_REQUEST_FRAME_EXTENTS] = "_NET_REQUEST_FRAME_EXTENTS",
[_NET_FRAME_EXTENTS] = "_NET_FRAME_EXTENTS",
+ [_MOTIF_WM_HINTS] = "_MOTIF_WM_HINTS",
[_SHOD_GROUP_TAB] = "_SHOD_GROUP_TAB",
[_SHOD_GROUP_CONTAINER] = "_SHOD_GROUP_CONTAINER",
};
@@ -907,6 +993,7 @@ getwin(Window win)
struct Row *row;
struct Tab *t;
struct Dialog *d;
+ struct Menu *menu;
struct Notification *n;
int i;
@@ -918,6 +1005,7 @@ getwin(Window win)
res.c = NULL;
res.t = NULL;
res.d = NULL;
+ res.menu = NULL;
if (win == dock.win) {
res.dock = &dock;
return res;
@@ -972,6 +1060,14 @@ getwin(Window win)
return res;
}
}
+ for (menu = t->menus; menu != NULL; menu = menu->next) {
+ if (win == menu->win || win == menu->frame || win == menu->button || win == menu->titlebar) {
+ res.c = c;
+ res.t = t;
+ res.menu = menu;
+ return res;
+ }
+ }
}
}
}
@@ -1035,30 +1131,102 @@ getatomprop(Window win, Atom prop)
return atom;
}
+/* get motif window manager hints property from window */
+static struct MwmHints *
+getmwmhints(Window win)
+{
+ struct MwmHints *mwmhints;
+ unsigned long dl;
+ Atom type;
+ int di;
+ int ret;
+
+ ret = XGetWindowProperty(dpy, win, atoms[_MOTIF_WM_HINTS],
+ 0L, PROP_MWM_HINTS_ELEMENTS,
+ False, atoms[_MOTIF_WM_HINTS],
+ &type, &di, &dl, &dl,
+ (unsigned char **)&mwmhints);
+ if ((ret == Success) && (type == atoms[_MOTIF_WM_HINTS]))
+ return mwmhints;
+ if (mwmhints != NULL)
+ XFree(mwmhints);
+ return NULL;
+}
+
+/* get tab given window is a dialog for */
+static struct Tab *
+getdialogfor(Window win)
+{
+ struct Winres res;
+ Window tmpwin;
+
+ if (XGetTransientForHint(dpy, win, &tmpwin)) {
+ res = getwin(tmpwin);
+ return res.t;
+ }
+ return NULL;
+}
+
+/* get tab equal to leader or having leader as group leader */
+static struct Tab *
+getleaderof(Window leader)
+{
+ struct Container *c;
+ struct Column *col;
+ struct Row *row;
+ struct Tab *t;
+
+ for (c = wm.c; c != NULL; c = c->next)
+ for (col = c->cols; col != NULL; col = col->next)
+ for (row = col->rows; row != NULL; row = row->next)
+ for (t = row->tabs; t != NULL; t = t->next)
+ if (t->win == leader || t->leader == leader)
+ return t;
+ return NULL;
+}
+
/* get type of window both by its WMHINTS and by the _NET_WM_WINDOW_TYPE hint */
static int
-getwintype(Window win)
+getwintype(Window win, struct Tab **parent, Window *leader)
{
+ struct MwmHints *mwmhints;
XWMHints *wmhints;
Atom prop;
- int isdockapp;
+ int isdockapp, ismenu;
prop = getatomprop(win, atoms[_NET_WM_WINDOW_TYPE]);
wmhints = XGetWMHints(dpy, win);
+ mwmhints = getmwmhints(win);
+ ismenu = mwmhints != NULL && (mwmhints->flags & MWM_HINTS_STATUS) && (mwmhints->status & MWM_TEAROFF_WINDOW);
isdockapp = (wmhints && (wmhints->flags & (IconWindowHint | StateHint)) && wmhints->initial_state == WithdrawnState);
+ *leader = (wmhints != NULL && (wmhints->flags & WindowGroupHint)) ? wmhints->window_group : None;
+ *parent = getdialogfor(win);
+ XFree(mwmhints);
if (wmhints != NULL)
XFree(wmhints);
- if (isdockapp) {
+ if (isdockapp)
return TYPE_DOCKAPP;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_DESKTOP]) {
+ if (prop == atoms[_NET_WM_WINDOW_TYPE_DESKTOP])
return TYPE_DESKTOP;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
+ if (prop == atoms[_NET_WM_WINDOW_TYPE_DOCK])
return TYPE_DOCK;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_NOTIFICATION]) {
+ if (prop == atoms[_NET_WM_WINDOW_TYPE_NOTIFICATION])
return TYPE_NOTIFICATION;
- } else if (prop == atoms[_NET_WM_WINDOW_TYPE_PROMPT]) {
+ if (prop == atoms[_NET_WM_WINDOW_TYPE_PROMPT])
return TYPE_PROMPT;
+ if (ismenu ||
+ prop == atoms[_NET_WM_WINDOW_TYPE_MENU] ||
+ prop == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
+ prop == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) {
+ if (*parent == NULL) {
+ *parent = getleaderof(*leader);
+ }
+ if (*parent != NULL) {
+ return TYPE_MENU;
+ }
}
+ if (*parent != NULL)
+ return TYPE_DIALOG;
return TYPE_NORMAL;
}
@@ -1099,20 +1267,6 @@ getstate(Window w)
return result;
}
-/* get tab given window is a dialog for */
-static struct Tab *
-getdialogfor(Window win)
-{
- struct Winres res;
- Window tmpwin;
-
- if (XGetTransientForHint(dpy, win, &tmpwin)) {
- res = getwin(tmpwin);
- return res.t;
- }
- return NULL;
-}
-
/* increment number of clients */
static void
clientsincr(void)
@@ -1385,9 +1539,8 @@ ewmhsetclients(void)
struct Column *col;
struct Row *row;
struct Tab *t;
- struct Dialog *d;
Window *wins = NULL;
- size_t i = 0;
+ int i = 0;
if (wm.nclients < 1)
return;
@@ -1397,9 +1550,6 @@ ewmhsetclients(void)
for (row = col->rows; row != NULL; row = row->next) {
for (t = row->tabs; t != NULL; t = t->next) {
wins[i++] = t->win;
- for (d = t->ds; d != NULL; d = d->next) {
- wins[i++] = d->win;
- }
}
}
}
@@ -1416,62 +1566,33 @@ ewmhsetclientsstacking(void)
struct Column *col;
struct Row *row;
struct Tab *t;
- struct Dialog *d;
Window *wins = NULL;
- size_t i = 0;
+ int i = 0;
if (wm.nclients < 1)
return;
wins = ecalloc(wm.nclients, sizeof *wins);
i = wm.nclients;
- for (c = wm.fulllist; c != NULL; c = c->rnext) {
- for (col = c->cols; col != NULL; col = col->next) {
- for (row = col->rows; row != NULL; row = row->next) {
- for (t = row->tabs; t != NULL; t = t->next) {
+ for (c = wm.fulllist; c != NULL; c = c->rnext)
+ for (col = c->cols; col != NULL; col = col->next)
+ for (row = col->rows; row != NULL; row = row->next)
+ for (t = row->tabs; t != NULL; t = t->next)
wins[--i] = t->win;
- for (d = t->ds; d != NULL; d = d->next) {
- wins[--i] = d->win;
- }
- }
- }
- }
- }
- for (c = wm.abovelist; c != NULL; c = c->rnext) {
- for (col = c->cols; col != NULL; col = col->next) {
- for (row = col->rows; row != NULL; row = row->next) {
- for (t = row->tabs; t != NULL; t = t->next) {
+ for (c = wm.abovelist; c != NULL; c = c->rnext)
+ for (col = c->cols; col != NULL; col = col->next)
+ for (row = col->rows; row != NULL; row = row->next)
+ for (t = row->tabs; t != NULL; t = t->next)
wins[--i] = t->win;
- for (d = t->ds; d != NULL; d = d->next) {
- wins[--i] = d->win;
- }
- }
- }
- }
- }
- for (c = wm.centerlist; c != NULL; c = c->rnext) {
- for (col = c->cols; col != NULL; col = col->next) {
- for (row = col->rows; row != NULL; row = row->next) {
- for (t = row->tabs; t != NULL; t = t->next) {
+ for (c = wm.centerlist; c != NULL; c = c->rnext)
+ for (col = c->cols; col != NULL; col = col->next)
+ for (row = col->rows; row != NULL; row = row->next)
+ for (t = row->tabs; t != NULL; t = t->next)
wins[--i] = t->win;
- for (d = t->ds; d != NULL; d = d->next) {
- wins[--i] = d->win;
- }
- }
- }
- }
- }
- for (c = wm.belowlist; c != NULL; c = c->rnext) {
- for (col = c->cols; col != NULL; col = col->next) {
- for (row = col->rows; row != NULL; row = row->next) {
- for (t = row->tabs; t != NULL; t = t->next) {
+ for (c = wm.belowlist; c != NULL; c = c->rnext)
+ for (col = c->cols; col != NULL; col = col->next)
+ for (row = col->rows; row != NULL; row = row->next)
+ for (t = row->tabs; t != NULL; t = t->next)
wins[--i] = t->win;
- for (d = t->ds; d != NULL; d = d->next) {
- wins[--i] = d->win;
- }
- }
- }
- }
- }
XChangeProperty(dpy, root, atoms[_NET_CLIENT_LIST_STACKING], XA_WINDOW, 32, PropModeReplace, (unsigned char *)wins+i, wm.nclients-i);
free(wins);
}
@@ -1574,11 +1695,19 @@ shodgrouptab(struct Container *c)
struct Column *col;
struct Row *row;
struct Tab *t;
+ struct Dialog *d;
+ struct Menu *menu;
for (col = c->cols; col != NULL; col = col->next) {
for (row = col->rows; row != NULL; row = row->next) {
for (t = row->tabs; t != NULL; t = t->next) {
XChangeProperty(dpy, t->win, atoms[_SHOD_GROUP_TAB], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&row->seltab->win, 1);
+ for (d = t->ds; d != NULL; d = d->next) {
+ XChangeProperty(dpy, d->win, atoms[_SHOD_GROUP_TAB], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&row->seltab->win, 1);
+ }
+ for (menu = t->menus; menu != NULL; menu = menu->next) {
+ XChangeProperty(dpy, menu->win, atoms[_SHOD_GROUP_TAB], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&row->seltab->win, 1);
+ }
}
}
}
@@ -1591,11 +1720,19 @@ shodgroupcontainer(struct Container *c)
struct Column *col;
struct Row *row;
struct Tab *t;
+ struct Dialog *d;
+ struct Menu *menu;
for (col = c->cols; col != NULL; col = col->next) {
for (row = col->rows; row != NULL; row = row->next) {
for (t = row->tabs; t != NULL; t = t->next) {
XChangeProperty(dpy, t->win, atoms[_SHOD_GROUP_CONTAINER], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&c->selcol->selrow->seltab->win, 1);
+ for (d = t->ds; d != NULL; d = d->next) {
+ XChangeProperty(dpy, d->win, atoms[_SHOD_GROUP_CONTAINER], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&c->selcol->selrow->seltab->win, 1);
+ }
+ for (menu = t->menus; menu != NULL; menu = menu->next) {
+ XChangeProperty(dpy, menu->win, atoms[_SHOD_GROUP_CONTAINER], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&c->selcol->selrow->seltab->win, 1);
+ }
}
}
}
@@ -1661,6 +1798,8 @@ winisurgent(Window win)
static int
tabgetstyle(struct Tab *t)
{
+ if (t == NULL)
+ return UNFOCUSED;
if (t->isurgent)
return URGENT;
if (t->row->col->c == wm.focused)
@@ -2253,16 +2392,14 @@ buttonleftdecorate(struct Row *row, int pressed)
/* draw title bar buttons */
static void
-buttonrightdecorate(struct Row *row, int pressed)
+buttonrightdecorate(Window button, Pixmap pix, int style, int pressed)
{
XGCValues val;
XPoint pts[9];
unsigned long mid, top, bot;
- int style;
int w;
w = (config.titlewidth - 11) / 2;
- style = (row->seltab) ? tabgetstyle(row->seltab) : UNFOCUSED;
mid = theme.title[style][COLOR_MID];
if (pressed) {
top = theme.title[style][COLOR_DARK];
@@ -2275,9 +2412,9 @@ buttonrightdecorate(struct Row *row, int pressed)
/* draw background */
val.foreground = mid;
XChangeGC(dpy, gc, GCForeground, &val);
- XFillRectangle(dpy, row->pixbr, gc, 0, 0, config.titlewidth, config.titlewidth);
+ XFillRectangle(dpy, pix, gc, 0, 0, config.titlewidth, config.titlewidth);
- drawrectangle(row->pixbr, (config.borderwidth - 4 > 0), 0, 0, config.titlewidth, config.titlewidth, top, bot);
+ drawrectangle(pix, (config.borderwidth - 4 > 0), 0, 0, config.titlewidth, config.titlewidth, top, bot);
if (w > 0) {
pts[0] = (XPoint){.x = 3, .y = config.titlewidth - 5};
@@ -2291,7 +2428,7 @@ buttonrightdecorate(struct Row *row, int pressed)
pts[8] = (XPoint){.x = 1, .y = 0};
val.foreground = (pressed) ? bot : top;
XChangeGC(dpy, gc, GCForeground, &val);
- XDrawLines(dpy, row->pixbr, gc, pts, 9, CoordModePrevious);
+ XDrawLines(dpy, pix, gc, pts, 9, CoordModePrevious);
pts[0] = (XPoint){.x = 3, .y = config.titlewidth - 4};
pts[1] = (XPoint){.x = 2, .y = 0};
@@ -2304,10 +2441,10 @@ buttonrightdecorate(struct Row *row, int pressed)
pts[8] = (XPoint){.x = 0, .y = -2};
val.foreground = (pressed) ? top : bot;
XChangeGC(dpy, gc, GCForeground, &val);
- XDrawLines(dpy, row->pixbr, gc, pts, 9, CoordModePrevious);
+ XDrawLines(dpy, pix, gc, pts, 9, CoordModePrevious);
}
- XCopyArea(dpy, row->pixbr, row->br, gc, 0, 0, config.titlewidth, config.titlewidth, 0, 0);
+ XCopyArea(dpy, pix, button, gc, 0, 0, config.titlewidth, config.titlewidth, 0, 0);
}
/* draw decoration on container frame */
@@ -2541,7 +2678,7 @@ containerdecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, in
/* draw buttons */
buttonleftdecorate(row, 0);
- buttonrightdecorate(row, 0);
+ buttonrightdecorate(row->br, row->pixbr, tabgetstyle(row->seltab), 0);
/* decorate tabs, if necessary */
if (recursive) {
@@ -2664,6 +2801,194 @@ notifdecorate(struct Notification *n)
XCopyArea(dpy, n->pix, n->frame, gc, 0, 0, n->w, n->h, 0, 0);
}
+/* decorate menu */
+static void
+menudecorate(struct Menu *menu, int titlepressed)
+{
+ XRectangle box, dr;
+ XGCValues val;
+ size_t len;
+ unsigned long top, bot;
+ int doubleshadow;
+ int tw, th;
+ int x, y;
+
+ doubleshadow = config.borderwidth - 4 > 0;
+ if (menu->pw != menu->w || menu->ph != menu->h || menu->pix == None) {
+ if (menu->pix != None)
+ XFreePixmap(dpy, menu->pix);
+ menu->pix = XCreatePixmap(dpy, menu->frame, menu->w, menu->h, depth);
+ }
+ menu->pw = menu->w;
+ menu->ph = menu->h;
+ tw = max(1, menu->w - 2 * config.borderwidth - config.titlewidth);
+ th = config.titlewidth;
+ if (menu->tw != tw || menu->th != th || menu->pixtitlebar == None) {
+ if (menu->pixtitlebar != None)
+ XFreePixmap(dpy, menu->pixtitlebar);
+ menu->pixtitlebar = XCreatePixmap(dpy, menu->titlebar, menu->w, menu->h, depth);
+ }
+ menu->tw = tw;
+ menu->th = th;
+
+ if (titlepressed) {
+ top = theme.title[FOCUSED][COLOR_DARK];
+ bot = theme.title[FOCUSED][COLOR_LIGHT];
+ } else {
+ top = theme.title[FOCUSED][COLOR_LIGHT];
+ bot = theme.title[FOCUSED][COLOR_DARK];
+ }
+ val.fill_style = FillSolid;
+ val.foreground = theme.title[FOCUSED][COLOR_MID];
+ XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val);
+ XFillRectangle(dpy, menu->pixtitlebar, gc, 0, 0, menu->tw, menu->th);
+ drawborders(menu->pix, doubleshadow, menu->w, menu->h, theme.border[FOCUSED]);
+ drawrectangle(menu->pixtitlebar, doubleshadow, 0, 0, menu->tw, config.titlewidth, top, bot);
+ /* write menu title */
+ if (menu->name != NULL) {
+ len = strlen(menu->name);
+ XmbTextExtents(theme.fontset, menu->name, len, &dr, &box);
+ x = (menu->tw - box.width) / 2 - box.x;
+ y = (config.titlewidth - box.height) / 2 - box.y;
+ val.foreground = theme.fg[FOCUSED];
+ XChangeGC(dpy, gc, GCForeground, &val);
+ XmbDrawString(dpy, menu->pixtitlebar, theme.fontset, gc, x, y, menu->name, len);
+ }
+ buttonrightdecorate(menu->button, menu->pixbutton, FOCUSED, 0);
+ XCopyArea(dpy, menu->pix, menu->frame, gc, 0, 0, menu->pw, menu->ph, 0, 0);
+ XCopyArea(dpy, menu->pixtitlebar, menu->titlebar, gc, 0, 0, menu->tw, menu->th, 0, 0);
+}
+
+/* map menus */
+static void
+menumap(struct Tab *t)
+{
+ struct Menu *menu;
+
+ if (t == NULL)
+ return;
+ for (menu = t->menus; menu != NULL; menu = menu->next) {
+ XMapWindow(dpy, menu->frame);
+ icccmwmstate(menu->win, NormalState);
+ }
+}
+
+/* unmap menus */
+static void
+menuunmap(struct Tab *t)
+{
+ struct Menu *menu;
+
+ if (t == NULL)
+ return;
+ for (menu = t->menus; menu != NULL; menu = menu->next) {
+ XUnmapWindow(dpy, menu->frame);
+ icccmwmstate(menu->win, IconicState);
+ }
+}
+
+/* raise menus */
+static void
+menuraise(struct Tab *t)
+{
+ struct Container *c;
+ struct Menu *menu;
+ Window wins[2], layer;
+
+ c = t->row->col->c;
+ if (c == NULL || c->isminimized)
+ return;
+ if (c->isfullscreen)
+ layer = wm.layerwins[LAYER_FULLSCREEN];
+ else if (c->layer > 0)
+ layer = wm.layerwins[LAYER_ABOVE];
+ else if (c->layer < 0)
+ layer = wm.layerwins[LAYER_BELOW];
+ else
+ layer = wm.layerwins[LAYER_NORMAL];
+ wins[0] = layer;
+ for (menu = t->menus; menu != NULL; menu = menu->next) {
+ wins[1] = menu->frame;
+ XRestackWindows(dpy, wins, 2);
+ wins[0] = menu->frame;
+ }
+}
+
+/* remove menu from the menu list */
+static void
+menudelraise(struct Tab *t, struct Menu *menu)
+{
+ if (menu->next != NULL) {
+ menu->next->prev = menu->prev;
+ }
+ if (menu->prev != NULL) {
+ menu->prev->next = menu->next;
+ } else if (t->menus == menu) {
+ t->menus = menu->next;
+ }
+ menu->next = NULL;
+ menu->prev = NULL;
+}
+
+/* put menu on beginning of menu list */
+static void
+menuaddraise(struct Tab *t, struct Menu *menu)
+{
+ menudelraise(t, menu);
+ menu->next = t->menus;
+ menu->prev = NULL;
+ if (t->menus != NULL)
+ t->menus->prev = menu;
+ t->menus = menu;
+}
+
+/* place menu next to its container */
+static void
+menuplace(struct Menu *menu)
+{
+ struct Container *c;
+
+ c = menu->t->row->col->c;
+ if (menu->x < c->mon->wx || menu->x + menu->w >= c->mon->wx + c->mon->ww)
+ menu->x = c->mon->wx;
+ if (menu->y < c->mon->wy || menu->y + menu->h >= c->mon->wy + c->mon->wh)
+ menu->y = c->mon->wy;
+ XMoveWindow(dpy, menu->frame, menu->x, menu->y);
+}
+
+/* delete dialog */
+static void
+menudel(struct Menu *menu)
+{
+ if (menu->next != NULL)
+ menu->next->prev = menu->prev;
+ if (menu->prev != NULL)
+ menu->prev->next = menu->next;
+ else
+ menu->t->menus = menu->next;
+ if (menu->pix != None)
+ XFreePixmap(dpy, menu->pix);
+ if (menu->pixbutton != None)
+ XFreePixmap(dpy, menu->pixbutton);
+ if (menu->pixtitlebar != None)
+ XFreePixmap(dpy, menu->pixtitlebar);
+ icccmdeletestate(menu->win);
+ XReparentWindow(dpy, menu->win, root, 0, 0);
+ XDestroyWindow(dpy, menu->frame);
+ XDestroyWindow(dpy, menu->titlebar);
+ XDestroyWindow(dpy, menu->button);
+ free(menu->name);
+ free(menu);
+}
+
+/* commit menu geometry */
+static void
+menumoveresize(struct Menu *menu)
+{
+ XMoveResizeWindow(dpy, menu->frame, menu->x, menu->y, menu->w, menu->h);
+ XResizeWindow(dpy, menu->win, menu->w - 2 * config.borderwidth, menu->h - 2 * config.borderwidth - config.titlewidth);
+}
+
/* remove container from the focus list */
static void
containerdelfocus(struct Container *c)
@@ -2706,10 +3031,12 @@ containerhide(struct Container *c, int hide)
if (c == NULL)
return;
c->ishidden = hide;
- if (hide)
+ if (hide) {
XUnmapWindow(dpy, c->frame);
- else
+ menuunmap(c->selcol->selrow->seltab);
+ } else {
XMapWindow(dpy, c->frame);
+ }
for (col = c->cols; col != NULL; col = col->next) {
for (row = col->rows; row != NULL; row = row->next) {
for (t = row->tabs; t != NULL; t = t->next) {
@@ -2932,6 +3259,7 @@ containerraise(struct Container *c)
else
wins[0] = wm.layerwins[LAYER_NORMAL];
XRestackWindows(dpy, wins, 2);
+ menuraise(c->selcol->selrow->seltab);
ewmhsetclientsstacking();
}
@@ -3098,33 +3426,18 @@ containernew(int x, int y, int w, int h)
struct Container *c;
int i;
+ x -= config.borderwidth,
+ y -= config.borderwidth,
+ w += 2 * config.borderwidth,
+ h += 2 * config.borderwidth + config.titlewidth,
c = emalloc(sizeof *c);
- c->prev = c->next = NULL;
- c->fprev = c->fnext = NULL;
- c->rprev = c->rnext = NULL;
- c->mon = NULL;
- c->cols = NULL;
- c->selcol = NULL;
- c->ncols = 0;
- c->isfullscreen = 0;
- c->ismaximized = 0;
- c->isminimized = 0;
- c->issticky = 0;
- c->isshaded = 0;
- c->ishidden = 0;
- c->layer = 0;
- c->desk = NULL;
- c->pw = c->ph = 0;
- c->x = c->nx = x - config.borderwidth;
- c->y = c->ny = y - config.borderwidth;
- c->w = c->nw = w + 2 * config.borderwidth;
- c->h = c->nh = h + 2 * config.borderwidth + config.titlewidth;
- c->b = config.borderwidth;
- c->pix = None;
- c->frame = XCreateWindow(dpy, root, c->x, c->y, c->w, c->h, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
-
+ *c = (struct Container) {
+ .x = x, .y = y, .w = w, .h = h,
+ .nx = x, .ny = y, .nw = w, .nh = h,
+ .b = config.borderwidth,
+ .pix = None,
+ };
+ c->frame = XCreateWindow(dpy, root, c->x, c->y, c->w, c->h, 0, depth, CopyFromParent, visual, clientmask, &clientswa);
c->curswin[BORDER_N] = XCreateWindow(dpy, c->frame, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWCursor, &(XSetWindowAttributes){.cursor = theme.cursors[CURSOR_N]});
c->curswin[BORDER_S] = XCreateWindow(dpy, c->frame, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWCursor, &(XSetWindowAttributes){.cursor = theme.cursors[CURSOR_S]});
c->curswin[BORDER_W] = XCreateWindow(dpy, c->frame, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWCursor, &(XSetWindowAttributes){.cursor = theme.cursors[CURSOR_W]});
@@ -3158,7 +3471,6 @@ dialogdel(struct Dialog *d)
icccmdeletestate(d->win);
XReparentWindow(dpy, d->win, root, 0, 0);
XDestroyWindow(dpy, d->frame);
- clientsdecr();
free(d);
}
@@ -3192,8 +3504,14 @@ tabdetach(struct Tab *t, int x, int y, int w, int h)
static void
tabdel(struct Tab *t)
{
- while (t->ds)
+ while (t->ds) {
+ XDestroyWindow(dpy, t->ds->win);
dialogdel(t->ds);
+ }
+ while (t->menus) {
+ XDestroyWindow(dpy, t->menus->win);
+ menudel(t->menus);
+ }
tabdetach(t, 0, 0, t->winw, t->winh);
if (t->pixtitle != None)
XFreePixmap(dpy, t->pixtitle);
@@ -3357,15 +3675,7 @@ colnew(void)
struct Column *col;
col = emalloc(sizeof(*col));
- col->prev = col->next = NULL;
- col->c = NULL;
- col->rows = NULL;
- col->selrow = NULL;
- col->maxrow = NULL;
- col->nrows = 0;
- col->x = 0;
- col->w = 0;
- col->fact = 0.0;
+ *col = (struct Column){ };
col->div = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
CopyFromParent, InputOnly, CopyFromParent, CWCursor,
&(XSetWindowAttributes){.cursor = theme.cursors[CURSOR_H]});
@@ -3415,14 +3725,9 @@ rownew(void)
struct Row *row;
row = emalloc(sizeof(*row));
- row->prev = row->next = NULL;
- row->col = NULL;
- row->tabs = NULL;
- row->seltab = NULL;
- row->ntabs = 0;
- row->y = 0;
- row->h = 0;
- row->pw = 0;
+ *row = (struct Row){
+ .pixbar = None,
+ };
row->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
depth, CopyFromParent, visual,
clientmask, &clientswa);
@@ -3440,7 +3745,6 @@ rownew(void)
CopyFromParent, InputOnly, CopyFromParent, CWCursor,
&(XSetWindowAttributes){.cursor = theme.cursors[CURSOR_V]});
row->pixbr = XCreatePixmap(dpy, row->bl, config.titlewidth, config.titlewidth, depth);
- row->pixbar = None;
XMapWindow(dpy, row->bl);
XMapWindow(dpy, row->br);
XDefineCursor(dpy, row->bl, theme.cursors[CURSOR_HAND]);
@@ -3550,12 +3854,66 @@ deskfocus(struct Desktop *desk, int focus)
}
}
+/* snap to edge */
+static void
+snaptoedge(int *x, int *y, int w, int h)
+{
+ struct Container *c;
+
+ if (config.snap <= 0)
+ return;
+ if (abs(*y - wm.selmon->wy) < config.snap) {
+ *y = wm.selmon->wy;
+ }
+ if (abs(*y + h - wm.selmon->wy - wm.selmon->wh) < config.snap) {
+ *y = wm.selmon->wy + wm.selmon->wh - h;
+ }
+ if (abs(*x - wm.selmon->wx) < config.snap) {
+ *x = wm.selmon->wx;
+ }
+ if (abs(*x + w - wm.selmon->wx - wm.selmon->ww) < config.snap) {
+ *x = wm.selmon->wx + wm.selmon->ww - w;
+ }
+ for (c = wm.c; c != NULL; c = c->next) {
+ if (!c->isminimized && c->mon == wm.selmon &&
+ (c->issticky || c->desk == wm.selmon->seldesk)) {
+ if (*x + w >= c->x && *x <= c->x + c->w) {
+ if (abs(*y + h - c->y) < config.snap) {
+ *y = c->y - h;
+ }
+ if (abs(*y - c->y) < config.snap) {
+ *y = c->y;
+ }
+ if (abs(*y + h - c->y - c->h) < config.snap) {
+ *y = c->y + c->h - h;
+ }
+ if (abs(*y - c->y - c->h) < config.snap) {
+ *y = c->y + c->h;
+ }
+ }
+ if (*y + h >= c->y && *y <= c->y + c->h) {
+ if (abs(*x + w - c->x) < config.snap) {
+ *x = c->x - w;
+ }
+ if (abs(*x - c->x) < config.snap) {
+ *x = c->x;
+ }
+ if (abs(*x + w - c->x - c->w) < config.snap) {
+ *x = c->x + c->w - w;
+ }
+ if (abs(*x - c->x - c->w) < config.snap) {
+ *x = c->x + c->w;
+ }
+ }
+ }
+ }
+}
+
/* move container x pixels to the right and y pixels down */
static void
containerincrmove(struct Container *c, int x, int y)
{
struct Monitor *monto;
- struct Container *tmp;
if (c == NULL || c->isminimized || c->ismaximized || c->isfullscreen)
return;
@@ -3563,53 +3921,7 @@ containerincrmove(struct Container *c, int x, int y)
c->ny += y;
c->x = c->nx;
c->y = c->ny;
- if (config.snap > 0) {
- if (abs(c->y - c->mon->wy) < config.snap) {
- c->y = c->mon->wy;
- }
- if (abs(c->y + c->h - c->mon->wy - c->mon->wh) < config.snap) {
- c->y = c->mon->wy + c->mon->wh - c->h;
- }
- if (abs(c->x - c->mon->wx) < config.snap) {
- c->x = c->mon->wx;
- }
- if (abs(c->x + c->w - c->mon->wx - c->mon->ww) < config.snap) {
- c->x = c->mon->wx + c->mon->ww - c->w;
- }
- for (tmp = wm.c; tmp != NULL; tmp = tmp->next) {
- if (!tmp->isminimized && tmp->mon == c->mon &&
- (tmp->issticky || tmp->desk == c->desk)) {
- if (c->x + c->w >= tmp->x && c->x <= tmp->x + tmp->w) {
- if (abs(c->y + c->h - tmp->y) < config.snap) {
- c->y = tmp->y - c->h;
- }
- if (abs(c->y - tmp->y) < config.snap) {
- c->y = tmp->y;
- }
- if (abs(c->y + c->h - tmp->y - tmp->h) < config.snap) {
- c->y = tmp->y + tmp->h - c->h;
- }
- if (abs(c->y - tmp->y - tmp->h) < config.snap) {
- c->y = tmp->y + tmp->h;
- }
- }
- if (c->y + c->h >= tmp->y && c->y <= tmp->y + tmp->h) {
- if (abs(c->x + c->w - tmp->x) < config.snap) {
- c->x = tmp->x - c->w;
- }
- if (abs(c->x - tmp->x) < config.snap) {
- c->x = tmp->x;
- }
- if (abs(c->x + c->w - tmp->x - tmp->w) < config.snap) {
- c->x = tmp->x + tmp->w - c->w;
- }
- if (abs(c->x - tmp->x - tmp->w) < config.snap) {
- c->x = tmp->x + tmp->w;
- }
- }
- }
- }
- }
+ snaptoedge(&c->x, &c->y, c->w, c->h);
containermoveresize(c);
if (!c->issticky) {
monto = getmon(c->nx + c->nw / 2, c->ny + c->nh / 2);
@@ -3624,27 +3936,20 @@ containerincrmove(struct Container *c, int x, int y)
/* create new tab */
static struct Tab *
-tabnew(Window win, int ignoreunmap)
+tabnew(Window win, Window leader, int ignoreunmap)
{
struct Tab *t;
t = emalloc(sizeof(*t));
- t->prev = t->next = NULL;
- t->row = NULL;
- t->ds = NULL;
- t->name = NULL;
- t->ignoreunmap = ignoreunmap;
- t->isurgent = 0;
- t->winw = t->winh = 0;
- t->x = t->w = 0;
- t->pw = 0;
- t->pix = None;
- t->pixtitle = None;
- t->title = None;
- t->win = win;
- t->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
+ *t = (struct Tab){
+ .ignoreunmap = ignoreunmap,
+ .pix = None,
+ .pixtitle = None,
+ .title = None,
+ .leader = leader,
+ .win = win,
+ };
+ t->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, CopyFromParent, visual, clientmask, &clientswa),
XReparentWindow(dpy, t->win, t->frame, 0, 0);
XMapWindow(dpy, t->win);
icccmwmstate(win, NormalState);
@@ -3713,6 +4018,7 @@ tabfocus(struct Tab *t, int gotodesk)
}
if (t->isurgent)
tabclearurgency(t);
+ menumap(t);
containeraddfocus(c);
containerdecorate(c, NULL, NULL, 1, 0);
containerminimize(c, 0, 0);
@@ -3725,6 +4031,8 @@ tabfocus(struct Tab *t, int gotodesk)
ewmhsetstate(c);
}
if (wm.prevfocused != NULL) {
+ if (t != wm.prevfocused->selcol->selrow->seltab)
+ menuunmap(wm.prevfocused->selcol->selrow->seltab);
containerdecorate(wm.prevfocused, NULL, NULL, 1, 0);
ewmhsetstate(wm.prevfocused);
}
@@ -3732,10 +4040,10 @@ tabfocus(struct Tab *t, int gotodesk)
/* update tab title */
static void
-tabupdatetitle(struct Tab *t)
+winupdatetitle(Window win, char **name)
{
- free(t->name);
- t->name = getwinname(t->win);
+ free(*name);
+ *name = getwinname(win);
}
/* try to attach tab in a client of specified client list */
@@ -3854,21 +4162,16 @@ dialognew(Window win, int maxw, int maxh, int ignoreunmap)
struct Dialog *d;
d = emalloc(sizeof(*d));
- d->prev = d->next = NULL;
- d->t = NULL;
- d->x = d->y = d->w = d->h = 0;
- d->pix = None;
- d->pw = d->ph = 0;
- d->maxw = maxw;
- d->maxh = maxh;
- d->ignoreunmap = ignoreunmap;
- d->win = win;
- d->frame = XCreateWindow(dpy, root, 0, 0, maxw, maxh, 0,
- depth, CopyFromParent, visual,
- clientmask, &clientswa);
+ *d = (struct Dialog){
+ .pix = None,
+ .maxw = maxw,
+ .maxh = maxh,
+ .ignoreunmap = ignoreunmap,
+ .win = win,
+ };
+ d->frame = XCreateWindow(dpy, root, 0, 0, maxw, maxh, 0, depth, CopyFromParent, visual, clientmask, &clientswa),
XReparentWindow(dpy, d->win, d->frame, 0, 0);
XMapWindow(dpy, d->win);
- clientsincr();
return d;
}
@@ -3891,12 +4194,16 @@ monnew(XineramaScreenInfo *info)
int i;
mon = emalloc(sizeof *mon);
- mon->prev = NULL;
- mon->next = NULL;
- mon->mx = mon->wx = info->x_org;
- mon->my = mon->wy = info->y_org;
- mon->mw = mon->ww = info->width;
- mon->mh = mon->wh = info->height;
+ *mon = (struct Monitor){
+ .mx = info->x_org,
+ .my = info->y_org,
+ .mw = info->width,
+ .mh = info->height,
+ .wx = info->x_org,
+ .wy = info->y_org,
+ .ww = info->width,
+ .wh = info->height,
+ };
mon->desks = ecalloc(config.ndesktops, sizeof(*mon->desks));
for (i = 0; i < config.ndesktops; i++) {
mon->desks[i].mon = mon;
@@ -3942,6 +4249,10 @@ monupdate(void)
struct Monitor *mon;
struct Monitor *tmp;
struct Container *c, *focus;
+ struct Column *col;
+ struct Row *row;
+ struct Tab *t;
+ struct Menu *menu;
int delselmon = 0;
int del, add;
int i, j, n;
@@ -4005,6 +4316,17 @@ monupdate(void)
focus = c;
containersendtodesk(c, wm.selmon->seldesk, 1, 0);
containermoveresize(c);
+
+ /* move menus to new monitor */
+ for (col = c->cols; col != NULL; col = col->next) {
+ for (row = col->rows; row != NULL; row = row->next) {
+ for (t = row->tabs; t != NULL; t = t->next) {
+ for (menu = t->menus; menu != NULL; menu = menu->next) {
+ menuplace(menu);
+ }
+ }
+ }
+ }
}
}
if (focus != NULL) /* if a contained changed desktop, focus it */
@@ -4151,9 +4473,10 @@ notifnew(Window win, int w, int h)
struct Notification *n;
n = emalloc(sizeof(*n));
- n->w = w + 2 * config.borderwidth;
- n->h = h + 2 * config.borderwidth;
- n->pw = n->ph = 0;
+ *n = (struct Notification){
+ .w = w + 2 * config.borderwidth,
+ .h = h + 2 * config.borderwidth,
+ };
n->prev = wm.ntail;
n->next = NULL;
if (wm.ntail != NULL)
@@ -4435,15 +4758,16 @@ dockappnew(Window win, int w, int h, int ignoreunmap)
struct Dockapp *dapp;
dapp = emalloc(sizeof(*dapp));
- dapp->win = win;
- dapp->x = dapp->y = 0;
- dapp->w = w;
- dapp->h = h;
- dapp->gw = config.dockspace * (((w - 1) / config.dockspace) + 1);
- dapp->gh = config.dockspace * (((h - 1) / config.dockspace) + 1);
+ *dapp = (struct Dockapp){
+ .win = win,
+ .w = w,
+ .h = h,
+ .ignoreunmap = ignoreunmap,
+ .gw = config.dockspace * (((w - 1) / config.dockspace) + 1),
+ .gh = config.dockspace * (((h - 1) / config.dockspace) + 1),
+ };
dapp->prev = dock.tail;
dapp->next = NULL;
- dapp->ignoreunmap = ignoreunmap;
if (dock.tail != NULL)
dock.tail->next = dapp;
else
@@ -4469,6 +4793,49 @@ dockappdel(struct Dockapp *dapp)
monupdatearea();
}
+/* create new menu */
+static struct Menu *
+menunew(Window win, int x, int y, int w, int h, int ignoreunmap)
+{
+ struct Menu *menu;
+
+ menu = emalloc(sizeof(*menu));
+ *menu = (struct Menu){
+ .titlebar = None,
+ .button = None,
+ .win = win,
+ .pix = None,
+ .pixbutton = None,
+ .pixtitlebar = None,
+ .x = x - config.borderwidth,
+ .y = y - config.borderwidth,
+ .w = w + config.borderwidth * 2,
+ .h = h + config.borderwidth * 2 + config.titlewidth,
+ .ignoreunmap = ignoreunmap,
+ };
+ menu->frame = XCreateWindow(dpy, root, 0, 0,
+ w + config.borderwidth * 2,
+ h + config.borderwidth * 2 + config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa),
+ menu->titlebar = XCreateWindow(dpy, menu->frame, config.borderwidth, config.borderwidth,
+ max(1, menu->w - 2 * config.borderwidth - config.titlewidth),
+ config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ menu->button = XCreateWindow(dpy, menu->frame, menu->w - config.borderwidth - config.titlewidth, config.borderwidth,
+ config.titlewidth, config.titlewidth, 0,
+ depth, CopyFromParent, visual,
+ clientmask, &clientswa);
+ menu->pixbutton = XCreatePixmap(dpy, menu->button, config.titlewidth, config.titlewidth, depth);
+ XDefineCursor(dpy, menu->button, theme.cursors[CURSOR_PIRATE]);
+ XReparentWindow(dpy, menu->win, menu->frame, config.borderwidth, config.borderwidth + config.titlewidth);
+ XMapWindow(dpy, menu->win);
+ XMapWindow(dpy, menu->button);
+ XMapWindow(dpy, menu->titlebar);
+ return menu;
+}
+
/* call the proper decorate function */
static void
decorate(struct Winres *res)
@@ -4479,6 +4846,10 @@ decorate(struct Winres *res)
XCopyArea(dpy, res->dock->pix, res->dock->win, gc, 0, 0, res->dock->w, res->dock->h, 0, 0);
} else if (res->n) {
XCopyArea(dpy, res->n->pix, res->n->frame, gc, 0, 0, res->n->w, res->n->h, 0, 0);
+ } else if (res->menu != NULL) {
+ XCopyArea(dpy, res->menu->pix, res->menu->frame, gc, 0, 0, res->menu->pw, res->menu->ph, 0, 0);
+ XCopyArea(dpy, res->menu->pixbutton, res->menu->button, gc, 0, 0, config.titlewidth, config.titlewidth, 0, 0);
+ XCopyArea(dpy, res->menu->pixtitlebar, res->menu->titlebar, gc, 0, 0, res->menu->tw, res->menu->th, 0, 0);
} else if (res->d != NULL) {
fullw = res->d->w + 2 * config.borderwidth;
fullh = res->d->h + 2 * config.borderwidth;
@@ -4502,11 +4873,11 @@ static void
managedialog(struct Tab *t, struct Dialog *d)
{
d->t = t;
- XReparentWindow(dpy, d->frame, t->frame, 0, 0);
- if (t->ds)
+ if (t->ds != NULL)
t->ds->prev = d;
d->next = t->ds;
t->ds = d;
+ XReparentWindow(dpy, d->frame, t->frame, 0, 0);
icccmwmstate(d->win, NormalState);
dialogcalcsize(d);
dialogmoveresize(d);
@@ -4517,6 +4888,24 @@ managedialog(struct Tab *t, struct Dialog *d)
ewmhsetclientsstacking();
}
+/* assign menu to tab */
+static void
+managemenu(struct Tab *t, struct Menu *menu)
+{
+ menu->t = t;
+ if (t->menus != NULL)
+ t->menus->prev = menu;
+ menu->next = t->menus;
+ t->menus = menu;
+ icccmwmstate(menu->win, NormalState);
+ menudecorate(menu, 0);
+ menuplace(menu);
+ if (wm.focused != NULL && wm.focused->selcol->selrow->seltab == t)
+ tabfocus(t, 0);
+ ewmhsetclients();
+ ewmhsetclientsstacking();
+}
+
/* map prompt, give it focus, wait for it to close, then revert focus to previously focused window */
static void
manageprompt(Window win, int w, int h)
@@ -4656,12 +5045,14 @@ manage(Window win, XWindowAttributes *wa, int ignoreunmap)
struct Tab *t;
struct Container *c;
struct Dialog *d;
+ struct Menu *menu;
+ Window leader;
int userplaced;
res = getwin(win);
- if (res.dock != NULL || res.c != NULL || res.bar != NULL || res.dapp != NULL || res.n != NULL)
+ if (res.dock != NULL || res.c != NULL || res.bar != NULL || res.dapp != NULL || res.n != NULL || res.menu != NULL)
return;
- switch (getwintype(win)) {
+ switch (getwintype(win, &t, &leader)) {
case TYPE_DESKTOP:
managedesktop(win);
break;
@@ -4679,18 +5070,24 @@ manage(Window win, XWindowAttributes *wa, int ignoreunmap)
preparewin(win);
manageprompt(win, wa->width, wa->height);
break;
+ case TYPE_DIALOG:
+ preparewin(win);
+ d = dialognew(win, wa->width, wa->height, ignoreunmap);
+ managedialog(t, d);
+ break;
+ case TYPE_MENU:
+ preparewin(win);
+ menu = menunew(win, wa->x, wa->y, wa->width, wa->height, ignoreunmap);
+ winupdatetitle(menu->win, &menu->name);
+ managemenu(t, menu);
+ break;
default:
preparewin(win);
- if ((t = getdialogfor(win)) != NULL) {
- d = dialognew(win, wa->width, wa->height, ignoreunmap);
- managedialog(t, d);
- } else {
- userplaced = isuserplaced(win);
- t = tabnew(win, ignoreunmap);
- tabupdatetitle(t);
- c = containernew(wa->x, wa->y, wa->width, wa->height);
- managecontainer(c, t, wm.selmon->seldesk, userplaced);
- }
+ userplaced = isuserplaced(win);
+ t = tabnew(win, leader, ignoreunmap);
+ winupdatetitle(t->win, &t->name);
+ c = containernew(wa->x, wa->y, wa->width, wa->height);
+ managecontainer(c, t, wm.selmon->seldesk, userplaced);
break;
}
}
@@ -4855,22 +5252,44 @@ done:
/* resize container with mouse */
static void
-mouseresize(struct Container *c, int xroot, int yroot, enum Octant o)
+mouseresize(int type, void *obj, int xroot, int yroot, enum Octant o)
{
+ struct Container *c;
+ struct Menu *menu;
struct Winres res;
+ Window frame;
Cursor curs;
XEvent ev;
Time lasttime;
+ int *nx, *ny, *nw, *nh;
int x, y, dx, dy;
- if (containerisshaded(c)) {
- if (o & W) {
- o = W;
- } else if (o & E) {
- o = E;
+ if (type == FLOAT_MENU) {
+ menu = (struct Menu *)obj;
+ nx = &menu->x;
+ ny = &menu->y;
+ nw = &menu->w;
+ nh = &menu->h;
+ frame = menu->frame;
+ menudecorate(menu, o != C);
+ } else {
+ c = (struct Container *)obj;
+ if (c->isfullscreen || c->b == 0)
+ return;
+ if (containerisshaded(c)) {
+ if (o & W) {
+ o = W;
+ } else if (o & E) {
+ o = E;
+ }
}
+ nx = &c->nx;
+ ny = &c->ny;
+ nw = &c->nw;
+ nh = &c->nh;
+ frame = c->frame;
+ containerdecorate(c, NULL, NULL, 0, o);
}
-
switch (o) {
case NW:
curs = theme.cursors[CURSOR_NW];
@@ -4901,22 +5320,19 @@ mouseresize(struct Container *c, int xroot, int yroot, enum Octant o)
break;
}
if (o & W)
- x = xroot - c->x - c->b;
+ x = xroot - *nx - config.borderwidth;
else if (o & E)
- x = c->x + c->w - c->b - xroot;
+ x = *nx + *nw - config.borderwidth - xroot;
else
x = 0;
if (o & N)
- y = yroot - c->y - c->b;
+ y = yroot - *ny - config.borderwidth;
else if (o & S)
- y = c->y + c->h - c->b - yroot;
+ y = *ny + *nh - config.borderwidth - yroot;
else
y = 0;
- XGrabPointer(dpy, c->frame, False,
- ButtonReleaseMask | PointerMotionMask,
- GrabModeAsync, GrabModeAsync, None, curs, CurrentTime);
+ XGrabPointer(dpy, frame, False, ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, curs, CurrentTime);
lasttime = 0;
- containerdecorate(c, NULL, NULL, 0, o);
while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
switch (ev.type) {
case Expose:
@@ -4929,46 +5345,51 @@ mouseresize(struct Container *c, int xroot, int yroot, enum Octant o)
goto done;
break;
case MotionNotify:
- if (x > c->w)
+ if (x > *nw)
x = 0;
- if (y > c->h)
+ if (y > *nh)
y = 0;
if (o & W &&
- ((ev.xmotion.x_root < xroot && x > ev.xmotion.x_root - c->nx) ||
- (ev.xmotion.x_root > xroot && x < ev.xmotion.x_root - c->nx))) {
+ ((ev.xmotion.x_root < xroot && x > ev.xmotion.x_root - *nx) ||
+ (ev.xmotion.x_root > xroot && x < ev.xmotion.x_root - *nx))) {
dx = xroot - ev.xmotion.x_root;
- if (c->nw + dx >= wm.minsize) {
- c->nx -= dx;
- c->nw += dx;
+ if (*nw + dx >= wm.minsize) {
+ *nx -= dx;
+ *nw += dx;
}
} else if (o & E &&
- ((ev.xmotion.x_root > xroot && x > c->nx + c->nw - ev.xmotion.x_root) ||
- (ev.xmotion.x_root < xroot && x < c->nx + c->nw - ev.xmotion.x_root))) {
+ ((ev.xmotion.x_root > xroot && x > *nx + *nw - ev.xmotion.x_root) ||
+ (ev.xmotion.x_root < xroot && x < *nx + *nw - ev.xmotion.x_root))) {
dx = ev.xmotion.x_root - xroot;
- if (c->nw + dx >= wm.minsize) {
- c->nw += dx;
+ if (*nw + dx >= wm.minsize) {
+ *nw += dx;
}
}
if (o & N &&
- ((ev.xmotion.y_root < yroot && y > ev.xmotion.y_root - c->ny) ||
- (ev.xmotion.y_root > yroot && y < ev.xmotion.y_root - c->ny))) {
+ ((ev.xmotion.y_root < yroot && y > ev.xmotion.y_root - *ny) ||
+ (ev.xmotion.y_root > yroot && y < ev.xmotion.y_root - *ny))) {
dy = yroot - ev.xmotion.y_root;
- if (c->nh + dy >= wm.minsize) {
- c->ny -= dy;
- c->nh += dy;
+ if (*nh + dy >= wm.minsize) {
+ *ny -= dy;
+ *nh += dy;
}
} else if (o & S &&
- ((ev.xmotion.y_root > yroot && c->ny + c->nh - ev.xmotion.y_root < y) ||
- (ev.xmotion.y_root < yroot && c->ny + c->nh - ev.xmotion.y_root > y))) {
+ ((ev.xmotion.y_root > yroot && *ny + *nh - ev.xmotion.y_root < y) ||
+ (ev.xmotion.y_root < yroot && *ny + *nh - ev.xmotion.y_root > y))) {
dy = ev.xmotion.y_root - yroot;
- if (c->nh + dy >= wm.minsize) {
- c->nh += dy;
+ if (*nh + dy >= wm.minsize) {
+ *nh += dy;
}
}
if (ev.xmotion.time - lasttime > RESIZETIME) {
- containercalccols(c, 0, 1);
- containermoveresize(c);
- containerredecorate(c, NULL, NULL, o);
+ if (type == FLOAT_MENU) {
+ menumoveresize(menu);
+ menudecorate(menu, o != C);
+ } else {
+ containercalccols(c, 0, 1);
+ containermoveresize(c);
+ containerredecorate(c, NULL, NULL, o);
+ }
lasttime = ev.xmotion.time;
}
xroot = ev.xmotion.x_root;
@@ -4983,17 +5404,29 @@ done:
XUngrabPointer(dpy, CurrentTime);
}
-/* move container with mouse */
+/* move floating object (container or menu) with mouse */
static void
-mousemove(struct Container *c, int xroot, int yroot, enum Octant o)
+mousemove(int type, void *obj, int xroot, int yroot, enum Octant o)
{
+ struct Container *c;
+ struct Menu *menu;
struct Winres res;
+ Window frame;
XEvent ev;
int x, y;
+ int floatx, floaty;
+ if (type == FLOAT_MENU) {
+ menu = (struct Menu *)obj;
+ menudecorate(menu, o);
+ frame = menu->frame;
+ } else {
+ c = (struct Container *)obj;
+ containerdecorate(c, NULL, NULL, 0, o);
+ frame = c->frame;
+ }
x = y = 0;
- containerdecorate(c, NULL, NULL, 0, o);
- XGrabPointer(dpy, c->frame, False,
+ XGrabPointer(dpy, frame, False,
ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, None, theme.cursors[CURSOR_MOVE], CurrentTime);
while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
@@ -5010,14 +5443,26 @@ mousemove(struct Container *c, int xroot, int yroot, enum Octant o)
case MotionNotify:
x = ev.xmotion.x_root - xroot;
y = ev.xmotion.y_root - yroot;
- containerincrmove(c, x, y);
+ if (type == FLOAT_MENU) {
+ menu->x += x;
+ menu->y += y;
+ floatx = menu->x;
+ floaty = menu->y;
+ snaptoedge(&floatx, &floaty, menu->w, menu->h);
+ XMoveWindow(dpy, menu->frame, floatx, floaty);
+ } else {
+ containerincrmove(c, x, y);
+ }
xroot = ev.xmotion.x_root;
yroot = ev.xmotion.y_root;
break;
}
}
done:
- containerdecorate(c, NULL, NULL, 0, 0);
+ if (type == FLOAT_MENU)
+ menudecorate(menu, 0);
+ else
+ containerdecorate(c, NULL, NULL, 0, 0);
XUngrabPointer(dpy, CurrentTime);
}
@@ -5108,14 +5553,26 @@ done:
/* close tab with mouse */
static void
-mouseclose(struct Row *row)
+mouseclose(int type, void *obj)
{
+ struct Row *row;
+ struct Menu *menu;
struct Winres res;
+ Window win, button;
XEvent ev;
- buttonrightdecorate(row, 1);
- XGrabPointer(dpy, row->br, False, ButtonReleaseMask,
- GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
+ if (type == FLOAT_MENU) {
+ menu = (struct Menu *)obj;
+ button = menu->button;
+ win = menu->win;
+ buttonrightdecorate(button, menu->pixbutton, FOCUSED, 1);
+ } else {
+ row = (struct Row *)obj;
+ button = row->br;
+ win = row->seltab->ds != NULL ? row->seltab->ds->win : row->seltab->win;
+ buttonrightdecorate(button, row->pixbr, tabgetstyle(row->seltab), 1);
+ }
+ XGrabPointer(dpy, button, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
while (!XMaskEvent(dpy, MOUSEEVENTMASK, &ev)) {
switch(ev.type) {
case Expose:
@@ -5125,16 +5582,20 @@ mouseclose(struct Row *row)
}
break;
case ButtonRelease:
- if (ev.xbutton.window == row->br &&
+ if (ev.xbutton.window == button &&
ev.xbutton.x >= 0 && ev.xbutton.x >= 0 &&
ev.xbutton.x < config.titlewidth && ev.xbutton.x < config.titlewidth)
- winclose(row->seltab->ds != NULL ? row->seltab->ds->win : row->seltab->win);
+ winclose(win);
goto done;
break;
}
}
done:
- buttonrightdecorate(row, 0);
+ if (type == FLOAT_MENU) {
+ buttonrightdecorate(menu->button, menu->pixbutton, FOCUSED, 0);
+ } else {
+ buttonrightdecorate(row->br, row->pixbr, tabgetstyle(row->seltab), 0);
+ }
XUngrabPointer(dpy, CurrentTime);
}
@@ -5289,6 +5750,8 @@ xeventbuttonpress(XEvent *e)
t = res.t;
} else if (res.d != NULL) {
t = res.d->t;
+ } else if (res.menu != NULL) {
+ t = res.menu->t;
} else if (res.row != NULL) {
t = res.row->seltab;
} else {
@@ -5298,26 +5761,38 @@ xeventbuttonpress(XEvent *e)
goto done;
}
+ /* raise menu above others */
+ if (res.menu != NULL)
+ menuaddraise(t, res.menu);
+
/* focus client */
if ((wm.focused == NULL || t != wm.focused->selcol->selrow->seltab) && ev->button == Button1)
tabfocus(t, 1);
/* raise client */
- if ((c != wm.abovelist || c != wm.centerlist || c != wm.belowlist) && ev->button == Button1)
+ if (ev->button == Button1)
containerraise(c);
/* do action performed by mouse on non-maximized windows */
if (XTranslateCoordinates(dpy, ev->window, c->frame, ev->x, ev->y, &x, &y, &dw) != True)
goto done;
o = getoctant(c, x, y);
- if (ev->window == t->title && ev->button == Button3) {
+ if (res.menu != NULL) {
+ if (ev->window == res.menu->titlebar && ev->button == Button1) {
+ mousemove(FLOAT_MENU, res.menu, ev->x_root, ev->y_root, 1);
+ } else if (ev->window == res.menu->button && ev->button == Button1) {
+ mouseclose(FLOAT_MENU, res.menu);
+ } else if (ev->window == res.menu->frame && ev->button == Button3) {
+ mousemove(FLOAT_MENU, res.menu, ev->x_root, ev->y_root, 0);
+ }
+ } else if (ev->window == t->title && ev->button == Button3) {
mouseretab(t, ev->x_root, ev->y_root, ev->x, ev->y);
} else if (res.row != NULL && ev->window == res.row->bl && ev->button == Button1) {
mousestack(res.row);
} else if (res.row != NULL && ev->window == res.row->bl && ev->button == Button3) {
mousererow(res.row);
} else if (res.row != NULL && ev->window == res.row->br && ev->button == Button1) {
- mouseclose(res.row);
+ mouseclose(FLOAT_CONTAINER, res.row);
} else if (ev->window == c->frame && ev->button == Button1 && o == C) {
getdivisions(c, &cdiv, &rdiv, x, y);
if (cdiv != NULL || rdiv != NULL) {
@@ -5325,9 +5800,9 @@ xeventbuttonpress(XEvent *e)
}
} else if (!c->isfullscreen && !c->isminimized && !c->ismaximized) {
if (ev->state == MODIFIER && ev->button == Button1) {
- mousemove(c, ev->x_root, ev->y_root, 0);
+ mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, 0);
} else if (ev->window == c->frame && ev->button == Button3) {
- mousemove(c, ev->x_root, ev->y_root, o);
+ mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
} else if ((ev->state == MODIFIER && ev->button == Button3) ||
(o != C && ev->window == c->frame && ev->button == Button1)) {
if (o == C) {
@@ -5344,10 +5819,10 @@ xeventbuttonpress(XEvent *e)
o = NW;
}
}
- mouseresize(c, ev->x_root, ev->y_root, o);
+ mouseresize(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, o);
} else if (ev->window == t->title && ev->button == Button1) {
tabdecorate(t, 1);
- mousemove(c, ev->x_root, ev->y_root, 0);
+ mousemove(FLOAT_CONTAINER, c, ev->x_root, ev->y_root, 0);
tabdecorate(t, 0);
}
}
@@ -5368,7 +5843,9 @@ xeventclientmessage(XEvent *e)
XClientMessageEvent *ev;
XWindowChanges wc;
unsigned value_mask = 0;
+ int floattype;
int i;
+ void *obj;
ev = &e->xclient;
res = getwin(ev->window);
@@ -5541,41 +6018,46 @@ xeventclientmessage(XEvent *e)
* Client-side decorated Gtk3 windows emit this signal when being
* dragged by their GtkHeaderBar
*/
- if (res.c == NULL)
+ if (res.d != NULL || res.c == NULL)
return;
+ if (res.menu != NULL) {
+ obj = res.menu;
+ floattype = FLOAT_MENU;
+ } else {
+ obj = res.c;
+ floattype = FLOAT_CONTAINER;
+ }
switch (ev->data.l[2]) {
- case _NET_WM_MOVERESIZE_CANCEL:
- XUngrabPointer(dpy, CurrentTime);
- break;
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], NW);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], NW);
break;
case _NET_WM_MOVERESIZE_SIZE_TOP:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], N);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], N);
break;
case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], NE);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], NE);
break;
case _NET_WM_MOVERESIZE_SIZE_RIGHT:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], E);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], E);
break;
case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], SE);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], SE);
break;
case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], S);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], S);
break;
case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], SW);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], SW);
break;
case _NET_WM_MOVERESIZE_SIZE_LEFT:
- mouseresize(res.c, ev->data.l[0], ev->data.l[1], W);
+ mouseresize(floattype, obj, ev->data.l[0], ev->data.l[1], W);
break;
case _NET_WM_MOVERESIZE_MOVE:
- mousemove(res.c, ev->data.l[0], ev->data.l[1], C);
+ mousemove(floattype, obj, ev->data.l[0], ev->data.l[1], C);
break;
default:
- return;
+ XUngrabPointer(dpy, CurrentTime);
+ break;
}
}
}
@@ -5644,6 +6126,8 @@ xeventdestroynotify(XEvent *e)
return;
} else if (res.d && ev->window == res.d->win) {
dialogdel(res.d);
+ } else if (res.menu && ev->window == res.menu->win) {
+ menudel(res.menu);
} else if (res.t && ev->window == res.t->win) {
unmanage(res.t);
}
@@ -5707,16 +6191,21 @@ xeventpropertynotify(XEvent *e)
if (ev->state == PropertyDelete)
return;
res = getwin(ev->window);
- if (res.t == NULL || ev->window != res.t->win)
- return;
- if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) {
- tabupdatetitle(res.t);
- tabdecorate(res.t, 0);
- } else if (ev->atom == XA_WM_HINTS) {
- tabupdateurgency(res.t, winisurgent(res.t->win));
- } else if (res.bar != NULL && (ev->atom == _NET_WM_STRUT_PARTIAL || ev->atom == _NET_WM_STRUT)) {
- barstrut(res.bar);
- monupdatearea();
+ if (res.t != NULL && ev->window == res.t->win) {
+ if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) {
+ winupdatetitle(res.t->win, &res.t->name);
+ tabdecorate(res.t, 0);
+ } else if (ev->atom == XA_WM_HINTS) {
+ tabupdateurgency(res.t, winisurgent(res.t->win));
+ } else if (res.bar != NULL && (ev->atom == _NET_WM_STRUT_PARTIAL || ev->atom == _NET_WM_STRUT)) {
+ barstrut(res.bar);
+ monupdatearea();
+ }
+ } else if (res.menu != NULL && ev->window == res.menu->win) {
+ if (ev->atom == XA_WM_NAME || ev->atom == atoms[_NET_WM_NAME]) {
+ winupdatetitle(res.menu->win, &res.menu->name);
+ menudecorate(res.menu, 0);
+ }
}
}
@@ -5749,6 +6238,13 @@ xeventunmapnotify(XEvent *e)
} else {
dialogdel(res.d);
}
+ } else if (res.menu && ev->window == res.menu->win) {
+ if (res.menu->ignoreunmap) {
+ res.menu->ignoreunmap--;
+ return;
+ } else {
+ menudel(res.menu);
+ }
} else if (res.t && ev->window == res.t->win) {
if (res.t->ignoreunmap) {
res.t->ignoreunmap--;