clicks on the client work as they should
[mikachu/openbox.git] / src / actions.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "actions.hh"
8 #include "widget.hh"
9 #include "openbox.hh"
10 #include "client.hh"
11 #include "python.hh"
12 #include "bindings.hh"
13 #include "otk/display.hh"
14
15 #include <stdio.h>
16
17 namespace ob {
18
19 const unsigned int OBActions::DOUBLECLICKDELAY = 300;
20 const int OBActions::BUTTONS;
21
22 OBActions::OBActions()
23   : _button(0)
24 {
25   for (int i=0; i<BUTTONS; ++i)
26     _posqueue[i] = new ButtonPressAction();
27 }
28
29
30 OBActions::~OBActions()
31 {
32   for (int i=0; i<BUTTONS; ++i)
33     delete _posqueue[i];
34 }
35
36
37 void OBActions::insertPress(const XButtonEvent &e)
38 {
39   ButtonPressAction *a = _posqueue[BUTTONS - 1];
40   for (int i=BUTTONS-1; i>0;)
41     _posqueue[i] = _posqueue[--i];
42   _posqueue[0] = a;
43   a->button = e.button;
44   a->pos.setPoint(e.x_root, e.y_root);
45
46   OBClient *c = Openbox::instance->findClient(e.window);
47   if (c) a->clientarea = c->area();
48 }
49
50 void OBActions::removePress(const XButtonEvent &e)
51 {
52   ButtonPressAction *a = 0;
53   for (int i=0; i<BUTTONS; ++i) {
54     if (_posqueue[i]->button == e.button)
55       a = _posqueue[i];
56     if (a) // found one and removed it
57       _posqueue[i] = _posqueue[i+1];
58   }
59   if (a) { // found one
60     _posqueue[BUTTONS-1] = a;
61     a->button = 0;
62   }
63 }
64
65 void OBActions::buttonPressHandler(const XButtonEvent &e)
66 {
67   OtkEventHandler::buttonPressHandler(e);
68   insertPress(e);
69   
70   // run the PRESS python hook
71   OBWidget *w = dynamic_cast<OBWidget*>
72     (Openbox::instance->findHandler(e.window));
73   assert(w); // everything should be a widget
74
75   unsigned int state = e.state & (ControlMask | ShiftMask | Mod1Mask |
76                                   Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
77   ButtonData *data = new_button_data(e.window, e.time, state, e.button,
78                                      w->mcontext(), MousePress);
79   Openbox::instance->bindings()->fire(data);
80   Py_DECREF((PyObject*)data);
81     
82   if (_button) return; // won't count toward CLICK events
83
84   _button = e.button;
85 }
86   
87
88 void OBActions::buttonReleaseHandler(const XButtonEvent &e)
89 {
90   OtkEventHandler::buttonReleaseHandler(e);
91   removePress(e);
92   
93   OBWidget *w = dynamic_cast<OBWidget*>
94     (Openbox::instance->findHandler(e.window));
95   assert(w); // everything should be a widget
96
97   // not for the button we're watching?
98   if (_button != e.button) return;
99
100   _button = 0;
101
102   // find the area of the window
103   XWindowAttributes attr;
104   if (!XGetWindowAttributes(otk::OBDisplay::display, e.window, &attr)) return;
105
106   // if not on the window any more, it isnt a CLICK
107   if (!(e.same_screen && e.x >= 0 && e.y >= 0 &&
108         e.x < attr.width && e.y < attr.height))
109     return;
110
111   // run the CLICK python hook
112   unsigned int state = e.state & (ControlMask | ShiftMask | Mod1Mask |
113                                   Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
114   ButtonData *data = new_button_data(e.window, e.time, state, e.button,
115                                      w->mcontext(), MouseClick);
116   Openbox::instance->bindings()->fire(data);
117     
118
119   if (e.time - _release.time < DOUBLECLICKDELAY &&
120       _release.win == e.window && _release.button == e.button) {
121
122     // run the DOUBLECLICK python hook
123     data->action = MouseDoubleClick;
124     Openbox::instance->bindings()->fire(data);
125     
126     // reset so you cant triple click for 2 doubleclicks
127     _release.win = 0;
128     _release.button = 0;
129     _release.time = 0;
130   } else {
131     // save the button release, might be part of a double click
132     _release.win = e.window;
133     _release.button = e.button;
134     _release.time = e.time;
135   }
136
137   Py_DECREF((PyObject*)data);
138 }
139
140
141 void OBActions::enterHandler(const XCrossingEvent &e)
142 {
143   OtkEventHandler::enterHandler(e);
144   
145   OBWidget *w = dynamic_cast<OBWidget*>
146     (Openbox::instance->findHandler(e.window));
147
148   // run the ENTER python hook
149   doCallback(Action_EnterWindow, e.window,
150              (OBWidget::WidgetType)(w ? w->type():-1), e.state, 0, 0, 0, 0);
151 }
152
153
154 void OBActions::leaveHandler(const XCrossingEvent &e)
155 {
156   OtkEventHandler::leaveHandler(e);
157
158   OBWidget *w = dynamic_cast<OBWidget*>
159     (Openbox::instance->findHandler(e.window));
160
161   // run the LEAVE python hook
162   doCallback(Action_LeaveWindow, e.window,
163              (OBWidget::WidgetType)(w ? w->type():-1), e.state, 0, 0, 0, 0);
164 }
165
166
167 void OBActions::keyPressHandler(const XKeyEvent &e)
168 {
169 //  OBWidget *w = dynamic_cast<OBWidget*>
170 //    (Openbox::instance->findHandler(e.window));
171
172   unsigned int state = e.state & (ControlMask | ShiftMask | Mod1Mask |
173                                   Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
174   Openbox::instance->bindings()->fire(state, e.keycode, e.time);
175 }
176
177
178 void OBActions::motionHandler(const XMotionEvent &e)
179 {
180   if (!e.same_screen) return; // this just gets stupid
181
182   int x_root = e.x_root, y_root = e.y_root;
183   
184   // compress changes to a window into a single change
185   XEvent ce;
186   while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
187     if (ce.xmotion.window != e.window) {
188       XPutBackEvent(otk::OBDisplay::display, &ce);
189       break;
190     } else {
191       x_root = e.x_root;
192       y_root = e.y_root;
193     }
194   }
195
196   OBWidget *w = dynamic_cast<OBWidget*>
197     (Openbox::instance->findHandler(e.window));
198   assert(w); // everything should be a widget
199
200   // run the MOTION python hook
201   unsigned int state = e.state & (ControlMask | ShiftMask | Mod1Mask |
202                                   Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
203   unsigned int button = _posqueue[0]->button;
204   MotionData *data = new_motion_data(e.window, e.time, state, button,
205                                      w->mcontext(), MouseMotion,
206                                      x_root, y_root, _posqueue[0]->pos,
207                                      _posqueue[0]->clientarea);
208   Openbox::instance->bindings()->fire((ButtonData*)data);
209   Py_DECREF((PyObject*)data);
210 }
211
212 void OBActions::mapRequestHandler(const XMapRequestEvent &e)
213 {
214   doCallback(Action_NewWindow, e.window, (OBWidget::WidgetType)-1,
215              0, 0, 0, 0, 0);
216 }
217
218 void OBActions::unmapHandler(const XUnmapEvent &e)
219 {
220   (void)e;
221   doCallback(Action_CloseWindow, e.window, (OBWidget::WidgetType)-1,
222              0, 0, 0, 0, 0);
223 }
224
225 void OBActions::destroyHandler(const XDestroyWindowEvent &e)
226 {
227   (void)e;
228   doCallback(Action_CloseWindow, e.window, (OBWidget::WidgetType)-1,
229              0, 0, 0, 0, 0);
230 }
231
232 void OBActions::doCallback(ActionType action, Window window,
233                            OBWidget::WidgetType type, unsigned int state,
234                            unsigned int button, int xroot, int yroot,
235                            Time time)
236 {
237   std::pair<CallbackMap::iterator, CallbackMap::iterator> it_pair =
238     _callbacks.equal_range(action);
239
240   CallbackMap::iterator it;
241 //  for (it = it_pair.first; it != it_pair.second; ++it)
242 //    python_callback(it->second, action, window, type, state,
243 //                    button, xroot, yroot, time);
244   // XXX do a callback
245 }
246
247 bool OBActions::registerCallback(ActionType action, PyObject *func,
248                                  bool atfront)
249 {
250   if (action < 0 || action >= OBActions::NUM_ACTIONS) {
251     return false;
252   }
253   if (!func)
254     return false;
255
256   std::pair<CallbackMap::iterator, CallbackMap::iterator> it_pair =
257     _callbacks.equal_range(action);
258
259   CallbackMap::iterator it;
260   for (it = it_pair.first; it != it_pair.second; ++it)
261     if (it->second == func)
262       return true; // already in there
263   if (atfront)
264     _callbacks.insert(_callbacks.begin(), CallbackMapPair(action, func));
265   else
266     _callbacks.insert(CallbackMapPair(action, func));
267   Py_INCREF(func);
268   return true;
269 }
270
271 bool OBActions::unregisterCallback(ActionType action, PyObject *func)
272 {
273   if (action < 0 || action >= OBActions::NUM_ACTIONS) {
274     return false;
275   }
276   if (!func)
277     return false;
278   
279   std::pair<CallbackMap::iterator, CallbackMap::iterator> it_pair =
280     _callbacks.equal_range(action);
281   
282   CallbackMap::iterator it;
283   for (it = it_pair.first; it != it_pair.second; ++it)
284     if (it->second == func)
285       break;
286   if (it != it_pair.second) { // its been registered before
287     Py_DECREF(func);
288     _callbacks.erase(it);
289   }
290   return true;
291 }
292
293 bool OBActions::unregisterAllCallbacks(ActionType action)
294 {
295   if (action < 0 || action >= OBActions::NUM_ACTIONS) {
296     return false;
297   }
298
299   while (!_callbacks.empty()) {
300     CallbackMap::iterator it = _callbacks.begin();
301     Py_DECREF(it->second);
302     _callbacks.erase(it);
303   }
304   return true;
305 }
306
307 }