├── mouse.lua
├── layout.lua
├── util.lua
├── init.lua
├── README.md
├── resize.lua
├── max.lua
├── focus.lua
└── screen.lua
/mouse.lua:
--------------------------------------------------------------------------------
1 | local capi = {screen=screen,client=client,mouse=mouse, keygrabber = keygrabber}
2 | local math,table = math,table
3 | local wibox = require( "wibox" )
4 | local awful = require( "awful" )
5 | local cairo = require( "lgi" ).cairo
6 | local color = require( "gears.color" )
7 | local beautiful = require( "beautiful" )
8 | local surface = require( "gears.surface" )
9 | local col_utils = require( "collision.util" )
10 |
11 | local module = {}
12 |
13 | local w = nil
14 |
15 | function module.highlight()
16 | if not w then
17 | w = wibox{}
18 | w.height = 100
19 | w.width = 100
20 | w.ontop = true
21 |
22 | local img = cairo.ImageSurface(cairo.Format.A8, 100,100)
23 | local cr = cairo.Context(img)
24 | -- cr:set_operator(cairo.Operator.CLEAR)
25 | cr:set_source_rgba(0,0,0,0)
26 | cr:paint()
27 | cr:set_operator(cairo.Operator.SOURCE)
28 | cr:set_source_rgba(1,1,1,1)
29 |
30 | cr:save()
31 | cr:translate(0,-30)
32 | cr:move_to(50,50)
33 | cr:rotate(math.pi)
34 | col_utils.arrow_path(cr,40,10)
35 | cr:fill()
36 | cr:restore()
37 |
38 | cr:save()
39 | cr:translate(-30,0)
40 | cr:move_to(50,50)
41 | cr:rotate(math.pi/2)
42 | col_utils.arrow_path(cr,40,10)
43 | cr:fill()
44 | cr:restore()
45 |
46 | cr:save()
47 | cr:translate(30,0)
48 | cr:move_to(50,50)
49 | cr:rotate(-math.pi/2)
50 | col_utils.arrow_path(cr,40,10)
51 | cr:fill()
52 | cr:restore()
53 |
54 | cr:save()
55 | cr:translate(0,30)
56 | cr:move_to(50,50)
57 | col_utils.arrow_path(cr,40,10)
58 | cr:fill()
59 | cr:restore()
60 |
61 | cr:arc(50,50,50-3,0,2*math.pi)
62 | cr:set_line_width(5)
63 | cr:stroke()
64 |
65 | cr:set_source_rgba(1,0,0,1)
66 | w.shape_bounding = img._native
67 |
68 |
69 | img = cairo.ImageSurface(cairo.Format.ARGB32, 100,100)
70 | cr = cairo.Context(img)
71 | cr:set_source(color(beautiful.bg_urgent))
72 | cr:paint()
73 | cr:set_source(color(beautiful.fg_normal))
74 | cr:arc(50,50,50-3,0,2*math.pi)
75 | cr:set_line_width(5)
76 | cr:stroke()
77 | w:set_bg(cairo.Pattern.create_for_surface(img))
78 | end
79 | w.x = capi.mouse.coords().x -50
80 | w.y = capi.mouse.coords().y -50
81 | w.visible = true
82 | end
83 |
84 | function module.hide()
85 | if w then
86 | w.visible = false
87 | end
88 | end
89 |
90 | return module
91 |
--------------------------------------------------------------------------------
/layout.lua:
--------------------------------------------------------------------------------
1 | -- This helper module help retro-generate the clients layout from awful
2 | -- this is a giant hack and doesn't even always work and require upstream
3 | -- patches
4 |
5 | local setmetatable = setmetatable
6 | local ipairs,math = ipairs,math
7 | local awful = require("awful")
8 | local beautiful = require("beautiful")
9 | local color = require( "gears.color")
10 | local util = require( "collision.util" )
11 | local shape = require( "gears.shape" )
12 | local capi = { screen = screen, client=client }
13 |
14 | local module = {}
15 | local margin = 2
16 | local radius = 4
17 |
18 | -- Emulate a client using meta table magic
19 | local function gen_cls(c,results)
20 | local ret = setmetatable({},{__index = function(t,i)
21 | local ret2 = c[i]
22 | if type(ret2) == "function" then
23 | if i == "geometry" then
24 | return function(self,...)
25 | if #{...} > 0 then
26 | local geom = ({...})[1]
27 | -- Make a copy as the original will be changed
28 | results[c] = awful.util.table.join(({...})[1],{})
29 | return geom
30 | end
31 | return c:geometry()
32 | end
33 | else
34 | return function(self,...) return ret2(c,...) end
35 | end
36 | end
37 | return ret2
38 | end})
39 | return ret
40 | end
41 |
42 | function module.get_geometry(tag)
43 | local cls,results,flt = {},setmetatable({},{__mode="k"}),{}
44 | local s = tag.screen
45 | local l = tag.layout
46 | local focus,focus_wrap = capi.client.focus,nil
47 | for k,c in ipairs (tag:clients()) do
48 | -- Handle floating client separately
49 | if not c.minimized then
50 | local floating = c.floating
51 | if (not floating) and (not l == awful.layout.suit.floating) then
52 | cls[#cls+1] = gen_cls(c,results)
53 | if c == focus then
54 | focus_wrap = cls[#cls]
55 | end
56 | else
57 | flt[#flt+1] = c:geometry()
58 | end
59 | end
60 | end
61 |
62 | -- The magnifier layout require a focussed client
63 | -- there wont be any as that layout is not selected
64 | -- take one at random or (TODO) use stack data
65 | if not focus_wrap then
66 | focus_wrap = cls[1]
67 | end
68 |
69 | local param = {
70 | tag = tag,
71 | screen = 1,
72 | clients = cls,
73 | focus = focus_wrap,
74 | workarea = capi.screen[s or 1].workarea
75 | }
76 |
77 | l.arrange(param)
78 |
79 | return results,flt
80 | end
81 |
82 | function module.draw(tag,cr,width,height)
83 | local worked = false
84 | local l,l2 = module.get_geometry(tag)
85 | local s = tag.screen
86 | local scr_geo = capi.screen[s or 1].workarea
87 | local ratio = height/scr_geo.height
88 | local w_stretch = width/(scr_geo.width*ratio)
89 | local r,g,b = util.get_rgb(
90 | beautiful.collision_max_fg or beautiful.fg_normal
91 | )
92 |
93 | local lshape = beautiful.collision_layout_shape or shape.rounded_rect
94 |
95 | cr:set_line_width(3)
96 | for c,ll in ipairs({l,l2}) do
97 | for c,geom in pairs(ll) do
98 | shape.transform(lshape)
99 | : translate(geom.x*ratio*w_stretch+margin, geom.y*ratio+margin) (
100 | cr,
101 | geom.width*ratio*w_stretch-margin*2,
102 | geom.height*ratio-margin*2,
103 | radius
104 | )
105 | cr:close_path()
106 | cr:set_source_rgba(r,g,b,0.7)
107 | cr:stroke_preserve()
108 | cr:set_source_rgba(r,g,b,0.2)
109 | cr:fill()
110 |
111 | -- Draw an icon in the region
112 | --TODO
113 |
114 | worked = true
115 | end
116 | end
117 | --TODO floating clients
118 | return worked
119 | end
120 |
121 | return module
122 | -- kate: space-indent on; indent-width 2; replace-tabs on;
123 |
--------------------------------------------------------------------------------
/util.lua:
--------------------------------------------------------------------------------
1 | local capi = {screen = screen}
2 | local math = math
3 | local color = require( "gears.color" )
4 | local beautiful = require( "beautiful" )
5 | local glib = require("lgi").GLib
6 | local cairo = require( "lgi" ).cairo
7 |
8 | local module = {settings={}}
9 |
10 |
11 | local rr,rg,rb
12 | function module.get_rgb(col)
13 | if not rr then
14 | local pat = color(col or beautiful.fg_normal)
15 | local s,r,g,b,a = pat:get_rgba()
16 | rr,rg,rb = r,g,b
17 | end
18 | return rr,rg,rb
19 | end
20 |
21 | --- Draw an arrow path. The current context position is the center of the arrow
22 | -- By default, the arrow is pointing up, use context rotation to get other directions
23 | --
24 | -- To get an arrow pointing down:
25 | -- @usage cr:move_to(width/2, height/2)
26 | -- cr:rotate(math.pi)
27 | --
28 | --
29 | function module.arrow_path(cr, width, sidesize)
30 | cr:rel_move_to( 0 , -width/2 )
31 | cr:rel_line_to( width/2 , width/2 )
32 | cr:rel_line_to( -sidesize , 0 )
33 | cr:rel_line_to( 0 , width/2 )
34 | cr:rel_line_to( (-width)+2*sidesize , 0 )
35 | cr:rel_line_to( 0 , -width/2 )
36 | cr:rel_line_to( -sidesize , 0 )
37 | cr:rel_line_to( width/2 , -width/2 )
38 | cr:close_path()
39 | end
40 |
41 | function module.arrow_path2(cr, width, height, head_width, shaft_width, shaft_length)
42 | local shaft_length = shaft_length or height / 2
43 | local shaft_width = shaft_width or width / 2
44 | local head_width = head_width or width
45 | local head_length = height - shaft_length
46 |
47 | cr:move_to( width/2 , 0 )
48 | cr:rel_line_to( head_width/2 , head_length )
49 | cr:rel_line_to( -(head_width-shaft_width)/2 , 0 )
50 | cr:rel_line_to( 0 , shaft_length )
51 | cr:rel_line_to( -shaft_width , 0 )
52 | cr:rel_line_to( 0 , -shaft_length )
53 | cr:rel_line_to( -(head_width-shaft_width)/2 , 0 )
54 | cr:close_path()
55 | end
56 |
57 | function module.arrow(width, sidesize, margin, bg_color, fg_color)
58 | local img = cairo.ImageSurface(cairo.Format.ARGB32, width+2*margin, width+2*margin)
59 | local cr = cairo.Context(img)
60 | cr:set_source(color(bg_color))
61 | cr:paint()
62 | cr:set_source(color(fg_color))
63 | cr:set_antialias(cairo.Antialias.NONE)
64 | cr:move_to(margin+width/2, margin+width/2)
65 | module.arrow_path(cr, width, sidesize)
66 | cr:fill()
67 | return cairo.Pattern.create_for_surface(img)
68 | end
69 |
70 | function module.draw_round_rect(cr,x,y,w,h,radius)
71 | cr:save()
72 | cr:translate(x,y)
73 | cr:move_to(0,radius)
74 | cr:arc(radius,radius,radius,math.pi,3*(math.pi/2))
75 | cr:arc(w-radius,radius,radius,3*(math.pi/2),math.pi*2)
76 | cr:arc(w-radius,h-radius,radius,math.pi*2,math.pi/2)
77 | cr:arc(radius,h-radius,radius,math.pi/2,math.pi)
78 | cr:close_path()
79 | cr:restore()
80 | end
81 |
82 | local function refresh_dt(last_sec,last_usec,callback,delay)
83 | local tv = glib.TimeVal()
84 | glib.get_current_time(tv)
85 | local dt = (tv.tv_sec*1000000+tv.tv_usec)-(last_sec*1000000+last_usec)
86 | if dt < delay then
87 | callback()
88 | end
89 | return tv.tv_sec,tv.tv_usec
90 | end
91 |
92 | function module.double_click(callback,delay)
93 | delay = delay or 250000
94 | local ds,du = 0,0
95 | return function()
96 | ds,du = refresh_dt(ds,du,callback,delay)
97 | end
98 | end
99 |
100 | -- Screen order is not always geometrical, sort them
101 | local screens,screens_inv
102 | function module.get_ordered_screens()
103 | if screens then return screens,screens_inv end
104 |
105 | screens = {}
106 | for i=1,capi.screen.count() do
107 | local geom = capi.screen[i].geometry
108 | if #screens == 0 then
109 | screens[1] = capi.screen[i]
110 | elseif geom.x < capi.screen[screens[1]].geometry.x then
111 | table.insert(screens,1,capi.screen[i])
112 | else
113 | for j=#screens,1,-1 do
114 | if geom.x > capi.screen[screens[j]].geometry.x then
115 | table.insert(screens,j+1,capi.screen[i])
116 | break
117 | end
118 | end
119 | end
120 | end
121 |
122 | screens_inv = {}
123 | for k,v in ipairs(screens) do
124 | screens_inv[v] = k
125 | end
126 |
127 | return screens,screens_inv
128 | end
129 |
130 | capi.screen.connect_signal("added", function()
131 | screens,screens_inv = nil, nil
132 | end)
133 | capi.screen.connect_signal("removed", function()
134 | screens,screens_inv = nil, nil
135 | end)
136 |
137 | --- Setup the whole thing and call fct(cr, width, height) then apply the shape
138 | -- fct should not set the source or color
139 | function module.apply_shape_bounding(c_or_w, fct)
140 | local geo = c_or_w:geometry()
141 |
142 | local img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height)
143 | local cr = cairo.Context(img)
144 |
145 | cr:set_source_rgba(0,0,0,0)
146 | cr:paint()
147 | cr:set_operator(cairo.Operator.SOURCE)
148 | cr:set_source_rgba(1,1,1,1)
149 |
150 | fct(cr, geo.width, geo.height)
151 |
152 | cr:fill()
153 |
154 | c_or_w.shape_bounding = img._native
155 | end
156 |
157 | return module
158 | -- kate: space-indent on; indent-width 2; replace-tabs on;
159 |
--------------------------------------------------------------------------------
/init.lua:
--------------------------------------------------------------------------------
1 | local capi = {root=root,client=client,mouse=mouse, timer=timer,
2 | screen = screen, keygrabber = keygrabber}
3 | local util = require( "awful.util" )
4 | local awful = require( "awful" )
5 | local glib = require( "lgi" ).GLib
6 | local col_utils = require( "collision.util" )
7 | local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
8 | local module = {
9 | _focus = require( "collision.focus" ),
10 | _resize = require( "collision.resize"),
11 | _max = require( "collision.max" ),
12 | _screen = require( "collision.screen"),
13 | mouse = require( "collision.mouse" ),
14 | settings= col_utils.settings ,
15 | util = col_utils ,
16 | }
17 |
18 | local current_mode = "focus"
19 |
20 | local event_callback = {
21 | focus = module._focus._global_bydirection_key,
22 | move = module._focus._global_bydirection_key,
23 | resize = module._resize.resize ,
24 | max = module._max.change_focus ,
25 | tag = module._max.change_tag ,
26 | screen = module._screen.reload ,
27 | }
28 |
29 | local start_callback = {
30 | focus = module._focus.display ,
31 | move = module._focus.display ,
32 | resize = module._resize.display ,
33 | max = module._max.display_clients,
34 | tag = module._max.display_tags ,
35 | screen = module._screen.display ,
36 | }
37 |
38 | local exit_callback = {
39 | focus = module._focus._quit ,
40 | move = module._focus._quit ,
41 | resize = module._resize.hide ,
42 | max = module._max.hide ,
43 | tag = module._max.hide ,
44 | screen = module._screen.hide ,
45 | }
46 |
47 | local keys = {--Normal Xephyr G510 alt G510
48 | up = {"Up" --[[, "&" , "XF86AudioPause" , "F15"]] },
49 | down = {"Down" --[[, "KP_Enter" , "XF86WebCam" , "F14"]] },
50 | left = {"Left" --[[, "#" , "Cancel" , "F13"]] },
51 | right = {"Right" --[[, "\"" , "XF86Paste" , "F17"]] },
52 | }
53 |
54 | local function exit_loop()
55 | exit_callback[current_mode]()
56 | capi.keygrabber.stop()
57 | return false
58 | end
59 |
60 | -- Event loop
61 | local function start_loop(is_swap,is_max)
62 | capi.keygrabber.run(function(mod, key, event)
63 | -- Detect the direction
64 | for k,v in pairs(keys) do
65 | if util.table.hasitem(v,key) then
66 | if event == "press" then
67 | if not event_callback[current_mode](mod,key,event,k,is_swap,is_max) then
68 | return exit_loop()
69 | end
70 | return
71 | end
72 | return #mod > 0
73 | end
74 | end
75 |
76 | if key == "Shift_L" or key == "Shift_R" then
77 | is_swap = event == "press"
78 | return #mod > 0
79 | elseif key == "Control_L" or key == "Control_R" then
80 | is_max = event == "press"
81 | return #mod > 0 and awful.util.table.hasitem(mod,"Mod4") or exit_loop()
82 | elseif key == "Alt_L" or key == "Alt_R" then
83 | exit_callback[current_mode]()
84 | current_mode = event == "press" and "resize" or "focus"
85 | start_callback[current_mode](mod,key,event,k,is_swap,is_max)
86 | return #mod > 0 and awful.util.table.hasitem(mod,"Mod4") or exit_loop()
87 | end
88 |
89 | return exit_loop()
90 | end)
91 | end
92 |
93 | function module.focus(direction,c,max)
94 | local screen = (c or ((capi.client.focus and capi.client.focus.focusable) and capi.client.focus or capi.mouse)).screen
95 | -- Useless when there is only 1 client tiled, incompatible with the "max_out" mode (in this case, focus floating mode)
96 | if awful.layout.get(screen) == awful.layout.suit.max and #awful.client.tiled(screen) > 1 and not max then
97 | current_mode = "max"
98 | module._max.display_clients(screen,direction)
99 | else
100 | current_mode = "focus"
101 | module._focus.global_bydirection(direction,c,false,max)
102 | end
103 | start_loop(false,max)
104 | end
105 |
106 | function module.move(direction,c,max)
107 | current_mode = "move"
108 | module._focus.global_bydirection(direction,c,true,max)
109 | start_loop(true,max)
110 | end
111 |
112 | function module.resize(direction,c,max)
113 | current_mode = "resize"
114 | start_loop(false,max)
115 | module._resize.display(c)
116 | end
117 |
118 | function module.tag(direction,swap,max)
119 | current_mode = "tag"
120 | local c = capi.client.focus
121 | module._max.display_tags((c) and c.screen or capi.mouse.screen,direction,c,swap,max)
122 | start_loop(swap,max)
123 | end
124 |
125 | function module.screen(direction, move)
126 | current_mode = "screen"
127 | module._screen.display(nil,direction,move)
128 | start_loop(false,max)
129 | end
130 |
131 | function module.select_screen(idx)
132 | if idx and idx > 0 and idx <= capi.screen.count() then
133 | module._screen.select_screen(idx)
134 | end
135 | end
136 |
137 | function module.highlight_cursor(timeout)
138 | module.mouse.highlight()
139 | if timer then
140 | local timer = capi.timer({ timeout = timeout }) -- 30 mins
141 | timer:connect_signal("timeout", function()
142 | module.mouse.hide()
143 | timer:stop()
144 | end)
145 | timer:start()
146 | end
147 | end
148 |
149 | local function new(k)
150 | -- Replace the keys array. The new one has to have a valid mapping
151 | keys = k or keys
152 | local aw = {}
153 |
154 | -- This have to be executer after rc.lua
155 | glib.idle_add(glib.PRIORITY_DEFAULT_IDLE, function()
156 | for k,v in pairs(keys) do
157 | for _,key_name in ipairs(v) do
158 | aw[#aw+1] = awful.key({ "Mod4", }, key_name, function () module.focus (k ) end,
159 | { description = "Change focus to the "..key_name, group = "Collision" })
160 | aw[#aw+1] = awful.key({ "Mod4", "Mod1" }, key_name, function () module.resize(k ) end,
161 | { description = "Resize to the "..key_name, group = "Collision" })
162 | aw[#aw+1] = awful.key({ "Mod4", "Shift" }, key_name, function () module.move (k ) end,
163 | { description = "Move to the "..key_name, group = "Collision" })
164 | aw[#aw+1] = awful.key({ "Mod4", "Shift", "Control" }, key_name, function () module.move (k,nil ,true) end,
165 | { description = "", group = "Collision" })
166 | aw[#aw+1] = awful.key({ "Mod4", "Control" }, key_name, function () module.focus (k,nil ,true) end,
167 | { description = "Change floating focus to the "..key_name, group = "Collision" })
168 | aw[#aw+1] = awful.key({ "Mod4", "Mod1" , "Control" }, key_name, function () module.screen(k ) end,
169 | { description = "Change screen to the "..key_name, group = "Collision" })
170 | aw[#aw+1] = awful.key({ "Mod1", "Shift", "Control", "Mod4" }, key_name, function () module.screen(k,true ) end,
171 | { description = "Move tag screen to the "..key_name, group = "Collision" })
172 | if k == "left" or k =="right" then -- Conflict with my text editor, so I say no
173 | aw[#aw+1] = awful.key({ "Mod1", "Control" }, key_name, function () module.tag (k,nil ,true) end,
174 | { description = "Select tag to the "..key_name, group = "Collision" })
175 | aw[#aw+1] = awful.key({ "Mod1", "Shift", "Control" }, key_name, function () module.tag (k,true,true) end,
176 | { description = "Move tag to the "..key_name, group = "Collision" })
177 | end
178 | end
179 | end
180 | capi.root.keys(awful.util.table.join(capi.root.keys(),unpack(aw)))
181 | end)
182 |
183 | return module
184 | end
185 |
186 | return setmetatable(module, { __call = function(_, ...) return new(...) end })
187 | -- kate: space-indent on; indent-width 2; replace-tabs on;
188 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Collision window navigation module for AwesomeWM
2 | ================================================
3 |
4 | **Warning:** The master branch is for Awesome 4.0+, use the `awesome3.5`
5 | branch for older versions of Awesome.
6 |
7 | This module add some visual indicators for common window management operations.
8 | It is now easier to know the impact of a given command as a visual queue will
9 | be printed on the screen. Collision has 3 modes:
10 |
11 | * **Focus**: Move the focus from client to client
12 | * **Move**: Move a client
13 | * **Resize**: Change a client dimensions
14 | * **Tag**: Move to the previous/next tag
15 |
16 | # Installation
17 |
18 | First, clone the repository
19 |
20 | ```lua
21 | mkdir -p ~/.config/awesome
22 | cd ~/.config/awesome
23 | git clone https://github.com/Elv13/collision
24 | ```
25 |
26 | Now, open ~/.config/awesome/rc.lua (or copy /etc/xdg/awesome/rc.lua to
27 | ~/.config/awesome/rc.lua fist if you never modified your Awesome config before)
28 | and add this line somewhere in your `rc.lua`:
29 |
30 | ```lua
31 | require("collision")()
32 | ```
33 |
34 | It is a very good idea to also change the default Mod4+arrow shortcut to
35 | something else:
36 |
37 | ```lua
38 | --Remove those lines:
39 | awful.key({ modkey, }, "Left", awful.tag.viewprev,
40 | {description = "view previous", group = "tag"}),
41 | awful.key({ modkey, }, "Right", awful.tag.viewnext,
42 | {description = "view next", group = "tag"}),
43 | ```
44 |
45 | Your done!
46 |
47 | # Usage
48 |
49 | Using Collision is easy. You just have to hit the arrow keys (➡ ⬆ ⬇ ⬅)
50 | with some modifiers keys. The Shift key is usually used for grabbing something
51 | while the Control key is used to max out the effect.
52 |
53 | | Modifier 1 | Modifier 2 | Modifier 3 | Effect |
54 | | :----------: | :----------: | :----------: | ------------------------------------------------------- |
55 | | Mod4 | | | Move the focus on the tiled layer |
56 | | Mod4 | | Control | Move the focus on the floating layer |
57 | | Mod4 | Shift | | Move a client in the tiled or floating layer |
58 | | Mod4 | Shift | Control | Move a floating client to the far side of that screen |
59 | | Mod4 | Mod1 (Alt) | | Increase a floating client size |
60 | | Mod4 | Mod1 (Alt) | Shift | Reduce a floating client size |
61 | | Control | Mod1 (Alt) | | Move to the next/previous tag |
62 | | Control | Mod4 | Mod1 (Alt) | Move to the next/previous screen |
63 | | Control | Mod4 | Mod1 (Alt) | + Shift Move tag to the next/previous screen |
64 |
65 | # Using different keys
66 |
67 | Due to the large ammount of keyboard shortcut Collision create, they are
68 | auto-generated automatically. While this make installation simpler, it also
69 | make Collision somewhat hard-coded magic. Some alternative keymaps can also
70 | be ackward to use because of the reliance on mod keys such as Alt and Control.
71 |
72 | That being said, Collision allow some basic remapping. Instead of:
73 |
74 | ```lua
75 | require("collision")()
76 | ```
77 |
78 | This can be used:
79 |
80 | ```lua
81 | require("collision") {
82 | -- Normal Xephyr Vim G510
83 | up = { "Up" , "&" , "k" , "F15" },
84 | down = { "Down" , "KP_Enter" , "j" , "F14" },
85 | left = { "Left" , "#" , "h" , "F13" },
86 | right = { "Right" , "\"" , "l" , "F17" },
87 | }
88 | ```
89 |
90 | Of course, if the `Vim` keys are used, any other shortcut binded to them have to
91 | be removed from rc.lua.
92 |
93 | # Appearance
94 |
95 | Collision appearance can be changed in yout theme using the `collision`
96 | namespace
97 |
98 | ## Focus
99 |
100 | | Variable | Description |
101 | | :---------------------------------: | ------------------------------------------------------ |
102 | | collision_resize_width | The size of the resize handles |
103 | | collision_resize_shape | The gears.shape used for the resize handle |
104 | | collision_resize_border_width | The resize handles border width |
105 | | collision_resize_border_color | The resize handles border colors |
106 | | collision_resize_padding | The resize handles padding between the boder and arrow |
107 | | collision_resize_bg | The resize handles background color |
108 | | collision_resize_fg | The resize handles arrow color |
109 | | collision_resize_arrow_border_color | The arrow border color |
110 | | collision_resize_arrow_border_width | The arrow border width |
111 | | collision_resize_size | Enable the size widget when resizng |
112 | | collision_resize_shape | |
113 | | collision_resize_size_border_width | |
114 | | collision_resize_size_border_color | |
115 | | collision_resize_size_font | |
116 | | collision_resize_size_shape | |
117 | | collision_resize_size_bg | |
118 | | collision_resize_size_fg | |
119 | | collision_focus_shape | The outer shape of the "arrow" widgets |
120 | | collision_focus_border_width | |
121 | | collision_focus_border_color | |
122 | | collision_focus_padding | |
123 | | collision_focus_bg | The background of the focus change arrow |
124 | | collision_focus_fg | The foregroung filling color of the arrow |
125 | | collision_focus_bg_center | The focussed client circle background |
126 | | collision_focus_shape_center | The focused client widget shape (default: circle) |
127 | | collision_focus_arrow_border_color | The color of the arrow border |
128 | | collision_focus_arrow_border_width | The width of the arrow border |
129 | | collision_screen_shape | |
130 | | collision_screen_border_width | |
131 | | collision_screen_border_color | |
132 | | collision_screen_padding | |
133 | | collision_screen_bg | |
134 | | collision_screen_fg | |
135 | | collision_screen_bg_focus | |
136 | | collision_screen_fg_focus | |
137 |
138 | # Settings
139 |
140 | ```lua
141 | -- Swap clients across screen instead of adding them to the other tag
142 | collision.settings.swap_across_screen = true
143 | ```
144 |
145 | # Other
146 |
147 | The `collision.select_screen(idx)` function allow to select a screen and can be
148 | assigned to shortcuts.
149 |
150 | The `collision.highlight_cursor(timeout)` method will highlight the current mouse
151 | cursor position.
152 |
153 | Use `collision.mouse.highlight()` and `collision.mouse.hide()`
154 |
155 | # Notes
156 |
157 | Using the focus arrows to select empty screens only work in Awesome 3.5.7+
158 |
--------------------------------------------------------------------------------
/resize.lua:
--------------------------------------------------------------------------------
1 | local capi = { client = client }
2 | local wibox = require( "wibox" )
3 | local beautiful = require( "beautiful" )
4 | local awful = require( "awful" )
5 | local surface = require( "gears.surface" )
6 | local shape = require( "gears.shape" )
7 |
8 | local module, indicators, cur_c, sizeboxes = {},nil,nil, {}
9 |
10 | local values = {"top" , "top_right" , "right" , "bottom_right" ,
11 | "bottom" , "bottom_left", "left" , "top_left" }
12 |
13 | local invert = {
14 | left = "right",
15 | right = "left" ,
16 | up = "down" ,
17 | down = "up" ,
18 | }
19 |
20 | local r_ajust = {
21 | left = function(c, d) return { x = c.x - d, width = c.width + d } end,
22 | right = function(c, d) return { width = c.width + d, } end,
23 | up = function(c, d) return { y = c.y - d, height = c.height + d } end,
24 | down = function(c, d) return { height = c.height + d, } end,
25 | }
26 |
27 | -- Resize tiled using the keyboard
28 | local layouts_all = {
29 | [awful.layout.suit.floating] = { right = "" },
30 | [awful.layout.suit.tile] = { right = {mwfact= 0.05}, left = {mwfact=-0.05}, up ={wfact=-0.1 }, down = {wfact = 0.1 } },
31 | [awful.layout.suit.tile.left] = { right = {mwfact=-0.05}, left = {mwfact= 0.05}, up ={wfact= 0.1 }, down = {wfact =-0.1 } },
32 | [awful.layout.suit.tile.bottom] = { right = {wfact=-0.1 }, left = {wfact= 0.1 }, up ={mwfact=-0.05}, down = {mwfact= 0.05} },
33 | [awful.layout.suit.tile.top] = { right = {wfact=-0.1 }, left = {wfact= 0.1 }, up ={mwfact= 0.05}, down = {mwfact=-0.05} },
34 | [awful.layout.suit.spiral] = { right = {wfact=-0.1 }, left = {wfact= 0.1 }, up ={mwfact= 0.05}, down = {mwfact=-0.05} },
35 | [awful.layout.suit.magnifier] = { right = {mwfact= 0.05}, left = {mwfact=-0.05}, up ={mwfact= 0.05}, down = {mwfact=-0.05} },
36 | -- The other layouts cannot be resized using variables
37 | }
38 |
39 | local function update_size_boxes(c, float_only)
40 | if not awful.popup then return end
41 | if not beautiful.collision_resize_size then return end
42 |
43 | local clients = c and {c} or awful.client.visible()
44 |
45 | if c and float_only then
46 | for c2, wb in pairs(sizeboxes) do
47 | if c2 ~= c and not c2.floating then
48 | wb.visible = false
49 | end
50 | end
51 | end
52 |
53 | for _, c in ipairs(clients) do
54 | if (not float_only) or c.floating then
55 | local wb = sizeboxes[c]
56 |
57 | if not wb then
58 | sizeboxes[c] = awful.popup {
59 | widget = {
60 | {
61 | id = "tb",
62 | text = "0x0",
63 | font = beautiful.collision_resize_size_font,
64 | widget = wibox.widget.textbox
65 | },
66 | margins = beautiful.collision_resize_size_padding or 4,
67 | widget = wibox.container.margin
68 | },
69 | visible = false,
70 | ontop = true,
71 | bg = beautiful.collision_resize_size_bg or beautiful.bg_normal,
72 | fg = beautiful.collision_resize_size_fg or beautiful.fg_normal,
73 | border_width = beautiful.collision_resize_size_border_width,
74 | border_color = beautiful.collision_resize_size_border_color,
75 | shape = beautiful.collision_resize_shape,
76 | placement = function(o)
77 | return awful.placement.centered(o, {parent = c})
78 | end,
79 | }
80 |
81 | wb = sizeboxes[c]
82 | end
83 |
84 | local geo = c:geometry()
85 | sizeboxes[c].visible = true
86 | sizeboxes[c].widget.tb.text = geo.width .. "x" .. geo.height
87 | end
88 | end
89 | end
90 |
91 | local function create_indicators()
92 | local ret = {}
93 | local angle = -((2*math.pi)/8)
94 |
95 | -- Get the parameters
96 | local size = beautiful.collision_resize_width or 40
97 | local s = beautiful.collision_resize_shape or shape.circle
98 | local bw = beautiful.collision_resize_border_width
99 | local bc = beautiful.collision_resize_border_color
100 | local padding = beautiful.collision_resize_padding or 7
101 | local bg = beautiful.collision_resize_bg or beautiful.bg_alternate or "#ff0000"
102 | local fg = beautiful.collision_resize_fg or beautiful.fg_normal or "#0000ff"
103 | local arrow_bc = beautiful.collision_resize_arrow_border_color
104 | local arrow_bw = beautiful.collision_resize_arrow_border_width or 0
105 |
106 | for k,v in ipairs(values) do
107 | local w = wibox {
108 | width = size,
109 | height = size,
110 | ontop = true,
111 | visible = true
112 | }
113 |
114 | angle = angle + (2*math.pi)/8
115 |
116 | local tr = (size - 2*arrow_bw - 2*padding) / 2
117 |
118 | w:setup {
119 | {
120 | {
121 | {
122 | widget = wibox.widget.imagebox
123 | },
124 | shape = shape.transform(shape.arrow)
125 | : translate( tr,tr )
126 | : rotate ( angle )
127 | : translate( -tr,-tr ),
128 | bg = fg,
129 | border_color = arrow_bc,
130 | border_width = arrow_bw,
131 | widget = wibox.container.background
132 | },
133 | margins = padding,
134 | widget = wibox.container.margin,
135 | },
136 | bg = bg,
137 | shape = s,
138 | shape_border_width = bw,
139 | shape_border_color = bc,
140 | widget = wibox.container.background
141 | }
142 |
143 | if awesome.version >= "v4.1" then
144 | w.shape = s
145 | else
146 | surface.apply_shape_bounding(w, s)
147 | end
148 |
149 | ret[v] = w
150 | end
151 |
152 | return ret
153 | end
154 |
155 | function module.hide()
156 | if not indicators then return end
157 |
158 | for k, v in ipairs(values) do indicators[v].visible = false end
159 |
160 | for _, wb in pairs(sizeboxes) do wb.visible = false end
161 |
162 | if not cur_c then return end
163 |
164 | cur_c:disconnect_signal("property::geometry", module.display)
165 | cur_c = nil
166 |
167 | sizeboxes = {}
168 | end
169 |
170 | function module.display(c,toggle)
171 | if type(c) ~= "client" then --HACK
172 | c = capi.client.focus
173 | end
174 |
175 | if not c then return end
176 |
177 | indicators = indicators or create_indicators()
178 |
179 | if c ~= cur_c then
180 | if cur_c then
181 | cur_c:disconnect_signal("property::geometry", module.display)
182 | end
183 | c:connect_signal("property::geometry", module.display)
184 | cur_c = c
185 | elseif toggle == true then
186 | module.hide()
187 | end
188 |
189 | for k,v in ipairs(values) do
190 | local w = indicators[v]
191 | awful.placement[v](w, {parent=c})
192 | w.visible = true
193 | end
194 |
195 | update_size_boxes(c.floating and c or nil, c.floating)
196 | end
197 |
198 | function module.resize(mod,key,event,direction,is_swap,is_max)
199 | local c = capi.client.focus
200 | if not c then return true end
201 |
202 | local del = is_swap and -100 or 100
203 | direction = is_swap and invert[direction] or direction
204 |
205 | local lay = awful.layout.get(c.screen)
206 |
207 | if c.floating or lay == awful.layout.suit.floating then
208 | c:emit_signal("request::geometry", "mouse.resize", r_ajust[direction](c, del))
209 | update_size_boxes(c, true)
210 | elseif layouts_all[lay] then
211 | local ret = layouts_all[lay][direction]
212 | if ret.mwfact then
213 | awful.tag.incmwfact(ret.mwfact)
214 | end
215 | if ret.wfact then
216 | awful.client.incwfact(ret.wfact, c)
217 | end
218 |
219 | update_size_boxes()
220 | end
221 |
222 |
223 | return true
224 | end
225 |
226 | -- Always display the arrows when resizing
227 | awful.mouse.resize.add_enter_callback(module.display, "mouse.resize")
228 | awful.mouse.resize.add_leave_callback(module.hide , "mouse.resize")
229 |
230 | return module
231 | -- kate: space-indent on; indent-width 4; replace-tabs on;
232 |
--------------------------------------------------------------------------------
/max.lua:
--------------------------------------------------------------------------------
1 | local capi = {screen=screen,client=client,mouse=mouse}
2 | local wibox = require("wibox")
3 | local awful = require("awful")
4 | local cairo = require( "lgi" ).cairo
5 | local color = require( "gears.color" )
6 | local beautiful = require( "beautiful" )
7 | local surface = require( "gears.surface" )
8 | local layout = require( "collision.layout" )
9 | local util = require( "collision.util" )
10 | local shape = require( "gears.shape" )
11 | local pango = require("lgi").Pango
12 | local pangocairo = require("lgi").PangoCairo
13 | local module = {}
14 |
15 | local w = nil
16 | local rad = 10
17 | local border = 3
18 |
19 | local function init()
20 | w = wibox{}
21 | w.ontop = true
22 | w.visible = true
23 | end
24 |
25 | local margin = 15
26 |
27 | local function create_arrow(cr,x,y,width, height,direction)
28 | local r,g,b = util.get_rgb()
29 | cr:set_source_rgba(r,g,b,0.15)
30 | cr:set_antialias(1)
31 |
32 | local s = shape.transform(shape.arrow)
33 | : rotate_at(width/4, height/4, math.pi/2)
34 |
35 | if direction == 1 then
36 | s : rotate_at(width/4, height/4, math.pi)
37 | end
38 |
39 | s : translate(x, y)
40 |
41 | s(cr, width/2, height/2, nil)
42 |
43 | cr:fill()
44 | end
45 |
46 | local pango_l = nil
47 | local function draw_shape(s,collection,current_idx,icon_f,y,text_height)
48 | local geo = capi.screen[s].geometry
49 | local wa =capi.screen[s].workarea
50 |
51 | --Compute thumb dimensions
52 | local margins = 2*20 + (#collection-1)*20
53 | local width = (geo.width - margins) / #collection
54 | local ratio = geo.height/geo.width
55 | local height = width*ratio
56 | local dx = 20
57 |
58 | -- Do not let the thumb get too big
59 | if height > 150 then
60 | height = 150
61 | width = 150 * (1.0/ratio)
62 | dx = (wa.width-margins-(#collection*width))/2 + 20
63 | end
64 |
65 | -- Resize the wibox
66 | w.x,w.y,w.width,w.height = math.floor(geo.x),math.floor(y or (wa.y+wa.height) - margin - height),math.floor(geo.width),math.floor(height)
67 |
68 | local rshape = beautiful.collision_max_shape or shape.rounded_rect
69 | local img = cairo.ImageSurface(cairo.Format.ARGB32, geo.width,geo.height)
70 | local img3 = cairo.ImageSurface(cairo.Format.ARGB32, geo.width,geo.height)
71 | local cr = cairo.Context(img)
72 | local cr3 = cairo.Context(img3)
73 | cr:set_source_rgba(0,0,0,0)
74 | cr:paint()
75 |
76 | -- Get the colors
77 | local white,bg = color("#FFFFFF"),color(
78 | beautiful.collision_max_bg or beautiful.menu_bg_normal or beautiful.bg_normal
79 | )
80 |
81 | local normal, focus = color(beautiful.collision_max_fg or beautiful.fg_normal), color(beautiful.bg_urgent)
82 |
83 | -- Init the text properties
84 | if not pango_l then
85 | local pango_crx = pangocairo.font_map_get_default():create_context()
86 | pango_l = pango.Layout.new(pango_crx)
87 | pango_l:set_font_description(beautiful.get_font(font))
88 | pango_l:set_alignment("CENTER")
89 | pango_l:set_wrap("CHAR")
90 | end
91 |
92 | for k,v in ipairs(collection) do
93 | -- Shape bounding
94 | cr:set_source(white)
95 |
96 | local s = shape.transform(rshape) : translate(dx, 0)
97 | s(cr, width, height, 10)
98 | cr:fill()
99 |
100 | -- Borders and background
101 | cr3:set_source(k==current_idx and focus or normal)
102 |
103 | cr3:move_to(0,0)
104 | s = shape.transform(rshape) : translate(dx+border, border)
105 | s(cr3, width-2*border, height-2*border, rad)
106 |
107 | cr3:set_line_width(2*border + 4) -- The 4 is required to cover the non-antialiased region
108 | cr3:stroke_preserve()
109 | cr3:set_source(bg)
110 | cr3:fill()
111 |
112 | -- Print the icon
113 | local icon = icon_f(v,width-20,height-20-text_height)
114 | if icon then
115 | cr3:save()
116 | cr3:translate(dx+10,10)
117 | cr3:set_source_surface(icon)
118 | cr3:paint_with_alpha(0.7)
119 | cr3:restore()
120 | end
121 |
122 | -- Print a pretty line
123 | local r,g,b = util.get_rgb(normal)
124 | cr3:set_source_rgba(r,g,b,0.7)
125 | cr3:set_line_width(1)
126 | cr3:move_to(dx+margin,height - text_height-border)
127 | cr3:line_to(dx+margin+width-2*margin,height - text_height-border)
128 | cr3:stroke()
129 |
130 | -- Pring the text
131 | pango_l.text = v.name
132 | pango_l.width = pango.units_from_double(width-16)
133 | pango_l.height = pango.units_from_double(height-text_height-10)
134 | cr3:move_to(dx+8,height-text_height-0)
135 | cr3:show_layout(pango_l)
136 |
137 | -- Draw an arrow
138 | if current_idx and k == current_idx-1 then
139 | create_arrow(cr3,dx,0,width,height,1)
140 | else
141 | create_arrow(cr3,dx,0,width,height,nil)
142 | end
143 |
144 | dx = dx + width + 20
145 | end
146 |
147 | w:set_bg(cairo.Pattern.create_for_surface(img3))
148 | w.shape_bounding = img._native
149 | w.visible = true
150 | end
151 |
152 | function module.hide()
153 | w.visible = false
154 | end
155 |
156 | --Client related
157 | local function client_icon(c,width,height)
158 | -- Get the content
159 | --TODO detect pure black frames
160 | local img = cairo.ImageSurface(cairo.Format.ARGB32, width, height)
161 | local rshape = beautiful.collision_max_shape or shape.rounded_rect
162 | local cr = cairo.Context(img)
163 |
164 | local geom = c:geometry()
165 | local scale = width/geom.width
166 | if geom.height*scale > height then
167 | scale = height/geom.height
168 | end
169 | local w,h = geom.width*scale,geom.height*scale
170 |
171 | -- Create a mask
172 | local s = shape.transform(rshape) : translate((width-w)/2, (height-h)/2)
173 | s(cr, w, h, 10)
174 |
175 | cr:clip()
176 |
177 | cr:translate((width-w)/2,(height-h)/2)
178 |
179 | -- Create a matrix to scale down the screenshot
180 | cr:save()
181 | cr:scale(scale,scale)
182 |
183 | -- Paint the screenshot in the rounded rectangle
184 | cr:set_source_surface(surface(c.content))
185 |
186 | cr:paint()
187 | cr:restore()
188 |
189 | -- Add icon on top, "solve" the black window issue
190 | local icon = surface(c.icon)
191 |
192 | if not icon then
193 | return img
194 | end
195 |
196 | -- local w,h = icon:get_width(),icon:get_height()
197 | -- local aspect,aspect_h = width / w,(height) / h
198 | -- if aspect > aspect_h then aspect = aspect_h end
199 | -- cr:translate((width-w*aspect)/2,(height-h*aspect)/2)
200 | -- cr:scale(aspect, aspect)
201 | -- cr:set_source_surface(icon)
202 | -- cr:paint_with_alpha(0.5)
203 |
204 | return img
205 | end
206 |
207 | function module.display_clients(s,direction)
208 | if not w then
209 | init()
210 | end
211 | if direction then
212 | awful.client.focus.byidx(direction == "right" and 1 or -1)
213 | capi.client.focus:raise()
214 | end
215 | local clients = awful.client.tiled(s)
216 | local fk = awful.util.table.hasitem(clients,capi.client.focus)
217 | draw_shape(s,clients,fk,client_icon,nil,50)
218 | end
219 |
220 | function module.change_focus(mod,key,event,direction,is_swap,is_max)
221 | awful.client.focus.byidx(direction == "right" and 1 or -1)
222 | local c = capi.client.focus
223 | local s = c.screen
224 | c:raise()
225 | local clients = awful.client.tiled(s)
226 | local fk = awful.util.table.hasitem(clients,c)
227 | draw_shape(s,clients,fk,client_icon,nil,50)
228 | return true
229 | end
230 |
231 | --Tag related
232 | local function tag_icon(t,width,height)
233 | local img = cairo.ImageSurface(cairo.Format.ARGB32, width, height)
234 | local cr = cairo.Context(img)
235 |
236 | local has_layout = layout.draw(t,cr,width,height)
237 |
238 | -- Create a monochrome representation of the icon
239 | local icon_orig = surface(t.icon)
240 | if icon_orig then
241 | local icon = cairo.ImageSurface(cairo.Format.ARGB32, icon_orig:get_width(), icon_orig:get_height())
242 | local cr2 = cairo.Context(icon)
243 | cr2:set_source_surface(icon_orig)
244 | cr2:paint()
245 |
246 | cr2:set_source(color(beautiful.fg_normal))
247 | cr2:set_operator(cairo.Operator.IN)
248 | cr2:paint()
249 |
250 | local w,h = icon:get_width(),icon:get_height()
251 | local aspect,aspect_h = width / w,(height) / h
252 | if aspect > aspect_h then aspect = aspect_h end
253 | cr:translate((width-w*aspect)/2,(height-h*aspect)/2)
254 | cr:scale(aspect, aspect)
255 | cr:set_source_surface(icon)
256 | cr:paint_with_alpha(has_layout and 0.35 or 1)
257 | end
258 | return img
259 | end
260 |
261 | local function change_tag(s,direction,is_swap)
262 | local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen
263 | if not is_swap then
264 | awful.tag[direction == "left" and "viewprev" or "viewnext"](s)
265 | else
266 | -- Move the tag
267 | local t = capi.screen[s].selected_tag
268 | local cur_idx,count = awful.tag.getidx(t),#capi.screen[s].tags
269 | cur_idx = cur_idx + (direction == "left" and -1 or 1)
270 | cur_idx = cur_idx == 0 and count or cur_idx > count and 1 or cur_idx
271 | t.index = cur_idx
272 | end
273 | local tags = capi.screen[s].tags
274 | local fk = awful.util.table.hasitem(tags,capi.screen[s].selected_tag)
275 | draw_shape(s,tags,fk,tag_icon,capi.screen[s].workarea.y + 15,20)
276 | end
277 |
278 | function module.display_tags(s,direction,c,is_swap,is_max)
279 | if not w then
280 | init()
281 | end
282 | change_tag(s,direction,is_swap)
283 | end
284 |
285 | function module.change_tag(mod,key,event,direction,is_swap,is_max)
286 | local s = capi.client.focus and capi.client.focus.screen or capi.mouse.screen
287 | change_tag(s,direction,is_swap)
288 | return true
289 | end
290 |
291 | return module
292 | -- kate: space-indent on; indent-width 2; replace-tabs on;
293 |
--------------------------------------------------------------------------------
/focus.lua:
--------------------------------------------------------------------------------
1 | local capi = { client = client , mouse = mouse ,
2 | screen = screen , keygrabber = keygrabber,}
3 |
4 | local setmetatable = setmetatable
5 | local ipairs = ipairs
6 | local surface = require( "gears.surface" )
7 | local shape = require( "gears.shape" )
8 | local util = require( "awful.util" )
9 | local client = require( "awful.client" )
10 | local tag = require( "awful.tag" )
11 | local alayout = require( "awful.layout" )
12 | local wibox = require( "wibox" )
13 | local cairo = require( "lgi" ).cairo
14 | local beautiful = require( "beautiful" )
15 | local color = require( "gears.color" )
16 | local col_utils = require( "collision.util" )
17 | local grect = require( "gears.geometry" ).rectangle
18 | local placement = require( "awful.placement")
19 |
20 | local module = {}
21 | local wiboxes,delta = nil,100
22 | local edge = nil
23 |
24 | ---------------- Visual -----------------------
25 | local function init()
26 | wiboxes = {}
27 |
28 | local s = beautiful.collision_focus_shape or shape.rounded_rect
29 | local bw = beautiful.collision_focus_border_width
30 | local bc = beautiful.collision_focus_border_color
31 | local padding = beautiful.collision_focus_padding or 7
32 | local bg = beautiful.collision_focus_bg or beautiful.bg_alternate or "#ff0000"
33 | local fg = beautiful.collision_focus_fg or beautiful.fg_normal or "#0000ff"
34 | local bg_focus = beautiful.collision_focus_bg_center or beautiful.bg_urgent or "#ff0000"
35 | local sw = beautiful.collision_shape_width or 75
36 | local sh = beautiful.collision_shape_height or 75
37 | local arrow_bc = beautiful.collision_focus_arrow_border_color
38 | local arrow_bw = beautiful.collision_focus_arrow_border_width or 0
39 | local cshape = beautiful.collision_focus_shape_center or shape.circle
40 |
41 | for k,v in ipairs({"up","right","down","left","center"}) do
42 | wiboxes[v] = wibox {
43 | height = sh,
44 | width = sw,
45 | ontop = true
46 | }
47 |
48 | local r_shape = v == "center" and cshape or s
49 | local r_bg = v == "center" and bg_focus or bg
50 | local x = (sw - arrow_bw*2)/2 - padding
51 | local y = (sh - arrow_bw*2)/2 - padding
52 |
53 | wiboxes[v]:setup {
54 | v ~= "center" and {
55 | {
56 | {
57 | widget = wibox.widget.imagebox
58 | },
59 | bg = fg,
60 | border_color = arrow_bc,
61 | border_width = arrow_bw,
62 | widget = wibox.container.background,
63 | shape = shape.transform(col_utils.arrow_path2)
64 | : rotate_at(x, y, (k-1)*(2*math.pi)/4),
65 | },
66 | margins = padding,
67 | widget = wibox.container.margin,
68 | } or {
69 | widget = wibox.widget.imagebox
70 | },
71 | bg = r_bg,
72 | shape = r_shape,
73 | shape_border_width = bw,
74 | shape_border_color = bc,
75 | widget = wibox.container.background
76 | }
77 |
78 | if awesome.version >= "v4.1" then
79 | wiboxes[v].shape = r_shape
80 | else
81 | surface.apply_shape_bounding(wiboxes[v], r_shape)
82 | end
83 | end
84 | end
85 |
86 | local function emulate_client(screen)
87 | return {is_screen = true, screen=screen, geometry=function() return capi.screen[screen].workarea end}
88 | end
89 |
90 | local function display_wiboxes(cltbl,geomtbl,float,swap,c)
91 | if not wiboxes then
92 | init()
93 | end
94 | local fc = capi.client.focus or emulate_client(capi.mouse.screen)
95 | for k,v in ipairs({"left","right","up","down","center"}) do
96 | local next_clients = (float and swap) and c or cltbl[grect.get_in_direction(v , geomtbl, fc:geometry())]
97 | if next_clients or k==5 then
98 | local parent = k==5 and fc or next_clients
99 | wiboxes[v].visible = true
100 | placement.centered(wiboxes[v], {parent = parent})
101 | else
102 | wiboxes[v].visible = false
103 | end
104 | end
105 | end
106 |
107 | ---------------- Position -----------------------
108 | local function float_move(dir,c)
109 | return ({left={x=c:geometry().x-delta},right={x=c:geometry().x+delta},up={y=c:geometry().y-delta},down={y=c:geometry().y+delta}})[dir]
110 | end
111 |
112 | local function float_move_max(dir,c)
113 | return ({left={x=capi.screen[c.screen].workarea.x},right={x=capi.screen[c.screen].workarea.width+capi.screen[c.screen].workarea.x-c:geometry().width}
114 | ,up={y=capi.screen[c.screen].workarea.y},down={y=capi.screen[c.screen].workarea.y+capi.screen[c.screen].workarea.height-c:geometry().height}})[dir]
115 | end
116 |
117 | local function floating_clients()
118 | local ret = {}
119 | for v in util.table.iterate(client.visible(),function(c) return c.floating end) do
120 | ret[#ret+1] = v
121 | end
122 | return ret
123 | end
124 |
125 | local function bydirection(dir, c, swap,max)
126 | if not c then
127 | c = emulate_client(capi.mouse.screen)
128 | end
129 |
130 | local float = nil
131 |
132 | if c.is_screen then
133 | float = false
134 | else
135 | float = (c.floating or alayout.get(c.screen) == alayout.suit.floating)
136 | end
137 |
138 | -- Move the client if floating, swaping wont work anyway
139 | if swap and float then
140 | c:geometry((max and float_move_max or float_move)(dir,c))
141 | display_wiboxes(nil,nil,float,swap,c)
142 | else
143 |
144 | if not edge then
145 | local scrs =col_utils.get_ordered_screens()
146 | local last_geo =capi.screen[scrs[#scrs]].geometry
147 | edge = last_geo.x + last_geo.width
148 | end
149 |
150 | -- Get all clients rectangle
151 | local cltbl,geomtbl,scrs,roundr,roundl = max and floating_clients() or client.tiled(),{},{},{},{}
152 | for i,cl in ipairs(cltbl) do
153 | local geo = cl:geometry()
154 | geomtbl[i] = geo
155 | scrs[capi.screen[cl.screen or 1]] = true
156 | if geo.x == 0 then
157 | roundr[#roundr+1] = cl
158 | elseif geo.x + geo.width >= edge -2 then
159 | roundl[#roundl+1] = cl
160 | end
161 | end
162 |
163 | --Add first client at the end to be able to rotate selection
164 | for k,c in ipairs(roundr) do
165 | local geo = c:geometry()
166 | geomtbl[#geomtbl+1] = {x=edge,width=geo.width,y=geo.y,height=geo.height}
167 | cltbl[#geomtbl] = c
168 | end
169 | for k,c in ipairs(roundl) do
170 | local geo = c:geometry()
171 | geomtbl[#geomtbl+1] = {x=-geo.width,width=geo.width,y=geo.y,height=geo.height}
172 | cltbl[#geomtbl] = c
173 | end
174 |
175 | -- Add rectangles for empty screens too
176 | for i = 1, capi.screen.count() do
177 | if not scrs[capi.screen[i]] then
178 | geomtbl[#geomtbl+1] = capi.screen[i].workarea
179 | cltbl[#geomtbl] = emulate_client(i)
180 | end
181 | end
182 |
183 | local target = grect.get_in_direction(dir, geomtbl, c:geometry())
184 | if swap ~= true then
185 | -- If we found a client to focus, then do it.
186 | if target then
187 | local cl = cltbl[target]
188 | if cl and cl.is_screen then
189 | capi.client.focus = nil --TODO Fix upstream fix
190 | capi.mouse.screen = capi.screen[cl.screen]
191 | else
192 | local old_src = capi.client.focus and capi.client.focus.screen
193 | capi.client.focus = cltbl[((not cl and #cltbl == 1) and 1 or target)]
194 | capi.client.focus:raise()
195 | if not old_src or capi.client.focus.screen ~= capi.screen[old_src] then
196 | capi.mouse.coords(capi.client.focus:geometry())
197 | end
198 | end
199 | end
200 | else
201 | if target then
202 | -- We found a client to swap
203 | local other = cltbl[((not cltbl[target] and #cltbl == 1) and 1 or target)]
204 | if capi.screen[other.screen] == capi.screen[c.screen] or col_utils.settings.swap_across_screen then
205 | --BUG swap doesn't work if the screen is not the same
206 | c:swap(other)
207 | else
208 | local t = capi.screen[other.screen].selected_tag --TODO get index
209 | c.screen = capi.screen[ other.screen]
210 | c:tags({t})
211 | end
212 | else
213 | -- No client to swap, try to find a screen.
214 | local screen_geom = {}
215 | for i = 1, capi.screen.count() do
216 | screen_geom[i] = capi.screen[i].workarea
217 | end
218 | target = grect.get_in_direction(dir, screen_geom, c:geometry())
219 | if target and target ~= c.screen then
220 | local t = capi.screen[target].selected_tag
221 | c.screen = target
222 | c:tags({t})
223 | c:raise()
224 | end
225 | end
226 | if target then
227 | -- Geometries have changed by swapping, so refresh.
228 | cltbl,geomtbl = max and floating_clients() or client.tiled(),{}
229 | for i,cl in ipairs(cltbl) do
230 | geomtbl[i] = cl:geometry()
231 | end
232 | end
233 | end
234 | display_wiboxes(cltbl,geomtbl,float,swap,c)
235 | end
236 | end
237 |
238 | function module.global_bydirection(dir, c,swap,max)
239 | bydirection(dir, c or capi.client.focus, swap,max)
240 | end
241 |
242 | function module._global_bydirection_key(mod,key,event,direction,is_swap,is_max)
243 | bydirection(direction,capi.client.focus,is_swap,is_max)
244 | return true
245 | end
246 |
247 | function module.display(mod,key,event,direction,is_swap,is_max)
248 | local c = capi.client.focus
249 | local cltbl,geomtbl = max and floating_clients() or client.tiled(),{}
250 | for i,cl in ipairs(cltbl) do
251 | geomtbl[i] = cl:geometry()
252 | end
253 |
254 | -- Sometime, there is no focussed clients
255 | if not c then
256 | c = geomtbl[1] or cltbl[1]
257 | end
258 |
259 | -- If there is still no accessible clients, there is nothing to display
260 | if not c then return end
261 |
262 | display_wiboxes(cltbl,geomtbl,c.floating or alayout.get(c.screen) == alayout.suit.floating,is_swap,c)
263 | end
264 |
265 | function module._quit()
266 | if not wiboxes then return end
267 | for k,v in ipairs({"left","right","up","down","center"}) do
268 | wiboxes[v].visible = false
269 | end
270 | end
271 |
272 | return setmetatable(module, { __call = function(_, ...) return new(...) end })
273 | -- kate: space-indent on; indent-width 4; replace-tabs on;
274 |
--------------------------------------------------------------------------------
/screen.lua:
--------------------------------------------------------------------------------
1 | local capi = {screen=screen,client=client,mouse=mouse, keygrabber = keygrabber}
2 | local math = math
3 | local wibox = require( "wibox" )
4 | local awful = require( "awful" )
5 | local beautiful = require( "beautiful" )
6 | local surface = require( "gears.surface" )
7 | local pango = require( "lgi" ).Pango
8 | local pangocairo = require( "lgi" ).PangoCairo
9 | local mouse = require( "collision.mouse" )
10 | local util = require( "collision.util" )
11 | local shape = require( "gears.shape" )
12 | local scale = pango.SCALE
13 |
14 | local module = {}
15 |
16 | local wiboxes = {}
17 | local wiboxes_s = setmetatable({},{__mode="k"})
18 | local bgs = {}
19 | local size = 75
20 | local pss = capi.mouse.screen
21 |
22 | -- Keep an index of the last selection client for each screen
23 | local last_clients = setmetatable({},{__mode="kv"})
24 | local last_clients_coords = {}
25 |
26 | local screens,screens_inv = util.get_ordered_screens()
27 |
28 | local function current_screen(focus)
29 | return (not focus) and capi.mouse.screen or (capi.client.focus and capi.client.focus.screen or capi.mouse.screen)
30 | end
31 |
32 | local function fit(self,_,width,height)
33 | -- Compute the optimal font size
34 | local padding = self._private.padding or 10
35 | local tm = self._private.top_margin or 0
36 | height = math.max(0, height - tm)
37 | local min = math.min(width, height)
38 | local font_size = math.max(0, min - 2*padding)
39 |
40 | self._private.desc:set_size( font_size * scale )
41 | self._private.layout:set_font_description(self._private.desc)
42 |
43 | local _, geo = self._private.layout:get_pixel_extents()
44 |
45 | -- The extra "min" is in case a font "lie" and is too big.
46 | return math.min(geo.x+geo.width, min), math.min(geo.y+geo.height, min) + tm
47 | end
48 |
49 | local function draw(self, _, cr, width, height)
50 | -- Some layouts never call fit()...
51 | fit(self, context, width, height)
52 |
53 | local _, geo = self._private.layout:get_pixel_extents()
54 | local tm = self._private.top_margin or 0
55 | local sz = math.min(width, height - tm)
56 |
57 | -- Translate the canvas to center the text
58 | local dy = ( sz - geo.height )/2 - geo.y + tm
59 | local dx = ( sz - geo.width )/2 - geo.x
60 | cr:move_to(dx, dy)
61 |
62 | -- Show the text
63 | cr:show_layout(self._private.layout)
64 | end
65 |
66 | local function set_screen(self, s)
67 | self._private.layout.text = tostring(s.index)
68 | self:emit_signal("widget::layout_changed")
69 | end
70 |
71 | local function set_padding(self, padding)
72 | self._private.padding = padding
73 | self:emit_signal("widget::layout_changed")
74 | end
75 |
76 | local function set_top_margin(margin)
77 | self._private.top_margin = margin or 0
78 | self:emit_signal("widget::layout_changed")
79 | end
80 |
81 | local function new_constrained_text(s)
82 | local wdg = wibox.widget.base.empty_widget()
83 |
84 | -- Add the basic functions
85 | rawset(wdg, "draw" , draw )
86 | rawset(wdg, "fit" , fit )
87 | rawset(wdg, "set_screen" , set_screen )
88 | rawset(wdg, "set_padding" , set_padding )
89 | rawset(wdg, "set_top_margin" , set_top_margin )
90 |
91 | -- Create the pango objects
92 | local pango_crx = pangocairo.font_map_get_default():create_context()
93 | local pango_l = pango.Layout.new(pango_crx)
94 | local desc = pango.FontDescription()
95 | desc:set_family( "Verdana" )
96 | desc:set_weight( pango.Weight.BOLD )
97 |
98 | wdg._private.layout = pango_l
99 | wdg._private.desc = desc
100 | wdg:emit_signal("widget::layout_changed")
101 |
102 | if s then set_screen(wdg, s) end
103 |
104 | return wdg
105 | end
106 |
107 | local function create_wibox(s)
108 | s = capi.screen[s]
109 |
110 | if wiboxes_s[s] then return wiboxes_s[s] end
111 |
112 | local wa = s.workarea
113 |
114 | -- Create a round wibox
115 | local w = wibox {
116 | width = size,
117 | height = size,
118 | x = math.floor(wa.x+wa.width /2-size/2),
119 | y = math.floor(wa.y+wa.height/2-size/2),
120 | ontop = true
121 | }
122 |
123 | -- Theme options
124 | local sh = beautiful.collision_screen_shape or shape.circle
125 | local bw = beautiful.collision_screen_border_width
126 | local bc = beautiful.collision_screen_border_color
127 | local padding = beautiful.collision_screen_padding or 10
128 | local bg = beautiful.collision_screen_bg or beautiful.bg_alternate or "#ff0000"
129 | local fg = beautiful.collision_screen_fg or beautiful.fg_normal or "#0000ff"
130 | local bg_focus = beautiful.collision_screen_bg_focus or beautiful.bg_urgent or "#ff0000"
131 | local fg_focus = beautiful.collision_screen_fg_focus or beautiful.fg_urgent or "#ff0000"
132 |
133 | -- Setup the widgets
134 | w:setup {
135 | {
136 | nil,
137 | {
138 | nil,
139 | {
140 | screen = s,
141 | padding = padding,
142 | widget = new_constrained_text
143 | },
144 | nil,
145 | layout = wibox.layout.align.vertical
146 | },
147 | nil,
148 | layout = wibox.layout.align.horizontal
149 | },
150 | bg = bg,
151 | shape = sh or shape.circle,
152 | shape_border_width = bw,
153 | shape_border_color = bc,
154 | id = "main_background",
155 | widget = wibox.container.background
156 | }
157 |
158 | -- Set the wibox shape
159 | surface.apply_shape_bounding(w, sh)
160 |
161 | wiboxes_s[s] = w
162 | wiboxes[s.index] = w --DEPRECATED
163 | bgs[s] = w:get_children_by_id("main_background")[1]
164 |
165 | return w
166 | end
167 |
168 | -- Hopefully, the wiboxes will be gargabe collected
169 | local init = false
170 | local function init_wiboxes()
171 | if init then return end
172 |
173 | awful.screen.connect_for_each_screen(function(s)
174 | create_wibox(s)
175 | end)
176 |
177 | init = true
178 |
179 | return true
180 | end
181 |
182 | local function select_screen(scr_index,move,old_screen)
183 | if capi.screen[scr_index] ~= capi.screen[old_screen or 1] then
184 | local c = last_clients[capi.screen[scr_index]]
185 |
186 | -- If the client is leaked elsewhere, prevent an error message
187 | if c and not pcall(function() return c.valid end) and not c.valid then
188 | last_clients[capi.screen[scr_index]] = nil
189 | c = nil
190 | end
191 |
192 | if c and c.valid and c:isvisible() then
193 | local geom = c:geometry()
194 | if last_clients_coords[scr_index] and last_clients_coords[scr_index].client == c then
195 | capi.mouse.coords(last_clients_coords[scr_index])
196 | else
197 | capi.mouse.coords({x=geom.x+geom.width/2,y=geom.y+geom.height/2+55})
198 | end
199 | else
200 | local geom = capi.screen[scr_index].geometry
201 | capi.mouse.coords({x=geom.x+geom.width/2,y=geom.y+geom.height/2+55})
202 | end
203 | mouse.highlight()
204 | end
205 |
206 | if move then
207 | local t = capi.screen[scr_index].selected_tag
208 | if t then
209 | t.screen = old_screen
210 | awful.tag.viewonly(t)
211 | end
212 | else
213 | local c = capi.mouse.current_client
214 | if c then
215 | capi.client.focus = c
216 | end
217 | end
218 |
219 | return capi.screen[scr_index]
220 | end
221 |
222 | local function in_rect(c,point)
223 | if not c then return true end
224 | local geo = c:geometry()
225 | return (
226 | geo.x < point.x and geo.y < point.y and
227 | geo.x + geo.width > point.x and geo.y + geo.height > point.y
228 | )
229 | end
230 |
231 | local function save_cursor_position()
232 | local coords = capi.mouse.coords()
233 | local c = capi.client.focus
234 | -- Be sure that that mouse in inside of the selected client before doing that
235 | if c and in_rect(c,coords) then
236 | last_clients_coords[c.screen] = {
237 | client = c,
238 | x = coords.x,
239 | y = coords.y,
240 | }
241 | else
242 | last_clients_coords[capi.mouse.screen] = nil
243 | end
244 | end
245 |
246 | local function next_screen(ss,dir,move)
247 | if capi.screen.count() == 1 then return 1 end
248 |
249 | local scr_index = capi.screen[screens_inv[ss]].index
250 |
251 | if type(scr_index) == "screen" then
252 | scr_index = scr_index.index
253 | end
254 | if dir == "left" then
255 | scr_index = scr_index == 1 and #screens or scr_index - 1
256 | elseif dir == "right" then
257 | scr_index = scr_index == #screens and 1 or scr_index+1
258 | end
259 |
260 | return select_screen(screens_inv[capi.screen[scr_index]],move,ss)
261 | end
262 |
263 | function module.display(_,dir,move)
264 | if #wiboxes == 0 then
265 | init_wiboxes()
266 | end
267 | save_cursor_position()
268 | module.reload(nil,dir)
269 | local ss = current_screen(move)
270 | next_screen(ss,dir,move)
271 | module.reload(nil,dir)
272 | end
273 |
274 | local function highlight_screen(ss)
275 | ss = capi.screen[ss]
276 | if pss ~= ss then
277 |
278 | local bg = beautiful.collision_screen_bg or beautiful.bg_alternate or "#ff0000"
279 | local fg = beautiful.collision_screen_fg or beautiful.fg_normal or "#0000ff"
280 | local bg_focus = beautiful.collision_screen_bg_focus or beautiful.bg_urgent or "#ff0000"
281 | local fg_focus = beautiful.collision_screen_fg_focus or beautiful.fg_urgent or "#ff0000"
282 |
283 | if pss then
284 | bgs[pss].bg = bg
285 | bgs[pss].fg = fg
286 | end
287 |
288 | pss = ss
289 |
290 | bgs[ss].bg = bg_focus
291 | bgs[ss].fg = fg_focus
292 | end
293 | end
294 |
295 | function module.hide()
296 | if #wiboxes == 0 then return end
297 |
298 | for s=1, capi.screen.count() do
299 | wiboxes[s].visible = false
300 | end
301 | mouse.hide()
302 | end
303 |
304 | local function show()
305 | for s=1, capi.screen.count() do
306 | wiboxes[s].visible = true
307 | end
308 | end
309 |
310 | function module.reload(mod,dir,_,_,move)
311 | local ss = current_screen(move)
312 | if dir then
313 | ss = next_screen(ss,dir:lower(),move or (mod and #mod == 4))
314 | end
315 |
316 | highlight_screen(ss)
317 |
318 | show()
319 |
320 | return true
321 | end
322 |
323 | function module.select_screen(idx)
324 | save_cursor_position()
325 | select_screen(screens_inv[idx],false)
326 | if #wiboxes == 0 then
327 | init_wiboxes()
328 | end
329 |
330 | highlight_screen(screens_inv[idx])
331 |
332 | show()
333 |
334 | capi.keygrabber.run(function(_, _, event)
335 | if event == "release" then
336 | module.hide()
337 | mouse.hide()
338 | capi.keygrabber.stop()
339 | return false
340 | end
341 | return true
342 | end)
343 | end
344 |
345 | -- Make sure this keeps working when a new screen is added
346 | awful.screen.connect_for_each_screen(function(s)
347 | if next(wiboxes) then
348 | create_wibox(s)
349 | end
350 | end)
351 |
352 | capi.client.connect_signal("focus",function(c)
353 | last_clients[c.screen] = c
354 | end)
355 |
356 | return module
357 | -- kate: space-indent on; indent-width 4; replace-tabs on;
358 |
--------------------------------------------------------------------------------