]>
Commit | Line | Data |
---|---|---|
19813400 | 1 | ---------------------------------------------------------------------------- |
2 | -- @author Lucas de Vries <lucas@tuple-typed.org> | |
3 | -- @copyright 2009-2010 Lucas de Vries | |
4 | -- Licensed under the WTFPL | |
5 | ---------------------------------------------------------------------------- | |
6 | ||
7 | -- Load awful | |
8 | require("awful") | |
9 | ||
10 | -- Load beautiful | |
11 | require("beautiful") | |
12 | ||
13 | ---- {{{ Grab environment | |
14 | local ipairs = ipairs | |
15 | local pairs = pairs | |
16 | local print = print | |
17 | local type = type | |
18 | local tonumber = tonumber | |
19 | local tostring = tostring | |
20 | local unpack = unpack | |
21 | local math = math | |
22 | local table = table | |
23 | local awful = awful | |
24 | local os = os | |
25 | local io = io | |
26 | local string = string | |
27 | local awful = awful | |
28 | local beautiful = beautiful | |
29 | ||
30 | -- Grab C API | |
31 | local capi = | |
32 | { | |
33 | root = root, | |
34 | awesome = awesome, | |
35 | screen = screen, | |
36 | client = client, | |
37 | mouse = mouse, | |
38 | button = button, | |
39 | titlebar = titlebar, | |
40 | widget = widget, | |
41 | hooks = hooks, | |
42 | keygrabber = keygrabber, | |
43 | wibox = wibox, | |
44 | widget = widget, | |
45 | } | |
46 | ||
47 | -- }}} | |
48 | ||
49 | --- Utilities for controlling the cursor | |
50 | module("rodentbane") | |
51 | ||
52 | -- Local data | |
53 | local bindings = {} | |
54 | local history = {} | |
55 | local current = nil | |
56 | local wiboxes = nil | |
57 | ||
58 | --- Create the wiboxes to display. | |
59 | function init() | |
60 | -- Wiboxes table | |
61 | wiboxes = {} | |
62 | ||
63 | -- Borders | |
64 | local borders = {"horiz", "vert", "left", "right", "top", "bottom"} | |
65 | ||
66 | -- Create wibox for each border | |
67 | for i, border in ipairs(borders) do | |
68 | wiboxes[border] = capi.wibox({ | |
69 | position = "floating", | |
70 | bg = beautiful.rodentbane_bg or beautiful.border_focus or "#C50B0B", | |
71 | ontop = true, | |
72 | }) | |
73 | end | |
74 | end | |
75 | ||
76 | --- Draw the guidelines on screen using wiboxes. | |
77 | -- @param area The area of the screen to draw on, defaults to current area. | |
78 | function draw(area) | |
79 | -- Default to current area | |
80 | local ar = area or current | |
81 | ||
82 | -- Get numbers | |
83 | local rwidth = beautiful.rodentbane_width or 2 | |
84 | ||
85 | -- Stop if the area is too small | |
86 | if ar.width < rwidth*3 or ar.height < rwidth*3 then | |
87 | stop() | |
88 | return false | |
89 | end | |
90 | ||
91 | -- Put the wiboxes on the correct screen | |
92 | for border, box in pairs(wiboxes) do | |
93 | box.screen = ar.screen | |
94 | end | |
95 | ||
96 | -- Horizontal border | |
97 | wiboxes.horiz:geometry({ | |
98 | x = ar.x+rwidth, | |
99 | y = ar.y+math.floor(ar.height/2), | |
100 | height = rwidth, | |
101 | width = ar.width-(rwidth*2), | |
102 | }) | |
103 | ||
104 | -- Vertical border | |
105 | wiboxes.vert:geometry({ | |
106 | x = ar.x+math.floor(ar.width/2), | |
107 | y = ar.y+rwidth, | |
108 | width = rwidth, | |
109 | height = ar.height-(rwidth*2), | |
110 | }) | |
111 | ||
112 | -- Left border | |
113 | wiboxes.left:geometry({ | |
114 | x = ar.x, | |
115 | y = ar.y, | |
116 | width = rwidth, | |
117 | height = ar.height, | |
118 | }) | |
119 | ||
120 | -- Right border | |
121 | wiboxes.right:geometry({ | |
122 | x = ar.x+ar.width-rwidth, | |
123 | y = ar.y, | |
124 | width = rwidth, | |
125 | height = ar.height, | |
126 | }) | |
127 | ||
128 | -- Top border | |
129 | wiboxes.top:geometry({ | |
130 | x = ar.x, | |
131 | y = ar.y, | |
132 | height = rwidth, | |
133 | width = ar.width, | |
134 | }) | |
135 | ||
136 | -- Bottom border | |
137 | wiboxes.bottom:geometry({ | |
138 | x = ar.x, | |
139 | y = ar.y+ar.height-rwidth, | |
140 | height = rwidth, | |
141 | width = ar.width, | |
142 | }) | |
143 | end | |
144 | ||
145 | --- Cut the navigation area into a direction. | |
146 | -- @param dir Direction to cut to {"up", "right", "down", "left"}. | |
147 | function cut(dir) | |
148 | -- Store previous area | |
149 | table.insert(history, 1, awful.util.table.join(current)) | |
150 | ||
151 | -- Cut in a direction | |
152 | if dir == "up" then | |
153 | current.height = math.floor(current.height/2) | |
154 | elseif dir == "down" then | |
155 | current.y = current.y+math.floor(current.height/2) | |
156 | current.height = math.floor(current.height/2) | |
157 | elseif dir == "left" then | |
158 | current.width = math.floor(current.width/2) | |
159 | elseif dir == "right" then | |
160 | current.x = current.x+math.floor(current.width/2) | |
161 | current.width = math.floor(current.width/2) | |
162 | end | |
163 | ||
164 | -- Redraw the box | |
165 | draw() | |
166 | end | |
167 | ||
168 | --- Move the navigation area in a direction. | |
169 | -- @param dir Direction to move to {"up", "right", "down", "left"}. | |
170 | -- @param ratio Ratio of movement, multiplied by the size of the current area, | |
171 | -- defaults to 0.5 (ie. half the area size. | |
172 | function move(dir, ratio) | |
173 | -- Store previous area | |
174 | table.insert(history, 1, awful.util.table.join(current)) | |
175 | ||
176 | -- Default to ratio 0.5 | |
177 | local rt = ratio or 0.5 | |
178 | ||
179 | -- Move to a direction | |
180 | if dir == "up" then | |
181 | current.y = current.y-math.floor(current.height*rt) | |
182 | elseif dir == "down" then | |
183 | current.y = current.y+math.floor(current.height*rt) | |
184 | elseif dir == "left" then | |
185 | current.x = current.x-math.floor(current.width*rt) | |
186 | elseif dir == "right" then | |
187 | current.x = current.x+math.floor(current.width*rt) | |
188 | end | |
189 | ||
190 | -- Redraw the box | |
191 | draw() | |
192 | end | |
193 | ||
194 | --- Bind a key in rodentbane mode. | |
195 | -- @param modkeys Modifier key combination to bind to. | |
196 | -- @param key Main key to bind to. | |
197 | -- @param func Function to bind the keys to. | |
198 | function bind(modkeys, key, func) | |
199 | -- Create binding | |
200 | local bind = {modkeys, key, func} | |
201 | ||
202 | -- Add to bindings table | |
203 | table.insert(bindings, bind) | |
204 | end | |
205 | ||
206 | --- Callback function for the keygrabber. | |
207 | -- @param modkeys Modkeys that were pressed. | |
208 | -- @param key Main key that was pressed. | |
209 | -- @param evtype Pressed or released event. | |
210 | function keyevent(modkeys, key, evtype) | |
211 | -- Ignore release events and modifier keys | |
212 | if evtype == "release" | |
213 | or key == "Shift_L" | |
214 | or key == "Shift_R" | |
215 | or key == "Control_L" | |
216 | or key == "Control_R" | |
217 | or key == "Super_L" | |
218 | or key == "Super_R" | |
219 | or key == "Hyper_L" | |
220 | or key == "Hyper_R" | |
221 | or key == "Alt_L" | |
222 | or key == "Alt_R" | |
223 | or key == "Meta_L" | |
224 | or key == "Meta_R" | |
225 | then | |
226 | return true | |
227 | end | |
228 | ||
229 | -- Special cases for printable characters | |
230 | -- HACK: Maybe we need a keygrabber that gives keycodes ? | |
231 | if key == " " then | |
232 | key = "Space" | |
233 | end | |
234 | ||
235 | -- Figure out what to call | |
236 | for ind, bind in ipairs(bindings) do | |
237 | if bind[2]:lower() == key:lower() | |
238 | and table_equals(bind[1], modkeys) | |
239 | then | |
240 | -- Call the function | |
241 | if type(bind[3]) == "table" then | |
242 | -- Allow for easy passing of arguments | |
243 | local func = bind[3][1] | |
244 | local args = {} | |
245 | ||
246 | -- Add the rest of the arguments | |
247 | for i, arg in ipairs(bind[3]) do | |
248 | if i > 1 then | |
249 | table.insert(args, arg) | |
250 | end | |
251 | end | |
252 | ||
253 | -- Call function with args | |
254 | func(unpack(args)) | |
255 | else | |
256 | -- Call function directly | |
257 | bind[3]() | |
258 | end | |
259 | ||
260 | -- A bind was found, continue grabbing | |
261 | return true | |
262 | end | |
263 | end | |
264 | ||
265 | -- No key was found, stop grabbing | |
266 | stop() | |
267 | return false | |
268 | end | |
269 | ||
270 | --- Check if two tables have the same values. | |
271 | -- @param t1 First table to check. | |
272 | -- @param t2 Second table to check. | |
273 | -- @return True if the tables are equivalent, false otherwise. | |
274 | function table_equals(t1, t2) | |
275 | -- Check first table | |
276 | for i, item in ipairs(t1) do | |
277 | if awful.util.table.hasitem(t2, item) == nil then | |
278 | -- An unequal item was found | |
279 | return false | |
280 | end | |
281 | end | |
282 | ||
283 | -- Check second table | |
284 | for i, item in ipairs(t2) do | |
285 | if awful.util.table.hasitem(t1, item) == nil then | |
286 | -- An unequal item was found | |
287 | return false | |
288 | end | |
289 | end | |
290 | ||
291 | -- All items were equal | |
292 | return true | |
293 | end | |
294 | ||
295 | --- Warp the mouse to the center of the navigation area | |
296 | function warp() | |
297 | capi.mouse.coords({ | |
298 | x = current.x+(current.width/2), | |
299 | y = current.y+(current.height/2), | |
300 | }) | |
301 | end | |
302 | ||
303 | --- Click with a button | |
304 | -- @param button Button number to click with, defaults to left (1) | |
305 | function click(button) | |
306 | -- Default to left click | |
307 | local b = button or 1 | |
308 | ||
309 | -- TODO: Figure out a way to use fake_input for clicks | |
310 | --capi.root.fake_input("button_press", button) | |
311 | --capi.root.fake_input("button_release", button) | |
312 | ||
313 | -- Use xdotool when available, otherwise try xte | |
314 | command = "xdotool click "..b.." &> /dev/null" | |
315 | .." || xte 'mouseclick "..b.."' &> /dev/null" | |
316 | .." || echo 'W: rodentbane: either xdotool or xte" | |
317 | .." is required to emulate mouse clicks, neither was found.'" | |
318 | ||
319 | awful.util.spawn_with_shell(command) | |
320 | end | |
321 | ||
322 | --- Undo a change to the area | |
323 | function undo() | |
324 | -- Restore area | |
325 | if #history > 0 then | |
326 | current = history[1] | |
327 | table.remove(history, 1) | |
328 | ||
329 | draw() | |
330 | end | |
331 | end | |
332 | ||
333 | --- Convenience function to bind to default keys. | |
334 | function binddefault() | |
335 | -- Cut with hjkl | |
336 | bind({}, "h", {cut, "left"}) | |
337 | bind({}, "j", {cut, "down"}) | |
338 | bind({}, "k", {cut, "up"}) | |
339 | bind({}, "l", {cut, "right"}) | |
340 | ||
341 | -- Move with Shift+hjkl | |
342 | bind({"Shift"}, "h", {move, "left"}) | |
343 | bind({"Shift"}, "j", {move, "down"}) | |
344 | bind({"Shift"}, "k", {move, "up"}) | |
345 | bind({"Shift"}, "l", {move, "right"}) | |
346 | ||
347 | -- Undo with u | |
348 | bind({}, "u", undo) | |
349 | ||
350 | -- Left click with space | |
351 | bind({}, "Space", function () | |
352 | warp() | |
353 | click() | |
354 | stop() | |
355 | end) | |
356 | ||
357 | -- Double Left click with alt+space | |
358 | bind({"Mod1"}, "Space", function () | |
359 | warp() | |
360 | click() | |
361 | click() | |
362 | stop() | |
363 | end) | |
364 | ||
365 | -- Middle click with Control+space | |
366 | bind({"Control"}, "Space", function () | |
367 | warp() | |
368 | click(2) | |
369 | stop() | |
370 | end) | |
371 | ||
372 | -- Right click with shift+space | |
373 | bind({"Shift"}, "Space", function () | |
374 | warp() | |
375 | click(3) | |
376 | stop() | |
377 | end) | |
378 | ||
379 | -- Only warp with return | |
380 | bind({}, "Return", function () | |
381 | warp() | |
382 | end) | |
383 | end | |
384 | ||
385 | --- Start the navigation sequence. | |
386 | -- @param screen Screen to start navigation on, defaults to current screen. | |
387 | -- @param recall Whether the previous area should be recalled (defaults to | |
388 | -- false). | |
389 | function start(screen, recall) | |
390 | -- Default to current screen | |
391 | local scr = screen or capi.mouse.screen | |
392 | ||
393 | -- Initialise if not already done | |
394 | if wiboxes == nil then | |
395 | -- Add default bindings if we have none ourselves | |
396 | if #bindings == 0 then | |
397 | binddefault() | |
398 | end | |
399 | ||
400 | -- Create the wiboxes | |
401 | init() | |
402 | end | |
403 | ||
404 | -- Empty current area if needed | |
405 | if not recall then | |
406 | -- Start with a complete area | |
407 | current = capi.screen[scr].workarea | |
408 | ||
409 | -- Empty history | |
410 | history = {} | |
411 | end | |
412 | ||
413 | -- Move to the right screen | |
414 | current.screen = scr | |
415 | ||
416 | -- Start the keygrabber | |
417 | capi.keygrabber.run(keyevent) | |
418 | ||
419 | -- Draw the box | |
420 | draw() | |
421 | end | |
422 | ||
423 | --- Stop the navigation sequence without doing anything. | |
424 | function stop() | |
425 | -- Stop the keygrabber | |
426 | capi.keygrabber.stop() | |
427 | ||
428 | -- Hide all wiboxes | |
429 | for border, box in pairs(wiboxes) do | |
430 | box.screen = nil | |
431 | end | |
432 | end |