/*
* Copyright 2010 Johan Veenhuizen
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include "wind.h"
#define EXT_TOP (lineheight + 2)
#define EXT_BOTTOM (halfleading + 1)
#define EXT_LEFT (halfleading + 1)
#define EXT_RIGHT (halfleading + 1)
struct frame {
struct listener listener;
struct client *client;
struct button *deletebutton;
struct dragger *topleftresizer;
struct dragger *toprightresizer;
Pixmap pixmap;
GC *background;
int namewidth;
int x;
int y;
int width;
int height;
Window window;
int downx; // window relative pointer x at button press
int downy; // window relative pointer y at button press
};
static void reorder(Window, Window);
static int getgrav(struct client *);
static void setgrav(Window, int);
static void gravitate(int, int, int *, int *);
static void confrequest(struct frame *, XConfigureRequestEvent *);
static void repaint(struct frame *);
static void buttonpress(struct frame *, XButtonEvent *);
static void buttonrelease(struct frame *, XButtonEvent *);
static void moveresize(struct frame *, int, int, int, int);
static void motionnotify(struct frame *, XMotionEvent*);
static void maprequest(struct frame *, XMapRequestEvent *);
static void expose(struct frame *, XExposeEvent *);
static void event(void *, XEvent *);
static void resizetopleft(void *, int, int, unsigned long, Time);
static void resizetopright(void *, int, int, unsigned long, Time);
static Cursor cursortopleft = None;
static Cursor cursortopright = None;
/*
* XXX: We cheat here and always estimate normal frame
* extents, even if the window is of a type that will
* not get a frame. This is hopefully okay since most
* clients requesting estimates of frame extents will
* probably be interested in having a frame.
*/
struct extents estimateframeextents(Window w)
{
return (struct extents){
.top = EXT_TOP,
.bottom = EXT_BOTTOM,
.left = EXT_LEFT,
.right = EXT_RIGHT };
}
static void reorder(Window ref, Window below)
{
XRestackWindows(dpy, (Window[]){ ref, below }, 2);
}
static int getgrav(struct client *c)
{
XSizeHints h;
if (cgetwmnormalhints(c, &h) && (h.flags & PWinGravity) != 0)
return h.win_gravity;
else
return NorthWestGravity;
}
static void setgrav(Window win, int grav)
{
XChangeWindowAttributes(dpy, win, CWWinGravity,
&(XSetWindowAttributes){ .win_gravity = grav });
}
static void gravitate(int wingrav, int borderwidth, int *dx, int *dy)
{
switch (wingrav) {
case NorthWestGravity:
*dx = 0;
*dy = 0;
break;
case NorthGravity:
*dx = borderwidth - (EXT_LEFT + EXT_RIGHT) / 2;
*dy = 0;
break;
case NorthEastGravity:
*dx = (2 * borderwidth) - (EXT_LEFT + EXT_RIGHT);
*dy = 0;
break;
case WestGravity:
*dx = 0;
*dy = borderwidth - (EXT_TOP + EXT_BOTTOM) / 2;
break;
case CenterGravity:
*dx = borderwidth - (EXT_LEFT + EXT_RIGHT) / 2;
*dy = borderwidth - (EXT_TOP + EXT_BOTTOM) / 2;
break;
case EastGravity:
*dx = (2 * borderwidth) - (EXT_LEFT + EXT_RIGHT);
*dy = borderwidth - (EXT_TOP + EXT_BOTTOM) / 2;
break;
case SouthWestGravity:
*dx = 0;
*dy = (2 * borderwidth) - (EXT_TOP + EXT_BOTTOM);
break;
case SouthGravity:
*dx = borderwidth - (EXT_LEFT + EXT_RIGHT) / 2;
*dy = (2 * borderwidth) - (EXT_TOP + EXT_BOTTOM);
break;
case SouthEastGravity:
*dx = (2 * borderwidth) - (EXT_LEFT + EXT_RIGHT);
*dy = (2 * borderwidth) - (EXT_TOP + EXT_BOTTOM);
break;
case StaticGravity:
*dx = borderwidth - EXT_LEFT;
*dy = borderwidth - EXT_TOP;;
break;
default:
errorf("unknown window gravity %d", wingrav);
*dx = 0;
*dy = 0;
break;
}
}
void fupdate(struct frame *f)
{
Bool hasfocus = chasfocus(f->client);
f->background = hasfocus ? &hlbackground : &background;
if (f->pixmap != None) {
XFreePixmap(dpy, f->pixmap);
f->pixmap = None;
}
f->namewidth = namewidth(font, f->client);
if (f->namewidth > 0) {
f->pixmap = XCreatePixmap(dpy, root, f->namewidth,
lineheight, DefaultDepth(dpy, scr));
XFillRectangle(dpy, f->pixmap, *f->background,
0, 0, f->namewidth, lineheight);
drawname(f->pixmap, font, hasfocus ? fhighlight: fnormal,
0, halfleading + font->ascent, f->client);
if (cgetdesk(f->client) == DESK_ALL) {
int y = halfleading + font->ascent + font->descent / 2;
XDrawLine(dpy, f->pixmap,
hasfocus ? hlforeground : foreground,
0, y, f->namewidth, y);
}
}
repaint(f);
}
static void repaint(struct frame *f)
{
if (f->pixmap != None)
XCopyArea(dpy, f->pixmap, f->window, foreground, 0, 0,
f->namewidth, lineheight, 1 + font->size, 1);
// Border
XDrawRectangle(dpy, f->window, foreground,
0, 0, f->width - 1, f->height - 1);
// Title bottom border
XDrawLine(dpy, f->window, foreground, EXT_LEFT, EXT_TOP - 1,
f->width - EXT_RIGHT - 1, EXT_TOP - 1);
// Title area
XFillRectangle(dpy, f->window, *f->background,
1, 1, font->size, lineheight);
int x = 1 + font->size + f->namewidth;
XFillRectangle(dpy, f->window, *f->background,
x, 1, f->width - 1 - x, lineheight);
// Window area
XFillRectangle(dpy, f->window, *f->background,
1, EXT_TOP, f->width - 2, f->height - 1 - EXT_TOP);
// Small areas to the left and right of the title bottom border
XFillRectangle(dpy, f->window, *f->background,
1, EXT_TOP - 1, EXT_LEFT - 1, 1);
XFillRectangle(dpy, f->window, *f->background,
f->width - EXT_RIGHT, EXT_TOP - 1, EXT_RIGHT - 1, 1);
}
static void confrequest(struct frame *f, XConfigureRequestEvent *e)
{
struct geometry g = cgetgeom(f->client);
if (e->value_mask & CWBorderWidth) {
g.borderwidth = e->border_width;
csetgeom(f->client, g);
}
int dx, dy;
gravitate(getgrav(f->client), g.borderwidth, &dx, &dy);
int x = f->x;
int y = f->y;
// Fetch requested geometry
if (e->value_mask & CWX)
x = e->x + dx;
if (e->value_mask & CWY)
y = e->y + dy;
if (e->value_mask & CWWidth)
g.width = e->width;
if (e->value_mask & CWHeight)
g.height = e->height;
int width = g.width + EXT_LEFT + EXT_RIGHT;
int height = g.height + EXT_TOP + EXT_BOTTOM;
moveresize(f, x, y, width, height);
}
static void buttonpress(struct frame *f, XButtonEvent *e)
{
if (e->button == Button1) {
cpopgrp(f->client);
cfocus(f->client, e->time);
if (e->y < EXT_TOP || (e->state & Mod1Mask) != 0) {
csetgrpfollowdesk(f->client, True);
f->downx = e->x;
f->downy = e->y;
XGrabPointer(dpy, f->window, False,
Button1MotionMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, None,
None, e->time);
}
}
}
static void buttonrelease(struct frame *f, XButtonEvent *e)
{
if (e->button == Button1) {
XUngrabPointer(dpy, e->time);
csetgrpfollowdesk(f->client, False);
}
}
// Move and resize the window, and send the necessary updates to
// the client window. The border width is used by configurewindow().
static void moveresize(struct frame *f, int x, int y, int w, int h)
{
if (x == f->x && y == f->y && w == f->width && h == f->height)
return;
struct geometry old = cgetgeom(f->client);
struct geometry new = {
.x = x + EXT_LEFT,
.y = y + EXT_TOP,
.width = w - EXT_LEFT - EXT_RIGHT,
.height = h - EXT_TOP - EXT_BOTTOM,
.borderwidth = old.borderwidth,
};
csetgeom(f->client, new);
XMoveResizeWindow(dpy, f->window, x, y, w, h);
f->x = x;
f->y = y;
f->width = w;
f->height = h;
if (new.width == old.width && new.height == old.height)
csendconf(f->client);
else
XResizeWindow(dpy, cgetwin(f->client), new.width, new.height);
}
static void motionnotify(struct frame *f, XMotionEvent *e)
{
moveresize(f, e->x_root - f->downx, e->y_root - f->downy,
f->width, f->height);
}
static void maprequest(struct frame *f, XMapRequestEvent *e)
{
Window win = cgetwin(f->client);
if (e->window == win)
redirect((XEvent *)e, win);
}
static void expose(struct frame *f, XExposeEvent *e)
{
if (e->count == 0)
repaint(f);
}
static void event(void *self, XEvent *e)
{
switch (e->type) {
case Expose:
expose(self, &e->xexpose);
break;
case MotionNotify:
motionnotify(self, &e->xmotion);
break;
case ButtonPress:
buttonpress(self, &e->xbutton);
break;
case ButtonRelease:
buttonrelease(self, &e->xbutton);
break;
case ConfigureRequest:
confrequest(self, &e->xconfigurerequest);
break;
case MapRequest:
maprequest(self, &e->xmaprequest);
break;
}
}
struct frame *fcreate(struct client *c)
{
static Bool firstcall = True;
if (firstcall) {
cursortopleft = XCreateFontCursor(dpy, XC_top_left_corner);
cursortopright = XCreateFontCursor(dpy, XC_top_right_corner);
firstcall = False;
}
struct frame *f = xmalloc(sizeof *f);
f->client = c;
f->pixmap = None;
f->namewidth = 0;
struct geometry g = cgetgeom(c);
int dx, dy;
gravitate(getgrav(c), g.borderwidth, &dx, &dy);
f->x = g.x + dx;
f->y = g.y + dy;
f->width = g.width + EXT_LEFT + EXT_RIGHT;
f->height = g.height + EXT_TOP + EXT_BOTTOM;
f->window = XCreateWindow(dpy, root, f->x, f->y, f->width, f->height,
0, CopyFromParent, InputOutput, CopyFromParent,
CWBitGravity,
&(XSetWindowAttributes){
.bit_gravity = NorthWestGravity });
Window clientwin = cgetwin(f->client);
reorder(clientwin, f->window);
f->listener.function = event;
f->listener.pointer = f;
setlistener(f->window, &f->listener);
XSelectInput(dpy, f->window,
SubstructureRedirectMask |
ButtonPressMask |
ExposureMask);
grabbutton(Button1, Mod1Mask, f->window, False, 0,
GrabModeAsync, GrabModeAsync, None, None);
/*
* The order in which the resizers and the delete button
* are created is important since it determines their
* stacking order. For very small windows it is important
* that the right resizer and the delete button are above
* the left resizer.
*/
int dw = font->size + 1;
int dh = lineheight + 2;
f->topleftresizer = dcreate(f->window, 0, 0, dw, dh,
NorthWestGravity, cursortopleft, resizetopleft, f);
f->toprightresizer = dcreate(f->window, f->width - dw, 0, dw, dh,
NorthEastGravity, cursortopright, resizetopright, f);
int bw = lineheight + 2;
int bh = lineheight + 2;
f->deletebutton = bcreate(cdelete, f->client, deletebitmap,
f->window, f->width - 1 - font->size - bw,
1 + (lineheight - bh) / 2, bw, bh, NorthEastGravity);
XSetWindowBorderWidth(dpy, clientwin, 0);
setgrav(clientwin, NorthWestGravity);
if (cismapped(f->client))
cignoreunmap(f->client);
XReparentWindow(dpy, clientwin, f->window, EXT_LEFT, EXT_TOP);
g.x += EXT_LEFT;
g.y += EXT_TOP;
csetgeom(f->client, g);
csendconf(f->client);
ewmh_notifyframeextents(clientwin, (struct extents){
.top = EXT_TOP,
.bottom = EXT_BOTTOM,
.right = EXT_RIGHT,
.left = EXT_LEFT });
fupdate(f);
if (cismapped(f->client))
XMapWindow(dpy, f->window);
return f;
}
void fdestroy(struct frame *f)
{
XUnmapWindow(dpy, f->window);
struct geometry g = cgetgeom(f->client);
Window clientwin = cgetwin(f->client);
XSetWindowBorderWidth(dpy, clientwin, g.borderwidth);
setgrav(clientwin, getgrav(f->client));
int dx, dy;
gravitate(getgrav(f->client), g.borderwidth, &dx, &dy);
if (cismapped(f->client))
cignoreunmap(f->client);
g.x = f->x - dx;
g.y = f->y - dy;
csetgeom(f->client, g);
XReparentWindow(dpy, clientwin, root, g.x, g.y);
ewmh_notifyframeextents(clientwin, (struct extents){
.top = 0,
.bottom = 0,
.left = 0,
.right = 0 });
reorder(f->window, clientwin);
if (chasfocus(f->client))
cfocus(f->client, CurrentTime);
setlistener(f->window, NULL);
ddestroy(f->topleftresizer);
ddestroy(f->toprightresizer);
bdestroy(f->deletebutton);
if (f->pixmap != None)
XFreePixmap(dpy, f->pixmap);
XDestroyWindow(dpy, f->window);
free(f);
}
Window fgetwin(struct frame *f)
{
return f->window;
}
struct geometry fgetgeom(struct frame *f)
{
return (struct geometry){
.x = f->x,
.y = f->y,
.width = f->width,
.height = f->height,
.borderwidth = 0 };
}
static void resizetopleft(void *self, int xdrag, int ydrag,
unsigned long counter, Time t)
{
struct frame *f = self;
int w = f->width - (xdrag - f->x);
int h = f->height - (ydrag - f->y);
w -= EXT_LEFT + EXT_RIGHT;
h -= EXT_TOP + EXT_BOTTOM;
adjustclientsize(f->client, w, h, &w, &h);
w += EXT_LEFT + EXT_RIGHT;
h += EXT_TOP + EXT_BOTTOM;
int x = f->x + f->width - w;
int y = f->y + f->height - h;
if (counter == 0) {
cpopgrp(f->client);
cfocus(f->client, t);
}
moveresize(f, x, y, w, h);
}
static void resizetopright(void *self, int xdrag, int ydrag,
unsigned long counter, Time t)
{
struct frame *f = self;
int w = xdrag + 1 - f->x;
int h = f->height - (ydrag - f->y);
w -= EXT_LEFT + EXT_RIGHT;
h -= EXT_TOP + EXT_BOTTOM;
adjustclientsize(f->client, w, h, &w, &h);
w += EXT_LEFT + EXT_RIGHT;
h += EXT_TOP + EXT_BOTTOM;
int x = f->x;
int y = f->y + f->height - h;
if (counter == 0) {
cpopgrp(f->client);
cfocus(f->client, t);
}
moveresize(f, x, y, w, h);
}