Search code examples
luasignalsawesome-wm

Awesome WM (v3.5.5) keygrabber alternative


I've never liked the default window switching possibilities in Awesome, so I thought I'd implement Alt-Tab behavior that takes history into account (and does fancy opacity effects).

When Alt-Tab is pressed, the entire history is recorded in a table, and appended to that history are the minimized windows (within the same tag). When this table is generated, I instantiate a keygrabber that captures Tab-press events (to switch to the next client in the table) and Alt-release events (to abort entirely).

A flag keeps track of whether the user is in the process of Alt-tabbing, to prevent the table from being generated over and over again.

The code (it's a lot and you probably don't need to see it, but my experience tells me that when I don't post all the code, people will ask for it eventually):

altTabbing = false
altTabIndex = 1
altTabHistory = {}
clientOpacities = {}

function altTabSetOpacities(restore)
   for i,c in pairs(altTabHistory) do
      if not restore and i ~= altTabIndex then
         c.opacity = 0.5
      else
         c.opacity = clientOpacities[i]
      end
   end
end

function myAltTab()
   -- First check if the user is already alttabbing, in which case the history
   -- should NOT be updated. If the user has just pressed alt-tab, generate a new 
   -- history-table

   if not altTabbing then -- generate history-table

      -- Clear Tables
      for i in pairs(altTabHistory) do altTabHistory[i] = nil end
      for i in pairs(clientOpacities) do clientOpacities[i] = nil end

      -- Get focus history for current tag
      local s = mouse.screen;
      local idx = 0
      local c = awful.client.focus.history.get(s, idx)

      while c do
         table.insert(altTabHistory, c)
         table.insert(clientOpacities, c.opacity)

         idx = idx + 1
         c = awful.client.focus.history.get(s, idx)
      end

      -- Minimized clients will not appear in the focus history
      -- Find them by cycling through all clients, and adding them to the list
      -- if not already there.
      -- This will preserve the history AND enable you to focus on minimized clients

      local t = awful.tag.selected(s)
      local all = client.get(s)

      for i = 1, #all do
         local c = all[i]
         local ctags = c:tags();

         -- check if the client is on the current tag
         local isCurrentTag = false
         for j = 1, #ctags do
            if t == ctags[j] then
               isCurrentTag = true
               break
            end
         end

         if isCurrentTag then
            -- check if client is already in the history
            -- if not, add it
            local addToHistory = true
            for k = 1, #altTabHistory do
               if altTabHistory[k] == c then
                  addToHistory = false
                  break
               end
            end

            if addToHistory then
               table.insert(altTabHistory, c)
               table.insert(clientOpacities, c.opacity)
            end
         end
      end

      -- reset current index and flag
      altTabIndex = 1
      altTabbing = true

      -- Now that we have collected all windows, we should run a keygrabber
      -- as long as the user is alt-tabbing:
      keygrabber.run(
         function (mod, key, event)  
            -- Stop alt-tabbing when the alt-key is released
            if key == "Alt_L" and event == "release" then
               altTabbing = false
               altTabSetOpacities(true)
               c = altTabHistory[altTabIndex]
               client.focus = c                  
               c:raise()   
               return false -- stop keygrabber
            end

            -- Move to next client on each Tab-press
            if key == "Tab" and event == "press" then
               myAltTab()
               return true -- keep going
            end

            return true -- keep going
         end
      )

   end -- if not altTabbing

   -- at this point, the user is alt-tabbing, so we should raise
   -- the next client in the history-table
   if #altTabHistory < 2 then return end

   -- Switch to next client
   altTabIndex = altTabIndex + 1
   if altTabIndex > #altTabHistory then
      altTabIndex = 1 -- wrap around
   end

   -- focus on current client
   local c = altTabHistory[altTabIndex]
   c.minimized = false
   c:raise()

   -- make current client stand out
   altTabSetOpacities(false)
end

I realize there's a lot of code, but the main thing is the keygrabber. For still unknown reasons, Awesome sometimes crashes while I'm Alt-Tabbing using this approach. I want to replace the keygrabber by connecting signals to the Alt and Tab keys, and disconnecting them as soon as the user is done. However, I'm not able to do this for some reason.

I instantiate a new key-object like this:

local altkey = awful.key({}, "Alt_L")[1]

I found out by trial and error that awful.key() actually returns a table of which I could query the first element for key, keysym etc, hence the [1]. However, when I try to connect a signal to this object, the LUA interpreter complains and tells me it's a nil object. So my question is: am I doing the right thing here? Is it even possible to replace the keygrabber in the way I intend to?


Solution

  • To use the Alt_L key in Awesome you should refer to "Mod1" in your rc.lua file, to make it mor readable I added the following line to the beginning of my configuration so Alt_L can be used.

    Alt_L = "Mod1"