├── README.md ├── example.rc.lua └── init.lua /README.md: -------------------------------------------------------------------------------- 1 | # shifty 2 | [Shifty](https://awesome.naquadah.org/wiki/Shifty) is an Awesome 3 extension 3 | that implements dynamic tagging. 4 | 5 | It also implements fine client matching configuration allowing _you_ to be 6 | the master of _your_ desktop. 7 | 8 | Here are a few ways of how shifty makes awesome awesomer: 9 | 10 | * on-the-fly tag creation and disposal 11 | * advanced client matching 12 | * easy moving of clients between tags 13 | * tag add/rename prompt in taglist (with completion) 14 | * reordering tags and configurable positioning 15 | * tag name guessing, automagic no-config client grouping 16 | * customizable keybindings per client and tag 17 | * simple yet powerful configuration 18 | 19 | ## Use 20 | 21 | 0. Go to configuration directory, usually `~/.config/awesome` 22 | 1. Clone repository: 23 | 24 | `git clone https://bioe007@github.com/bioe007/awesome-shifty.git shifty` 25 | 26 | 2. Move the example `rc.lua` file into your configuration directory. 27 | 28 | `cp shifty/example.rc.lua rc.lua` 29 | 30 | 3. Restart awesome and enjoy. 31 | 32 | There are many configuration options for shifty, the `example.rc.lua` is 33 | provided merely as a starting point. The most important variables are the 34 | tables: 35 | 36 | * `shifty.config.tags = {}` 37 | - Sets predefined tags, which are not necessarily initialized. 38 | * `shifty.config.apps = {}` 39 | - How to handle certain applications. 40 | * `shifty.config.defaults = {}` 41 | - Fallback values used when a preset is not found in the first two 42 | configuration tables. 43 | 44 | But for each of these there are _tons_ of shifty variables and settings, its 45 | easiest to check out the wiki page or the module itself. 46 | 47 | In the `example.rc.lua` searching for `shifty` in your editor can also help to 48 | make sense of these. 49 | 50 | ## Help 51 | Help is best found in this order: 52 | 53 | 1. Web search, e.g. [Google](http://www.google.com) is your friend... 54 | 2. `#awesome` on irc.oftc.net is good for immediate aid, especially with 55 | configuration questions and such. 56 | 3. The [awesome users mailing list](mailto:awesome@naquadah.org) 57 | 4. Messaging through github 58 | 5. Directly e-mailing the [author](mailto:resixian@gmail.com) 59 | - _Please_ use this as a last resort, not that I mind, but the other formats 60 | allow others to benefit as well. 61 | 62 | ## Development 63 | Report bugs at the [github 64 | repo](https://github.com/bioe007/awesome-shifty/issues). Please include at least 65 | the current versions of awesome and shifty, as well as distribution. 66 | 67 | ## Credits 68 | * [Perry Hargrave](mailto:resixian@gmail.com) 69 | - Current maintainer and point of contact. 70 | * [koniu](mailto:gkusnierz@gmail.com) 71 | - Original author 72 | 73 | ## License 74 | Current awesome wm license or if thats not defined, GPLv2. 75 | -------------------------------------------------------------------------------- /example.rc.lua: -------------------------------------------------------------------------------- 1 | -- default rc.lua for shifty 2 | -- 3 | -- Standard awesome library 4 | require("awful") 5 | require("awful.autofocus") 6 | -- Theme handling library 7 | require("beautiful") 8 | -- Notification library 9 | require("naughty") 10 | -- shifty - dynamic tagging library 11 | require("shifty") 12 | 13 | -- useful for debugging, marks the beginning of rc.lua exec 14 | print("Entered rc.lua: " .. os.time()) 15 | 16 | -- Variable definitions 17 | -- Themes define colours, icons, and wallpapers 18 | -- The default is a dark theme 19 | theme_path = "/usr/share/awesome/themes/default/theme.lua" 20 | -- Uncommment this for a lighter theme 21 | -- theme_path = "/usr/share/awesome/themes/sky/theme" 22 | 23 | -- Actually load theme 24 | beautiful.init(theme_path) 25 | 26 | -- This is used later as the default terminal and editor to run. 27 | browser = "firefox" 28 | mail = "thunderbird" 29 | terminal = "xterm" 30 | editor = os.getenv("EDITOR") or "nano" 31 | editor_cmd = terminal .. " -e " .. editor 32 | 33 | -- Default modkey. 34 | -- Usually, Mod4 is the key with a logo between Control and Alt. 35 | -- If you do not like this or do not have such a key, I suggest you to remap 36 | -- Mod4 to another key using xmodmap or other tools. However, you can use 37 | -- another modifier like Mod1, but it may interact with others. 38 | modkey = "Mod4" 39 | 40 | -- Table of layouts to cover with awful.layout.inc, order matters. 41 | layouts = 42 | { 43 | awful.layout.suit.tile, 44 | awful.layout.suit.tile.left, 45 | awful.layout.suit.tile.bottom, 46 | awful.layout.suit.tile.top, 47 | awful.layout.suit.fair, 48 | awful.layout.suit.fair.horizontal, 49 | awful.layout.suit.max, 50 | awful.layout.suit.max.fullscreen, 51 | awful.layout.suit.magnifier, 52 | awful.layout.suit.floating 53 | } 54 | 55 | -- Define if we want to use titlebar on all applications. 56 | use_titlebar = false 57 | 58 | -- Shifty configured tags. 59 | shifty.config.tags = { 60 | w1 = { 61 | layout = awful.layout.suit.max, 62 | mwfact = 0.60, 63 | exclusive = false, 64 | position = 1, 65 | init = true, 66 | screen = 1, 67 | slave = true, 68 | }, 69 | web = { 70 | layout = awful.layout.suit.tile.bottom, 71 | mwfact = 0.65, 72 | exclusive = true, 73 | max_clients = 1, 74 | position = 4, 75 | spawn = browser, 76 | }, 77 | mail = { 78 | layout = awful.layout.suit.tile, 79 | mwfact = 0.55, 80 | exclusive = false, 81 | position = 5, 82 | spawn = mail, 83 | slave = true 84 | }, 85 | media = { 86 | layout = awful.layout.suit.float, 87 | exclusive = false, 88 | position = 8, 89 | }, 90 | office = { 91 | layout = awful.layout.suit.tile, 92 | position = 9, 93 | }, 94 | } 95 | 96 | -- SHIFTY: application matching rules 97 | -- order here matters, early rules will be applied first 98 | shifty.config.apps = { 99 | { 100 | match = { 101 | "Navigator", 102 | "Vimperator", 103 | "Gran Paradiso", 104 | }, 105 | tag = "web", 106 | }, 107 | { 108 | match = { 109 | "Shredder.*", 110 | "Thunderbird", 111 | "mutt", 112 | }, 113 | tag = "mail", 114 | }, 115 | { 116 | match = { 117 | "pcmanfm", 118 | }, 119 | slave = true 120 | }, 121 | { 122 | match = { 123 | "OpenOffice.*", 124 | "Abiword", 125 | "Gnumeric", 126 | }, 127 | tag = "office", 128 | }, 129 | { 130 | match = { 131 | "Mplayer.*", 132 | "Mirage", 133 | "gimp", 134 | "gtkpod", 135 | "Ufraw", 136 | "easytag", 137 | }, 138 | tag = "media", 139 | nopopup = true, 140 | }, 141 | { 142 | match = { 143 | "MPlayer", 144 | "Gnuplot", 145 | "galculator", 146 | }, 147 | float = true, 148 | }, 149 | { 150 | match = { 151 | terminal, 152 | }, 153 | honorsizehints = false, 154 | slave = true, 155 | }, 156 | { 157 | match = {""}, 158 | buttons = awful.util.table.join( 159 | awful.button({}, 1, function (c) client.focus = c; c:raise() end), 160 | awful.button({modkey}, 1, function(c) 161 | client.focus = c 162 | c:raise() 163 | awful.mouse.client.move(c) 164 | end), 165 | awful.button({modkey}, 3, awful.mouse.client.resize) 166 | ) 167 | }, 168 | } 169 | 170 | -- SHIFTY: default tag creation rules 171 | -- parameter description 172 | -- * floatBars : if floating clients should always have a titlebar 173 | -- * guess_name : should shifty try and guess tag names when creating 174 | -- new (unconfigured) tags? 175 | -- * guess_position: as above, but for position parameter 176 | -- * run : function to exec when shifty creates a new tag 177 | -- * all other parameters (e.g. layout, mwfact) follow awesome's tag API 178 | shifty.config.defaults = { 179 | layout = awful.layout.suit.tile.bottom, 180 | ncol = 1, 181 | mwfact = 0.60, 182 | floatBars = true, 183 | guess_name = true, 184 | guess_position = true, 185 | } 186 | 187 | -- Wibox 188 | -- Create a textbox widget 189 | mytextclock = awful.widget.textclock({align = "right"}) 190 | 191 | -- Create a laucher widget and a main menu 192 | myawesomemenu = { 193 | {"manual", terminal .. " -e man awesome"}, 194 | {"edit config", 195 | editor_cmd .. " " .. awful.util.getdir("config") .. "/rc.lua"}, 196 | {"restart", awesome.restart}, 197 | {"quit", awesome.quit} 198 | } 199 | 200 | mymainmenu = awful.menu( 201 | { 202 | items = { 203 | {"awesome", myawesomemenu, beautiful.awesome_icon}, 204 | {"open terminal", terminal}} 205 | }) 206 | 207 | mylauncher = awful.widget.launcher({image = image(beautiful.awesome_icon), 208 | menu = mymainmenu}) 209 | 210 | -- Create a systray 211 | mysystray = widget({type = "systray", align = "right"}) 212 | 213 | -- Create a wibox for each screen and add it 214 | mywibox = {} 215 | mypromptbox = {} 216 | mylayoutbox = {} 217 | mytaglist = {} 218 | mytaglist.buttons = awful.util.table.join( 219 | awful.button({}, 1, awful.tag.viewonly), 220 | awful.button({modkey}, 1, awful.client.movetotag), 221 | awful.button({}, 3, function(tag) tag.selected = not tag.selected end), 222 | awful.button({modkey}, 3, awful.client.toggletag), 223 | awful.button({}, 4, awful.tag.viewnext), 224 | awful.button({}, 5, awful.tag.viewprev) 225 | ) 226 | 227 | mytasklist = {} 228 | mytasklist.buttons = awful.util.table.join( 229 | awful.button({}, 1, function(c) 230 | if not c:isvisible() then 231 | awful.tag.viewonly(c:tags()[1]) 232 | end 233 | client.focus = c 234 | c:raise() 235 | end), 236 | awful.button({}, 3, function() 237 | if instance then 238 | instance:hide() 239 | instance = nil 240 | else 241 | instance = awful.menu.clients({width=250}) 242 | end 243 | end), 244 | awful.button({}, 4, function() 245 | awful.client.focus.byidx(1) 246 | if client.focus then client.focus:raise() end 247 | end), 248 | awful.button({}, 5, function() 249 | awful.client.focus.byidx(-1) 250 | if client.focus then client.focus:raise() end 251 | end)) 252 | 253 | for s = 1, screen.count() do 254 | -- Create a promptbox for each screen 255 | mypromptbox[s] = 256 | awful.widget.prompt({layout = awful.widget.layout.leftright}) 257 | -- Create an imagebox widget which will contains an icon indicating which 258 | -- layout we're using. We need one layoutbox per screen. 259 | mylayoutbox[s] = awful.widget.layoutbox(s) 260 | mylayoutbox[s]:buttons(awful.util.table.join( 261 | awful.button({}, 1, function() awful.layout.inc(layouts, 1) end), 262 | awful.button({}, 3, function() awful.layout.inc(layouts, -1) end), 263 | awful.button({}, 4, function() awful.layout.inc(layouts, 1) end), 264 | awful.button({}, 5, function() awful.layout.inc(layouts, -1) end))) 265 | -- Create a taglist widget 266 | mytaglist[s] = awful.widget.taglist.new(s, 267 | awful.widget.taglist.label.all, 268 | mytaglist.buttons) 269 | 270 | -- Create a tasklist widget 271 | mytasklist[s] = awful.widget.tasklist.new(function(c) 272 | return awful.widget.tasklist.label.currenttags(c, s) 273 | end, 274 | mytasklist.buttons) 275 | 276 | -- Create the wibox 277 | mywibox[s] = awful.wibox({position = "top", screen = s}) 278 | -- Add widgets to the wibox - order matters 279 | mywibox[s].widgets = { 280 | { 281 | mylauncher, 282 | mytaglist[s], 283 | mypromptbox[s], 284 | layout = awful.widget.layout.horizontal.leftright 285 | }, 286 | mylayoutbox[s], 287 | mytextclock, 288 | s == 1 and mysystray or nil, 289 | mytasklist[s], 290 | layout = awful.widget.layout.horizontal.rightleft 291 | } 292 | 293 | mywibox[s].screen = s 294 | end 295 | 296 | -- SHIFTY: initialize shifty 297 | -- the assignment of shifty.taglist must always be after its actually 298 | -- initialized with awful.widget.taglist.new() 299 | shifty.taglist = mytaglist 300 | shifty.init() 301 | 302 | -- Mouse bindings 303 | root.buttons(awful.util.table.join( 304 | awful.button({}, 3, function() mymainmenu:show({keygrabber=true}) end), 305 | awful.button({}, 4, awful.tag.viewnext), 306 | awful.button({}, 5, awful.tag.viewprev) 307 | )) 308 | 309 | -- Key bindings 310 | globalkeys = awful.util.table.join( 311 | -- Tags 312 | awful.key({modkey,}, "Left", awful.tag.viewprev), 313 | awful.key({modkey,}, "Right", awful.tag.viewnext), 314 | awful.key({modkey,}, "Escape", awful.tag.history.restore), 315 | 316 | -- Shifty: keybindings specific to shifty 317 | awful.key({modkey, "Shift"}, "d", shifty.del), -- delete a tag 318 | awful.key({modkey, "Shift"}, "n", shifty.send_prev), -- client to prev tag 319 | awful.key({modkey}, "n", shifty.send_next), -- client to next tag 320 | awful.key({modkey, "Control"}, 321 | "n", 322 | function() 323 | local t = awful.tag.selected() 324 | local s = awful.util.cycle(screen.count(), t.screen + 1) 325 | awful.tag.history.restore() 326 | t = shifty.tagtoscr(s, t) 327 | awful.tag.viewonly(t) 328 | end), 329 | awful.key({modkey}, "a", shifty.add), -- creat a new tag 330 | awful.key({modkey,}, "r", shifty.rename), -- rename a tag 331 | awful.key({modkey, "Shift"}, "a", -- nopopup new tag 332 | function() 333 | shifty.add({nopopup = true}) 334 | end), 335 | 336 | awful.key({modkey,}, "j", 337 | function() 338 | awful.client.focus.byidx(1) 339 | if client.focus then client.focus:raise() end 340 | end), 341 | awful.key({modkey,}, "k", 342 | function() 343 | awful.client.focus.byidx(-1) 344 | if client.focus then client.focus:raise() end 345 | end), 346 | awful.key({modkey,}, "w", function() mymainmenu:show(true) end), 347 | 348 | -- Layout manipulation 349 | awful.key({modkey, "Shift"}, "j", 350 | function() awful.client.swap.byidx(1) end), 351 | awful.key({modkey, "Shift"}, "k", 352 | function() awful.client.swap.byidx(-1) end), 353 | awful.key({modkey, "Control"}, "j", function() awful.screen.focus(1) end), 354 | awful.key({modkey, "Control"}, "k", function() awful.screen.focus(-1) end), 355 | awful.key({modkey,}, "u", awful.client.urgent.jumpto), 356 | awful.key({modkey,}, "Tab", 357 | function() 358 | awful.client.focus.history.previous() 359 | if client.focus then 360 | client.focus:raise() 361 | end 362 | end), 363 | 364 | -- Standard program 365 | awful.key({modkey,}, "Return", function() awful.util.spawn(terminal) end), 366 | awful.key({modkey, "Control"}, "r", awesome.restart), 367 | awful.key({modkey, "Shift"}, "q", awesome.quit), 368 | 369 | awful.key({modkey,}, "l", function() awful.tag.incmwfact(0.05) end), 370 | awful.key({modkey,}, "h", function() awful.tag.incmwfact(-0.05) end), 371 | awful.key({modkey, "Shift"}, "h", function() awful.tag.incnmaster(1) end), 372 | awful.key({modkey, "Shift"}, "l", function() awful.tag.incnmaster(-1) end), 373 | awful.key({modkey, "Control"}, "h", function() awful.tag.incncol(1) end), 374 | awful.key({modkey, "Control"}, "l", function() awful.tag.incncol(-1) end), 375 | awful.key({modkey,}, "space", function() awful.layout.inc(layouts, 1) end), 376 | awful.key({modkey, "Shift"}, "space", 377 | function() awful.layout.inc(layouts, -1) end), 378 | 379 | -- Prompt 380 | awful.key({modkey}, "F1", function() 381 | awful.prompt.run({prompt = "Run: "}, 382 | mypromptbox[mouse.screen].widget, 383 | awful.util.spawn, awful.completion.shell, 384 | awful.util.getdir("cache") .. "/history") 385 | end), 386 | 387 | awful.key({modkey}, "F4", function() 388 | awful.prompt.run({prompt = "Run Lua code: "}, 389 | mypromptbox[mouse.screen].widget, 390 | awful.util.eval, nil, 391 | awful.util.getdir("cache") .. "/history_eval") 392 | end) 393 | ) 394 | 395 | -- Client awful tagging: this is useful to tag some clients and then do stuff 396 | -- like move to tag on them 397 | clientkeys = awful.util.table.join( 398 | awful.key({modkey,}, "f", function(c) c.fullscreen = not c.fullscreen end), 399 | awful.key({modkey, "Shift"}, "c", function(c) c:kill() end), 400 | awful.key({modkey, "Control"}, "space", awful.client.floating.toggle), 401 | awful.key({modkey, "Control"}, "Return", 402 | function(c) c:swap(awful.client.getmaster()) end), 403 | awful.key({modkey,}, "o", awful.client.movetoscreen), 404 | awful.key({modkey, "Shift"}, "r", function(c) c:redraw() end), 405 | awful.key({modkey}, "t", awful.client.togglemarked), 406 | awful.key({modkey,}, "m", 407 | function(c) 408 | c.maximized_horizontal = not c.maximized_horizontal 409 | c.maximized_vertical = not c.maximized_vertical 410 | end) 411 | ) 412 | 413 | -- SHIFTY: assign client keys to shifty for use in 414 | -- match() function(manage hook) 415 | shifty.config.clientkeys = clientkeys 416 | shifty.config.modkey = modkey 417 | 418 | -- Compute the maximum number of digit we need, limited to 9 419 | for i = 1, (shifty.config.maxtags or 9) do 420 | globalkeys = awful.util.table.join(globalkeys, 421 | awful.key({modkey}, i, function() 422 | local t = awful.tag.viewonly(shifty.getpos(i)) 423 | end), 424 | awful.key({modkey, "Control"}, i, function() 425 | local t = shifty.getpos(i) 426 | t.selected = not t.selected 427 | end), 428 | awful.key({modkey, "Control", "Shift"}, i, function() 429 | if client.focus then 430 | awful.client.toggletag(shifty.getpos(i)) 431 | end 432 | end), 433 | -- move clients to other tags 434 | awful.key({modkey, "Shift"}, i, function() 435 | if client.focus then 436 | -- remember the focused client because getpos() switch 437 | -- to new tag (if it doesn't exist) and the client lost focus 438 | local c = client.focus 439 | t = shifty.getpos(i) 440 | awful.client.movetotag(t, c) 441 | awful.tag.viewonly(t) 442 | end 443 | end)) 444 | end 445 | 446 | -- Set keys 447 | root.keys(globalkeys) 448 | 449 | -- Hook function to execute when focusing a client. 450 | client.add_signal("focus", function(c) 451 | if not awful.client.ismarked(c) then 452 | c.border_color = beautiful.border_focus 453 | end 454 | end) 455 | 456 | -- Hook function to execute when unfocusing a client. 457 | client.add_signal("unfocus", function(c) 458 | if not awful.client.ismarked(c) then 459 | c.border_color = beautiful.border_normal 460 | end 461 | end) 462 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | --- Shifty: Dynamic tagging library for awesome3-git 2 | -- @author koniu <gkusnierz@gmail.com> 3 | -- @author resixian (aka bioe007) <resixian@gmail.com> 4 | -- 5 | -- http://awesome.naquadah.org/wiki/index.php?title=Shifty 6 | 7 | -- environment 8 | local type = type 9 | local ipairs = ipairs 10 | local table = table 11 | local string = string 12 | local beautiful = require("beautiful") 13 | local awful = require("awful") 14 | local pairs = pairs 15 | local io = io 16 | local tonumber = tonumber 17 | local dbg= dbg 18 | local capi = { 19 | client = client, 20 | tag = tag, 21 | image = image, 22 | screen = screen, 23 | button = button, 24 | mouse = mouse, 25 | root = root, 26 | timer = timer 27 | } 28 | 29 | module("shifty") 30 | 31 | -- variables 32 | config = {} 33 | config.tags = {} 34 | config.apps = {} 35 | config.defaults = {} 36 | config.float_bars = false 37 | config.guess_name = true 38 | config.guess_position = true 39 | config.remember_index = true 40 | config.sloppy = true 41 | config.default_name = "new" 42 | config.clientkeys = {} 43 | config.globalkeys = nil 44 | config.layouts = {} 45 | config.prompt_sources = { 46 | "config_tags", 47 | "config_apps", 48 | "existing", 49 | "history" 50 | } 51 | config.prompt_matchers = { 52 | "^", 53 | ":", 54 | "" 55 | } 56 | 57 | local matchp = "" 58 | local index_cache = {} 59 | for i = 1, capi.screen.count() do index_cache[i] = {} end 60 | 61 | --name2tags: matches string 'name' to tag objects 62 | -- @param name : tag name to find 63 | -- @param scr : screen to look for tags on 64 | -- @return table of tag objects or nil 65 | function name2tags(name, scr) 66 | local ret = {} 67 | local a, b = scr or 1, scr or capi.screen.count() 68 | for s = a, b do 69 | for i, t in ipairs(capi.screen[s]:tags()) do 70 | if name == t.name then 71 | table.insert(ret, t) 72 | end 73 | end 74 | end 75 | if #ret > 0 then return ret end 76 | end 77 | 78 | function name2tag(name, scr, idx) 79 | local ts = name2tags(name, scr) 80 | if ts then return ts[idx or 1] end 81 | end 82 | 83 | --tag2index: finds index of a tag object 84 | -- @param scr : screen number to look for tag on 85 | -- @param tag : the tag object to find 86 | -- @return the index [or zero] or end of the list 87 | function tag2index(scr, tag) 88 | for i, t in ipairs(capi.screen[scr]:tags()) do 89 | if t == tag then return i end 90 | end 91 | end 92 | 93 | --rename 94 | --@param tag: tag object to be renamed 95 | --@param prefix: if any prefix is to be added 96 | --@param no_selectall: 97 | function rename(tag, prefix, no_selectall) 98 | local theme = beautiful.get() 99 | local t = tag or awful.tag.selected(capi.mouse.screen) 100 | local scr = t.screen 101 | local bg = nil 102 | local fg = nil 103 | local text = prefix or t.name 104 | local before = t.name 105 | 106 | if t == awful.tag.selected(scr) then 107 | bg = theme.bg_focus or '#535d6c' 108 | fg = theme.fg_urgent or '#ffffff' 109 | else 110 | bg = theme.bg_normal or '#222222' 111 | fg = theme.fg_urgent or '#ffffff' 112 | end 113 | 114 | awful.prompt.run({ 115 | fg_cursor = fg, bg_cursor = bg, ul_cursor = "single", 116 | text = text, selectall = not no_selectall}, 117 | taglist[scr][tag2index(scr, t) * 2], 118 | function (name) if name:len() > 0 then t.name = name; end end, 119 | completion, 120 | awful.util.getdir("cache") .. "/history_tags", 121 | nil, 122 | function () 123 | if t.name == before then 124 | if awful.tag.getproperty(t, "initial") then del(t) end 125 | else 126 | awful.tag.setproperty(t, "initial", true) 127 | set(t) 128 | end 129 | tagkeys(capi.screen[scr]) 130 | t:emit_signal("property::name") 131 | end 132 | ) 133 | end 134 | 135 | --send: moves client to tag[idx] 136 | -- maybe this isn't needed here in shifty? 137 | -- @param idx the tag number to send a client to 138 | function send(idx) 139 | local scr = capi.client.focus.screen or capi.mouse.screen 140 | local sel = awful.tag.selected(scr) 141 | local sel_idx = tag2index(scr, sel) 142 | local tags = capi.screen[scr]:tags() 143 | local target = awful.util.cycle(#tags, sel_idx + idx) 144 | awful.client.movetotag(tags[target], capi.client.focus) 145 | awful.tag.viewonly(tags[target]) 146 | end 147 | 148 | function send_next() send(1) end 149 | function send_prev() send(-1) end 150 | 151 | --pos2idx: translate shifty position to tag index 152 | --@param pos: position (an integer) 153 | --@param scr: screen number 154 | function pos2idx(pos, scr) 155 | local v = 1 156 | if pos and scr then 157 | for i = #capi.screen[scr]:tags() , 1, -1 do 158 | local t = capi.screen[scr]:tags()[i] 159 | if awful.tag.getproperty(t, "position") and 160 | awful.tag.getproperty(t, "position") <= pos then 161 | v = i + 1 162 | break 163 | end 164 | end 165 | end 166 | return v 167 | end 168 | 169 | --select : helper function chooses the first non-nil argument 170 | --@param args - table of arguments 171 | function select(args) 172 | for i, a in pairs(args) do 173 | if a ~= nil then 174 | return a 175 | end 176 | end 177 | end 178 | 179 | --tagtoscr : move an entire tag to another screen 180 | -- 181 | --@param scr : the screen to move tag to 182 | --@param t : the tag to be moved [awful.tag.selected()] 183 | --@return the tag 184 | function tagtoscr(scr, t) 185 | -- break if called with an invalid screen number 186 | if not scr or scr < 1 or scr > capi.screen.count() then return end 187 | -- tag to move 188 | local otag = t or awful.tag.selected() 189 | 190 | otag.screen = scr 191 | -- set screen and then reset tag to order properly 192 | if #otag:clients() > 0 then 193 | for _ , c in ipairs(otag:clients()) do 194 | if not c.sticky then 195 | c.screen = scr 196 | c:tags({otag}) 197 | else 198 | awful.client.toggletag(otag, c) 199 | end 200 | end 201 | end 202 | return otag 203 | end 204 | 205 | --set : set a tags properties 206 | --@param t: the tag 207 | --@param args : a table of optional (?) tag properties 208 | --@return t - the tag object 209 | function set(t, args) 210 | if not t then return end 211 | if not args then args = {} end 212 | 213 | -- set the name 214 | t.name = args.name or t.name 215 | 216 | -- attempt to load preset on initial run 217 | local preset = (awful.tag.getproperty(t, "initial") and 218 | config.tags[t.name]) or {} 219 | 220 | -- pick screen and get its tag table 221 | local scr = args.screen or 222 | (not t.screen and preset.screen) or 223 | t.screen or 224 | capi.mouse.screen 225 | 226 | local clientstomove = nil 227 | if scr > capi.screen.count() then scr = capi.screen.count() end 228 | if t.screen and scr ~= t.screen then 229 | tagtoscr(scr, t) 230 | t.screen = nil 231 | end 232 | local tags = capi.screen[scr]:tags() 233 | 234 | -- try to guess position from the name 235 | local guessed_position = nil 236 | if not (args.position or preset.position) and config.guess_position then 237 | local num = t.name:find('^[1-9]') 238 | if num then guessed_position = tonumber(t.name:sub(1, 1)) end 239 | end 240 | 241 | -- allow preset.layout to be a table to provide a different layout per 242 | -- screen for a given tag 243 | local preset_layout = preset.layout 244 | if preset_layout and preset_layout[scr] then 245 | preset_layout = preset.layout[scr] 246 | end 247 | 248 | -- select from args, preset, getproperty, 249 | -- config.defaults.configs or defaults 250 | local props = { 251 | layout = select{args.layout, preset_layout, 252 | awful.tag.getproperty(t, "layout"), 253 | config.defaults.layout, awful.layout.suit.tile}, 254 | mwfact = select{args.mwfact, preset.mwfact, 255 | awful.tag.getproperty(t, "mwfact"), 256 | config.defaults.mwfact, 0.55}, 257 | nmaster = select{args.nmaster, preset.nmaster, 258 | awful.tag.getproperty(t, "nmaster"), 259 | config.defaults.nmaster, 1}, 260 | ncol = select{args.ncol, preset.ncol, 261 | awful.tag.getproperty(t, "ncol"), 262 | config.defaults.ncol, 1}, 263 | matched = select{args.matched, awful.tag.getproperty(t, "matched")}, 264 | exclusive = select{args.exclusive, preset.exclusive, 265 | awful.tag.getproperty(t, "exclusive"), 266 | config.defaults.exclusive}, 267 | persist = select{args.persist, preset.persist, 268 | awful.tag.getproperty(t, "persist"), 269 | config.defaults.persist}, 270 | nopopup = select{args.nopopup, preset.nopopup, 271 | awful.tag.getproperty(t, "nopopup"), 272 | config.defaults.nopopup}, 273 | leave_kills = select{args.leave_kills, preset.leave_kills, 274 | awful.tag.getproperty(t, "leave_kills"), 275 | config.defaults.leave_kills}, 276 | max_clients = select{args.max_clients, preset.max_clients, 277 | awful.tag.getproperty(t, "max_clients"), 278 | config.defaults.max_clients}, 279 | position = select{args.position, preset.position, guessed_position, 280 | awful.tag.getproperty(t, "position")}, 281 | icon = select{args.icon and capi.image(args.icon), 282 | preset.icon and capi.image(preset.icon), 283 | awful.tag.getproperty(t, "icon"), 284 | config.defaults.icon and capi.image(config.defaults.icon)}, 285 | icon_only = select{args.icon_only, preset.icon_only, 286 | awful.tag.getproperty(t, "icon_only"), 287 | config.defaults.icon_only}, 288 | sweep_delay = select{args.sweep_delay, preset.sweep_delay, 289 | awful.tag.getproperty(t, "sweep_delay"), 290 | config.defaults.sweep_delay}, 291 | overload_keys = select{args.overload_keys, preset.overload_keys, 292 | awful.tag.getproperty(t, "overload_keys"), 293 | config.defaults.overload_keys}, 294 | } 295 | 296 | -- get layout by name if given as string 297 | if type(props.layout) == "string" then 298 | props.layout = getlayout(props.layout) 299 | end 300 | 301 | -- set keys 302 | if args.keys or preset.keys then 303 | local keys = awful.util.table.join(config.globalkeys, 304 | args.keys or preset.keys) 305 | if props.overload_keys then 306 | props.keys = keys 307 | else 308 | props.keys = squash_keys(keys) 309 | end 310 | end 311 | 312 | -- calculate desired taglist index 313 | local index = args.index or preset.index or config.defaults.index 314 | local rel_index = args.rel_index or 315 | preset.rel_index or 316 | config.defaults.rel_index 317 | local sel = awful.tag.selected(scr) 318 | --TODO: what happens with rel_idx if no tags selected 319 | local sel_idx = (sel and tag2index(scr, sel)) or 0 320 | local t_idx = tag2index(scr, t) 321 | local limit = (not t_idx and #tags + 1) or #tags 322 | local idx = nil 323 | 324 | if rel_index then 325 | idx = awful.util.cycle(limit, (t_idx or sel_idx) + rel_index) 326 | elseif index then 327 | idx = awful.util.cycle(limit, index) 328 | elseif props.position then 329 | idx = pos2idx(props.position, scr) 330 | if t_idx and t_idx < idx then idx = idx - 1 end 331 | elseif config.remember_index and index_cache[scr][t.name] then 332 | idx = index_cache[scr][t.name] 333 | elseif not t_idx then 334 | idx = #tags + 1 335 | end 336 | 337 | -- if we have a new index, remove from old index and insert 338 | if idx then 339 | if t_idx then table.remove(tags, t_idx) end 340 | table.insert(tags, idx, t) 341 | index_cache[scr][t.name] = idx 342 | end 343 | 344 | local c = capi.client.focus 345 | -- set tag properties and push the new tag table 346 | capi.screen[scr]:tags(tags) 347 | -- restore previously focused client 348 | if c ~= nil then capi.client.focus = c end 349 | for prop, val in pairs(props) do awful.tag.setproperty(t, prop, val) end 350 | 351 | -- execute run/spawn 352 | if awful.tag.getproperty(t, "initial") then 353 | local spawn = args.spawn or preset.spawn or config.defaults.spawn 354 | local run = args.run or preset.run or config.defaults.run 355 | if spawn and args.matched ~= true then 356 | awful.util.spawn_with_shell(spawn, scr) 357 | end 358 | if run then run(t) end 359 | awful.tag.setproperty(t, "initial", nil) 360 | end 361 | 362 | 363 | return t 364 | end 365 | 366 | function shift_next() set(awful.tag.selected(), {rel_index = 1}) end 367 | function shift_prev() set(awful.tag.selected(), {rel_index = -1}) end 368 | 369 | --add : adds a tag 370 | --@param args: table of optional arguments 371 | function add(args) 372 | if not args then args = {} end 373 | local name = args.name or " " 374 | 375 | -- initialize a new tag object and its data structure 376 | local t = capi.tag{name = name} 377 | 378 | -- tell set() that this is the first time 379 | awful.tag.setproperty(t, "initial", true) 380 | 381 | -- apply tag settings 382 | set(t, args) 383 | 384 | -- unless forbidden or if first tag on the screen, show the tag 385 | if not (awful.tag.getproperty(t, "nopopup") or args.noswitch) or 386 | #capi.screen[t.screen]:tags() == 1 then 387 | awful.tag.viewonly(t) 388 | end 389 | 390 | -- get the name or rename 391 | if args.name then 392 | t.name = args.name 393 | else 394 | -- FIXME: hack to delay rename for un-named tags for 395 | -- tackling taglist refresh which disabled prompt 396 | -- from being rendered until input 397 | awful.tag.setproperty(t, "initial", true) 398 | local f 399 | local tmr 400 | if args.position then 401 | f = function() rename(t, args.rename, true); tmr:stop() end 402 | else 403 | f = function() rename(t); tmr:stop() end 404 | end 405 | tmr = capi.timer({timeout = 0.01}) 406 | tmr:add_signal("timeout", f) 407 | tmr:start() 408 | end 409 | 410 | return t 411 | end 412 | 413 | --del : delete a tag 414 | --@param tag : the tag to be deleted [current tag] 415 | function del(tag) 416 | local scr = (tag and tag.screen) or capi.mouse.screen or 1 417 | local tags = capi.screen[scr]:tags() 418 | local sel = awful.tag.selected(scr) 419 | local t = tag or sel 420 | local idx = tag2index(scr, t) 421 | 422 | -- return if tag not empty (except sticky) 423 | local clients = t:clients() 424 | local sticky = 0 425 | for i, c in ipairs(clients) do 426 | if c.sticky then sticky = sticky + 1 end 427 | end 428 | if #clients > sticky then return end 429 | 430 | -- store index for later 431 | index_cache[scr][t.name] = idx 432 | 433 | -- remove tag 434 | t.screen = nil 435 | 436 | -- if the current tag is being deleted, restore from history 437 | if t == sel and #tags > 1 then 438 | awful.tag.history.restore(scr, 1) 439 | -- this is supposed to cycle if history is invalid? 440 | -- e.g. if many tags are deleted in a row 441 | if not awful.tag.selected(scr) then 442 | awful.tag.viewonly(tags[awful.util.cycle(#tags, idx - 1)]) 443 | end 444 | end 445 | 446 | -- FIXME: what is this for?? 447 | if capi.client.focus then capi.client.focus:raise() end 448 | end 449 | 450 | --is_client_tagged : replicate behavior in tag.c - returns true if the 451 | --given client is tagged with the given tag 452 | function is_client_tagged(tag, client) 453 | for i, c in ipairs(tag:clients()) do 454 | if c == client then 455 | return true 456 | end 457 | end 458 | return false 459 | end 460 | 461 | --match : handles app->tag matching, a replacement for the manage hook in 462 | -- rc.lua 463 | --@param c : client to be matched 464 | function match(c, startup) 465 | local nopopup, intrusive, nofocus, run, slave, notagsteal 466 | local wfact, struts, geom, float 467 | local target_tag_names, target_tags = {}, {} 468 | local typ = c.type 469 | local cls = c.class 470 | local inst = c.instance 471 | local role = c.role 472 | local name = c.name 473 | local keys = config.clientkeys or c:keys() or {} 474 | local target_screen = capi.mouse.screen 475 | 476 | c.border_color = beautiful.border_normal 477 | c.border_width = beautiful.border_width 478 | 479 | -- try matching client to config.apps 480 | for i, a in ipairs(config.apps) do 481 | if a.match then 482 | local matched = false 483 | -- match only class 484 | if not matched and cls and a.match.class then 485 | for k, w in ipairs(a.match.class) do 486 | matched = cls:find(w) 487 | if matched then 488 | break 489 | end 490 | end 491 | end 492 | -- match only instance 493 | if not matched and inst and a.match.instance then 494 | for k, w in ipairs(a.match.instance) do 495 | matched = inst:find(w) 496 | if matched then 497 | break 498 | end 499 | end 500 | end 501 | -- match only name 502 | if not matched and name and a.match.name then 503 | for k, w in ipairs(a.match.name) do 504 | matched = name:find(w) 505 | if matched then 506 | break 507 | end 508 | end 509 | end 510 | -- match only role 511 | if not matched and role and a.match.role then 512 | for k, w in ipairs(a.match.role) do 513 | matched = role:find(w) 514 | if matched then 515 | break 516 | end 517 | end 518 | end 519 | -- match only type 520 | if not matched and typ and a.match.type then 521 | for k, w in ipairs(a.match.type) do 522 | matched = typ:find(w) 523 | if matched then 524 | break 525 | end 526 | end 527 | end 528 | -- check everything else against all attributes 529 | if not matched then 530 | for k, w in ipairs(a.match) do 531 | matched = (cls and cls:find(w)) or 532 | (inst and inst:find(w)) or 533 | (name and name:find(w)) or 534 | (role and role:find(w)) or 535 | (typ and typ:find(w)) 536 | if matched then 537 | break 538 | end 539 | end 540 | end 541 | -- set attributes 542 | if matched then 543 | if a.screen then target_screen = a.screen end 544 | if a.tag then 545 | if type(a.tag) == "string" then 546 | target_tag_names = {a.tag} 547 | else 548 | target_tag_names = a.tag 549 | end 550 | end 551 | if a.startup and startup then 552 | a = awful.util.table.join(a, a.startup) 553 | end 554 | if a.geometry ~=nil then 555 | geom = {x = a.geometry[1], 556 | y = a.geometry[2], 557 | width = a.geometry[3], 558 | height = a.geometry[4]} 559 | end 560 | if a.float ~= nil then float = a.float end 561 | if a.slave ~=nil then slave = a.slave end 562 | if a.border_width ~= nil then 563 | c.border_width = a.border_width 564 | end 565 | if a.nopopup ~=nil then nopopup = a.nopopup end 566 | if a.intrusive ~=nil then 567 | intrusive = a.intrusive 568 | end 569 | if a.fullscreen ~=nil then 570 | c.fullscreen = a.fullscreen 571 | end 572 | if a.honorsizehints ~=nil then 573 | c.size_hints_honor = a.honorsizehints 574 | end 575 | if a.kill ~=nil then c:kill(); return end 576 | if a.ontop ~= nil then c.ontop = a.ontop end 577 | if a.above ~= nil then c.above = a.above end 578 | if a.below ~= nil then c.below = a.below end 579 | if a.buttons ~= nil then 580 | c:buttons(a.buttons) 581 | end 582 | if a.nofocus ~= nil then nofocus = a.nofocus end 583 | if a.keys ~= nil then 584 | keys = awful.util.table.join(keys, a.keys) 585 | end 586 | if a.hidden ~= nil then c.hidden = a.hidden end 587 | if a.minimized ~= nil then 588 | c.minimized = a.minimized 589 | end 590 | if a.dockable ~= nil then 591 | awful.client.dockable.set(c, a.dockable) 592 | end 593 | if a.urgent ~= nil then 594 | c.urgent = a.urgent 595 | end 596 | if a.opacity ~= nil then 597 | c.opacity = a.opacity 598 | end 599 | if a.run ~= nil then run = a.run end 600 | if a.sticky ~= nil then c.sticky = a.sticky end 601 | if a.wfact ~= nil then wfact = a.wfact end 602 | if a.struts then struts = a.struts end 603 | if a.skip_taskbar ~= nil then 604 | c.skip_taskbar = a.skip_taskbar 605 | end 606 | if a.props then 607 | for kk, vv in pairs(a.props) do 608 | awful.client.property.set(c, kk, vv) 609 | end 610 | end 611 | if a.notagsteal ~= nil then notagsteal = a.notagsteal end 612 | end 613 | end 614 | end 615 | 616 | -- set key bindings 617 | c:keys(keys) 618 | 619 | -- Add titlebars to all clients when the float, remove when they are 620 | -- tiled. 621 | if config.float_bars then 622 | c:add_signal("property::floating", function(c) 623 | if awful.client.floating.get(c) then 624 | awful.titlebar.add(c, {modkey=modkey}) 625 | else 626 | awful.titlebar.remove(c) 627 | end 628 | awful.placement.no_offscreen(c) 629 | end) 630 | end 631 | 632 | -- set properties of floating clients 633 | if float ~= nil then 634 | awful.client.floating.set(c, float) 635 | awful.placement.no_offscreen(c) 636 | end 637 | 638 | local sel = awful.tag.selectedlist(target_screen) 639 | if not target_tag_names or #target_tag_names == 0 then 640 | -- if not matched to some names try putting 641 | -- client in c.transient_for or current tags 642 | if c.transient_for then 643 | target_tags = c.transient_for:tags() 644 | elseif #sel > 0 then 645 | for i, t in ipairs(sel) do 646 | local mc = awful.tag.getproperty(t, "max_clients") 647 | if intrusive or 648 | not (awful.tag.getproperty(t, "exclusive") or 649 | (mc and mc >= #t:clients())) then 650 | table.insert(target_tags, t) 651 | end 652 | end 653 | end 654 | end 655 | 656 | if (not target_tag_names or #target_tag_names == 0) and 657 | (not target_tags or #target_tags == 0) then 658 | -- if we still don't know any target names/tags guess 659 | -- name from class or use default 660 | if config.guess_name and cls then 661 | target_tag_names = {cls:lower()} 662 | else 663 | target_tag_names = {config.default_name} 664 | end 665 | end 666 | 667 | if #target_tag_names > 0 and #target_tags == 0 then 668 | -- translate target names to tag objects, creating 669 | -- missing ones 670 | for i, tn in ipairs(target_tag_names) do 671 | local res = {} 672 | for j, t in ipairs(name2tags(tn, target_screen) or 673 | name2tags(tn) or {}) do 674 | local mc = awful.tag.getproperty(t, "max_clients") 675 | local tagged = is_client_tagged(t, c) 676 | if intrusive or 677 | not (mc and (((#t:clients() >= mc) and not 678 | tagged) or 679 | (#t:clients() > mc))) or 680 | intrusive then 681 | table.insert(res, t) 682 | end 683 | end 684 | if #res == 0 then 685 | table.insert(target_tags, 686 | add({name = tn, 687 | noswitch = true, 688 | matched = true})) 689 | else 690 | target_tags = awful.util.table.join(target_tags, res) 691 | end 692 | end 693 | end 694 | 695 | -- set client's screen/tag if needed 696 | target_screen = target_tags[1].screen or target_screen 697 | if c.screen ~= target_screen then c.screen = target_screen end 698 | if slave then awful.client.setslave(c) end 699 | c:tags(target_tags) 700 | 701 | if wfact then awful.client.setwfact(wfact, c) end 702 | if geom then c:geometry(geom) end 703 | if struts then c:struts(struts) end 704 | 705 | -- prevent the client from stealing focus if it is on another tag 706 | if notagsteal then 707 | nofocus = true 708 | nopopup = true 709 | 710 | for i, t1 in ipairs(target_tags) do 711 | for j, t2 in ipairs(sel) do 712 | if t1 == t2 then 713 | nofocus = false 714 | nopopup = false 715 | end 716 | end 717 | end 718 | end 719 | 720 | local showtags = {} 721 | local u = nil 722 | if #target_tags > 0 and not startup then 723 | -- switch or highlight 724 | for i, t in ipairs(target_tags) do 725 | if not (nopopup or awful.tag.getproperty(t, "nopopup")) then 726 | table.insert(showtags, t) 727 | elseif not startup then 728 | c.urgent = true 729 | end 730 | end 731 | if #showtags > 0 then 732 | local ident = false 733 | -- iterate selected tags and and see if any targets 734 | -- currently selected 735 | for kk, vv in pairs(showtags) do 736 | for _, tag in pairs(sel) do 737 | if tag == vv then 738 | ident = true 739 | end 740 | end 741 | end 742 | if not ident then 743 | awful.tag.viewmore(showtags, c.screen) 744 | end 745 | end 746 | end 747 | 748 | if not (nofocus or c.hidden or c.minimized) then 749 | --focus and raise accordingly or lower if supressed 750 | if (target and target ~= sel) and 751 | (awful.tag.getproperty(target, "nopopup") or nopopup) then 752 | awful.client.focus.history.add(c) 753 | else 754 | capi.client.focus = c 755 | end 756 | c:raise() 757 | else 758 | c:lower() 759 | end 760 | 761 | if config.sloppy then 762 | -- Enable sloppy focus 763 | c:add_signal("mouse::enter", function(c) 764 | if awful.client.focus.filter(c) and 765 | awful.layout.get(c.screen) ~= awful.layout.suit.magnifier then 766 | capi.client.focus = c 767 | end 768 | end) 769 | end 770 | 771 | -- execute run function if specified 772 | if run then run(c, target) end 773 | 774 | end 775 | 776 | --sweep : hook function that marks tags as used, visited, 777 | --deserted also handles deleting used and empty tags 778 | function sweep() 779 | for s = 1, capi.screen.count() do 780 | for i, t in ipairs(capi.screen[s]:tags()) do 781 | local clients = t:clients() 782 | local sticky = 0 783 | for i, c in ipairs(clients) do 784 | if c.sticky then sticky = sticky + 1 end 785 | end 786 | if #clients == sticky then 787 | if awful.tag.getproperty(t, "used") and 788 | not awful.tag.getproperty(t, "persist") then 789 | if awful.tag.getproperty(t, "deserted") or 790 | not awful.tag.getproperty(t, "leave_kills") then 791 | local delay = awful.tag.getproperty(t, "sweep_delay") 792 | if delay then 793 | local tmr 794 | local f = function() 795 | del(t); tmr:stop() 796 | end 797 | tmr = capi.timer({timeout = delay}) 798 | tmr:add_signal("timeout", f) 799 | tmr:start() 800 | else 801 | del(t) 802 | end 803 | else 804 | if awful.tag.getproperty(t, "visited") and 805 | not t.selected then 806 | awful.tag.setproperty(t, "deserted", true) 807 | end 808 | end 809 | end 810 | else 811 | awful.tag.setproperty(t, "used", true) 812 | end 813 | if t.selected then 814 | awful.tag.setproperty(t, "visited", true) 815 | end 816 | end 817 | end 818 | end 819 | 820 | --getpos : returns a tag to match position 821 | -- @param pos : the index to find 822 | -- @return v : the tag (found or created) at position == 'pos' 823 | function getpos(pos, scr_arg) 824 | local v = nil 825 | local existing = {} 826 | local selected = nil 827 | local scr = scr_arg or capi.mouse.screen or 1 828 | 829 | -- search for existing tag assigned to pos 830 | for i = 1, capi.screen.count() do 831 | for j, t in ipairs(capi.screen[i]:tags()) do 832 | if awful.tag.getproperty(t, "position") == pos then 833 | table.insert(existing, t) 834 | if t.selected and i == scr then 835 | selected = #existing 836 | end 837 | end 838 | end 839 | end 840 | 841 | if #existing > 0 then 842 | -- if there is no selected tag on current screen, look for the first one 843 | if not selected then 844 | for _, tag in pairs(existing) do 845 | if tag.screen == scr then return tag end 846 | end 847 | 848 | -- no tag found, loop through the other tags 849 | selected = #existing 850 | end 851 | 852 | -- look for the next unselected tag 853 | i = selected 854 | repeat 855 | i = awful.util.cycle(#existing, i + 1) 856 | tag = existing[i] 857 | 858 | if (scr_arg == nil or tag.screen == scr_arg) and not tag.selected then return tag end 859 | until i == selected 860 | 861 | -- if the screen is not specified or 862 | -- if a selected tag exists on the specified screen 863 | -- return the selected tag 864 | if scr_arg == nil or existing[selected].screen == scr then return existing[selected] end 865 | 866 | -- if scr_arg ~= nil and no tag exists on this screen, continue 867 | end 868 | 869 | local screens = {} 870 | for s = 1, capi.screen.count() do table.insert(screens, s) end 871 | 872 | -- search for preconf with 'pos' on current screen and create it 873 | for i, j in pairs(config.tags) do 874 | local tag_scr = j.screen or screens 875 | if type(tag_scr) ~= 'table' then tag_scr = {tag_scr} end 876 | 877 | if j.position == pos and awful.util.table.hasitem(tag_scr, scr) then 878 | return add({name = i, 879 | position = pos, 880 | noswitch = not switch}) 881 | end 882 | end 883 | 884 | -- not existing, not preconfigured 885 | return add({position = pos, 886 | rename = pos .. ':', 887 | no_selectall = true, 888 | noswitch = not switch}) 889 | end 890 | 891 | --init : search shifty.config.tags for initial set of 892 | --tags to open 893 | function init() 894 | local numscr = capi.screen.count() 895 | 896 | local screens = {} 897 | for s = 1, capi.screen.count() do table.insert(screens, s) end 898 | 899 | for i, j in pairs(config.tags) do 900 | local scr = j.screen or screens 901 | if type(scr) ~= 'table' then 902 | scr = {scr} 903 | end 904 | for _, s in pairs(scr) do 905 | if j.init and (s <= numscr) then 906 | add({name = i, 907 | persist = true, 908 | screen = s, 909 | layout = j.layout, 910 | mwfact = j.mwfact}) 911 | end 912 | end 913 | end 914 | end 915 | 916 | --count : utility function returns the index of a table element 917 | --FIXME: this is currently used only in remove_dup, so is it really 918 | --necessary? 919 | function count(table, element) 920 | local v = 0 921 | for i, e in pairs(table) do 922 | if element == e then v = v + 1 end 923 | end 924 | return v 925 | end 926 | 927 | --remove_dup : used by shifty.completion when more than one 928 | --tag at a position exists 929 | function remove_dup(table) 930 | local v = {} 931 | for i, entry in ipairs(table) do 932 | if count(v, entry) == 0 then v[#v+ 1] = entry end 933 | end 934 | return v 935 | end 936 | 937 | --completion : prompt completion 938 | -- 939 | function completion(cmd, cur_pos, ncomp, sources, matchers) 940 | 941 | -- get sources and matches tables 942 | sources = sources or config.prompt_sources 943 | matchers = matchers or config.prompt_matchers 944 | 945 | local get_source = { 946 | -- gather names from config.tags 947 | config_tags = function() 948 | local ret = {} 949 | for n, p in pairs(config.tags) do 950 | table.insert(ret, n) 951 | end 952 | return ret 953 | end, 954 | -- gather names from config.apps 955 | config_apps = function() 956 | local ret = {} 957 | for i, p in pairs(config.apps) do 958 | if p.tag then 959 | if type(p.tag) == "string" then 960 | table.insert(ret, p.tag) 961 | else 962 | ret = awful.util.table.join(ret, p.tag) 963 | end 964 | end 965 | end 966 | return ret 967 | end, 968 | -- gather names from existing tags, starting with the 969 | -- current screen 970 | existing = function() 971 | local ret = {} 972 | for i = 1, capi.screen.count() do 973 | local s = awful.util.cycle(capi.screen.count(), 974 | capi.mouse.screen + i - 1) 975 | local tags = capi.screen[s]:tags() 976 | for j, t in pairs(tags) do 977 | table.insert(ret, t.name) 978 | end 979 | end 980 | return ret 981 | end, 982 | -- gather names from history 983 | history = function() 984 | local ret = {} 985 | local f = io.open(awful.util.getdir("cache") .. 986 | "/history_tags") 987 | for name in f:lines() do table.insert(ret, name) end 988 | f:close() 989 | return ret 990 | end, 991 | } 992 | 993 | -- if empty, match all 994 | if #cmd == 0 or cmd == " " then cmd = "" end 995 | 996 | -- match all up to the cursor if moved or no matchphrase 997 | if matchp == "" or 998 | cmd:sub(cur_pos, cur_pos+#matchp) ~= matchp then 999 | matchp = cmd:sub(1, cur_pos) 1000 | end 1001 | 1002 | -- find matching commands 1003 | local matches = {} 1004 | for i, src in ipairs(sources) do 1005 | local source = get_source[src]() 1006 | for j, matcher in ipairs(matchers) do 1007 | for k, name in ipairs(source) do 1008 | if name:find(matcher .. matchp) then 1009 | table.insert(matches, name) 1010 | end 1011 | end 1012 | end 1013 | end 1014 | 1015 | -- no matches 1016 | if #matches == 0 then return cmd, cur_pos end 1017 | 1018 | -- remove duplicates 1019 | matches = remove_dup(matches) 1020 | 1021 | -- cycle 1022 | while ncomp > #matches do ncomp = ncomp - #matches end 1023 | 1024 | -- put cursor at the end of the matched phrase 1025 | if #matches == 1 then 1026 | cur_pos = #matches[ncomp] + 1 1027 | else 1028 | cur_pos = matches[ncomp]:find(matchp) + #matchp 1029 | end 1030 | 1031 | -- return match and position 1032 | return matches[ncomp], cur_pos 1033 | end 1034 | 1035 | -- tagkeys : hook function that sets keybindings per tag 1036 | function tagkeys(s) 1037 | local sel = awful.tag.selected(s.index) 1038 | local keys = awful.tag.getproperty(sel, "keys") or 1039 | config.globalkeys 1040 | if keys and sel.selected then capi.root.keys(keys) end 1041 | end 1042 | 1043 | -- squash_keys: helper function which removes duplicate 1044 | -- keybindings by picking only the last one to be listed in keys 1045 | -- table arg 1046 | function squash_keys(keys) 1047 | local squashed = {} 1048 | local ret = {} 1049 | for i, k in ipairs(keys) do 1050 | squashed[table.concat(k.modifiers) .. k.key] = k 1051 | end 1052 | for i, k in pairs(squashed) do 1053 | table.insert(ret, k) 1054 | end 1055 | return ret 1056 | end 1057 | 1058 | -- getlayout: returns a layout by name 1059 | function getlayout(name) 1060 | for _, layout in ipairs(config.layouts) do 1061 | if awful.layout.getname(layout) == name then 1062 | return layout 1063 | end 1064 | end 1065 | end 1066 | 1067 | -- signals 1068 | capi.client.add_signal("manage", match) 1069 | capi.client.add_signal("unmanage", sweep) 1070 | capi.client.remove_signal("manage", awful.tag.withcurrent) 1071 | 1072 | for s = 1, capi.screen.count() do 1073 | awful.tag.attached_add_signal(s, "property::selected", sweep) 1074 | awful.tag.attached_add_signal(s, "tagged", sweep) 1075 | capi.screen[s]:add_signal("tag::history::update", tagkeys) 1076 | end 1077 | 1078 | --------------------------------------------------------------------------------