Shifty

Introduction
Shifty is an Awesome 3 extension that implements dynamic tagging.

It also implements a client matching configuration that simplifies tag-client matching.

Here are a few ways of how shifty makes awesome awesomer:


 * On-the-fly tag creation and disposal
 * Advanced client matching
 * Easy moving of clients between tags
 * Tag add/rename prompt in taglist completion
 * Reordering tags and configurable positioning
 * Tag name guessing, automagic no-config client grouping
 * Customizable keybindings per client and tag
 * Simple yet powerful configuration

Note: Shifty is Awesome and served us well. Awesome now support dynamic tags by default (since mid-3.4 cycle). If you use Shifty only for dynamic tags, you can use awful.tag.add and awful.tag.remove directly. If you use only basic Shifty configuration system, then Tyrannicalis a newer (and faster) configuration system mostly compatible with Shifty but without all the more advanced features.

Videos
To see an early version of Shifty in action check: http://garoth.com/awesome/shifty.ogv

TODO Get somebody to do some video...

Installation
This absolute simplest way to get started is using the example.rc.lua included with shifty.

# for 3.4: git clone git://github.com/masterkorp/awesome-shifty.git # for 3.5 git clone git://github.com/cdump/awesome-shifty cp shifty/example.rc.lua rc.lua  WARNING: If you already have a custom rc.lua, move it first.
 * 1) Go to your configuration directory (usually ~/.config/awesome).
 * 2) Clone repository
 * 1) Move the example rc.lua file into your configuration directory.
 * 1) Restart awesome and enjoy.

Configuration
You can (and should) look in the example rc.lua to see how all this flows together. The example is the meant to be the simplest implementation that follows the default rc.lua. While its not absolutely necessary to start from the example configuration, it may be easier start with for the new user.

The author highly recommends starting with the example rc.lua. It will really save you some time and heartache.

Tags
Predefined tags are configured through the shifty.config.tags variable. Note that this does not imply the tags will be created at startup. These tags may not exist until a client that 'needs' them is spawned.

shifty.config.tags = { ["1:sys"] = { init    = true, -- Create this tag at startup and be persistent. position = 1, screen  = 1,   -- Only create this tag on screen 1. mwfact  = 0.60 -- Sets the master window size factor to 0.60 (60%) -- in the tag. },   ["2:term"] = { persist = true,  -- Don't delete after last client closes. position = 2, },   ["3:www"] = { position   = 3, spawn      = "firefox" -- Open firefox when created. exclusive  = true, -- Only clients matched from config.apps will -- be allowed here. max_clients = 1,   -- If more than one client is started, then a                            -- new tag is made. },   ["ardour"] = { nopopup    = true, -- Prevents focusing on creation. leave_kills = true, -- Don't destroy after closing the last client -- until switching another tag. },   ["p2p"] = { icon_only = true,              -- only show the icon icon     = "~/.icons/p2p.png", -- path to icon for taglist },   ["gimp"] = { layout = "tile", mwfact = 0.18, icon  = "~/.icons/gimp.png", -- Display an icon, but will also -- have text. },   ["fs"] = { rel_index = 1, -- always open next to the current tag and not at the -- end. (0 would create it before current tag) }, }


 * screen

TIP: screen = math.max(screen.count, 2) will assign a tag to the second screen but only when it is attached.


 * position

causes tag sys to always be inserted as 1st in the list, term as 2nd and www as 3rd.


 * nopopup
 * Do not focus on creation.

Valid keys for config.tags and config.defaults: layout       = func   -- a layout from awful.layout.suit (e.g. awful.layout.suit.tile) mwfact       = float  -- how big the master window is    nmaster       = int    -- how many columns for master windows ncol         = int    -- how many columns for non-master windows exclusive    = bool   -- if true, only clients from rules (config.apps) allowed in this tag persist      = bool   -- persist after no apps are in it    nopopup       = bool   -- do not focus on creation leave_kills  = bool   -- if true, tag won't be deleted until unselected position     = int    -- determines position in taglist (then what does index do?) icon         = string -- image file for icon icon_only    = bool   -- if true, no text (just icon) init         = bool   -- if true, create on startup (implies persist) sweep_delay  = int    -- ??? keys         = {}     -- a table of keys, which are associated with the tag overload_keys = {}    -- ??? index        = int    -- ??? rel_index    = int    -- ??? run          = func   -- a lua function which is execute on tag creation spawn        = string -- shell command which is execute on tag creation (ex. a programm) screen       = int    -- which screen to spawn on (see above) max_clients  = int    -- if more than this many clients are started, then a new tag is made

Notes init implies persist

Persistent tags are never deleted automatically (they can be deleted by shifty.delete) and tags with leave_kills set will not be deleted until the tag is unselected.

Running shifty.init after config variables are set creates tags configured with init flag.

The run key defines a function that will be run on tag creation.

Applications
Example application configuration table: shifty.config.apps = { {       match  = {"htop", "Wicd", "jackctl"}, tag   = "1:sys", screen = 1, },   {        match = {"Iceweasel.*", "Firefox.*"}, tag  = "3:www", },   {        match  = {"urxvt"}, tag   = "2:term", screen = 1, },   {        match   = {"Ardour.*", "Jamin"}, tag    = "ardour", nopopup = true, },   {        match = {"Gimp","Ufraw"}, tag = {"graph", "gimp"}, -- Both tags will be applied. },   {        match = {"gimp%-image%-window"}, slave = true,                   -- Client is started as a slave. },   {        match = {"gcolor2", "xmag", "MPlayer"}, float = true, intrusive = true,  -- Disregard a tag's exclusive property. },   {        match = {"gcolor2"}, geometry = {100, 100, nil, nil}, },   {        match = {""},   -- Matches all clients to provide button behaviors. buttons = { button({}, 1, function (c) client.focus = c; c:raise end), button({modkey}, 1, function (c) awful.mouse.client.move end), button({modkey}, 3, awful.mouse.client.resize), },   }, }

Valid keys for config.apps: above         = bool below         = bool border_width  = int buttons       = {} dockable      = bool float         = bool fullscreen    = bool geometry      = {x, y, w, h}    hidden         = bool honorsizehints = bool intrusive     = bool keys          = {} kill          = bool minimized     = bool nofocus       = bool nopopup       = bool ontop         = bool opacity       = float props         = {} run           = func screen        = int skip_taskbar  = bool slave         = bool startup       = bool sticky        = bool struts        = {} tag           = string titlebar      = bool urgent        = bool wfact         = float

Client tag matching
Its possible to match for specific attributes like class, instance, name, role and type. Example: {    match  = { class= "URxvt", name="root. "  --Notice the dot },    tag    = "5:admin", screen = 1, },

This will keep all urxvt windows that are running as root on the 5th tag.

If you dont specify any type all options are searched: {    match = { class = { "Do", },      "clock%-applet", },    float = true, intrusive = true, },

The pattern is lua pattern matching. To check the proprieties use xprop and click in the desired window.

Notes:

In tag field of config.apps you can even provide tag names that don't have presets in config.tags, they'll be created with the name from config.apps and according to config.defaults (if any).

Take notice of the { match = "" }, ... entry in config.apps - you need that if you want to have client buttons.

The geometry table causes client window to be created at specified coordinates and of a prescribed size. Table format is {x, y, width, height}.

Defaults
Fallback values used when a preset is not found in the first two configuration tables. shifty.config.defaults = { layout = "max", mwfact = 0.5, run = function(tag) naughty.notify({text = tag.name}) end, } Here new tags that are not preconfigured get 'max' layout, a mwfact of 0.5 and will show a desktop notification.

Keybindings
Example keybindings. These are the very basics, consider this just a launching point to get started.

In the 'big' table of globalkeys we keep these: awful.key({}, "XF86Back", awful.tag.viewprev), awful.key({}, "XF86Forward", awful.tag.viewnext), awful.key({modkey}, "XF86Back", shifty.shift_prev), awful.key({modkey}, "XF86Forward", shifty.shift_next), awful.key({modkey}, "t", function shifty.add({ rel_index = 1 }) end), awful.key({modkey, "Control"},           "t",            function shifty.add({ rel_index = 1, nopopup = true }) end            ), awful.key({modkey, "Shift"}, "r", shifty.rename), awful.key({modkey}, "w", shifty.del),

And as a replacement for the stock numerical keybindings: for i=1,9 do   globalkeys = awful.util.table.join(                        globalkeys,                        awful.key({modkey}, i,                            function awful.tag.viewonly(shifty.getpos(i)) end)) globalkeys = awful.util.table.join(                       globalkeys,                        awful.key({modkey, "Control"}, i,                            function local t = shifty.getpos(i) t.selected = not t.selected end)) globalkeys = awful.util.table.join(globalkeys,                               awful.key({modkey, "Control", "Shift"}, i,                function if client.focus then awful.client.toggletag(shifty.getpos(i)) end end)) -- move clients to other tags globalkeys = awful.util.table.join(                   globalkeys,                    awful.key({modkey, "Shift"}, i,                        function if client.focus then local t = shifty.getpos(i) awful.client.movetotag(t) awful.tag.viewonly(t) end end)) end

NOTE: Shifty operates numerical bindings according to attribute position with a function called getpos

Function shifty.getpos is meant to handle mod+i combination by mapping tags' position attribute.
 * tag with position == i exists, switches to it
 * more than one tag with position == i, cycle through all of of them
 * position i tag doesn't exist, create a new tag

Globals
config.guess_name = true If set to true (default) shifty will attempt to guess new tag name from client's class. This has effect only when a client is unmatched and being opened when there's no tags or current tag is solitary or exclusive.

config.guess_position = true If set to true (default) shifty will check first character of a tag name for being a number and set tag's position according to that. Providing position explicitly overrides this.

config.remember_index = true If set to true (default) shifty will keep track of tag's taglist index and if closed reopen the tag at the same place. Specifying position, index or rel_index overrides this.

config.layouts = {} If set (to a table of layout functions), enables setting layouts by short name

config.clientkeys = {} Default table of client keys, this is usually just your clientkeys.

config.globalkeys = nil Default table of global keys, this is usually just your globalkeys.

config.prompt_sources = { "config_tags", "config_apps", "existing", "history" } Table where the keys are used as strings for prompt completion.

config.prompt_matchers = {"^", ":", ""} Config for prompt completion matching, a table of strings (symbols)

Advanced
If you don't want to start from the example.rc.lua then here are some steps that will help you integrate shifty in your existing configuration.

1. Remove:
 * Anything related to tag creation
 * your manage function

2. Require the module require('shifty')  -- Near the top of rc.lua

3. Define the shifty.config variables (detailed above)

4. After taglist definitions in rc.lua, add this (note that mytaglist must be a table of taglists, not a single taglist object): shifty.taglist = mytaglist

5. Add some keybindings to rc.lua.

6. Replace mod+i (numerical) keybindings loop with the example above.

10. Following keytables' (globalkeys, clientkeys) definitions tell shifty what's going on: root.keys(globalkeys) shifty.config.globalkeys = globalkeys shifty.config.clientkeys = clientkeys

11. You made it! Now when you start Awesome there'll be no tags created unless you preconfigured some with init flag. In former case, opening a window that doesn't have a preset will create a new tag named after client's class (or "new" if shifty.config.guess is unset/false).

Support
Help is best found in this order:

1. Web search, e.g. Google is your friend...

2. #awesome on irc.oftc.net is good for immediate aid, especially with configuration questions and such.

3. Messaging through github

4. Mail the [mailto:awesome@naquadah.org awesome mailing list]

5. Mail the [mailto:masterkorp@masterkorp.net author].

Bugs
Please file all bugs and feature requests using the issue tracker on github.