WMII-like tag management

From awesome

Jump to: navigation, search

[edit] WMII - like tag management

Note: You can also accomplish the below more easily with the Shifty library for Awesome.

WMII [1] deals with tags in a bit more dynamic way than awesome does. On the beginning of session only one tag is created (named '1'). If we enter a tag that didn't exist before, it's created, when we leave an empty tag, it's destroyed.

It's a behaviour that I (a WMII refugee) missed a lot. Luckly it was not too hard to implement with some lua code and help of Garoth and psychon on IRC.

Note: all bits and pieces of the config file are for awesome 3.3-rc2. Other versions may require some tweaking.

First, we need some utility functions we'll use later on:

   function dictlen(tbl)
       local result = 0
       for _, __ in pairs(tbl) do
           result = result + 1
       end
       return result
   end
   function get_keys(tbl)
       local result = {}
       for key, _ in pairs(tbl) do
           if key then table.insert(result, key) end
       end
       return result
   end
   function make_completer(choices)
       return function(cmd, cur_pos, ncomp)
           local matches = {}
           -- abort completion under certain circumstances
           if #cmd == 0 or (cur_pos ~= #cmd + 1 and cmd:sub(cur_pos, cur_pos) ~= " ") then
               return cmd, cur_pos
           end     
           -- match
           for _, match in pairs(choices) do
               if match:find("^" .. cmd:sub(1,cur_pos)) then
                   table.insert(matches, match)
               end     
           end       
           -- if there are no matches
           if #matches == 0 then
               return cmd, cur_pos
           end 
           -- cycle
           while ncomp > #matches do
               ncomp = ncomp - #matches
           end     
           -- return match and position
           return matches[ncomp], cur_pos
       end
   end

Replace loop that add MOD+number keybindings (it's shown below):

   -- Compute the maximum number of digit we need, limited to 9
   keynumber = 0
   for s = 1, screen.count() do
      keynumber = math.min(9, math.max(#tags[s], keynumber));
   end
   for i = 1, keynumber do
       table.foreach(awful.key({ modkey }, i,
                     function () -- [cut]
                           end), function(_, k) table.insert(globalkeys, k) end)
       -- [cut]
   end

with something like this:

   for i = 0, 9 do
       table.foreach(awful.key({ modkey }, i,
                     function ()
                           local screen = mouse.screen
                           awful.tag.viewonly(get_tag(screen, i))
                     end), function(_, k) table.insert(globalkeys, k) end)
       table.foreach(awful.key({ modkey, "Control" }, i,
                     function ()
                         local screen = mouse.screen
                         local tag = get_tag(screen, i)
                         if tag then
                             tag.selected = not tag.selected
                         end
                     end), function(_, k) table.insert(globalkeys, k) end)
       table.foreach(awful.key({ modkey, "Shift" }, i,
                     function ()
                         if client.focus then
                             awful.client.movetotag(get_tag(client.focus.screen, i))
                         end
                     end), function(_, k) table.insert(globalkeys, k) end)
       table.foreach(awful.key({ modkey, "Control", "Shift" }, i,
                     function ()
                         if client.focus then
                             awful.client.toggletag(get_tag(client.focus.screen, i))
                         end
                     end), function(_, k) table.insert(globalkeys, k) end)
       if i > 0 then
           table.foreach(awful.key({ modkey, "Shift" }, "F" .. i,
                         function ()
                             local screen = mouse.screen
                             if tags[screen][i] then
                                 for k, c in pairs(awful.client.getmarked()) do
                                     awful.client.movetotag(get_tag(screen, i), c)
                                 end
                             end
                          end), function(_, k) table.insert(globalkeys, k) end)
       end
   end

Having that we can move on and create some tag-manipulating functions. Replace all of the "-- {{{ Tags" fold in your rc.lua with this:

   -- {{{ Tags
   -- Define tags table.
   tags = {}
   
   -- put all clients with no tags to "0" tag on screen 1
   function handle_orphans()
       for _, c in pairs(client.get()) do
           if #c:tags() == 0 then
               c:tags({get_tag(1, "0")})
           end
       end
   end
   
   function sort_tags(screen_no)
       local all_tags = screen[screen_no]:tags()
       table.sort(all_tags, function (a, b) return a.name < b.name end)
       screen[screen_no]:tags(all_tags)
   end
   
   function delete_tag(screen_no, name)
       local strname =  .. name
       if protected_tag == screen_no .. strname then return end
       local result = tags[screen_no][strname]
       if result ~= nil then
           result.screen = nil
           tags[screen_no][strname] = nil
       end
   end
   
   -- gets or creates tag
   function get_tag(screen_no, name)
       local strname =  .. name
       local result = tags[screen_no][strname]
       if not tags[screen_no][strname] then
           protected_tag = screen_no .. strname
           result = tag(strname)
           result.screen = screen_no
           tags[screen_no][strname] = result
           awful.layout.set(layouts[1], result)
       end
       protected_tag = nil
       return result
   end
   
   for s = 1, screen.count() do
       -- Each screen has its own tag table.
       tags[s] = {}
       get_tag(s, 1).selected = true
   end
   
   -- }}}


To make unused tags disappear and have them nicely sorted, add the following at the end of awful.hooks.arrange.register hook

   if screen == mouse.screen then
       for n, t in pairs(tags[screen]) do
           if #t:clients() == 0 and t ~= awful.tag.selected() and dictlen(tags[screen]) > 1 then
               delete_tag(screen, n)
           end
       end
   end
   
   sort_tags(screen)
   handle_orphans()


Now, let's have a prompt for our tag manipulation:

   function tag_prompt(txt, callback)
       return function()
           local screen = client.focus and client.focus.screen or mouse.screen
           awful.prompt.run({ prompt = txt }, 
           mypromptbox[screen].widget,
           function(t) callback(screen, t) end,
           make_completer(get_keys(tags[screen])),
           awful.util.getdir("cache") .. "/history_tags")
       end
   end

Let's bind it to keys (check if those bindings conflict with ones you already have!)

   awful.key({ modkey,           },       "t", tag_prompt("go to tag: ",   function (screen, tag) awful.tag.viewonly(get_tag(screen, tag)) end)),
   awful.key({ modkey, "Shift"   },       "t", tag_prompt("move to tag: ", function (screen, tag) awful.client.movetotag(get_tag(screen, tag)) end)),
   awful.key({ modkey, "Ctrl", "Shift" }, "t", tag_prompt("toggle tag: ",  function (screen, tag) awful.tag.toggletag(get_tag(screen, tag)) end))

That's it, enjoy!

[edit] Further work

There are some obvious places that you can change/improve this, like:

 * +tag and -tag should be supported
 * dmenu can be used for tag selection instead of awesome's prompt for more WMII - like experience


[edit] Working example

You can download my rc.lua that uses stuff described above from here: [2]

Personal tools