/*
* 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 eventlistener listener;
struct client *client;
struct button *deletebutton;
struct dragger *topleftresizer;
struct dragger *toprightresizer;
Pixmap pixmap;
int namewidth;
Bool moving;
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 eventlistener(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);
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,
font->size, DefaultDepth(dpy, scr));
XFillRectangle(dpy, f->pixmap,
hasfocus ? hlbackground : background,
0, 0, f->namewidth, font->size);
drawname(f->pixmap, font, hasfocus ? fhighlight: fnormal,
0, font->ascent, f->client);
if (cgetdesk(f->client) == DESK_ALL) {
int y = font->ascent + font->descent / 2;
XDrawLine(dpy, f->pixmap, foreground,
0, y, f->namewidth, y);
}
}
XSetWindowBackground(dpy, f->window,
hasfocus ? hlbackgroundpixel : backgroundpixel);
XClearWindow(dpy, f->window);
repaint(f);
}
static void repaint(struct frame *f)
{
if (f->pixmap != None)
XCopyArea(dpy, f->pixmap, f->window, foreground, 0, 0,
f->namewidth, font->size,
1 + font->size, 1 + halfleading);
XDrawRectangle(dpy, f->window, foreground,
0, 0, f->width - 1, f->height - 1);
XDrawLine(dpy, f->window, foreground, EXT_LEFT, EXT_TOP - 1,
f->width - EXT_RIGHT - 1, EXT_TOP - 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)
return;
cpopgrp(f->client);
cfocus(f->client, e->time);
if (e->y < EXT_TOP) {
f->moving = True;
f->downx = e->x;
f->downy = e->y;
XGrabPointer(dpy, f->window, True,
ButtonMotionMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, None,
None, e->time);
}
}
static void buttonrelease(struct frame *f, XButtonEvent *e)
{
if (e->button == Button1) {
XUngrabPointer(dpy, e->time);
f->moving = 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)
{
if (f->moving && e->state & Button1Mask)
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 eventlistener(void *self, XEvent *e)
{
struct frame *f = self;
switch (e->type) {
case Expose:
expose(f, &e->xexpose);
break;
case MotionNotify:
motionnotify(f, &e->xmotion);
break;
case ButtonPress:
buttonpress(f, &e->xbutton);
break;
case ButtonRelease:
buttonrelease(f, &e->xbutton);
break;
case ConfigureRequest:
confrequest(f, &e->xconfigurerequest);
break;
case MapRequest:
maprequest(f, &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;
f->moving = False;
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,
CWOverrideRedirect | CWBackPixel,
&(XSetWindowAttributes){
.override_redirect = True,
.background_pixel = backgroundpixel });
Window clientwin = cgetwin(f->client);
reorder(clientwin, f->window);
f->listener.function = eventlistener;
f->listener.pointer = f;
seteventlistener(f->window, &f->listener);
XSelectInput(dpy, f->window,
SubstructureRedirectMask |
ButtonPressMask |
ExposureMask);
/*
* 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.
*/
f->topleftresizer = createdragger(f->window, 0, 0,
1 + font->size, lineheight + 2, NorthWestGravity,
cursortopleft, resizetopleft, f);
f->toprightresizer = createdragger(f->window,
f->width - 2 - font->size, 0,
1 + font->size, lineheight + 2, NorthEastGravity,
cursortopright, resizetopright, f);
int bw = lineheight + 2;
int bh = lineheight + 2;
f->deletebutton = bcreate(cdelete, f->client, deletebitmap,
f->window, f->width - 1 - bw - font->size,
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);
seteventlistener(f->window, NULL);
destroydragger(f->topleftresizer);
destroydragger(f->toprightresizer);
bdestroy(f->deletebutton);
if (f->pixmap != None)
XFreePixmap(dpy, f->pixmap);
XDestroyWindow(dpy, f->window);
free(f);
}
Bool fismoving(struct frame *f)
{
return f->moving;
}
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);
}