/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- AZMouseHandler.m for the Azalea window manager Copyright (c) 2006 Yen-Ju Chen mouse.c for the Openbox window manager Copyright (c) 2003 Ben Jansens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. See the COPYING file for a copy of the GNU General Public License. */ #import "AZMouseHandler.h" #import "AZEventHandler.h" #import "AZDebug.h" #import "AZClientManager.h" #import "openbox.h" #import "config.h" #import "action.h" #import "prop.h" #import "grab.h" #import "translate.h" @interface AZMouseBinding: NSObject { unsigned int state; unsigned int button; NSMutableArray *actions[OB_NUM_MOUSE_ACTIONS]; /* lists of Action pointers */ } - (unsigned int) state; - (unsigned int) button; - (void) set_state: (unsigned int) state; - (void) set_button: (unsigned int) button; - (NSMutableArray **) actions; @end @implementation AZMouseBinding - (unsigned int) state { return state; } - (unsigned int) button { return button; } - (void) set_state: (unsigned int) s { state = s; } - (void) set_button: (unsigned int) b { button = b; } - (NSMutableArray **) actions { return actions; } @end #define FRAME_CONTEXT(co, cl) ((cl && [cl type] != OB_CLIENT_TYPE_DESKTOP) ? \ co == OB_FRAME_CONTEXT_FRAME : NO) #define CLIENT_CONTEXT(co, cl) ((cl && [cl type] == OB_CLIENT_TYPE_DESKTOP) ? \ co == OB_FRAME_CONTEXT_DESKTOP : \ co == OB_FRAME_CONTEXT_CLIENT) static AZMouseHandler *sharedInstance = nil; @interface AZMouseHandler (AZPrivate) - (void) grabAllClients: (BOOL) grab; - (BOOL) fireBinding: (ObMouseAction) a context: (ObFrameContext) context client: (AZClient *) c state: (unsigned int) state button: (unsigned int) button x: (int) x y: (int) y; @end @implementation AZMouseHandler + (AZMouseHandler *) defaultHandler { if (sharedInstance == nil) sharedInstance = [[AZMouseHandler alloc] init]; return sharedInstance; } - (ObFrameContext) frameContext: (ObFrameContext) context withButton: (unsigned int) button { ObFrameContext x = context; NSArray *array = bound_contexts[context]; int i, count = [array count]; for (i = 0; i < count; i++) { AZMouseBinding *b = [array objectAtIndex: i]; if ([b button] == button) return context; } switch (context) { case OB_FRAME_CONTEXT_NONE: case OB_FRAME_CONTEXT_DESKTOP: case OB_FRAME_CONTEXT_CLIENT: case OB_FRAME_CONTEXT_TITLEBAR: case OB_FRAME_CONTEXT_HANDLE: case OB_FRAME_CONTEXT_FRAME: case OB_FRAME_CONTEXT_MOVE_RESIZE: break; case OB_FRAME_CONTEXT_BLCORNER: case OB_FRAME_CONTEXT_BRCORNER: x = OB_FRAME_CONTEXT_HANDLE; break; case OB_FRAME_CONTEXT_TLCORNER: case OB_FRAME_CONTEXT_TRCORNER: case OB_FRAME_CONTEXT_MAXIMIZE: case OB_FRAME_CONTEXT_ALLDESKTOPS: case OB_FRAME_CONTEXT_SHADE: case OB_FRAME_CONTEXT_ICONIFY: case OB_FRAME_CONTEXT_ICON: case OB_FRAME_CONTEXT_CLOSE: x = OB_FRAME_CONTEXT_TITLEBAR; break; case OB_FRAME_NUM_CONTEXTS: NSAssert(0, @"Should not reach here"); } return x; } - (void) grab: (BOOL) grab forClient: (AZClient *) client { int i; for (i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i) { NSArray *array = bound_contexts[i]; int j, jcount = [array count]; for (j = 0; j < jcount; j++) { /* grab/ungrab the button */ AZMouseBinding *b = [array objectAtIndex: j]; Window win; int mode; unsigned int mask; if (FRAME_CONTEXT(i, client)) { win = [[client frame] window]; mode = GrabModeAsync; mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask; } else if (CLIENT_CONTEXT(i, client)) { win = [[client frame] plate]; mode = GrabModeSync; /* this is handled in event */ mask = ButtonPressMask; /* can't catch more than this with Sync mode the release event is manufactured in event() */ } else continue; if (grab) grab_button_full([b button], [b state], win, mask, mode, OB_CURSOR_NONE); else ungrab_button([b button], [b state], win); } } } - (void) unbindAll { int i; for(i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i) { NSArray *array = bound_contexts[i]; int k, kcount = [array count]; for (k = 0; k < kcount; k++) { AZMouseBinding *b = [array objectAtIndex: k]; int j; for (j = 0; j < OB_NUM_MOUSE_ACTIONS; ++j) { DESTROY([b actions][j]); } DESTROY(b); } DESTROY(bound_contexts[i]); } } - (void) processEvent: (XEvent *) e forClient: (AZClient *) client { static Time ltime; static unsigned int button = 0, state = 0, lbutton = 0; static Window lwindow = None; static int px, py; ObFrameContext context; BOOL click = NO; BOOL dclick = NO; switch (e->type) { case ButtonPress: context = frame_context(client, e->xany.window); context = [self frameContext: context withButton: e->xbutton.button]; px = e->xbutton.x_root; py = e->xbutton.y_root; button = e->xbutton.button; state = e->xbutton.state; [self fireBinding: OB_MOUSE_ACTION_PRESS context: context client: client state: e->xbutton.state button: e->xbutton.button x: e->xbutton.x_root y: e->xbutton.y_root]; if (CLIENT_CONTEXT(context, client)) { /* Replay the event, so it goes to the client*/ XAllowEvents(ob_display, ReplayPointer, [[AZEventHandler defaultHandler] eventLastTime]/*event_lasttime*/); /* Fall through to the release case! */ } else break; case ButtonRelease: context = frame_context(client, e->xany.window); context = [self frameContext: context withButton: e->xbutton.button]; if (e->xbutton.button == button) { /* clicks are only valid if its released over the window */ int junk1, junk2; Window wjunk; unsigned int ujunk, b, w, h; /* this can cause errors to occur when the window closes */ AZXErrorSetIgnore(YES); junk1 = XGetGeometry(ob_display, e->xbutton.window, &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk); AZXErrorSetIgnore(NO); if (junk1) { if (e->xbutton.x >= (signed)-b && e->xbutton.y >= (signed)-b && e->xbutton.x < (signed)(w+b) && e->xbutton.y < (signed)(h+b)) { click = YES; /* double clicks happen if there were 2 in a row! */ if (lbutton == button && lwindow == e->xbutton.window && e->xbutton.time - config_mouse_dclicktime <= ltime) { dclick = YES; lbutton = 0; } else { lbutton = button; lwindow = e->xbutton.window; } } else { lbutton = 0; lwindow = None; } } button = 0; state = 0; ltime = e->xbutton.time; } [self fireBinding: OB_MOUSE_ACTION_RELEASE context: context client: client state: e->xbutton.state button: e->xbutton.button x: e->xbutton.x_root y: e->xbutton.y_root]; if (click) [self fireBinding: OB_MOUSE_ACTION_CLICK context: context client: client state: e->xbutton.state button: e->xbutton.button x: e->xbutton.x_root y: e->xbutton.y_root]; if (dclick) [self fireBinding: OB_MOUSE_ACTION_DOUBLE_CLICK context: context client: client state: e->xbutton.state button: e->xbutton.button x: e->xbutton.x_root y: e->xbutton.y_root]; break; case MotionNotify: if (button) { context = frame_context(client, e->xany.window); context = [self frameContext: context withButton: button]; if (ABS(e->xmotion.x_root - px) >= config_mouse_threshold || ABS(e->xmotion.y_root - py) >= config_mouse_threshold) { /* You can't drag on buttons */ if (context == OB_FRAME_CONTEXT_MAXIMIZE || context == OB_FRAME_CONTEXT_ALLDESKTOPS || context == OB_FRAME_CONTEXT_SHADE || context == OB_FRAME_CONTEXT_ICONIFY || context == OB_FRAME_CONTEXT_ICON || context == OB_FRAME_CONTEXT_CLOSE) break; [self fireBinding: OB_MOUSE_ACTION_MOTION context: context client: client state: state button: button x: px y: py]; button = 0; state = 0; } } break; default: NSAssert(0, @"Should not reach here"); } } - (BOOL) bind: (NSString *) buttonstr context: (NSString *) contextstr mouseAction: (ObMouseAction) mact action: (AZAction *) action { unsigned int state, button; ObFrameContext context; AZMouseBinding *b; if (!translate_button(buttonstr, &state, &button)) { NSLog(@"invalid button '%@'", buttonstr); return NO; } context = frame_context_from_string([contextstr cString]); if (!context) { NSLog(@"invalid context '%@'", contextstr); return NO; } NSArray *array = bound_contexts[context]; int i, count = [array count]; for (i = 0; i < count; i++) { b = [array objectAtIndex: i]; if ([b state] == state && [b button] == button) { if ([b actions][mact] == nil) { [b actions][mact] = [[NSMutableArray alloc] init]; } [[b actions][mact] addObject: action]; return YES; } } /* when there are no modifiers in the binding, then the action cannot be interactive */ if (!state && [action data].any.interactive) { [action data_pointer]->any.interactive = NO; [action data_pointer]->inter.final = YES; } /* add the binding */ b = [[AZMouseBinding alloc] init]; [b set_state: state]; [b set_button: button]; [b actions][mact] = [[NSMutableArray alloc] init]; [[b actions][mact] addObject: action]; if (bound_contexts[context] == nil) { bound_contexts[context] = [[NSMutableArray alloc] init]; } [bound_contexts[context] addObject: b]; /* Release in -unbindAll*/ return YES; } - (void) startup: (BOOL) reconfig { [self grabAllClients: YES]; } - (void) shutdown: (BOOL) reconfig { [self grabAllClients: NO]; [self unbindAll]; } @end @implementation AZMouseHandler (AZPrivate) - (void) grabAllClients: (BOOL) grab { AZClientManager *cManager = [AZClientManager defaultManager]; int i, count = [cManager count]; for (i = 0; i < count; i++) { AZClient *data = [cManager clientAtIndex: i]; [self grab: grab forClient: data]; } } - (BOOL) fireBinding: (ObMouseAction) a context: (ObFrameContext) context client: (AZClient *) c state: (unsigned int) state button: (unsigned int) button x: (int) x y: (int) y { AZMouseBinding *b; NSArray *array = bound_contexts[context]; int i, count = [array count]; BOOL found = NO; for (i = 0; i < count; i++) { b = [array objectAtIndex: i]; if ([b state] == state && [b button] == button) { found = YES; break; } } /* if not bound, then nothing to do! */ if (found == NO) return NO; action_run_mouse([b actions][a], c, context, state, button, x, y); return YES; } @end