Run or raise

From awesome
Jump to: navigation, search

Some clients you want quick one-key access to and you don't want more than one of them running at a time (Firefox springs to mind). The function run_or_raise starts the client if it is not running, otherwise it jumps you to the running client window. If there are multiple matching client windows, like a few Firefox windows, it will cycle through them. Client windows can be grouped and cycled through by their class, instance or name attributes.

Caveat: I haven't tested this on multiple screens yet, it may fail miserably...

There are two ways to set this up. One way is to put everything in the user's config file ~/.config/awesome/rc.lua. The other is modular and much cleaner.

01.01.2013: Both approaches are compatible with awesome 3.5

First, the modular approach[edit]

You'll need the two files below for this. The aweror.lua file contains the function module and ror.lua is where the desired key bindings are listed.

aweror.lua can go in either the system wide awesome library directory (/usr/local/share/awesome/lib/ or /usr/share/awesome/lib/) or in the user's config directory (~/.config/awesome/).

ror.lua contains the user's desired keybindings and should go in the user's config directory (~/.config/awesome) unless system wide key bindings are wanted. Edit the key bindings to match your needs (the format is described in the file).

Once the aweror.lua and ror.lua files are in place the user's config file ~/.config/awesome/rc.lua needs to be edited a little. In that file there will be a root.keys(globalkeys) command that sets the user's key bindings. The aweror function needs to be loaded and the 'run or raise' key bindings added to the globalkeys table before that command. On the lines before the root.keys(globalkeys) we need to add these two commands:

-- load the 'run or raise' function
local ror = require("aweror")

-- generate and add the 'run or raise' key bindings to the globalkeys table
globalkeys = awful.util.table.join(globalkeys, ror.genkeys(modkey))

root.keys(globalkeys)

With the rc.lua config file edited and the aweror.lua and ror.lua files in place the new 'run or raise' key bindings should be good to go. Now you can cycle through your Firefox windows, or Emacs windows, or whatnot with a single "Modkey + key" press.


The aweror.lua function file:

-- aweror.lua
-- Save this file as "aweror.lua" in awesome's system library directory "/usr/local/share/awesome/lib",
-- "/usr/share/awesome/lib", or your own config directory "~/.config/awesome/".
-- You will also need a "ror.lua" file (contains your key bindings) in your config directory.
-- Now you need to generate and add the keybindings in your ~/.config/awesome/rc.lua
-- In the rc.lua file there will be a "root.keys(globalkeys)" command that sets
-- your keys, you need to add the following lines just before that command:
--
-- local ror = require("aweror")
-- globalkeys = awful.util.table.join(globalkeys, ror.genkeys(modkey))


-- get our key bindings from separate ror.lua file
require("ror")

local awful= require("awful")
local client=client
local pairs=pairs
local table=table
local allt1=table5
local print=print
local USE_T = true
--local USE_T = false

local aweror = {}

function aweror.run_or_raise(cmd, properties)
   local clients = client.get()
   local focused = awful.client.next(0)
   local findex = 0
   local matched_clients = {}
   local n = 0
   for i, c in pairs(clients) do
      --make an array of matched clients
      if match(properties, c) then
         n = n + 1
         matched_clients[n] = c
         if c == focused then
            findex = n
         end
      end
   end
   if n > 0 then
      local c = matched_clients[1]
      -- if the focused window matched switch focus to next in list
      if 0 < findex and findex < n then
         c = matched_clients[findex+1]
      end
      local ctags = c:tags()
      if #ctags == 0 then
         -- ctags is empty, show client on current tag
         local curtag = awful.tag.selected()
         awful.client.movetotag(curtag, c)
      else
         -- Otherwise, pop to first tag client is visible on
         awful.tag.viewonly(ctags[1])
      end
      -- And then focus the client
      client.focus = c
      c:raise()
      return
   end
   awful.util.spawn(cmd)
end

-- Returns true if all pairs in table1 are present in table2
function match (table1, table2)
   for k, v in pairs(table1) do
      if table2[k] == nil or (table2[k] ~= v and not table2[k]:find(v)) then
         return false
      end
   end
   return true
end

function genfun(t3)
   local cmd=t3[1]
   local rule=t3[2]
   local flag=t3[3]
   local table1={}
   s1="class"
   if flag then
     s1=flag
   end
   table1[s1]=rule
   return function()
     aweror.run_or_raise(cmd,table1)
   end
end

function aweror.genkeys(mod1)
  rorkeys = awful.util.table.join()
  for i,v in pairs(allt1) do
    modifier=""
    if i:len() > 1 then
      modifier=i:sub(1, i:find("-")-1)
      i=i:sub(-1,-1)
    end
    rorkeys = awful.util.table.join(rorkeys,
      awful.key({ mod1, modifier}, i, genfun(v)))
  end
  return rorkeys
end

return aweror

Sample ror.lua key binding file (edit to taste):

-- ror.lua
-- This is the file goes in your ~/.config/awesome/ directory
-- It contains your table of 'run or raise' key bindings for aweror.lua
-- Table entry format: ["key"]={"function", "match string", "optional attribute to match"}
-- The "key" can include "Control-", "Shift-", and "Mod1-" modifiers (eg "Control-z")
-- The "key" will be bound as "modkey + key". (eg from above would end up as modkey+Control+z)
-- The "function" is what gets run if no matching client windows are found.
-- Usual attributes are "class","instance", or "name". If no attribute is given it defaults to "class".
-- The "match string"  will match substrings.  So "Firefox" will match "blah Firefox blah"  
-- Use xprop to get this info from a window.  WM_CLASS(STRING) gives you "instance", "class".
-- WM_NAME(STRING) gives you the name of the selected window (usually something like the web page title
-- for browsers, or the file name for emacs).

table5={
   ["Control-z"]={"google-chrome --app=http://www.rdio.com","www.rdio.com", "instance"},
   ["e"]={"emacsclient -a emacs -n -c","Emacs"}, 
   ["w"]={"firefox","Firefox"}, 
   ["v"]={"firefox -new-window 'http://www.evernote.com/Home.action?login=true#v=l&so=mn'","Evernote", "name"}, 
   ["g"]={"firefox -new-window 'http://mail.google.com/mail/'","Gmail","name"}, 
   ["x"]={"xterm","xterm", "instance"}, 
   ["f"]={"xterm -name mcTerm -e mc -d","mcTerm", "instance"}, 
   ["Shift-s"]={"xterm -name rootTerm -cr red -title rootTerm -e su","rootTerm", "instance"}, 
   ["t"]={"xterm -name htopTerm -e htop","htopTerm","instance"}, 
   ["b"]={"xterm -name rtorrentTerm -e rtorrent","rtorrentTerm","instance"}, 
   ["z"]={"xterm -name mocpTerm -e mocp","mocpTerm", "instance"} 
}

Another example, all matching with the default class attribute.

-- ror.lua
table5={
   ["f"]={"firefox","Namoroka"},
  ["a"]={"chromium --user-data-dir=/dev/shm/goo", "Chromium" },
  ["s"]={"xterm -class Screen -e screen -xRRS xterm  ", "Screen" },
  ["v"]={"gvim", "GVIM"},
  ["e"]={"emacs", "Emacs"}
}

The all in your rc.lua approach[edit]

In this approach everything goes in your user's config file ~/.config/awesome/rc.lua.

Just add the following function to the bottom of your config file and then add the desired key bindings to the globalkeys table (defined in the same file).

The key binding will look like:

awful.key({ modkey, }, "KEY", function () run_or_raise("FUNCTION", { ATTRIBUTE = "MATCH STRING" }) end),

KEY = the desired key to be bound

FUNCTION = the function to run if no client windows match

ATTRIBUTE = the window attribute to match. Either class, instance, or name.

MATCH STRING = the substring to match in the attribute

Some examples are provided below.

--- Spawns cmd if no client can be found matching properties
-- If such a client can be found, pop to first tag where it is visible, and give it focus
-- @param cmd the command to execute
-- @param properties a table of properties to match against clients.  Possible entries: any properties of the client object
function run_or_raise(cmd, properties)
   local clients = client.get()
   local focused = awful.client.next(0)
   local findex = 0
   local matched_clients = {}
   local n = 0
   for i, c in pairs(clients) do
      --make an array of matched clients
      if match(properties, c) then
         n = n + 1
         matched_clients[n] = c
         if c == focused then
            findex = n
         end
      end
   end
   if n > 0 then
      local c = matched_clients[1]
      -- if the focused window matched switch focus to next in list
      if 0 < findex and findex < n then
         c = matched_clients[findex+1]
      end
      local ctags = c:tags()
      if #ctags == 0 then
         -- ctags is empty, show client on current tag
         local curtag = awful.tag.selected()
         awful.client.movetotag(curtag, c)
      else
         -- Otherwise, pop to first tag client is visible on
         awful.tag.viewonly(ctags[1])
      end
      -- And then focus the client
      client.focus = c
      c:raise()
      return
   end
   awful.util.spawn(cmd)
end

-- Returns true if all pairs in table1 are present in table2
function match (table1, table2)
   for k, v in pairs(table1) do
      if table2[k] ~= v and not table2[k]:find(v) then
         return false
      end
   end
   return true
end

Usage example:

The following are the key bindings I have for google-chrome that use the substring matching with the NAME attribute. They allow me to cycle through my web browser windows seperately from my gmail, and evernote windows. Hotkey launching, or raising, of evernote and gmail is very sweet.

Add something like the following to the global key bindings table in your rc.lua:

awful.key({ modkey, }, "f", function () run_or_raise("xterm -name mcTerm -e mc -d", { instance = "mcTerm" }) end),
awful.key({ modkey, }, "w", function () run_or_raise("google-chrome", { name = "Google Chrome" }) end),
awful.key({ modkey, }, "v", function () run_or_raise("google-chrome --app='http://www.evernote.com/Home.action?login=true'", { name = "Evernote" }) end),
awful.key({ modkey, }, "g", function () run_or_raise("google-chrome --app='http://mail.google.com/mail/'", { name = "Gmail" }) end),

Integrating with dmenu[edit]

Run or raise with dmenu section on Using dmenu page has a very simple example showing how to use dmenu with run or raise. Though the example doesn't directly use the run or raise setup described here, it can be easily extended.