1 ----------------------------------------------------------------------------
2 -- @author Lucas de Vries <lucas@tuple-typed.org>
3 -- @copyright 2009-2010 Lucas de Vries
4 -- Licensed under the WTFPL
5 ----------------------------------------------------------------------------
13 ---- {{{ Grab environment
18 local tonumber = tonumber
19 local tostring = tostring
28 local beautiful = beautiful
42 keygrabber = keygrabber,
49 --- Utilities for controlling the cursor
58 --- Create the wiboxes to display.
64 local borders = {"horiz", "vert", "left", "right", "top", "bottom"}
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",
76 --- Draw the guidelines on screen using wiboxes.
77 -- @param area The area of the screen to draw on, defaults to current area.
79 -- Default to current area
80 local ar = area or current
83 local rwidth = beautiful.rodentbane_width or 2
85 -- Stop if the area is too small
86 if ar.width < rwidth*3 or ar.height < rwidth*3 then
91 -- Put the wiboxes on the correct screen
92 for border, box in pairs(wiboxes) do
93 box.screen = ar.screen
97 wiboxes.horiz:geometry({
99 y = ar.y+math.floor(ar.height/2),
101 width = ar.width-(rwidth*2),
105 wiboxes.vert:geometry({
106 x = ar.x+math.floor(ar.width/2),
109 height = ar.height-(rwidth*2),
113 wiboxes.left:geometry({
121 wiboxes.right:geometry({
122 x = ar.x+ar.width-rwidth,
129 wiboxes.top:geometry({
137 wiboxes.bottom:geometry({
139 y = ar.y+ar.height-rwidth,
145 --- Cut the navigation area into a direction.
146 -- @param dir Direction to cut to {"up", "right", "down", "left"}.
148 -- Store previous area
149 table.insert(history, 1, awful.util.table.join(current))
151 -- Cut in a direction
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)
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))
176 -- Default to ratio 0.5
177 local rt = ratio or 0.5
179 -- Move to a direction
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)
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)
200 local bind = {modkeys, key, func}
202 -- Add to bindings table
203 table.insert(bindings, bind)
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"
215 or key == "Control_L"
216 or key == "Control_R"
229 -- Special cases for printable characters
230 -- HACK: Maybe we need a keygrabber that gives keycodes ?
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)
241 if type(bind[3]) == "table" then
242 -- Allow for easy passing of arguments
243 local func = bind[3][1]
246 -- Add the rest of the arguments
247 for i, arg in ipairs(bind[3]) do
249 table.insert(args, arg)
253 -- Call function with args
256 -- Call function directly
260 -- A bind was found, continue grabbing
265 -- No key was found, stop grabbing
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)
276 for i, item in ipairs(t1) do
277 if awful.util.table.hasitem(t2, item) == nil then
278 -- An unequal item was found
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
291 -- All items were equal
295 --- Warp the mouse to the center of the navigation area
298 x = current.x+(current.width/2),
299 y = current.y+(current.height/2),
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
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)
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.'"
319 awful.util.spawn_with_shell(command)
322 --- Undo a change to the area
327 table.remove(history, 1)
333 --- Convenience function to bind to default keys.
334 function binddefault()
336 bind({}, "h", {cut, "left"})
337 bind({}, "j", {cut, "down"})
338 bind({}, "k", {cut, "up"})
339 bind({}, "l", {cut, "right"})
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"})
350 -- Left click with space
351 bind({}, "Space", function ()
357 -- Double Left click with alt+space
358 bind({"Mod1"}, "Space", function ()
365 -- Middle click with Control+space
366 bind({"Control"}, "Space", function ()
372 -- Right click with shift+space
373 bind({"Shift"}, "Space", function ()
379 -- Only warp with return
380 bind({}, "Return", function ()
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
389 function start(screen, recall)
390 -- Default to current screen
391 local scr = screen or capi.mouse.screen
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
400 -- Create the wiboxes
404 -- Empty current area if needed
406 -- Start with a complete area
407 current = capi.screen[scr].workarea
413 -- Move to the right screen
416 -- Start the keygrabber
417 capi.keygrabber.run(keyevent)
423 --- Stop the navigation sequence without doing anything.
425 -- Stop the keygrabber
426 capi.keygrabber.stop()
429 for border, box in pairs(wiboxes) do