Lua Plumber.

Lua is a very Popular embedded script. The only contender I know is Squirrel.

Lua script version for this Pipe and Fork tutorial is very similar with PHP script version. Almost only syntax differences they have. It would not be hard to understand between the two.

Goal: A script that continuously show date and time, with Dzen2, and Conky.

Before you dip your toe to scripting, you might desire to know the reason by reading this overview.

Reading


Piping and Forking in Many Languages

This article is one part of a collection. All integrated, one related to another. So we can compare each other quickly.

Tutorial/ Guidance/ Article: [ Pipe Overview ] [ BASH ] [ Perl ] [ Python ] [ Ruby ] [ PHP ] [ Lua ] [ Haskell ]

Source Code Directory: [ BASH ] [ Perl ] [ Python ] [ Ruby ] [ PHP ] [ Lua ] [ Haskell ]


Pipe and Fork in Desktop Ricing

Why Lua ?

Lua only used as embedded script. But I can see Lua’s potential. I have never seen anybody utilize Lua as main scripting tools in dotfiles, except Conky, and AwesomeWM that use Lua as main scripting configuration. Outside the world of Ricing, Lua also extensively used in Gaming.. I myself utilize a lot of Conky Lua a lot. So why not make a start ?


Be Creative

Lua lack is so lightweight that it lacks some common commands. We have to emulate these below:

  • sleep

  • _ dirname _

  • exec

Everything is fine now.


Start Simple

Welcome to n00berland. Begin with simple script. We will use this loop as a source feed to pipe. This step won’t introduce Pipe nor Fork.

This script only show an infinite loop showing local time. Each updated in one second interval. We manage this interval by delaying, using sleep code.internal function.

The first issue with Lua is, it does not have native sleep function. So we either call it from system or emulate it with.

Source:

#!/usr/bin/lua

local timeformat = '%a %b %d %H:%M:%S'

function sleep (n)
    local t = os.clock()
    while os.clock() - t <= n do
        -- nothing
    end
end

while true do
    print(os.date(timeformat))
    sleep(1)
end

Call to this simple code would produce time marching, one after another, below the command line prompt.

Pipe: Basic


External Command as Source Feed

Beside previous simple loop that is used as Internal Command, this tutorial also provide Conky as External Command in asset directory. I made it as simple as possible.

Source:

conky.config = {
    out_to_x = false,
    out_to_console = true,
    short_units = true,
    update_interval = 1
}

conky.text = [[\
${time %a %b %d %H:%M:%S}\
]]

A Unidirectional Pipe Between External Command

This step is overview of Pipe between two external command. Instead of system command, this utilize popen using two handles: pipein and pipeout.

This is a very simple. Just a few self explanatory lines. This very short script is using conky as pipe source feed and less as pipe target. Showing time and date forever in the console.

This infinite pipe run in time-less fashioned.

The second issue with Lua is, it does not have native _ dirname _ directive. that’s why emulate it with debug.getinfo

Source:

#!/usr/bin/lua

local dirname  = debug.getinfo(1).source:match("@?(.*/)")
local path     = dirname .. "../assets"
local cmdin    = 'conky -c ' .. path .. '/conky.lua'
local cmdout   = 'less' -- or 'dzen2'

local pipein  = assert(io.popen(cmdin,  'r'))
local pipeout = assert(io.popen(cmdout, 'w'))
  
for line in pipein:lines() do
    pipeout:write(line.."\n")
    pipeout:flush()
end -- for loop
   
pipein:close()
pipeout:close()

You can see, how simple it is. This would have less output similar to this below.

Pipe: to Less

Your wallpaper might be different than mine.

How does it works ?

Lua act as middle man, anything read from pipein:lines() written by pipeout:write()

for line in pipein:lines() do
    pipeout:write(line.."\n")
    pipeout:flush()
end

A Unidirectional Pipe from Internal Function

Still with popen. Instead of using pipein , we use internal function as source feed. And pipeout to external command.

Do not forget to flush.

Source:

#!/usr/bin/lua

local timeformat = '%a %b %d %H:%M:%S'

function sleep (n)
    local t = os.clock()
    while os.clock() - t <= n do
        -- nothing
    end
end

local cmdout   = 'less' -- or 'dzen2'
local pipeout = assert(io.popen(cmdout, 'w'))

while true do
    local datestr = os.date(timeformat).."\n"
    pipeout:write(datestr)
    pipeout:flush()
    
    sleep(1)
end

pipeout:close()

How does it works ?

The same as previous. But instead of reading from pipein, it is managed by internal process using pipeout:write().

    local datestr = os.date(timeformat).."\n"
    pipeout:write(datestr)

Fork Overview

Fork in Lua is also simple. But it require external luaposix Library. Arch user should get it easy in AUR (Arch user Repository).

This step use internal function as source feed, as continuation of previous step.

This step use dzen2, with complete parameters. This dzen2 is forked, running in the background. Detached from the script, no need to wait for dzen2 to finish the script.

Source:

#!/usr/bin/lua

-- luaposix available in AUR
local posix = require "posix"

-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ---
-- helper

function sleep (n)
    local t = os.clock()
    while os.clock() - t <= n do
        -- nothing
    end
end

-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ---
-- application related function

function get_dzen2_parameters ()
    local xpos    = '0'
    local ypos    = '0'
    local width   = '640'
    local height  = '24'
    local fgcolor = '#000000'
    local bgcolor = '#ffffff'
    local font    = '-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*'

    local parameters = ""
        .. " -x "..xpos.." -y "..ypos
        .. " -w "..width.." -h "..height
        .. " -fn '"..font.."'"
        .. " -ta c -bg '"..bgcolor.."' -fg '"..fgcolor.."'"
        .. " -title-name dzentop"

    return parameters
end

function generated_output (process)
    local timeformat = '%a %b %d %H:%M:%S'

    while true do
        local datestr = os.date(timeformat).."\n"
        process:write(datestr)
        process:flush()

        sleep(1)
    end
end

function run_dzen2 ()
    local cmdout  = 'dzen2 ' .. get_dzen2_parameters()
    local pipeout = assert(io.popen(cmdout, 'w'))
    
    generated_output(pipeout)
    
    pipeout:close()
end


function detach_dzen2()
    local pid = posix.fork()

    if pid == 0 then -- this is the child process
        run_dzen2()
    else             -- this is the parent process
        -- nothing
    end
end

-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ---
-- main

-- remove all dzen2 instance
os.execute('pkill dzen2')

-- run process in the background
detach_dzen2()

This step also add system command that kill any previous dzen2 instance. So it will be guaranteed, that the dzen2 shown is coming from the latest script.


How does it works ?

Any code after the posix.fork() executed in both parent and child. The child process has been detached from parent process. The only different is the pid.

function detach_dzen2()
    local pid = posix.fork()

    if pid == 0 then -- this is the child process
        run_dzen2()
    else             -- this is the parent process
        -- nothing
    end
end

Polishing The Script

This step, we use conky again, as a source feed. And also parameterized dzen2 as continuation of previous step.

This step add optional transset transparency, detached from script. So we two forks, dzen and transset.

Source:

#!/usr/bin/lua

-- luaposix available in AUR
local posix = require "posix"

-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ---
-- helper

function sleep (n)
    local t = os.clock()
    while os.clock() - t <= n do
        -- nothing
    end
end

-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ---
-- application related function

function get_dzen2_parameters ()
    local xpos    = '0'
    local ypos    = '0'
    local width   = '640'
    local height  = '24'
    local fgcolor = '#000000'
    local bgcolor = '#ffffff'
    local font    = '-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*'

    local parameters = ""
        .. " -x "..xpos.." -y "..ypos
        .. " -w "..width.." -h "..height
        .. " -fn '"..font.."'"
        .. " -ta c -bg '"..bgcolor.."' -fg '"..fgcolor.."'"
        .. " -title-name dzentop"

    return parameters
end

function generated_output (process)
    local dirname  = debug.getinfo(1).source:match("@?(.*/)")
    local path     = dirname .. "../assets"
    local cmdin    = 'conky -c ' .. path .. '/conky.lua'
    
    local pipein  = assert(io.popen(cmdin,  'r'))
  
    for line in pipein:lines() do
        process:write(line.."\n")
        process:flush()
    end -- for loop
   
    pipein:close()
end

function run_dzen2 ()
    local cmdout  = 'dzen2 ' .. get_dzen2_parameters()
    local pipeout = assert(io.popen(cmdout, 'w'))
    
    generated_output(pipeout)
    
    pipeout:close()
end


function detach_dzen2()
    local pid = posix.fork()

    if pid == 0 then -- this is the child process
        run_dzen2()
    else             -- this is the parent process
        -- nothing
    end
end

function detach_transset()
    local pid = posix.fork()

    if pid == 0 then -- this is the child process
        sleep(1)
        os.execute('transset .8 -n dzentop >/dev/null')        
    else             -- this is the parent process
        -- nothing
    end
end


-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ---
-- main

-- remove all dzen2 instance
os.execute('pkill dzen2')

-- run process in the background
detach_dzen2()

-- optional transparency
detach_transset()

This would have dzen2 output similar to this below.

Pipe: to Dzen2

You may use transset-df instead of transset.

How does it works ?

Nothing new here.

Lemonbar

I also provide Lemonbar, instead of Dzen2. The code is very similar.

Source:


Coming up Next

There already an advance case of Pipe and Fork. Multitier, and bidirectional.


There above are some simple codes I put together. I’m mostly posting codes so I won’t have any problems finding it in the future.

Thank you for reading.