Categories
Computers Lua Programming

An Awesome Mail Widget

The name really does lend itself to abuse. Regardless, I leveraged some previous lua code to create a nice little email widget that checks my email account for new mail and, if so, creates a menu of the mailboxes which have new mail that I can select and launches mutt with that folder open.

Code and explanation after the jump.

This is the module that my awesome rc.lua file includes:

-- note that 'awful' is already in the global namespace
local naughty = require("naughty")
local imaplib = require("imap4")
local string = require("string")
local table = require("table")

-- create table so we can use this file as a Module
local M = {}

-- mailhost and user info
local mailhost = "mailserver"
local mailuser = "somebody"
local mailpw = "secret"

-- we don't care if new mail is in these folders
local mb_filter = { Trash = 1,
                    spam = 1,
                    spam2mail = 1,
                    Sent = 1,
                    Junk = 1
                  }

-------------------------------------------------------------------------------
--
local function chk_result(r)
    if r:getTaggedResult() ~= 'OK' then
        imap:shutdown()
        return nil
    end
    return r
end

--------------------------------------------------------------------------------
--
local function connect(host, username, password)
    local imap = imaplib.IMAP4:new(host)

    -- make sure server supports IMAP4rev1
    local r = chk_result(imap:CAPABILITY())
    if not r then return nil end
    -- now check for IMAP4Rev1 compatibility
    local capability = r:getUntaggedContent('CAPABILITY')[1]
    if not capability:find('IMAP4rev1') then
        imap:shutdown()
        return nil
    end
    -- bail if we can't use STARTTLS
    if not (capability:find('STARTTLS') and 
            chk_result(imap:STARTTLS()) and
            chk_result(imap:LOGIN(username, password))) then
        return nil
    end

    return imap
end

--------------------------------------------------------------------------------
--
local function getMailboxes(imap)
    local r = chk_result(imap:LIST('""', '*'))
    if not r then
        return nil
    else
        return r:getUntaggedContent('LIST')
    end
end

-------------------------------------------------------------------------------
--
local function mailChecker(imap)
    local mailboxes = getMailboxes(imap)
    if not mailboxes then return nil end

    local mail = {} -- table to hold mailbox names with new mail count
    for i,v in ipairs(mailboxes) do
        local mb = v:match([[.* %"(.-)%"$]])
        if mb_filter[mb] == nil then
            local rs = chk_result(imap:STATUS(mb, 'UNSEEN'))
            local mbstat = rs:getUntaggedContent('STATUS')[1]
            local nm_cnt = mbstat:match([[%(UNSEEN (%d+)%)$]])
            if nm_cnt ~= '0' then
                mail[mb] = nm_cnt
            end
        end
    end

    return mail
end

-------------------------------------------------------------------------------
--
local email_cmd = "x-terminal-emulator --title mutt -e sh -c 'sleep 0.5s; mutt -f ='"
local function showMail()

    -- quick function to check if a 'dict' style table is empty, lua's '#'
    -- operator doesn't work on dict type tables
    local function empty(t)
        local len = 0
        for k,v in pairs(t) do
            len = len + 1
        end
        return len
    end

    -- establish server connection
    local imap = connect(mailhost, mailuser, mailpw)
    if not imap then return end
    -- perform the check
    local newmail = mailChecker(imap)
    if not newmail then return end  -- some kind of problem
    -- this logs us out and shuts down the network connection
    imap:shutdown()
    imap = nil

    -- everything OK to now- if the table is length 0 then no new mail
    if not empty(newmail) then
        local mailmenu = {}
        for mb, cnt in pairs(newmail) do
            table.insert(mailmenu, {string.format("%s:\t%u", mb, cnt),
                                    function ()
                                        awful.util.spawn(email_cmd..mb)
                                    end
                                   } 
                        )
        end
        local mm = awful.menu.new({ items = mailmenu })
        mm:show({ keygrabber = true })
    else
        naughty.notify({ text = "No New Mail", 
                         title = "MailChecker", 
                         timeout = 2 
                       }
                      )
    end
end

-- create the widget with the mouse button binding
local mail_w = widget({ type = "imagebox" })
mail_w.image = image("/path/to/icon.png")
mail_w:buttons(awful.util.table.join(awful.button({}, 1, showMail)))

-- now populate namespace table
M.widget = mail_w
return M

The key to this module is an IMAP4 client library I wrote awhile back. The module can be found here. It can be installed to a system directory like /usr/local/share/lua/5.1/ or right into the same directory of the source code that is using it. Basically, install it into the lua path for your system. When in doubt, the same directory as the rest of your code should work.

Staying out of the IMAP specific stuff, the idea is after establishing a connection with the server, the code queries the server for a list of mailboxes. It then requests a status of each of those mailboxes, while ignoring the mb_filter ones, and builds a table containing the mailbox name and newmail count for that mailbox.

Once the table is built, we check to see if it’s empty. If not, then build a popup menu using the mailbox names and mail count to create each menu item. The email_cmd string uses mutt's option to launch into a particular folder, so the mailbox name is just appended to the command for each entry. Then it’s just a matter of showing it. Note I show the menu with keygrabber = true so I can just hit escape if I’m not interested in dealing with the new mail.

If no new email is present, then a popup notification is used to inform the user that there isn’t any mail. Sorry, no one loves you… 🙁

The widget creation is simple, consisting of 3 lines of code. I chose to make it an imagebox widget.

To use the code, simply put it into a file like mail.lua and put the file in the ~/.config/awesome directory. Then in whatever module the wibox is managed use a line like this:

local mail = require("mail")

Finally, simply locate mail.widget somewhere within the wibox layout. I put mine just before the layout box widget, for whatever that’s worth.

Thinking about it, it would be simple to create a keybinding that executes the same code. No pointing required. Hmmmm….

2 replies on “An Awesome Mail Widget”

I have just gotten mutt running almost where I want it to be. As I was stumbling around, I have just moved from that painfully slow Unity to using Awesome, and loving it (some more crap I am going to fine tune!).
So what is is the trick to get the mutt widget to work if I am using it with my local imapoffline maildir? And then after that I need to figure out how to get get mail notification running under Awesome…

I’m not familiar with offlineimap, so I’m afraid I’m not much help to you. This widget is something I concocted and simply queries all the mailboxes on an IMAP server for new mail, then builds a popup of the IMAP folders with that mail. I don’t use the offline syncing features of IMAP.

Leave a Reply

Your email address will not be published. Required fields are marked *