Learn and Discover Open Source with Daily Genuine Experience. From Coding, Design, Package Management Tweak to Desktop Customization.
 
   

Preface

Goal: Preparing modularized configuration by examining folder structure.

Table of Content

  • Preface: Table of Content

  • 1: Config Comparation

  • 2: Directory Structure

  • 3: Passing Value from Module

  • What is Next ?


1: Config Comparation

As already said, Awesome configuration is long as 564 lines, as you can see in original provided configuration for 4.3 series below:

After modularized, the config would be, similar as 80 lines of code as below:

pcall(require, "luarocks.loader")

-- Standard awesome library
local gears = require("gears")
local awful = require("awful")

-- Theme handling library
local beautiful = require("beautiful")

-- Miscellanous awesome library
local menubar = require("menubar")

RC = {} -- global namespace, on top before require any modules
RC.vars = require("main.user-variables")
modkey = RC.vars.modkey

-- Error handling
require("main.error-handling")

-- Variable definitions
beautiful.init(gears.filesystem.get_themes_dir() .. "default/theme.lua")
beautiful.wallpaper = RC.vars.wallpaper

-- -- --

-- Calling All Module Libraries

-- Custom Local Library
local main = {
  layouts = require("main.layouts"),
  tags    = require("main.tags"),
  menu    = require("main.menu"),
  rules   = require("main.rules"),
}

-- Custom Local Library: Keys and Mouse Binding
local binding = {
  globalbuttons = require("binding.globalbuttons"),
  clientbuttons = require("binding.clientbuttons"),
  globalkeys    = require("binding.globalkeys"),
  bindtotags    = require("binding.bindtotags"),
  clientkeys    = require("binding.clientkeys")
}

-- Layouts
RC.layouts = main.layouts()

-- Tags
RC.tags = main.tags()

-- Menu
RC.mainmenu = awful.menu({ items = main.menu() }) -- in globalkeys
RC.launcher = awful.widget.launcher(
  { image = beautiful.awesome_icon, menu = RC.mainmenu }
)
menubar.utils.terminal = RC.vars.terminal

-- Mouse and Key bindings
RC.globalkeys = binding.globalkeys()
RC.globalkeys = binding.bindtotags(RC.globalkeys)

-- Set root
root.buttons(binding.globalbuttons())
root.keys(RC.globalkeys)

-- Keyboard map indicator and switcher
mykeyboardlayout = awful.widget.keyboardlayout()

-- Statusbar: Wibar
require("deco.statusbar")

-- Rules
awful.rules.rules = main.rules(
  binding.clientkeys(),
  binding.clientbuttons()
)

-- Signals
require("main.signals")

The main rc.lua, should only contain skeleton of the configuration.


2: Directory Structure

Beside the main rc.lua, all codes resides in these three folders:

  • main

  • bindings

  • deco

Of course you can use any folder name that suitable for you, and even use very different structure, other than provided in this article.

Directory Tree

I try not to make any customization at all, so that the structure should reflect, the original configuration provided by Awesome WM.

$ tree
.
├── binding
│   ├── bindtotags.lua
│   ├── clientbuttons.lua
│   ├── clientkeys.lua
│   ├── globalbuttons.lua
│   └── globalkeys.lua
├── deco
│   ├── statusbar.lua
│   ├── taglist.lua
│   ├── tasklist.lua
│   ├── titlebar.lua
│   └── wallpaper.lua
├── main
│   ├── error-handling.lua
│   ├── layouts.lua
│   ├── menu.lua
│   ├── rules.lua
│   ├── signals.lua
│   ├── tags.lua
│   └── user-variables.lua
└── rc.lua

3 directories, 18 files

Awesome WM: Directory Structure

This is the basic structure. Structure may grow as needed. There will be other structure, for Awesome WM customization, in other articles.


3: Passing Value from Module

Moving code can be easy, or though task, depend on each case. For most part there are only these two cases:

  • just cut-then-paste, and done

  • require using metatable, usually when returning values.

Module: Error Handling

Consider jump with an example.

Always remember to add required library

Moving code is just a matter of cut-then-paste, from the main rc.lua, to related module.

-- Notification library
local naughty = require("naughty")

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

-- Check if awesome encountered an error during startup and fell back to
-- another config (This code will only ever execute for the fallback config)
if awesome.startup_errors then
  naughty.notify({ 
    preset = naughty.config.presets.critical,
    title = "Oops, there were errors during startup!",
    text = awesome.startup_errors
  })
end

-- Handle runtime errors after startup
do
  local in_error = false
  awesome.connect_signal("debug::error", function (err)
    -- Make sure we don't go into an endless error loop
    if in_error then return end
    in_error = true

    naughty.notify({
      preset = naughty.config.presets.critical,
      title = "Oops, an error happened!",
      text = tostring(err) 
    })
    in_error = false
  end)
end

The only thing different is, you still have to add this.

-- Notification library
local naughty = require("naughty")

Calling Module without Passing Value

In the main rc.lua, you can call the moudle by either of using do:

local config_path = awful.util.getdir("config") .. "/"

-- Error handling
dofile(config_path .. "main/error-handling.lua")

Or require:

-- Error handling
require("main.error-handling")

I found that require is easier. require also use dot (.), so you do not need to worry about using slash in linux, or backslash in windows.

This way is sufficient, since it doesn’t need any value to be returned, from this module

Module: User Variables

Returning value require different approach. Consider moving user variables into modules.

local home = os.getenv("HOME")

local _M = {
  -- This is used later as the default terminal and editor to run.
  terminal = "xfce4-terminal",
   
  -- Default modkey.
  modkey = "Mod4",

  -- user defined wallpaper
  --wallpaper = home .. "/Pictures/your-wallpaper-here.jpg",
}

return _M

This user variables module will grow as your code grown.

.

As you can see in above code, we need to return values from the module. Thi would be easier by wrapping these values in local variable. You can use any name that you like, now consider name this internal variable as _M.

local _M = {
  -- some setting here
}

return _M

This internal variables _M, would not be used in main configuration.

These approach works for simple value passing, in fact most module here only need this approach.

Calling Module with Returning Value

In main configuration, we can call use require as usual.

vars = require("main.user-variables")
modkey = RC.vars.modkey

And later we can access inside value with

vars = require("main.user-variables")
modkey = vars.modkey

For a reason, I do not want this configuration, to be polluted by having too many variables. So I wrapped them in one variable named RC. And the final result is as below.

RC = {} -- global namespace, on top before require any modules
RC.vars = require("main.user-variables")
modkey = RC.vars.modkey

Using Metatable

For complex situation, we need a better approach, using metatable, such as when a module contain some other function, that should be called from main configuration.

We can rewrite code above using metatable.

local _M = {}

function _M.get ()
  local variable_name = {
    -- some setting here
  }

  return variable_name
end

return setmetatable(
  {}, 
  { __call = function(_, ...) return _M.get(...) end }
)

.

This basic modularized modules does need any metatable, but I use it anyway. For personal reason, I need to be familiar with this metatable.


What is Next ?

Consider continue reading [ Awesome WM - Main Modules ]. There are, some interesting topic, about refactoringAwesome WM using Lua.

What do you think ?