Introduction π¦π
Hey there everybody !! Before starting, i want to say that this is my first online post, i am writing in my entire life. So please do bear with my silly mistakes or anything wrong i will be writing in this post.
So now going with the flow ; i am an avid music lover and i like the whole idea of development and programming alongside listening to music.
It's been a year since i migrated to linux ( Btw i use Arch Linux ), and i can't stress enough, how much my productivity, speed and ease of doing things have increased since then.
My system runs a vanilla arch setup ( barebone arch architecture without any setup ) running Awesome Window Manager. So recently, i thought about making a music bar from scratch. Since Awesome WM is highly configurable and comes with a lot helper utilities, it was really easy to setup such a thing.
Structure π§¬
~/.config/awesome
|--daemon
|--init.lua
|--moe.lua
|--spotify.lua
|--mpd.lua
|--widget
|--moe.lua
|--spotify_song.lua
|--spotify_buttons.lua
|--mpd_song.lua
|--mpd_buttons.lua
|--musicbar.lua
|--helpers.lua
|--rc.lua
|--default.jpg
daemons - contains the daemons or background process that provide us information about our subscribed events.
widget - contains the widgets that collectively make the musicbar widget.
musicbar.lua - the overall shell widget / parent widget that contains all other music widgets in widget directory.
helpers.lua - contains helper functions needed by many modules.
rc.lua - the main file that is executed by awesome on startup.
default.jpg - the default image to show if no cover image is available from listen.moe. Like this one :
Prerequisite π
Necessary Fonts:
- Material Design Icons - dropbox
Once you download them and unpack them, place them into ~/.fonts
or ~/.local/share/fonts
.
- You will need to create the directory if it does not exist.
- It does not matter that the actual font files (
.ttf
) are deep inside multiple directories. They will be detected as long as they can be accessed from~/.fonts
or~/.local/share/fonts
.
Finally, run the following in order for your system to detect the newly installed fonts.
fc-cache -v
Making some helper functions π€π€
helpers.lua is a file which contains some functions that are going to be used frequently in this project codebase. These include colorizing text, connecting signals with a defined action, configuring shapes of widgets etc.
helpers.lua
local wibox = require("wibox")
local gears = require("gears")
local helpers = {}
helpers.colorize_text = function(text, color)
return "<span foreground='"..color.."'>"..text.."</span>"
end
helpers.prrect = function(radius, tl, tr, br, bl)
return function(cr, width, height)
gears.shape.partially_rounded_rect(cr, width, height, tl, tr, br, bl, radius)
end
end
function helpers.vertical_pad(height)
return wibox.widget{
forced_height = height,
layout = wibox.layout.fixed.vertical
}
end
function helpers.horizontal_pad(width)
return wibox.widget{
forced_width = width,
layout = wibox.layout.fixed.horizontal
}
end
function helpers.add_hover_cursor(w, hover_cursor)
local original_cursor = "left_ptr"
w:connect_signal("mouse::enter", function ()
local w = _G.mouse.current_wibox
if w then
w.cursor = hover_cursor
end
end)
w:connect_signal("mouse::leave", function ()
local w = _G.mouse.current_wibox
if w then
w.cursor = original_cursor
end
end)
end
return helpers
Configuring Daemons πΏπΏ
Our very first challenge is to make and configure daemons, which will provide us with information and updates over regular interval.
Daemons can be made using the signals library of awesome. Here we have to configure three daemons specifically for ( listen.moe, spotify, mpd )
The daemons will be initiated with an init.lua . Using it we can easily turn on/off a daemon .
init.lua
require("daemon.moe")
require("daemon.mpd")
require("daemon.spotify")
Now, Here is the code for the 3 daemon files.
moe.lua
local awful = require("awful")
local function emit_info (moe_script_output)
local count = moe_script_output:match('count(%d*)cover')
local cover = moe_script_output:match('cover(.*)title')
local title = moe_script_output:match('title(.*)artist')
local artist = moe_script_output:match('artist(.*)end')
awesome.emit_signal("daemon::moe", count, cover, title, artist)
end
local moe_script = [[
sh -c 'python $HOME/projects/moe.py'
]]
awful.spawn.easy_async_with_shell("ps x | grep \"python /home/lucifer/projects/moe.py\" | grep -v grep | awk '{print $1}' | xargskill", function()
awful.spawn.with_line_callback(moe_script, {
stdout = function (line)
emit_info(line)
end
})
end)
Firstly, we are requiring the 'awful' library for spawning process here. The function emit_info takes the stdout of a python script (moe.py - a python script which establishes a web socket connection with listen.moe and writes data on song change ) , grabs the necessary information out of it and emits a signal with that information.
The moe.py script can be placed anywhere but you should update it's path in the local moe_script. Next we use awful.spawn to kill the python script process ( moe.py ) if it already exists ( in case of restart of awesome ).
After that in the callback function we use awful.spawn.with_line_callback to asynchronously read each line generated by the moe.py script and call emit_info function for that line to grab the necessary information out of it to emit a signal.
Additionaly the moe.py
script that provides the information for moe daemon :
#!/usr/bin/python
from sys import stdout
from os.path import exists
from time import sleep
from requests import get
import wget
import json
import asyncio
import websockets
path = '/home/lucifer/projects/'
album_cover_url = 'https://cdn.listen.moe/covers/'
artist_cover_url = 'https://cdn.listen.moe/artists/'
async def send_ws(ws, data):
json_data = json.dumps(data)
await ws.send(json_data)
async def _send_pings(ws, interval=45):
while True:
await asyncio.sleep(interval)
msg = {'op': 9}
await send_ws(ws, msg)
def _save_cover(cover, URL):
PATH = path + cover
if not exists(PATH):
try:
wget.download(URL, out=PATH, bar=None)
# file = get(URL, allow_redirects=True, timeout=2.5)
# open(PATH, 'wb').write(file.content)
return True
except:
return False
else:
return True
async def main(loop):
url = 'wss://listen.moe/gateway_v2'
count = ''
cover = '!Available'
artist = 'Not Available'
title = 'Not Available'
while True:
try:
ws = await websockets.connect(url)
break
except:
sleep(30)
while True:
data = json.loads(await ws.recv())
if data['op'] == 0:
heartbeat = data['d']['heartbeat'] / 1000
loop.create_task(_send_pings(ws, heartbeat))
elif data['op'] == 1:
if data['d']:
if data['d']['listeners']:
count = str(data['d']['listeners'])
if data['d']['song']['title']:
title = data['d']['song']['title']
if data['d']['song']['artists']:
artist = data['d']['song']['artists'][0]['nameRomaji'] or data['d']['song']['artists'][0]['name'] or 'NotAvailable'
if data['d']['song']['albums'] and data['d']['song']['albums'][0]['image']:
cover = data['d']['song']['albums'][0]['image']
if not _save_cover(cover, album_cover_url + cover):
cover = '!Available'
elif data['d']['song']['artists'] and data['d']['song']['artists'][0]['image']:
cover = data['d']['song']['artists'][0]['image']
if not _save_cover(cover, artist_cover_url):
cover = '!Available'
stdout.write(
f'count{count}cover{cover}title{title}artist{artist}end\n')
stdout.flush()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
The script when run establishes a web socket connection to a dedicated listen.moe web socket server. The script sleeps 30 sec if no internet connection is established and then fires again to establish the connection.
The script receives a json response object from which we extract the artist name, title name and the cover image endpoint. Images are downloaded using the cover image endpoint with wget python package. The path variable holds the download directory for images and should be updated to your preference in the script.
Some may prefer keeping the covers of the song, but i usually don't like saving them. So i added a cronjob to remove these images on every reboot.
@reboot rm /home/lucifer/projects/*.{png,jpg,jpeg,tmp}
spotify.lua
local awful = require("awful")
local function emit_info(playerctl_output)
local artist = playerctl_output:match('artist_start(.*)title_start')
local title = playerctl_output:match('title_start(.*)status_start')
-- Use the lower case of status
local status = playerctl_output:match('status_start(.*)'):lower()
status = string.gsub(status, '^%s*(.-)%s*$', '%1')
awesome.emit_signal("daemon::spotify", artist, title, status)
end
-- Sleeps until spotify changes state (pause/play/next/prev)
local spotify_script = [[
sh -c '
playerctl --player spotify metadata --format 'artist_start{{artist}}title_start{{title}}status_start{{status}}' --follow
']]
-- Kill old playerctl process
awful.spawn.easy_async_with_shell("ps x | grep \"playerctl --player spotify metadata\" | grep -v grep | awk '{print $1}' | xargs kill", function ()
-- Emit song info with each line printed
awful.spawn.with_line_callback(spotify_script, {
stdout = function(line)
emit_info(line)
end
})
end)
spotify.lua takes the stdout from playerctl ( A utility command line tool to control MPRIS-enabled media players ). You need to have playerctl installed to make it work.
The script is very much alike to moe.lua except that it depends on playerctl for obtain information regarding the status of spotify player ( song name, album name, playing status ).
mpd.lua
local awful = require("awful")
local function emit_info()
awful.spawn.easy_async_with_shell("sh -c 'mpc -f ARTIST@%artist%@TITLE@%title%@FILE@%file%@'",
function(stdout)
local artist = stdout:match('^ARTIST@(.*)@TITLE')
local title = stdout:match('@TITLE@(.*)@FILE')
local status = stdout:match('\n%[(.*)%]')
if not artist or artist == "" then
artist = "N/A"
end
if not title or title == "" then
title = stdout:match('@FILE@(.*)@')
if not title or title == "" then
title = "N/A"
end
end
local paused
if status == "playing" then
paused = false
else
paused = true
end
awesome.emit_signal("daemon::mpd", artist, title, paused)
end
)
end
-- Run once to initialize widgets
emit_info()
-- Sleeps until mpd changes state (pause/play/next/prev)
local mpd_script = [[
sh -c '
mpc idleloop player
']]
-- Kill old mpc idleloop player process
awful.spawn.easy_async_with_shell("ps x | grep \"mpc idleloop player\" | grep -v grep | awk '{print $1}' | xargs kill", function ()
-- Emit song info with each line printed
awful.spawn.with_line_callback(mpd_script, {
stdout = function()
emit_info()
end
})
end)
----------------------------------------------------------
-- MPD Volume
local function emit_volume_info()
awful.spawn.easy_async_with_shell("mpc volume | awk '{print substr($2, 1, length($2)-1)}'",
function(stdout)
awesome.emit_signal("daemon::mpd_volume", tonumber(stdout))
end
)
end
-- Run once to initialize widgets
emit_volume_info()
-- Sleeps until mpd volume changes
-- >> We use `sed '1~2d'` to remove every other line since the mixer event
-- is printed twice for every volume update.
-- >> The `-u` option forces sed to work in unbuffered mode in order to print
-- without waiting for `mpc idleloop mixer` to finish
local mpd_volume_script = [[
sh -c "
mpc idleloop mixer | sed -u '1~2d'
"]]
-- Kill old mpc idleloop mixer process
awful.spawn.easy_async_with_shell("ps x | grep \"mpc idleloop mixer\" | grep -v grep | awk '{print $1}' | xargs kill", function ()
-- Emit song info with each line printed
awful.spawn.with_line_callback(mpd_volume_script, {
stdout = function()
emit_volume_info()
end
})
end)
local mpd_options_script = [[
sh -c "
mpc idleloop options
"]]
local function emit_options_info()
awful.spawn.easy_async_with_shell("mpc | tail -1",
function(stdout)
local loop = stdout:match('repeat: (.*)')
local random = stdout:match('random: (.*)')
awesome.emit_signal("daemon::mpd_options", loop:sub(1, 2) == "on", random:sub(1, 2)== "on")
end
)
end
-- Run once to initialize widgets
emit_options_info()
-- Kill old mpc idleloop options process
awful.spawn.easy_async_with_shell("ps x | grep \"mpc idleloop options\" | grep -v grep | awk '{print $1}' | xargs kill", function ()
-- Emit song info with each line printed
awful.spawn.with_line_callback(mpd_options_script, {
stdout = function()
emit_options_info()
end
})
end)
mpd.lua script provides us with updates from our mpd client regarding ( song name, album name, player status, volume, random/loop enabled ). The script uses mpc ( command line client for MPD player ) to read the status of MPD player.
To get subscribed to events of MPD player, we use the mpc idleloop. Thus on any event ( song change, volume up, pause ) we get some feedback over which we can implement some action based on the event that occoured.
There are 3 signals being generated by the mpd.lua ( song status , volume status , options status ). I basically use the first one only ( song status ), but you can also use the other two to get subscribed about the volume and option updates as well.
Making Widgets π₯π₯
Now that we have configured our daemons, our next task is to make widgets for displaying the information provided to us. Our widgets will depend upon these daemons to update their state and change themselves based on subscribed events.
There are 5 widgets we are going to make :
- moe.lua
- spotify_song.lua
- spotify_buttons.lua
- mpd_song.lua
- mpd_buttons.lua
moe.lua
local awful = require("awful")
local wibox = require("wibox")
local naughty = require("naughty")
local helpers = require("helpers")
local default_image = os.getenv("HOME")..".config/awesome/default.jpg"
local moe_playing_colors = {
x.color7,
x.color8,
x.color9,
x.color10,
x.color11,
x.color12,
}
local moe_cover = wibox.widget.imagebox()
local moe_title = wibox.widget.textbox()
local moe_artist = wibox.widget.textbox()
local moe_listeners_count = wibox.widget.textbox()
local moe_play_icon = wibox.widget.textbox()
moe_play_icon.markup = helpers.colorize_text("ο
", x.color4)
moe_play_icon.font = "Material Icons medium 30"
moe_play_icon.align = "center"
moe_play_icon.valign = "center"
local moe_listeners_icon = wibox.widget.textbox()
moe_listeners_icon.markup = helpers.colorize_text("ο", x.color6)
moe_listeners_icon.font = "Material Icons medium 27"
moe_listeners_icon.align = "center"
moe_listeners_icon.valign = "center"
local function toggle_icon()
if moe_play_icon.text == "ο
" then
moe_play_icon.markup = helpers.colorize_text("ο", x.color4)
else
moe_play_icon.markup = helpers.colorize_text("ο
", x.color4)
end
end
moe_play_icon:buttons(
gears.table.join(
awful.button({ }, 1, function ()
toggle_icon()
awful.spawn.with_shell("moe")
end)
))
helpers.add_hover_cursor(moe_play_icon, "hand1")
local moe_widget = wibox.widget {
-- Cover Image
{
{
{
image = default_image,
clip_shape = helpers.rrect(dpi(16)),
widget = moe_cover
},
halign = 'center',
valign = 'center',
layout = wibox.container.place
},
---shape = helpers.rrect(box_radius / 2),
---widget = wibox.container.background
height = dpi(250),
width = dpi(250),
layout = wibox.container.constraint
},
helpers.vertical_pad(dpi(10)),
-- Title widget
{
{
align = "center",
markup = helpers.colorize_text("MoeChan", x.color4),
font = "sans medium 14",
widget = moe_title
},
left = dpi(20),
right = dpi(20),
widget = wibox.container.margin
},
-- Artist widget
{
{
align = "center",
text = "unavailable",
font = "sans medium 12",
widget = moe_artist
},
left = dpi(20),
right = dpi(20),
widget = wibox.container.margin
},
helpers.vertical_pad(dpi(5)),
{
{
-- play icon
{
moe_play_icon,
widget = wibox.container.background
},
helpers.horizontal_pad(dpi(70)),
-- headphone icon
{
moe_listeners_icon,
widget = wibox.container.background,
},
-- listener count
{
align = "center",
text = "",
font = "sans medium 12",
widget = moe_listeners_count
},
spacing = 10,
widget = wibox.layout.fixed.horizontal
},
align = "center",
valign = "center",
widget = wibox.container.place
},
spacing = 4,
layout = wibox.layout.fixed.vertical
}
awesome.connect_signal("daemon::moe", function(count ,cover, title, artist)
if tostring(cover) == "!Available" then
moe_cover.image = default_image
else
moe_cover.image = os.getenv("HOME").."/projects/"..cover
end
moe_title.markup = helpers.colorize_text(title, moe_playing_colors[math.random(6)])
moe_artist.text = artist
moe_listeners_count.text = tostring(count)
naughty.notify({ title = "Moe | Now Playing", message = title.." by "..artist })
end)
return moe_widget
moe.lua returns the listen.moe widget for the musicbar. The libraries include :
- awful -> For buttons and spawning processes.
- wibox -> The main widget box library.
- naughty -> The notification library.
- helpers -> The user defined helper library.
The file consists of moe_playing_colors which are essentially theme colors taken from xrdb and are defined in rc.lua ( later in this post ). Alongside we have a local variable default image which is basically a image that is put in place of cover image if it's not available.
Next we have created 1 imagebox and 4 textbox for imagecover, song title, artist title, play icon and listener count.
moe_play_icon:buttons control the action when the icon is clicked. When it does, it fires a shell script 'moe' ( defined below ) which plays the music from listen.moe using mpv player as a background process.
You can either install the fonts defined in {textbox}.font or change them according to your preference.
moe_widget is our main listen.moe widget which consists of all individual wibox widgets ( cover image, song title, artist title, play icon, listeners count ).
For subscribing to the daemon updates ( daemon/moe.lua ), we use the awesome.connect_signal and based on the information update our widget state.
Additionaly the shell script moe :
#!/bin/sh
moe="https://listen.moe/stream"
pkill -f $moe || mpv "$moe"
Keep in mind you need to add this script to your path to make it available everywhere.
spotify_song.lua
local wibox = require("wibox")
-- Declare widgets
local spotify_artist = wibox.widget.textbox()
local spotify_title = wibox.widget.textbox()
-- Main widget that includes all others
local spotify_widget = wibox.widget {
-- Title widget
{
align = "center",
text = "Spotify",
font = "sans 14",
widget = spotify_title
},
-- Artist widget
{
align = "center",
text = "unavailable",
font = "sans 10",
widget = spotify_artist
},
spacing = 2,
layout = wibox.layout.fixed.vertical
}
-- Subcribe to spotify updates
awesome.connect_signal("daemon::spotify", function(artist, title, status)
-- Do whatever you want with artist, title, status
-- ...
spotify_artist.text = artist
spotify_title.text = title
-- Example notification (might not be needed if spotify already sends one)
-- if status == "playing" then
-- naughty.notify({ title = "Spotify | Now Playing", message= title.." by "..artist })
-- end
end)
return spotify_widget
spotify.lua returns the spotify song widget which contains the song artist and song title.
The code is very much similar to moe.lua widget except that here we are connecting to the daemon::spotify signal.
Since spotify already notifies us about song changes i do not like receiving extra notification, thus i commented out the notification line in awesome.connect_signal. This is just to avoid getting 2 notifications.
spotify_buttons.lua
local gears = require("gears")
local awful = require("awful")
local wibox = require("wibox")
local helpers = require("helpers")
local spotify_prev_symbol = wibox.widget.textbox()
spotify_prev_symbol.markup = helpers.colorize_text("βͺ", x.foreground.."33")
spotify_prev_symbol.font = "Material Icons Bold 18"
spotify_prev_symbol.align = "center"
spotify_prev_symbol.valign = "center"
local spotify_next_symbol = wibox.widget.textbox()
spotify_next_symbol.markup = helpers.colorize_text("β«", x.foreground.."33")
spotify_next_symbol.font = "Material Icons Bold 18"
spotify_next_symbol.align = "center"
spotify_next_symbol.valign = "center"
-- local note_symbol = "ξ‘"
-- local note_symbol = "β«"
local note_symbol = "γ"
local big_note = wibox.widget.textbox()
big_note.font = "Material Icons Bold 17"
big_note.align = "center"
big_note.markup = helpers.colorize_text(note_symbol, x.foreground.."33")
local small_note = wibox.widget.textbox()
small_note.align = "center"
small_note.markup = helpers.colorize_text(note_symbol, x.foreground.."55")
small_note.font = "Material Icons Bold 13"
-- small_note.valign = "bottom"
local double_note = wibox.widget {
big_note,
-- small_note,
{
small_note,
top = dpi(11),
widget = wibox.container.margin
},
spacing = dpi(-5),
layout = wibox.layout.fixed.horizontal
}
local spotify_toggle_icon = wibox.widget {
double_note,
-- bg = "#00000000",
widget = wibox.container.background
}
spotify_toggle_icon:buttons(gears.table.join(
awful.button({ }, 1, function ()
awful.spawn.with_shell("playerctl --player spotify play-pause")
end)
))
local spotify_prev_icon = wibox.widget {
spotify_prev_symbol,
shape = gears.shape.circle,
widget = wibox.container.background
}
spotify_prev_icon:buttons(gears.table.join(
awful.button({ }, 1, function ()
awful.spawn.with_shell("playerctl --player spotify previous")
end)
))
local spotify_next_icon = wibox.widget {
spotify_next_symbol,
shape = gears.shape.circle,
widget = wibox.container.background
}
spotify_next_icon:buttons(gears.table.join(
awful.button({ }, 1, function ()
awful.spawn.with_shell("playerctl --player spotify next")
end)
))
local music_playing_colors = {
x.color1,
x.color2,
x.color3,
x.color4,
x.color5,
x.color6,
}
awesome.connect_signal("daemon::spotify", function(artist, title, status)
local accent, small_note_color
if string.lower(status) == "paused" then
accent = x.foreground.."33"
small_note_color = x.foreground.."55"
else
accent = music_playing_colors[math.random(6)]
small_note_color = x.foreground
end
big_note.markup = helpers.colorize_text(note_symbol, accent)
small_note.markup = helpers.colorize_text(note_symbol, small_note_color)
spotify_prev_symbol.markup = helpers.colorize_text(spotify_prev_symbol.text, accent)
spotify_next_symbol.markup = helpers.colorize_text(spotify_next_symbol.text, accent)
end)
local spotify_buttons = wibox.widget {
nil,
{
spotify_prev_icon,
spotify_toggle_icon,
spotify_next_icon,
spacing = dpi(14),
layout = wibox.layout.fixed.horizontal
},
expand = "none",
layout = wibox.layout.align.horizontal,
}
-- Add clickable mouse effects on some widgets
helpers.add_hover_cursor(spotify_next_icon, "hand1")
helpers.add_hover_cursor(spotify_prev_icon, "hand1")
helpers.add_hover_cursor(spotify_toggle_icon, "hand1")
return spotify_buttons
spotify_buttons.lua contains the buttons for controlling the spotify directly from your musicbar.
The play/pause, previous, next icons are binded to certain action. The playerctl utility is the tool which is used for carrying out these actions ( pause/play, next song, previous song ).
The color of the icons changes when a song is playing. The colors are randomly picked from an array music_playing_colors.
mpd_song.lua
local gears = require("gears")
local wibox = require("wibox")
-- Set colors
local title_color = x.color7
local artist_color = x.color7
local paused_color = x.color8
local mpd_title = wibox.widget{
text = "---------",
align = "center",
valign = "center",
widget = wibox.widget.textbox
}
local mpd_artist = wibox.widget{
text = "---------",
align = "center",
valign = "center",
widget = wibox.widget.textbox
}
-- Main widget
local mpd_song = wibox.widget{
mpd_title,
mpd_artist,
layout = wibox.layout.fixed.vertical
}
local artist_fg
local artist_bg
awesome.connect_signal("daemon::mpd", function(artist, title, status)
if status == "paused" then
artist_fg = paused_color
title_fg = paused_color
else
artist_fg = artist_color
title_fg = title_color
end
-- Escape &'s
title = string.gsub(title, "&", "&")
artist = string.gsub(artist, "&", "&")
mpd_title.markup =
"<span foreground='" .. title_fg .."'>"
.. title .. "</span>"
mpd_artist.markup =
"<span foreground='" .. artist_fg .."'>"
.. artist .. "</span>"
end)
return mpd_song
mpd_song.lua returns us the widget for displaying songs playing through MPD. Most of this is similar to spotify_song.lua.
Here we are connecting to the daemon::mpd signal to receive the information about events we are subscribed to.
mpd_buttons.lua
-- Text buttons for mpd control using "Material Design Icons" font
local gears = require("gears")
local awful = require("awful")
local wibox = require("wibox")
local helpers = require("helpers")
local mpd_prev_symbol = wibox.widget.textbox()
mpd_prev_symbol.markup = helpers.colorize_text("ξ", x.foreground)
mpd_prev_symbol.font = "Material Icons Bold 18"
mpd_prev_symbol.align = "center"
mpd_prev_symbol.valign = "center"
local mpd_next_symbol = wibox.widget.textbox()
mpd_next_symbol.markup = helpers.colorize_text("ξ", x.foreground)
mpd_next_symbol.font = "Material Icons Bold 18"
mpd_next_symbol.align = "center"
mpd_next_symbol.valign = "center"
local note_symbol = "ξ‘"
local big_note = wibox.widget.textbox(note_symbol)
big_note.font = "Material Icons Bold 15"
big_note.align = "center"
local small_note = wibox.widget.textbox()
small_note.align = "center"
small_note.markup = helpers.colorize_text(note_symbol, x.foreground)
small_note.font = "Material Icons Bold 11"
-- small_note.valign = "bottom"
local double_note = wibox.widget {
big_note,
-- small_note,
{
small_note,
top = dpi(11),
widget = wibox.container.margin
},
spacing = dpi(-9),
layout = wibox.layout.fixed.horizontal
}
local mpd_toggle_icon = wibox.widget {
double_note,
-- bg = "#00000000",
widget = wibox.container.background
}
mpd_toggle_icon:buttons(
awful.button({ }, 1, function ()
awful.spawn.with_shell("mpc -q toggle")
end)
)
local mpd_prev_icon = wibox.widget {
mpd_prev_symbol,
shape = gears.shape.circle,
widget = wibox.container.background
}
mpd_prev_icon:buttons(
awful.button({ }, 1, function ()
awful.spawn.with_shell("mpc -q prev")
end)
)
local mpd_next_icon = wibox.widget {
mpd_next_symbol,
shape = gears.shape.circle,
widget = wibox.container.background
}
mpd_next_icon:buttons(
awful.button({ }, 1, function ()
awful.spawn.with_shell("mpc -q next")
end)
)
local music_playing_counter = 0
local last_artist
local last_title
local music_playing_colors = {
x.color1,
x.color2,
x.color3,
x.color4,
x.color5,
x.color6,
}
local last_color = music_playing_colors[1]
awesome.connect_signal("daemon::mpd", function(artist, title, paused)
local accent, small_note_color
if paused then
accent = x.foreground.."33"
small_note_color = x.foreground.."55"
else
if artist ~= last_artist and title ~= last_title then
accent = music_playing_colors[(music_playing_counter % #music_playing_colors) + 1]
music_playing_counter = music_playing_counter + 1
else
accent = last_color
end
last_artist = artist
last_title = title
last_color = accent
small_note_color = x.foreground
end
big_note.markup = helpers.colorize_text(note_symbol, accent)
small_note.markup = helpers.colorize_text(note_symbol, small_note_color)
-- mpd_prev_icon.bg = accent
-- mpd_next_icon.bg = accent
mpd_prev_symbol.markup = helpers.colorize_text(mpd_prev_symbol.text, accent)
mpd_next_symbol.markup = helpers.colorize_text(mpd_next_symbol.text, accent)
end)
local mpd_buttons = wibox.widget {
nil,
{
mpd_prev_icon,
mpd_toggle_icon,
mpd_next_icon,
spacing = dpi(14),
layout = wibox.layout.fixed.horizontal
},
expand = "none",
layout = wibox.layout.align.horizontal,
}
-- Add clickable mouse effects on some widgets
helpers.add_hover_cursor(mpd_next_icon, "hand1")
helpers.add_hover_cursor(mpd_prev_icon, "hand1")
helpers.add_hover_cursor(mpd_toggle_icon, "hand1")
return mpd_buttons
mpd_buttons.lua returns the widget for button control for controlling you MPD client straight from musicbar. It has the same spotify_buttons.lua buttons.
However, one minor change in mpd_buttons.lua is that it keeps track of the last color used and thus doesn't display same color twice in succession for the icons color.
Putting together the Musicbar π
Now that we have finally configured all our daemons and widgets, it is finally time to assemble the full musicbar with these widgets and daemons.
The music bar is erected on the right side of my screen and pops out when i hover the mouse pointer over the right edge.
musicbar.lua
-- modules
local awful = require("awful")
local helpers = require("helpers")
local wibox = require("wibox")
-- Moe
local moe = require("widget.moe")
-- Spotify
local spotify_buttons = require("widget.spotify_buttons")
local spotify = require("widget.spotify")
local spotify_widget_children = spotify:get_all_children()
local spotify_title = spotify_widget_children[1]
local spotify_artist = spotify_widget_children[2]
spotify_title.forced_height = dpi(22)
spotify_artist.forced_height = dpi(16)
-- Mpd
local mpd_buttons = require("widget.mpd_buttons")
local mpd_song = require("widget.mpd_song")
local mpd_widget_children = mpd_song:get_all_children()
local mpd_title = mpd_widget_children[1]
local mpd_artist = mpd_widget_children[2]
mpd_title.font = "sans medium 14"
mpd_artist.font = "sans medium 10"
-- Set forced height in order to limit the widgets to one line.
-- Might need to be adjusted depending on the font.
mpd_title.forced_height = dpi(22)
mpd_artist.forced_height = dpi(16)
mpd_song:buttons(gears.table.join(
awful.button({ }, 1, function ()
awful.spawn.with_shell("mpc -q toggle")
end),
awful.button({ }, 3, apps.music),
awful.button({ }, 4, function ()
awful.spawn.with_shell("mpc -q prev")
end),
awful.button({ }, 5, function ()
awful.spawn.with_shell("mpc -q next")
end)
))
-- Create the music sidebar
music_sidebar = wibox({visible = false, ontop = true, type = "dock", screen = screen.primary})
music_sidebar.bg = "#00000000" -- For anti aliasing
music_sidebar.fg = x.color7
music_sidebar.opacity = 1
music_sidebar.height = screen.primary.geometry.height/1.5
music_sidebar.width = dpi(300)
music_sidebar.y = 0
awful.placement.right(music_sidebar)
awful.placement.maximize_vertically(sidebar, { honor_workarea = true, margins = { top = dpi(5) * 2 } })
music_sidebar:buttons(gears.table.join(
-- Middle click - Hide sidebar
awful.button({ }, 2, function ()
music_sidebar_hide()
end)
))
music_sidebar:connect_signal("mouse::leave", function ()
music_sidebar_hide()
end)
music_sidebar_show = function()
music_sidebar.visible = true
end
music_sidebar_hide = function()
music_sidebar.visible = false
end
music_sidebar_toggle = function()
if music_sidebar.visible then
music_sidebar_hide()
else
music_sidebar.visible = true
end
end
-- Activate sidebar by moving the mouse at the edge of the screen
local music_sidebar_activator = wibox({y = music_sidebar.y, width = 1, visible = true, ontop = false, opacity = 0, below = true, screen = screen.primary})
music_sidebar_activator.height = music_sidebar.height
music_sidebar_activator:connect_signal("mouse::enter", function ()
music_sidebar.visible = true
end)
awful.placement.right(music_sidebar_activator)
-- Music sidebar placement
music_sidebar:setup {
{
{
{
{
helpers.vertical_pad(dpi(25)),
moe,
helpers.vertical_pad(dpi(15)),
layout = wibox.layout.fixed.vertical
},
halign = 'center',
valign = 'center',
layout = wibox.container.place
},
shape = helpers.prrect(dpi(40), true, false, false, true),
bg = x.color8.."30",
widget = wibox.container.background
},
{
{
helpers.vertical_pad(dpi(30)),
{
spotify_buttons,
spotify,
spacing = dpi(5),
layout = wibox.layout.fixed.vertical
},
layout = wibox.layout.fixed.vertical
},
left = dpi(20),
right = dpi(20),
widget = wibox.container.margin
},
{
{
mpd_buttons,
mpd_song,
spacing = dpi(5),
layout = wibox.layout.fixed.vertical
},
top = dpi(40),
bottom = dpi(20),
left = dpi(20),
right = dpi(20),
widget = wibox.container.margin
},
helpers.vertical_pad(dpi(25)),
layout = wibox.layout.fixed.vertical
},
shape = helpers.prrect(dpi(40), true, false, false, true),
bg = x.background,
widget = wibox.container.background
}
Bringing the musicbar alive in rc.lua β¨β¨
Now that we have assembled all the required pieces to the puzzle, it's time to assemble and put them to work in rc.lua.
Add the following lines to the top of your rc.lua
-- Initialization
-- ===================================================================
-- Theme handling library
local beautiful = require("beautiful")
local xrdb = beautiful.xresources.get_current_theme()
-- Make dpi function global
dpi = beautiful.xresources.apply_dpi
-- Make xresources colors global
x = {
-- xrdb variable
background = xrdb.background,
foreground = xrdb.foreground,
color0 = xrdb.color0,
color1 = xrdb.color1,
color2 = xrdb.color2,
color3 = xrdb.color3,
color4 = xrdb.color4,
color5 = xrdb.color5,
color6 = xrdb.color6,
color7 = xrdb.color7,
color8 = xrdb.color8,
color9 = xrdb.color9,
color10 = xrdb.color10,
color11 = xrdb.color11,
color12 = xrdb.color12,
color13 = xrdb.color13,
color14 = xrdb.color14,
color15 = xrdb.color15,
}
-- Load helper functions
local helpers = require("helpers")
-- Load the musicbar
require("musicbar")
-- >> Daemons
-- Make sure to initialize it last in order to allow all widgets to connect to their needed daemon signals.
require("daemon")
-----------<< Rest of your config >>-----------------
With this, hopefully if you'll restart your awesome window manager you will get a musicbar on your right edge when you hover the mouse over that edge.
Thank You, for following me to the end. Hopefully, it was benificial to you as much as it was to me.
Resources βοΈ
All configs along with directory structure :
ashish-patwal / Moe-Lua-Daemon
Music Bar for Awesome WM ( listen.moe , spotify, MPD )
Update
- This repository now holds the code for musicbar ( listen.moe, mpd, spotify ).
Basic Info
- A lua daemon for fetching information from listen.moe using websocket through python websocket script.
- Also contains code for mpd and spotify music bar .
- Used for Awesome Window Manager .
ToDo
- Add widget for displaying info
- Add async fetching of images of artists / album covers to display in widget
- Ability to stream the music alongside using mpv
- Ability to Start / Stop
Do give me a follow if you liked the content. I will be posting some really interesting stuff.
Ashish Patwal@ashishpatwal147I have gone from " I wish they provided this feature " to " I'll just program my own feature " . Wrote my own music bar ( listen.moe , Spotify , and mpd player ) with lua and awesome wm .14:13 PM - 14 Jan 2022
Top comments (5)
God, this is amazing
Lua may sound intimidating, but it is actually one of the most beginner-friendly programming languages, making it easy to learn. Awesome WM functions as a framework for Lua, similar to how Django functions for Python. Implementing Awesome WM may take some time, but the benefits outweigh the investment.
If you want to download our post Spotify MOD APK, then we have given the link of this Spotify on the download button below. You can easily download this Spotify by clicking on the button of Download and install it on your mobile or laptop and save it.
WOW this is really cool. It must have been a pain to work in Lua though right?
I think lua just sounds scary . Infact it's the most beginners friendly language as its easy to learn . Awesome WM is just like a framework for lua ( like django is for python ) . It takes time to implement in Awesome but it's worth every penny .