/**
* @file event.c
* @author Joe Wingbermuehle
* @date 2004-2006
*
* @brief Functions to handle X11 events.
*
*/
#include "jwm.h"
#include "event.h"
#include "client.h"
#include "clientlist.h"
#include "clock.h"
#include "confirm.h"
#include "cursor.h"
#include "desktop.h"
#include "dock.h"
#include "hint.h"
#include "icon.h"
#include "key.h"
#include "main.h"
#include "move.h"
#include "pager.h"
#include "place.h"
#include "popup.h"
#include "resize.h"
#include "root.h"
#include "swallow.h"
#include "taskbar.h"
#include "timing.h"
#include "tray.h"
#include "traybutton.h"
#include "winmenu.h"
#define MIN_TIME_DELTA 50
static void Signal();
static void DispatchBorderButtonEvent(const XButtonEvent *event,
ClientNode *np);
static void HandleConfigureRequest(const XConfigureRequestEvent *event);
static int HandleExpose(const XExposeEvent *event);
static int HandlePropertyNotify(const XPropertyEvent *event);
static void HandleClientMessage(const XClientMessageEvent *event);
static void HandleColormapChange(const XColormapEvent *event);
static int HandleDestroyNotify(const XDestroyWindowEvent *event);
static void HandleMapRequest(const XMapEvent *event);
static void HandleUnmapNotify(const XUnmapEvent *event);
static void HandleButtonEvent(const XButtonEvent *event);
static void HandleKeyPress(const XKeyEvent *event);
static void HandleKeyRelease(const XKeyEvent *event);
static void HandleEnterNotify(const XCrossingEvent *event);
static void HandleLeaveNotify(const XCrossingEvent *event);
static void HandleMotionNotify(const XMotionEvent *event);
static int HandleSelectionClear(const XSelectionClearEvent *event);
static void HandleNetMoveResize(const XClientMessageEvent *event,
ClientNode *np);
static void HandleNetWMState(const XClientMessageEvent *event,
ClientNode *np);
#ifdef USE_SHAPE
static void HandleShapeEvent(const XShapeEvent *event);
#endif
/** Wait for an event and process it. */
void WaitForEvent(XEvent *event) {
struct timeval timeout;
fd_set fds;
int fd;
int handled;
fd = JXConnectionNumber(display);
do {
while(JXPending(display) == 0) {
FD_ZERO(&fds);
FD_SET(fd, &fds);
timeout.tv_usec = 0;
timeout.tv_sec = 1;
if(select(fd + 1, &fds, NULL, NULL, &timeout) <= 0) {
Signal();
}
}
Signal();
JXNextEvent(display, event);
switch(event->type) {
case ConfigureRequest:
HandleConfigureRequest(&event->xconfigurerequest);
handled = 1;
break;
case MapRequest:
HandleMapRequest(&event->xmap);
handled = 1;
break;
case PropertyNotify:
handled = HandlePropertyNotify(&event->xproperty);
break;
case ClientMessage:
HandleClientMessage(&event->xclient);
handled = 1;
break;
case UnmapNotify:
HandleUnmapNotify(&event->xunmap);
handled = 1;
break;
case Expose:
handled = HandleExpose(&event->xexpose);
break;
case ColormapNotify:
HandleColormapChange(&event->xcolormap);
handled = 1;
break;
case DestroyNotify:
handled = HandleDestroyNotify(&event->xdestroywindow);
break;
case SelectionClear:
handled = HandleSelectionClear(&event->xselectionclear);
break;
case ResizeRequest:
handled = HandleDockResizeRequest(&event->xresizerequest);
break;
case MotionNotify:
SetMousePosition(event->xmotion.x_root, event->xmotion.y_root);
handled = 0;
break;
case ReparentNotify:
HandleDockReparentNotify(&event->xreparent);
handled = 1;
break;
case ConfigureNotify:
handled = 0;
break;
case CreateNotify:
case MapNotify:
case GraphicsExpose:
case NoExpose:
handled = 1;
break;
default:
#ifdef USE_SHAPE
if(haveShape && event->type == shapeEvent) {
HandleShapeEvent((XShapeEvent*)event);
handled = 1;
} else {
handled = 0;
}
#else
handled = 0;
#endif
break;
}
if(!handled) {
handled = ProcessTrayEvent(event);
}
if(!handled) {
handled = ProcessDialogEvent(event);
}
if(!handled) {
handled = ProcessSwallowEvent(event);
}
if(!handled) {
handled = ProcessPopupEvent(event);
}
} while(handled && !shouldExit);
}
/** Wake up components that need to run at certain times. */
void Signal() {
static TimeType last = ZERO_TIME;
TimeType now;
int x, y;
GetCurrentTime(&now);
if(GetTimeDifference(&now, &last) < MIN_TIME_DELTA) {
return;
}
last = now;
GetMousePosition(&x, &y);
SignalTaskbar(&now, x, y);
SignalTrayButton(&now, x, y);
SignalClock(&now, x, y);
SignalTray(&now, x, y);
SignalPopup(&now, x, y);
}
/** Process an event. */
void ProcessEvent(XEvent *event) {
switch(event->type) {
case ButtonPress:
case ButtonRelease:
HandleButtonEvent(&event->xbutton);
break;
case KeyPress:
HandleKeyPress(&event->xkey);
break;
case KeyRelease:
HandleKeyRelease(&event->xkey);
break;
case EnterNotify:
HandleEnterNotify(&event->xcrossing);
break;
case LeaveNotify:
HandleLeaveNotify(&event->xcrossing);
break;
case MotionNotify:
while(JXCheckTypedEvent(display, MotionNotify, event));
HandleMotionNotify(&event->xmotion);
break;
case DestroyNotify:
case Expose:
case ConfigureNotify:
break;
default:
Debug("Unknown event type: %d", event->type);
break;
}
}
/** Discard motion events for the specified window. */
void DiscardMotionEvents(XEvent *event, Window w) {
XEvent temp;
while(JXCheckTypedEvent(display, MotionNotify, &temp)) {
SetMousePosition(temp.xmotion.x_root, temp.xmotion.y_root);
if(temp.xmotion.window == w) {
*event = temp;
}
}
}
/** Process a selection clear event. */
int HandleSelectionClear(const XSelectionClearEvent *event) {
return HandleDockSelectionClear(event);
}
/** Process a button event. */
void HandleButtonEvent(const XButtonEvent *event) {
int x, y;
ClientNode *np;
int north, south, east, west;
np = FindClientByParent(event->window);
if(np) {
RaiseClient(np);
if(focusModel == FOCUS_CLICK) {
FocusClient(np);
}
switch(event->button) {
case Button1:
DispatchBorderButtonEvent(event, np);
break;
case Button2:
MoveClient(np, event->x, event->y);
break;
case Button3:
GetBorderSize(np, &north, &south, &east, &west);
x = event->x + np->x - west;
y = event->y + np->y - north;
ShowWindowMenu(np, x, y);
break;
case Button4:
ShadeClient(np);
break;
case Button5:
UnshadeClient(np);
break;
default:
break;
}
} else if(event->window == rootWindow && event->type == ButtonPress) {
if(!ShowRootMenu(event->button, event->x, event->y)) {
if(event->button == 4) {
PreviousDesktop();
} else if(event->button == 5) {
NextDesktop();
}
}
} else {
np = FindClientByWindow(event->window);
if(np) {
switch(event->button) {
case Button1:
case Button2:
RaiseClient(np);
if(focusModel == FOCUS_CLICK) {
FocusClient(np);
}
if(event->state & Mod1Mask) {
GetBorderSize(np, &north, &south, &east, &west);
MoveClient(np, event->x + west, event->y + north);
}
break;
case Button3:
if(event->state & Mod1Mask) {
GetBorderSize(np, &north, &south, &east, &west);
ResizeClient(np, BA_RESIZE | BA_RESIZE_E | BA_RESIZE_S,
event->x + west, event->y + north);
} else {
RaiseClient(np);
if(focusModel == FOCUS_CLICK) {
FocusClient(np);
}
}
break;
default:
break;
}
JXAllowEvents(display, ReplayPointer, CurrentTime);
}
}
UpdatePager();
}
/** Process a key press event. */
void HandleKeyPress(const XKeyEvent *event) {
ClientNode *np;
KeyType key;
key = GetKey(event);
np = GetActiveClient();
switch(key & 0xFF) {
case KEY_EXEC:
RunKeyCommand(event);
break;
case KEY_DESKTOP:
if(key >> 8) {
ChangeDesktop((key >> 8) - 1);
} else {
NextDesktop();
}
break;
case KEY_NEXT:
FocusNext();
break;
case KEY_NEXTSTACK:
StartWindowStackWalk();
WalkWindowStack();
break;
case KEY_CLOSE:
if(np) {
DeleteClient(np);
}
break;
case KEY_SHADE:
if(np) {
if(np->state.status & STAT_SHADED) {
UnshadeClient(np);
} else {
ShadeClient(np);
}
}
break;
case KEY_MOVE:
if(np) {
MoveClientKeyboard(np);
}
break;
case KEY_RESIZE:
if(np) {
ResizeClientKeyboard(np);
}
break;
case KEY_MIN:
if(np) {
MinimizeClient(np);
}
break;
case KEY_MAX:
if(np) {
MaximizeClient(np, 1, 1);
}
break;
case KEY_ROOT:
ShowKeyMenu(event);
break;
case KEY_WIN:
if(np) {
ShowWindowMenu(np, np->x, np->y);
}
break;
case KEY_RESTART:
Restart();
break;
case KEY_EXIT:
Exit();
break;
default:
break;
}
}
/** Handle a key release event. */
void HandleKeyRelease(const XKeyEvent *event) {
KeyType key;
key = GetKey(event);
switch(key & 0xFF) {
case KEY_NEXTSTACK_END:
StopWindowStackWalk();
break;
default:
break;
}
}
/** Process a configure request. */
void HandleConfigureRequest(const XConfigureRequestEvent *event) {
XWindowChanges wc;
ClientNode *np;
int north, south, east, west;
int changed;
int handled;
handled = HandleDockConfigureRequest(event);
if(handled) {
return;
}
np = FindClientByWindow(event->window);
if(np && np->window == event->window) {
/* We own this window, make sure it's not trying to do something bad. */
changed = 0;
if((event->value_mask & CWWidth) && (event->width != np->width)) {
np->width = event->width;
changed = 1;
}
if((event->value_mask & CWHeight) && (event->height != np->height)) {
np->height = event->height;
changed = 1;
}
if((event->value_mask & CWX) && (event->x != np->x)) {
np->x = event->x;
changed = 1;
}
if((event->value_mask & CWY) && (event->y != np->y)) {
np->y = event->y;
changed = 1;
}
if(!changed) {
return;
}
if(np->controller) {
(np->controller)(0);
}
GetBorderSize(np, &north, &south, &east, &west);
wc.stack_mode = Above;
wc.sibling = np->parent;
wc.border_width = 0;
ConstrainSize(np);
np->state.status &= ~(STAT_HMAX | STAT_VMAX);
wc.x = np->x;
wc.y = np->y;
wc.width = np->width + east + west;
wc.height = np->height + north + south;
JXConfigureWindow(display, np->parent, event->value_mask, &wc);
wc.x = west;
wc.y = north;
wc.width = np->width;
wc.height = np->height;
JXConfigureWindow(display, np->window, event->value_mask, &wc);
} else {
/* We don't know about this window, just let the configure through. */
wc.stack_mode = event->detail;
wc.sibling = event->above;
wc.border_width = event->border_width;
wc.x = event->x;
wc.y = event->y;
wc.width = event->width > rootWidth ? rootWidth : event->width;
wc.height = event->height > rootHeight ? rootHeight : event->height;
JXConfigureWindow(display, event->window, event->value_mask, &wc);
}
}
/** Process an enter notify event. */
void HandleEnterNotify(const XCrossingEvent *event) {
ClientNode *np;
Cursor cur;
SetMousePosition(event->x_root, event->y_root);
np = FindClientByWindow(event->window);
if(np) {
if(!(np->state.status & STAT_ACTIVE) && (focusModel == FOCUS_SLOPPY)) {
FocusClient(np);
}
if(np->parent == event->window) {
np->borderAction = GetBorderActionType(np, event->x, event->y);
cur = GetFrameCursor(np->borderAction);
JXDefineCursor(display, np->parent, cur);
} else if(np->borderAction != BA_NONE) {
SetDefaultCursor(np->parent);
np->borderAction = BA_NONE;
}
}
}
/** Process a leave notify event. */
void HandleLeaveNotify(const XCrossingEvent *event) {
ClientNode *np;
SetMousePosition(event->x_root, event->y_root);
np = FindClientByParent(event->window);
if(np) {
SetDefaultCursor(np->parent);
}
}
/** Handle an expose event. */
int HandleExpose(const XExposeEvent *event) {
ClientNode *np;
np = FindClientByWindow(event->window);
if(np) {
if(event->window == np->parent) {
DrawBorder(np, event);
return 1;
} else if(event->window == np->window
&& np->state.status & STAT_WMDIALOG) {
/* Dialog expose events are handled elsewhere. */
return 0;
} else {
/* Ignore other expose events. */
return 1;
}
} else {
return event->count ? 1 : 0;
}
}
/** Handle a property notify event. */
int HandlePropertyNotify(const XPropertyEvent *event) {
ClientNode *np;
int changed;
np = FindClientByWindow(event->window);
if(np) {
changed = 0;
switch(event->atom) {
case XA_WM_NAME:
ReadWMName(np);
changed = 1;
break;
case XA_WM_NORMAL_HINTS:
ReadWMNormalHints(np);
changed = 1;
break;
case XA_WM_HINTS:
case XA_WM_ICON_NAME:
case XA_WM_CLIENT_MACHINE:
break;
default:
if(event->atom == atoms[ATOM_WM_COLORMAP_WINDOWS]) {
ReadWMColormaps(np);
UpdateClientColormap(np);
} else if(event->atom == atoms[ATOM_NET_WM_ICON]) {
LoadIcon(np);
changed = 1;
} else if(event->atom == atoms[ATOM_NET_WM_NAME]) {
ReadWMName(np);
changed = 1;
} else if(event->atom == atoms[ATOM_NET_WM_STRUT_PARTIAL]) {
ReadClientStrut(np);
} else if(event->atom == atoms[ATOM_NET_WM_STRUT]) {
ReadClientStrut(np);
}
break;
}
if(changed) {
DrawBorder(np, NULL);
UpdateTaskBar();
UpdatePager();
}
if(np->state.status & STAT_WMDIALOG) {
return 0;
} else {
return 1;
}
}
return 1;
}
/** Handle a client message. */
void HandleClientMessage(const XClientMessageEvent *event) {
ClientNode *np;
long mask, flags;
#ifdef DEBUG
char *atomName;
#endif
np = FindClientByWindow(event->window);
if(np) {
if(event->message_type == atoms[ATOM_WIN_STATE]) {
mask = event->data.l[0];
flags = event->data.l[1];
if(mask & WIN_STATE_STICKY) {
if(flags & WIN_STATE_STICKY) {
SetClientSticky(np, 1);
} else {
SetClientSticky(np, 0);
}
}
if(mask & WIN_STATE_HIDDEN) {
if(flags & WIN_STATE_HIDDEN) {
np->state.status |= STAT_NOLIST;
} else {
np->state.status &= ~STAT_NOLIST;
}
UpdateTaskBar();
UpdatePager();
}
} else if(event->message_type == atoms[ATOM_WIN_LAYER]) {
SetClientLayer(np, event->data.l[0]);
} else if(event->message_type == atoms[ATOM_WM_CHANGE_STATE]) {
if(np->controller) {
(np->controller)(0);
}
switch(event->data.l[0]) {
case WithdrawnState:
SetClientWithdrawn(np);
break;
case IconicState:
MinimizeClient(np);
break;
case NormalState:
RestoreClient(np, 1);
break;
default:
break;
}
} else if(event->message_type == atoms[ATOM_NET_ACTIVE_WINDOW]) {
RestoreClient(np, 1);
FocusClient(np);
} else if(event->message_type == atoms[ATOM_NET_WM_DESKTOP]) {
if(event->data.l[0] == ~0L) {
SetClientSticky(np, 1);
} else {
if(np->controller) {
(np->controller)(0);
}
if(event->data.l[0] >= 0 && event->data.l[0] < (long)desktopCount) {
np->state.status &= ~STAT_STICKY;
SetClientDesktop(np, event->data.l[0]);
}
}
} else if(event->message_type == atoms[ATOM_NET_CLOSE_WINDOW]) {
DeleteClient(np);
} else if(event->message_type == atoms[ATOM_NET_MOVERESIZE_WINDOW]) {
HandleNetMoveResize(event, np);
} else if(event->message_type == atoms[ATOM_NET_WM_STATE]) {
HandleNetWMState(event, np);
} else {
#ifdef DEBUG
atomName = JXGetAtomName(display, event->message_type);
Debug("Unknown ClientMessage to client: %s", atomName);
JXFree(atomName);
#endif
}
} else if(event->window == rootWindow) {
if(event->message_type == atoms[ATOM_JWM_RESTART]) {
Restart();
} else if(event->message_type == atoms[ATOM_JWM_EXIT]) {
Exit();
} else if(event->message_type == atoms[ATOM_NET_CURRENT_DESKTOP]) {
ChangeDesktop(event->data.l[0]);
} else {
#ifdef DEBUG
atomName = JXGetAtomName(display, event->message_type);
Debug("Unknown ClientMessage to root: %s", atomName);
JXFree(atomName);
#endif
}
} else if(event->message_type == atoms[ATOM_NET_SYSTEM_TRAY_OPCODE]) {
HandleDockEvent(event);
}
}
/** Handle a _NET_MOVERESIZE_WINDOW request. */
void HandleNetMoveResize(const XClientMessageEvent *event, ClientNode *np) {
long flags, gravity;
long x, y;
long width, height;
int deltax, deltay;
int north, south, east, west;
Assert(event);
Assert(np);
gravity = event->data.l[0] & 0xFF;
flags = event->data.l[0] >> 8;
x = np->x;
y = np->y;
width = np->width;
height = np->height;
if(flags & (1 << 0)) {
x = event->data.l[1];
}
if(flags & (1 << 1)) {
y = event->data.l[2];
}
if(flags & (1 << 2)) {
width = event->data.l[3];
}
if(flags & (1 << 3)) {
height = event->data.l[4];
}
if(gravity == 0) {
gravity = np->gravity;
}
GetBorderSize(np, &north, &south, &east, &west);
GetGravityDelta(np, &deltax, &deltay);
x -= deltax;
y -= deltay;
np->x = x;
np->y = y;
np->width = width;
np->height = height;
JXMoveResizeWindow(display, np->parent,
np->x - west, np->y - north,
np->width + east + west,
np->height + north + south);
JXMoveResizeWindow(display, np->window, west, north,
np->width, np->height);
WriteState(np);
SendConfigureEvent(np);
}
/** Handle a _NET_WM_STATE request. */
void HandleNetWMState(const XClientMessageEvent *event, ClientNode *np) {
int actionMaxH;
int actionMaxV;
int actionStick;
int actionShade;
int actionFullScreen;
int x;
/* Up to two actions to be applied together, figure it out. */
actionMaxH = 0;
actionMaxV = 0;
actionStick = 0;
actionShade = 0;
actionFullScreen = 0;
for(x = 1; x <= 2; x++) {
if(event->data.l[x]
== (long)atoms[ATOM_NET_WM_STATE_STICKY]) {
actionStick = 1;
} else if(event->data.l[x]
== (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT]) {
actionMaxV = 1;
} else if(event->data.l[x]
== (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) {
actionMaxH = 1;
} else if(event->data.l[x]
== (long)atoms[ATOM_NET_WM_STATE_SHADED]) {
actionShade = 1;
} else if(event->data.l[x]
== (long)atoms[ATOM_NET_WM_STATE_FULLSCREEN]) {
actionFullScreen = 1;
}
}
switch(event->data.l[0]) {
case 0: /* Remove */
if(actionStick) {
SetClientSticky(np, 0);
}
if(actionMaxH || actionMaxV) {
if(np->state.status & (STAT_HMAX | STAT_VMAX)) {
MaximizeClient(np, 0, 0);
}
}
if(actionShade) {
UnshadeClient(np);
}
if(actionFullScreen) {
SetClientFullScreen(np, 0);
}
break;
case 1: /* Add */
if(actionStick) {
SetClientSticky(np, 1);
}
if(!(np->state.status & (STAT_HMAX | STAT_VMAX))) {
MaximizeClient(np, actionMaxH, actionMaxV);
}
if(actionShade) {
ShadeClient(np);
}
if(actionFullScreen) {
SetClientFullScreen(np, 1);
}
break;
case 2: /* Toggle */
if(actionStick) {
if(np->state.status & STAT_STICKY) {
SetClientSticky(np, 0);
} else {
SetClientSticky(np, 1);
}
}
if(actionMaxH || actionMaxV) {
MaximizeClient(np, actionMaxH, actionMaxV);
}
if(actionShade) {
if(np->state.status & STAT_SHADED) {
UnshadeClient(np);
} else {
ShadeClient(np);
}
}
if(actionFullScreen) {
if(np->state.status & STAT_FULLSCREEN) {
SetClientFullScreen(np, 0);
} else {
SetClientFullScreen(np, 1);
}
}
break;
default:
Debug("bad _NET_WM_STATE action: %ld", event->data.l[0]);
break;
}
}
/** Handle a motion notify event. */
void HandleMotionNotify(const XMotionEvent *event) {
ClientNode *np;
Cursor cur;
BorderActionType action;
if(event->is_hint) {
return;
}
SetMousePosition(event->x_root, event->y_root);
np = FindClientByParent(event->window);
if(np && (np->state.border & BORDER_OUTLINE)) {
action = GetBorderActionType(np, event->x, event->y);
if(np->borderAction != action) {
np->borderAction = action;
cur = GetFrameCursor(action);
JXDefineCursor(display, np->parent, cur);
}
}
}
/** Handle a shape event. */
#ifdef USE_SHAPE
void HandleShapeEvent(const XShapeEvent *event) {
ClientNode *np;
np = FindClientByWindow(event->window);
if(np) {
SetShape(np);
}
}
#endif /* USE_SHAPE */
/** Handle a colormap event. */
void HandleColormapChange(const XColormapEvent *event) {
ClientNode *np;
if(event->new == True) {
np = FindClientByWindow(event->window);
if(np) {
np->cmap = event->colormap;
UpdateClientColormap(np);
}
}
}
/** Handle a map request. */
void HandleMapRequest(const XMapEvent *event) {
ClientNode *np;
Assert(event);
if(CheckSwallowMap(event)) {
return;
}
np = FindClientByWindow(event->window);
if(!np) {
JXSync(display, False);
JXGrabServer(display);
np = AddClientWindow(event->window, 0, 1);
if(np) {
if(focusModel == FOCUS_CLICK) {
FocusClient(np);
}
} else {
JXMapWindow(display, event->window);
}
JXSync(display, False);
JXUngrabServer(display);
} else {
if(!(np->state.status & STAT_MAPPED)) {
np->state.status |= STAT_MAPPED;
np->state.status &= ~STAT_MINIMIZED;
np->state.status &= ~STAT_SDESKTOP;
np->state.status &= ~STAT_HIDDEN;
np->state.status &= ~STAT_SHADED;
if(!(np->state.status & STAT_STICKY)) {
np->state.desktop = currentDesktop;
}
JXMapWindow(display, np->window);
JXMapWindow(display, np->parent);
RaiseClient(np);
FocusClient(np);
UpdateTaskBar();
UpdatePager();
}
}
RestackClients();
}
/** Handle an unmap notify event. */
void HandleUnmapNotify(const XUnmapEvent *event) {
ClientNode *np;
XEvent e;
Assert(event);
np = FindClientByWindow(event->window);
if(np && np->window == event->window) {
if(JXCheckTypedWindowEvent(display, np->window, DestroyNotify, &e)) {
HandleDestroyNotify(&e.xdestroywindow);
return;
}
if(np->controller) {
(np->controller)(1);
}
if(np->state.status & STAT_MAPPED) {
np->state.status &= ~STAT_MAPPED;
JXUnmapWindow(display, np->parent);
WriteState(np);
UpdateTaskBar();
UpdatePager();
}
}
}
/** Handle a destroy notify event. */
int HandleDestroyNotify(const XDestroyWindowEvent *event) {
ClientNode *np;
np = FindClientByWindow(event->window);
if(np && np->window == event->window) {
if(np->controller) {
(np->controller)(1);
}
RemoveClient(np);
return 1;
} else if(!np) {
return HandleDockDestroy(event->window);
}
return 0;
}
/** Take the appropriate action for a click on a client border. */
void DispatchBorderButtonEvent(const XButtonEvent *event, ClientNode *np) {
static Time lastClickTime = 0;
static int lastX = 0, lastY = 0;
static int doubleClickActive = 0;
BorderActionType action;
int bsize;
action = GetBorderActionType(np, event->x, event->y);
switch(action & 0x0F) {
case BA_RESIZE:
if(event->type == ButtonPress) {
ResizeClient(np, action, event->x, event->y);
}
break;
case BA_MOVE:
if(event->type == ButtonPress) {
if(doubleClickActive
&& abs(event->time - lastClickTime) > 0
&& abs(event->time - lastClickTime) <= doubleClickSpeed
&& abs(event->x - lastX) <= doubleClickDelta
&& abs(event->y - lastY) <= doubleClickDelta) {
MaximizeClientDefault(np);
doubleClickActive = 0;
} else {
if(MoveClient(np, event->x, event->y)) {
doubleClickActive = 0;
} else {
doubleClickActive = 1;
lastClickTime = event->time;
lastX = event->x;
lastY = event->y;
}
}
}
break;
case BA_MENU:
if(event->type == ButtonPress) {
if(np->state.border & BORDER_OUTLINE) {
bsize = borderWidth;
} else {
bsize = 0;
}
ShowWindowMenu(np, np->x + event->x - bsize,
np->y + event->y - titleHeight - bsize);
}
break;
case BA_CLOSE:
if(event->type == ButtonRelease) {
DeleteClient(np);
}
break;
case BA_MAXIMIZE:
if(event->type == ButtonRelease) {
MaximizeClientDefault(np);
}
break;
case BA_MINIMIZE:
if(event->type == ButtonRelease) {
MinimizeClient(np);
}
break;
default:
break;
}
}
syntax highlighted by Code2HTML, v. 0.9.1