]> git.pld-linux.org Git - packages/awesome-plugin-rodentbane.git/blob - rodentbane.lua
- builder script needs %prep
[packages/awesome-plugin-rodentbane.git] / rodentbane.lua
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
This page took 0.096131 seconds and 3 git commands to generate.