init(git): Base commit

This commit is contained in:
Michael Carlberg 2016-05-19 16:41:06 +02:00
commit eeefb3c610
107 changed files with 11464 additions and 0 deletions

5
.exrc Normal file
View File

@ -0,0 +1,5 @@
let &path.="include,src,"
let g:alternateSearchPath = 'sfr:../src,sfr:../../src/modules,sfr:../../src/utils,sfr:../../src/interfaces,sfr:../../src/services,sfr:../../src/drawtypes,sfr:../include,sfr:../../include/modules,sfr:../../include/interfaces,sfr:../../include/utils,sfr:../../include/services,sfr:../../include/drawtypes,'
let g:alternateExtensions_cpp = 'hpp'
" let tag_path = expand("%:p:h") . "/tags"
set tags+=/home/jaagr/var/github/jaagr/lemonbuddy/tags

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
build
tags
*.bak
*.pyc
*.tmp
include/config.hpp

8
.gitmodules vendored Normal file
View File

@ -0,0 +1,8 @@
[submodule "contrib/lemonbar-sm-git"]
path = contrib/lemonbar-sm-git
url = https://github.com/jaagr/bar
branch = master
[submodule "contrib/i3ipcpp"]
path = contrib/i3ipcpp
url = https://github.com/jaagr/i3ipcpp
branch = master

130
.ycm_extra_conf.py Normal file
View File

@ -0,0 +1,130 @@
# Here's the license text for this file:
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>
import os
import ycm_core
flags = [
'-std=c++14',
'-Wall',
'-Wextra',
'-Wpedantic',
]
compilation_database_folder = ''
if os.path.exists( compilation_database_folder ):
database = ycm_core.CompilationDatabase( compilation_database_folder )
else:
database = None
SOURCE_EXTENSIONS = ['.cpp', '.cxx', '.cc', '.c', '.m', '.mm']
def DirectoryOfThisScript():
return os.path.dirname( os.path.abspath( __file__ ) )
flags.append('-I'+ DirectoryOfThisScript() +'/src')
flags.append('-I'+ DirectoryOfThisScript() +'/include')
flags.append('-I'+ DirectoryOfThisScript() +'/contrib/i3ipcpp/include')
def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
if not working_directory:
return list( flags )
new_flags = []
make_next_absolute = False
path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
for flag in flags:
new_flag = flag
if make_next_absolute:
make_next_absolute = False
if not flag.startswith( '/' ):
new_flag = os.path.join( working_directory, flag )
for path_flag in path_flags:
if flag == path_flag:
make_next_absolute = True
break
if flag.startswith( path_flag ):
path = flag[ len( path_flag ): ]
new_flag = path_flag + os.path.join( working_directory, path )
break
if new_flag:
new_flags.append( new_flag )
return new_flags
def IsHeaderFile( filename ):
extension = os.path.splitext( filename )[ 1 ]
return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
def GetCompilationInfoForFile( filename ):
# The compilation_commands.json file generated by CMake does not have entries
# for header files. So we do our best by asking the db for flags for a
# corresponding source file, if any. If one exists, the flags for that file
# should be good enough.
if IsHeaderFile( filename ):
basename = os.path.splitext( filename )[ 0 ]
for extension in SOURCE_EXTENSIONS:
replacement_file = basename + extension
if os.path.exists( replacement_file ):
compilation_info = database.GetCompilationInfoForFile(
replacement_file )
if compilation_info.compiler_flags_:
return compilation_info
return None
return database.GetCompilationInfoForFile( filename )
def FlagsForFile( filename, **kwargs ):
# if database:
# # Bear in mind that compilation_info.compiler_flags_ does NOT return a
# # python list, but a "list-like" StringVec object
# compilation_info = GetCompilationInfoForFile( filename )
# if not compilation_info:
# return None
#
# final_flags = MakeRelativePathsInFlagsAbsolute(
# compilation_info.compiler_flags_,
# compilation_info.compiler_working_dir_ )
#
# # NOTE: This is just for YouCompleteMe; it's highly likely that your project
# # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR
# # ycm_extra_conf IF YOU'RE NOT 100% SURE YOU NEED IT.
# try:
# final_flags.remove( '-stdlib=libc++' )
# except ValueError:
# pass
# else:
relative_to = DirectoryOfThisScript()
final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
return {
'flags': final_flags,
'do_cache': True
}

145
CMakeLists.txt Normal file
View File

@ -0,0 +1,145 @@
#
# Build configuration
#
cmake_minimum_required(VERSION 3.0)
set(CMAKE_CXX_COMPILER "/usr/bin/clang++")
project(lemonbuddy VERSION 1.0.0)
set(CMAKE_MODULE_PATH
"${CMAKE_MODULE_PATH}"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wextra -Wpedantic -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3 -DDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
#
# Internal values and switches
#
option(ENABLE_CCACHE "Enable ccache support" ON)
option(ENABLE_ALSA "Enable alsa support" ON)
option(ENABLE_MPD "Enable mpd support" ON)
option(ENABLE_I3 "Enable i3 support" OFF)
message(STATUS "---------------------------")
message(STATUS " Enable ccache support ${ENABLE_CCACHE}")
message(STATUS " Enable mpd support ${ENABLE_MPD}")
message(STATUS " Enable alsa support ${ENABLE_ALSA}")
message(STATUS " Enable i3 support ${ENABLE_I3}")
message(STATUS "---------------------------")
if(ENABLE_ALSA)
set(SETTING_ALSA_SOUNDCARD "default"
CACHE STRING "Name of the ALSA soundcard driver")
endif()
if(ENABLE_MPD)
set(SETTING_MPD_HOST "127.0.0.1"
CACHE STRING "Address MPD is bound to")
set(SETTING_MPD_PASSWORD ""
CACHE STRING "Password required for authentication")
set(SETTING_MPD_PORT "6600"
CACHE STRING "Port MPD is bound to")
endif()
set(SETTING_CONNECTION_TEST_IP "8.8.8.8"
CACHE STRING "Address to ping when testing network connection")
set(SETTING_PATH_BACKLIGHT_VAL "/sys/class/backlight/%card%/brightness"
CACHE STRING "Path to file containing the current backlight value")
set(SETTING_PATH_BACKLIGHT_MAX "/sys/class/backlight/%card%/max_brightness"
CACHE STRING "Path to file containing the maximum backlight value")
set(SETTING_PATH_BATTERY_WATCH "/sys/class/power_supply/%battery%/charge_now"
CACHE STRING "Path to attach inotify watch to")
set(SETTING_PATH_BATTERY_CAPACITY "/sys/class/power_supply/%battery%/capacity"
CACHE STRING "Path to file containing the current battery capacity")
set(SETTING_PATH_ADAPTER_STATUS "/sys/class/power_supply/%adapter%/online"
CACHE STRING "Path to file containing the current adapter status")
set(SETTING_BSPWM_SOCKET_PATH "/tmp/bspwm_0_0-socket"
CACHE STRING "Path to bspwm socket")
set(SETTING_BSPWM_STATUS_PREFIX "W"
CACHE STRING "Prefix prepended to the bspwm status line")
set(SETTING_PATH_CPU_INFO "/proc/stat"
CACHE STRING "Path to file containing cpu info")
set(SETTING_PATH_MEMORY_INFO "/proc/meminfo"
CACHE STRING "Path to file containing memory info")
configure_file("${CMAKE_SOURCE_DIR}/include/config.hpp.cmake" "${CMAKE_SOURCE_DIR}/include/config.hpp" ESCAPE_QUOTES @ONLY)
if(ENABLE_CCACHE)
find_program(CCACHE_FOUND "ccache")
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "ccache")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "ccache")
else()
message(WARNING "ccache not found. Ignoring the flag...")
endif()
endif()
#
# Locate and insert libs
#
find_package("Boost" REQUIRED)
#find_package("Boost" REQUIRED "regex")
find_package("Threads" REQUIRED)
find_package("X11" REQUIRED "X11_Xrandr" "X11_Xutil" "X11_Xlib")
include_directories(
"${PROJECT_SOURCE_DIR}/include"
${Boost_INCLUDE_DIRS}
${X11_INCLUDE_DIR}
${X11_Xrandr_INCLUDE_PATH})
link_directories(
${Boost_LIBRARY_DIRS}
${X11_LIBRARY_DIRS})
set(LINK_LIBS
${Boost_LIBRARIES}
${X11_LIBRARIES}
${X11_Xrandr_LIB}
${CMAKE_THREAD_LIBS_INIT})
if(ENABLE_ALSA)
find_package("ALSA" REQUIRED)
include_directories(${ALSA_INCLUDE_DIRS})
link_directories(${ALSA_LIBRARY_DIRS})
set(LINK_LIBS ${LINK_LIBS} ${ALSA_LIBRARIES})
endif()
if(ENABLE_MPD)
find_package("LibMPDClient" REQUIRED)
include_directories(${LIBMPDCLIENT_INCLUDE_DIRS})
link_directories(${LIBMPDCLIENT_LIBRARY_DIRS})
set(LINK_LIBS ${LINK_LIBS} ${LIBMPDCLIENT_LIBRARY})
endif()
#
# Install executable and wrapper
#
add_subdirectory("${PROJECT_SOURCE_DIR}/src")
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
if(ENABLE_I3)
add_subdirectory("${PROJECT_SOURCE_DIR}/contrib/i3ipcpp" EXCLUDE_FROM_ALL)
include_directories(${SIGCPP_INCLUDE_DIRS} ${I3IPCpp_INCLUDE_DIRS})
link_directories(${SIGCPP_LIBRARY_DIRS} ${I3IPCpp_LIBRARY_DIRS})
set(LINK_LIBS "${LINK_LIBS};${I3IPCpp_LIBRARIES}")
endif()
target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})
install(TARGETS ${PROJECT_NAME} DESTINATION "bin")
install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/lemonbuddy_wrapper.sh" DESTINATION "bin")
#
# Uninstall target
#
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/cmake/uninstall.cmake"
IMMEDIATE @ONLY)
add_custom_target(uninstall COMMAND ${CMAKE_COMMAND}
-P "${CMAKE_CURRENT_BINARY_DIR}/cmake/uninstall.cmake")

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
The MIT License (MIT)
Copyright (c) 2016 Michael Carlberg
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

11
README.md Normal file
View File

@ -0,0 +1,11 @@
Lemonbuddy
==========
A fast and easy-to-use wrapper for [Lemonbar](https://github.com/LemonBoy/bar/),
helping you to style and organize your favorite status bar.
### Todo
- Write this README
- Create build script to make it easier to build the project for people who
aren't familiar with cmake

View File

@ -0,0 +1,31 @@
# - Try to find LibMPDClient
# Once done, this will define
#
# LIBMPDCLIENT_FOUND - System has LibMPDClient
# LIBMPDCLIENT_INCLUDE_DIRS - The LibMPDClient include directories
# LIBMPDCLIENT_LIBRARIES - The libraries needed to use LibMPDClient
# LIBMPDCLIENT_DEFINITIONS - Compiler switches required for using LibMPDClient
find_package(PkgConfig)
pkg_check_modules(PC_LIBMPDCLIENT QUIET libmpdclient)
set(LIBMPDCLIENT_DEFINITIONS ${PC_LIBMPDCLIENT_CFLAGS_OTHER})
find_path(LIBMPDCLIENT_INCLUDE_DIR
NAMES mpd/player.h
HINTS ${PC_LIBMPDCLIENT_INCLUDEDIR} ${PC_LIBMPDCLIENT_INCLUDE_DIRS}
)
find_library(LIBMPDCLIENT_LIBRARY
NAMES mpdclient
HINTS ${PC_LIBMPDCLIENT_LIBDIR} ${PC_LIBMPDCLIENT_LIBRARY_DIRS}
)
set(LIBMPDCLIENT_LIBRARIES ${LIBMPDCLIENT_LIBRARY})
set(LIBMPDCLIENT_INCLUDE_DIRS ${LIBMPDCLIENT_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibMPDClient DEFAULT_MSG
LIBMPDCLIENT_LIBRARY LIBMPDCLIENT_INCLUDE_DIR
)
mark_as_advanced(LIBMPDCLIENT_LIBRARY LIBMPDCLIENT_INCLUDE_DIR)

26
cmake/uninstall.cmake.in Normal file
View File

@ -0,0 +1,26 @@
set(INSTALL_MANIFEST "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
if (NOT EXISTS ${INSTALL_MANIFEST})
message(FATAL_ERROR
"Cannot find install manifest:
\"${INSTALL_MANIFEST}\"")
endif(NOT EXISTS ${INSTALL_MANIFEST})
file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
string(REGEX REPLACE "\n" ";" files "${files}")
list(REVERSE files)
foreach (file ${files})
message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
if (EXISTS "$ENV{DESTDIR}${file}")
execute_process(
COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}"
OUTPUT_VARIABLE rm_out
RESULT_VARIABLE rm_retval)
if(NOT ${rm_retval} EQUAL 0)
message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
endif (NOT ${rm_retval} EQUAL 0)
else (EXISTS "$ENV{DESTDIR}${file}")
message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
endif (EXISTS "$ENV{DESTDIR}${file}")
endforeach(file)

947
config Normal file
View File

@ -0,0 +1,947 @@
;
; Bar configurations
; ---------------------------------------
;
; Quote the value to keep spaces:
; key = " value"
;
; Values for the current bar can be accessed using:
; ${BAR.foreground}
;
; Other values can be referenced using:
; ${section.key}
;
; format:NAME = <TAG...>
; label:NAME[:(foreground|background|(under|over)line|font|padding)] =
; icon:NAME[:(foreground|background|(under|over)line|font|padding)] =
; ramp:NAME:[0-9]+[:(foreground|background|(under|over)line|font|padding)] =
; animation:NAME:[0-9]+[:(foreground|background|(under|over)line|font|padding)] =
;
; bar:NAME:width =
; bar:NAME:format = (tokens: %fill% %indicator% %empty%)
; bar:NAME:foreground:[0-9]+ =
; bar:NAME:indicator[:(foreground|background|(under|over)line|font|padding)] =
; bar:NAME:fill[:(foreground|background|(under|over)line|font|padding)] =
; bar:NAME:empty[:(foreground|background|(under|over)line|font|padding)] =
;
; These keys can be used to style the module container
; format:NAME:spacing = N (unit: whitespace)
; format:NAME:padding = N (unit: whitespace)
; format:NAME:margin = N (unit: whitespace)
; format:NAME:offset = N (unit: pixels)
; format:NAME:foreground = #hexcolor
; format:NAME:background = #hexcolor
; format:NAME:underline = #hexcolor
; format:NAME:overline = #hexcolor
;
; Module types:
; internal/backlight
; internal/battery
; internal/bspwm
; internal/cpu
; internal/date
; internal/memory
; internal/mpd
; internal/network
; internal/rtorrent
; internal/volume
;
; custom/text
; content
; click:(left|middle|right)
; scroll:(up|down)
; custom/script
; exec
; interval
; format
; click:(left|middle|right)
; scroll:(up|down)
; custom/menu
; format
; label:open
; label:close
; menu:LEVEL:n
; menu:LEVEL:n:exec
;
[bar/top]
monitor = eDP-1
width = 100%
height = 30
clickareas = 35
background = #222222
foreground = #eefafafa
linecolor = ${bar/top.background}
;separator = |
spacing = 3
lineheight = 14
;padding_left = 5
;padding_right = 2
module_margin_left = 3
module_margin_right = 3
font:0 = NotoSans-Regular:size=8;0
font:1 = MaterialIcons:size=10;0
font:2 = Termsynu:size=8;-1
font:3 = FontAwesome:size=10;0
; modules:left = powermenu mpd
; modules:right = backlight volume wireless-network wired-network battery date
modules:right = battery
[bar/bottom]
monitor = eDP-1
bottom = true
width = 100%
height = 27
;clickareas = 25
background = #111111
foreground = #ccffffff
linecolor = ${bar/bottom.background}
spacing = 3
lineheight = 2
;padding_left = 0
padding_right = 4
module_margin_left = 0
module_margin_right = 6
; font:idx = font:size=N;offsetY
font:0 = NotoSans-Regular:size=8;0
font:1 = Unifont:size=6;-3
;font:1 = Termsynu:size=8;-1
font:2 = FontAwesome:size=8;-2
font:3 = NotoSans-Regular:size=8;-1
font:4 = MaterialIcons:size=10;-1
modules:left = bspwm
modules:right = rtorrent cpu memory
; modules:right = cpu memory
[bar/external_bottom]
monitor = HDMI-1
bottom = true
width = 100%
height = 27
;clickareas = 25
background = #111111
foreground = #ccffffff
linecolor = ${bar/external_bottom.background}
spacing = 3
lineheight = 2
;padding_left = 0
padding_right = 3
module_margin_left = 0
module_margin_right = 6
font:0 = NotoSans-Regular:size=8;0
font:1 = Unifont:size=6;-3
font:2 = FontAwesome:size=8;-2
font:3 = NotoSans-Regular:size=8;-1
font:4 = MaterialIcons:size=10;0
modules:left = bspwm
modules:right = clock
[module/backlight]
type = internal/backlight
; Use the following command to list available cards:
; $ ls -1 /sys/class/backlight/
card = intel_backlight
; Available tags:
; <label> (default)
; <bar>
format = %{A4:backlight_percentage%__p5:}%{A5:backlight_percentage%__m5:} <ramp> <bar> %{A}%{A}
; Available tokens:
; %percentage% (default)
;label = %percentage%
; Required if <ramp> is used
ramp:0 = 
ramp:1 = 
ramp:2 = 
; Required if <bar> is used
bar:width = 10
bar:format = %{+u}%{+o}%fill%%{-u}%{-o}%indicator%%{+u}%{+o}%empty%%{-u}%{-o}
bar:indicator = |
bar:indicator:foreground = #ddffffff
bar:indicator:font = 3
bar:fill = █
bar:fill:foreground = #99ffffff
bar:fill:font = 3
bar:empty = █
bar:empty:font = 3
bar:empty:foreground = #44ffffff
[module/battery]
type = internal/battery
;battery = BAT0
;adapter = ADP1
full_at = 99
; Available tags:
; <label:charging> (default)
; <bar:capaity>
; <ramp:capacity>
; <animation:charging>
format:charging = Charging <animation:charging> <label:charging>
; Available tags:
; <label:discharging> (default)
; <bar:capaity>
; <ramp:capacity>
format:discharging = Discharging <ramp:capacity> <label:discharging>
; Available tags:
; <label:full> (default)
; <bar:capaity>
; <ramp:capacity>
format:full = Fully charged <ramp:capacity> <label:full>
; Available tokens:
; %percentage% (default)
;label:charging = Charging %percentage%
; Available tokens:
; %percentage% (default)
;label:discharging = Discharging %percentage%
; Available tokens:
; %percentage% (default)
;label:full = Fully charged
; Required if <ramp:capacity> is used
ramp:capacity:0 = 
ramp:capacity:0:foreground = #f53c3c
ramp:capacity:1 = 
ramp:capacity:1:foreground = #ffa900
ramp:capacity:2 = 
ramp:capacity:2:foreground = #ffffff
ramp:capacity:3 = 
ramp:capacity:3:foreground = #ffffff
ramp:capacity:4 = 
ramp:capacity:4:foreground = #ffffff
; Required if <bar:capacity> is used
bar:capacity:width = 10
bar:capacity:format = %{+u}%{+o}%fill%%empty%%{-u}%{-o}
bar:capacity:fill = █
bar:capacity:fill:foreground = #ddffffff
bar:capacity:fill:font = 3
bar:capacity:empty = █
bar:capacity:empty:font = 3
bar:capacity:empty:foreground = #44ffffff
; Required if <animation:charging> is used
; animation:charging:0 = %{T3}%{F#ddffffff}%{+u}%{+o}█%{F#44ffffff}█████████%{T-}%{F-}%{-u}%{-o}
; animation:charging:1 = %{T3}%{F#ddffffff}%{+u}%{+o}██%{F#44ffffff}████████%{T-}%{F-}%{-u}%{-o}
; animation:charging:2 = %{T3}%{F#ddffffff}%{+u}%{+o}███%{F#44ffffff}███████%{T-}%{F-}%{-u}%{-o}
; animation:charging:3 = %{T3}%{F#ddffffff}%{+u}%{+o}████%{F#44ffffff}██████%{T-}%{F-}%{-u}%{-o}
; animation:charging:4 = %{T3}%{F#ddffffff}%{+u}%{+o}█████%{F#44ffffff}█████%{T-}%{F-}%{-u}%{-o}
; animation:charging:5 = %{T3}%{F#ddffffff}%{+u}%{+o}██████%{F#44ffffff}████%{T-}%{F-}%{-u}%{-o}
; animation:charging:6 = %{T3}%{F#ddffffff}%{+u}%{+o}███████%{F#44ffffff}███%{T-}%{F-}%{-u}%{-o}
; animation:charging:7 = %{T3}%{F#ddffffff}%{+u}%{+o}████████%{F#44ffffff}██%{T-}%{F-}%{-u}%{-o}
; animation:charging:8 = %{T3}%{F#ddffffff}%{+u}%{+o}█████████%{F#44ffffff}█%{T-}%{F-}%{-u}%{-o}
; animation:charging:9 = %{T3}%{F#ddffffff}%{+u}%{+o}██████████%{T-}%{F-}%{-u}%{-o}
animation:charging:0 = 
animation:charging:1 = 
animation:charging:2 = 
animation:charging:3 = 
animation:charging:4 = 
animation:charging:framerate_ms = 750
[module/bspwm]
type = internal/bspwm
; workspace_icon:[0-9]+ = label;icon
; workspace_icon:default = icon
workspace_icon:0 = term;
workspace_icon:1 = web;
workspace_icon:2 = code;
workspace_icon:3 = music;
workspace_icon:4 = irssi;
workspace_icon:default = 
; Available tags:
; <label:state> (default) - gets replaced with <label:(active|urgent|occupied|empty)>
; <label:mode> - gets replaced with <label:(monocle|tiled|fullscreen|floating|locked|sticky|private)>
format = <label:state> <label:mode>
; If any of these are defined, the workspace/mode colors will get overridden
; with these values if the monitor is out of focus
;label:dimmed:foreground = #555
;label:dimmed:background = ${BAR.background}
label:dimmed:underline = ${BAR.background}
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:active = %icon%
label:active:foreground = #ffffff
label:active:background = #3f3f3f
label:active:underline = #fba922
label:active:font = 4
label:active:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:occupied = %icon%
label:occupied:underline = #555555
label:occupied:font = 4
label:occupied:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:urgent = %icon%
label:urgent:foreground = #000000
label:urgent:background = #bd2c40
label:urgent:underline = #9b0a20
label:urgent:font = 4
label:urgent:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:empty = %icon%
label:empty:foreground = #55ffffff
label:empty:font = 4
label:empty:padding = 4
; Available tokens:
; None
label:monocle = 
label:monocle:underline = ${module/bspwm.label:active:underline}
label:monocle:padding = 2
;label:tiled = 
;label:fullscreen = 
;label:floating = 
label:locked = 
label:locked:foreground = #bd2c40
label:locked:underline = ${module/bspwm.label:monocle:underline}
label:locked:padding = ${module/bspwm.label:monocle:padding}
label:sticky = 
label:sticky:foreground = #fba922
label:sticky:underline = ${module/bspwm.label:monocle:underline}
label:sticky:padding = ${module/bspwm.label:monocle:padding}
label:private = 
label:private:foreground = #bd2c40
label:private:underline = ${module/bspwm.label:monocle:underline}
label:private:padding = ${module/bspwm.label:monocle:padding}
[module/i3]
type = internal/i3
; workspace_icon:[0-9]+ = label;icon
; workspace_icon:default = icon
workspace_icon:0 = 1;
workspace_icon:1 = 2;
workspace_icon:2 = 3;
workspace_icon:3 = 4;
workspace_icon:4 = 5;
workspace_icon:default = 
; Available tags:
; <label:state> (default) - gets replaced with <label:(focused|unfocused|visible|urgent)>
;format = <label:state>
; If any of these are defined, the workspace/mode colors will get overridden
; with these values if the monitor is out of focus
;label:dimmed:foreground = #555
;label:dimmed:background = ${BAR.background}
label:dimmed:underline = ${BAR.background}
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:focused = %icon%
label:focused:foreground = #ffffff
label:focused:background = #3f3f3f
label:focused:underline = #fba922
label:focused:font = 4
label:focused:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:unfocused = %icon%
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:visible = %icon%
label:visible:underline = #555555
label:visible:font = 4
label:visible:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:urgent = %icon%
label:urgent:foreground = #000000
label:urgent:background = #bd2c40
label:urgent:underline = #9b0a20
label:urgent:font = 4
label:urgent:padding = 4
; Available tokens:
; None
label:monocle = 
label:monocle:underline = ${module/bspwm.label:active:underline}
label:monocle:padding = 2
;label:tiled = 
;label:fullscreen = 
;label:floating = 
label:locked = 
label:locked:foreground = #bd2c40
label:locked:underline = ${module/bspwm.label:monocle:underline}
label:locked:padding = ${module/bspwm.label:monocle:padding}
label:sticky = 
label:sticky:foreground = #fba922
label:sticky:underline = ${module/bspwm.label:monocle:underline}
label:sticky:padding = ${module/bspwm.label:monocle:padding}
label:private = 
label:private:foreground = #bd2c40
label:private:underline = ${module/bspwm.label:monocle:underline}
label:private:padding = ${module/bspwm.label:monocle:padding}
[module/cpu]
type = internal/cpu
; Seconds to sleep between updates
interval = 0.5
; Available tags:
; <label> (default)
; <bar:load>
; <ramp:load>
; <ramp:load_per_core>
format = <label> <ramp:load_per_core>
; Available tokens:
; %percentage% (default)
label = CPU
; Required if <ramp:core_load> is used
ramp:load_per_core:0 = ▁
ramp:load_per_core:0:font = 2
ramp:load_per_core:0:foreground = #55aa55
ramp:load_per_core:1 = ▂
ramp:load_per_core:1:font = 2
ramp:load_per_core:1:foreground = #55aa55
ramp:load_per_core:2 = ▃
ramp:load_per_core:2:font = 2
ramp:load_per_core:2:foreground = #55aa55
ramp:load_per_core:3 = ▄
ramp:load_per_core:3:font = 2
ramp:load_per_core:3:foreground = #55aa55
ramp:load_per_core:4 = ▅
ramp:load_per_core:4:font = 2
ramp:load_per_core:4:foreground = #f5a70a
ramp:load_per_core:5 = ▆
ramp:load_per_core:5:font = 2
ramp:load_per_core:5:foreground = #f5a70a
ramp:load_per_core:6 = ▇
ramp:load_per_core:6:font = 2
ramp:load_per_core:6:foreground = #ff5555
ramp:load_per_core:7 = █
ramp:load_per_core:7:font = 2
ramp:load_per_core:7:foreground = #ff5555
; Required if <bar:total_load> is used
;bar:total_load:width = 10
;bar:total_load:indicator = |
;bar:total_load:fill = =
;bar:total_load:empty = =
[module/date]
type = internal/date
; see "man date" for formatting
; if date_detailed is defined, clicking the area will toggle between formats
; if you want to use lemonbar tags here you need to use %%{...}
date = %%{F#888}%Y-%m-%d%%{F-} %%{F#fff}%H:%M%%{F-}
date_detailed = %%{F#888}%A, %d %B %Y% %%{F#fff}%H:%M%%{F#666}:%%{F#fba922}%S%%{F-}
; Seconds to sleep between updates
;interval = 1.0
; Available tags:
; <date> (default)
format =  <date>
format:background = #111
format:padding = 5
[module/memory]
type = internal/memory
; Seconds to sleep between updates
;interval = 1.0
; Available tags:
; <label> (default)
; <bar:used>
; <bar:free>
format = <label> <bar:used>
; Available tokens:
; %percentage_used% (default)
; %percentage_free%
; %gb_used%
; %gb_free%
; %gb_total%
; %mb_used%
; %mb_free%
; %mb_total%
label = RAM
; Required if <bar:used> is used
bar:used:width = 50
bar:used:foreground:0 = #55aa55
bar:used:foreground:1 = #557755
bar:used:foreground:2 = #f5a70a
bar:used:foreground:3 = #ff5555
bar:used:indicator = ▐
bar:used:indicator:font = 2
bar:used:indicator:foreground = #ddffffff
bar:used:fill = ▐
bar:used:fill:font = 2
bar:used:empty = ▐
bar:used:empty:font = 2
bar:used:empty:foreground = #444444
; Required if <bar:free> is used
;bar:free:width = 50
;bar:free:foreground:0 = #ff5555
;bar:free:foreground:1 = #f5a70a
;bar:free:foreground:2 = #557755
;bar:free:foreground:3 = #55aa55
;bar:free:indicator = ▐
;bar:free:indicator:font = 2
;bar:free:indicator:foreground = #ddffffff
;bar:free:fill = ▐
;bar:free:fill:font = 2
;bar:free:empty = ▐
;bar:free:empty:font = 2
;bar:free:empty:foreground = #444444
[module/mpd]
type = internal/mpd
; Seconds to sleep between progressbar/song timer updates
;interval = 0.5
; Available tags:
; <label:song> (default)
; <label:time>
; <bar:progress>
; <toggle> - gets replaced with <icon:(pause|play)>
; <icon:random>
; <icon:repeat>
; <icon:repeatone>
; <icon:prev>
; <icon:stop>
; <icon:play>
; <icon:pause>
; <icon:next>
format:online = <icon:prev> <icon:stop> <toggle> <icon:next> <icon:repeat> <icon:random> <bar:progress> <label:time> <label:song>
; Available tags:
; <label:offline>
format:offline = <label:offline>
format:offline:offset = -8
; Available tokens:
; %artist%
; %album%
; %title%
; Default: %artist% - %title%
;label:song =  %artist% - %title%
;label:song:foreground = ${BAR.foreground}
; Available tokens:
; %elapsed%
; %total%
; Default: %elapsed% / %total%
;label:time = %elapsed% / %total%
label:time:foreground = #66fafafa
; Available tokens:
; None
label:offline =  mpd is off
label:offline:foreground = #66fafafa
icon:play = 
icon:pause = 
icon:stop = 
icon:prev = 
icon:next = 
icon:random = 
icon:repeat = 
;icon:repeatone = 🔂
; Used to display the state of random/repeat/repeatone
toggle_on:foreground =
toggle_off:foreground = #66fafafa
; Required if <bar:progress> is used
bar:progress:width = 45
bar:progress:format = %{+u}%{+o}%fill%%{-u}%{-o}%indicator%%{+u}%{+o}%empty%%{-u}%{-o}
bar:progress:indicator = |
bar:progress:indicator:foreground = #ddffffff
bar:progress:indicator:font = 3
bar:progress:fill = █
bar:progress:fill:foreground = #aaffffff
bar:progress:fill:font = 3
bar:progress:empty = █
bar:progress:empty:font = 3
bar:progress:empty:foreground = #44ffffff
[module/wireless-network]
type = internal/network
interface = net1
; Seconds to sleep between updates
interval = 2.0
; Seconds to sleep between connectivity tests
; A value of 0 disables the testing
; Default: 0
connectivity_test_interval = 10
; Available tags:
; <label:connected> (default)
; <ramp:signal>
format:connected = <ramp:signal> <label:connected>
; Available tags:
; <label:disconnected> (default)
;format:disconnected = <label:disconnected>
; Available tags:
; <label:packetloss> (default)
; <animation:packetloss>
; format:packetloss = <animation:packetloss> <label:packetloss>
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
label:connected = %essid%
label:connected:foreground = #eefafafa
; Available tokens:
; %ifname% [wireless+wired]
; Default: (none)
label:disconnected =  not connected
label:disconnected:foreground = #66ffffff
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
; ------------------------- NOT ACTIVATED (Needs more testing)
;label:packetloss = %essid%
;label:packetloss:foreground = #eefafafa
; Required if <ramp:signal> is used
ramp:signal:0 = 
ramp:signal:0:foreground = #33ffffff
ramp:signal:1 = 
ramp:signal:1:foreground = #66ffffff
ramp:signal:2 = 
ramp:signal:2:foreground = #99ffffff
ramp:signal:3 = 
ramp:signal:3:foreground = #ccffffff
ramp:signal:4 = 
ramp:signal:4:foreground = #ffffffff
; Required if <animation:packetloss> is used
animation:packetloss:0 = 
animation:packetloss:0:foreground = #ffa64c
animation:packetloss:1 = 
animation:packetloss:1:foreground = ${bar/top.foreground}
animation:packetloss:framerate_ms = 500
[module/wired-network]
type = internal/network
interface = net0
; Seconds to sleep between updates
interval = 2.0
; Seconds to sleep between connectivity tests
; A value of 0 disables the testing
; Default: 0
;connectivity_test_interval = 0
; Available tags:
; <label:connected> (default)
; <ramp:signal>
;format:connected = <label:connected>
; Available tags:
; <label:disconnected> (default)
;format:disconnected = <label:disconnected>
; Available tags:
; <label:packetloss> (default)
; <animation:packetloss>
; format:packetloss = <animation:packetloss> <label:packetloss>
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
label:connected =  %{T3}%local_ip%%{T-}
;label:connected:foreground = #eefafafa
; Available tokens:
; %ifname% [wireless+wired]
; Default: (none)
;label:disconnected =  not connected
;label:disconnected:foreground = #66ffffff
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
; ------------------------- NOT ACTIVATED (Needs more testing)
;label:packetloss = %essid%
;label:packetloss:foreground = #eefafafa
[module/rtorrent]
type = internal/rtorrent
script = /home/jaagr/var/github/jaagr/lemonbuddy/scripts/torrents.sh
rtorrent_session_dir = /home/jaagr/.cache/rtorrent
display_count = 2
title_maxlen = 30
; Available tags:
; <label> (default)
; <bar:progress>
;format = <label>
; Available tokens:
; %title%
; %percentage%
; Default: %label% (%percentage%)
label = %{F#fba922}%{F-} %{F#eefafafa}%title% %percentage%%{F-}
;label:foreground = #eefafafa
; Required if <bar:progress> is used
;bar:progress:width = 10
;bar:progress:format = %{+u}%{+o}%fill%%{-u}%{-o}%indicator%%{+u}%{+o}%empty%%{-u}%{-o}
;bar:progress:indicator = |
;bar:progress:indicator:foreground = ${BAR.foreground}
;bar:progress:indicator:font = 2
;bar:progress:fill = █
;bar:progress:fill:foreground = #5a5
;bar:progress:fill:font = 2
;bar:progress:empty = █
;bar:progress:empty:foreground = #555
;bar:progress:empty:font = 2
[module/volume]
type = internal/volume
; Use the following command to list available mixer controls:
; $ amixer scontrols | sed -nr "s/.*'([[:alnum:]]+)'.*/\1/p"
speaker_mixer = Speaker
headphone_mixer = Headphone
; Use the following command to list available device controls
; $ amixer controls | sed -r "/CARD/\!d; s/.*=([0-9]+).*name='([^']+)'.*/printf '%3.0f: %s\n' '\1' '\2'/e" | sort
headphone_control_numid = 9
; Available tags:
; <label:volume> (default)
; <ramp:volume>
; <bar:volume>
format:volume = <ramp:volume> <label:volume>
; Available tags:
; <label:muted> (default)
; <ramp:volume>
; <bar:volume>
;format:muted = <label:muted>
; Available tokens:
; %percentage% (default)
;label:volume = %percentage%
label:volume:foreground = #ffffff
; Available tokens:
; %percentage% (default)
label:muted =  muted
label:muted:foreground = #66ffffff
; Required if <ramp:volume> is used
ramp:volume:0 = 
ramp:volume:0:foreground = #99ffffff
ramp:volume:1 = 
ramp:volume:1:foreground = #bbffffff
ramp:volume:2 = 
ramp:volume:2:foreground = #ddffffff
ramp:volume:3 = 
ramp:volume:3:foreground = #ffffffff
; Required if <bar:capacity> is used
bar:volume:width = 10
bar:volume:format = %{+u}%{+o}%fill%%empty%%{-u}%{-o}
bar:volume:fill = █
bar:volume:fill:foreground = #ddffffff
bar:volume:fill:font = 3
bar:volume:empty = █
bar:volume:empty:font = 3
bar:volume:empty:foreground = #44ffffff
[module/powermenu]
type = custom/menu
; Available tags:
; <label:toggle> (default) - gets replaced with <label:(open|close)>
; <menu> (default)
;format = <label:toggle> <menu>
format:background = #111111
format:padding = 3
label:open = 
label:close = 
; "menu:LEVEL:N" has the same properties as "label:NAME" with
; the additional "exec" property
;
; Available exec commands:
; menu_open:LEVEL
; menu_close
; Other commands will be executed using "/usr/bin/env sh -c $COMMAND"
menu:0:0 = Terminate WM
menu:0:0:foreground = #fba922
menu:0:0:exec = bspc quit -1
menu:0:1 = Reboot
menu:0:1:foreground = #fba922
menu:0:1:exec = menu_open:1
menu:0:2 = Power off
menu:0:2:foreground = #fba922
menu:0:2:exec = menu_open:2
menu:1:0 = Cancel
menu:1:0:foreground = #fba922
menu:1:0:exec = menu_open:0
menu:1:1 = Reboot
menu:1:1:foreground = #fba922
menu:1:1:exec = sudo reboot
menu:2:0 = Power off
menu:2:0:foreground = #fba922
menu:2:0:exec = sudo poweroff
menu:2:1 = Cancel
menu:2:1:foreground = #fba922
menu:2:1:exec = menu_open:0
[module/text-example]
type = custom/text
; "content" has the same properties as "format:NAME"
content = 
content:background = #000
content:foreground = #fff
content:padding = 4
; "click:(left|middle|right)" will be executed using "/usr/bin/env sh -c $COMMAND"
click:left = echo left
click:middle = echo middle
click:right = echo right
; "scroll:(up|down)" will be executed using "/usr/bin/env sh -c $COMMAND"
scroll:up = echo scroll up
scroll:down = echo scroll down
[module/script-example]
type = custom/script
; Available tokens:
; %counter%
;
; The "exec" command will be executed using "/usr/bin/env sh -c [command]"
exec = echo %counter%
; Seconds to sleep between updates
interval = 0.5
; Available tags:
; <output> (default)
;format = <output>
format:background = #999
format:foreground = #000
format:padding = 4
; Available tokens:
; %counter%
;
; "click:(left|middle|right)" will be executed using "/usr/bin/env sh -c [command]"
click:left = echo left %counter%
click:middle = echo middle %counter%
click:right = echo right %counter%
; Available tokens:
; %counter%
;
; "scroll:(up|down)" will be executed using "/usr/bin/env sh -c [command]"
scroll:up = echo scroll up %counter%
scroll:down = echo scroll down %counter%
[module/clock]
type = internal/date
interval = 5
date = %%{F#999}%Y-%m-%d%%{F-} %%{F#fff}%H:%M%%{F-}
; vim:ft=dosini

11
contrib/dosini.vim Normal file
View File

@ -0,0 +1,11 @@
"
" Enables syntax folding for the configuration file.
" Removes the need to clutter the file with fold markers.
"
" Put the file in $VIM/after/syntax/dosini.vim
"
syn region dosiniSection start="^\[" end="\(\n\+\[\)\@=" contains=dosiniLabel,dosiniHeader,dosiniComment keepend fold
setlocal foldmethod=syntax
" Uncomment to start with folds open
"setlocal foldlevel=20

1
contrib/i3ipcpp Submodule

@ -0,0 +1 @@
Subproject commit ec2d88919d5037360e61932492eb3f98f9b9df28

@ -0,0 +1 @@
Subproject commit 8ed285ec2289761e6585090724f73093d62f290f

60
examples/config.i3.edp1 Normal file
View File

@ -0,0 +1,60 @@
; vim:ft=dosini
[bar/i3]
monitor = eDP1
height = 29
lineheight = 3
bottom = true
background = #e22
foreground = #000
linecolor = #000
spacing = 3
font:0 = NotoSans-Regular:size=12;0
font:1 = MaterialIcons:size=14;0
font:2 = FontAwesome:size=14;0
modules:left = i3
[module/i3]
type = internal/i3
; Only include workspaces located inside the
; current bar's monitor
;
; Default: true
local_workspaces = true
; Remove N chars from the start of the workspace name
; This is useful since you can define the i3 workspaces with
; both a friendly name and a number, e.g. "1.code", "2.chat", etc
; If you don't prefix the workspaces with a number in your i3
; config, all workspaces will be assigned a number of -1.
;
; Default: 0
workspace_name_strip_nchars = 2
; workspae_icon:N = workspace_name;icon
workspace_icon:0 = term;
workspace_icon:1 = web;
workspace_icon:2 = code;
workspace_icon:3 = music;
workspace_icon:4 = irssi;
workspace_icon:default = 
label:focused = %icon% %name%
label:focused:padding = 2
label:focused:underline = #000
label:unfocused = %index%
label:unfocused:padding = 2
label:unfocused:foreground = #99ee2222
;label:visible = %index%
label:visible:padding = 2
; label:visible:underline = #444
; label:urgent = %icon% %index%
; label:urgent:padding = 2
label:dimmed:underline = ${bar/i3.background}

172
examples/config.i3.hdmi1 Normal file
View File

@ -0,0 +1,172 @@
; vim:ft=dosini
[bar/i3]
monitor = HDMI1
height = 35
lineheight = 4
dock = true
offset_x = 60
offset_y = 15
width = 100%
background = #228f8f8f
foreground = #ffffffff
spacing = 3
; font:0 = NotoSans-Bold:size=10;0
font:0 = LiberationMono:weight=200:size=9;0
font:1 = MaterialIcons:size=11;0
font:2 = FontAwesome:weight=200:size=10;0
font:3 = Termsynu:size=8;-1
padding_left = 2
padding_right = 2
modules:left = i3
modules:right = date
[bar/i3_bottom]
monitor = HDMI1
height = 35
lineheight = 4
dock = true
offset_x = 60
offset_y = 15
width = 100%
bottom = true
background = #228f8f8f
foreground = #ffffffff
spacing = 3
; font:0 = NotoSans-Bold:size=10;0
font:0 = LiberationMono:weight=200:size=9;0
font:1 = MaterialIcons:size=11;0
font:2 = FontAwesome:weight=200:size=10;0
font:3 = Termsynu:size=8;-1
padding_left = 2
padding_right = 2
modules:center = mpd
[module/i3]
type = internal/i3
local_workspaces = true
workspace_name_strip_nchars = 2
workspace_icon:0 = console;
workspace_icon:1 = web;
workspace_icon:2 = code;
workspace_icon:3 = music;
workspace_icon:4 = irssi;
workspace_icon:default = 
label:focused = %icon% %name%
label:focused:padding = 1
label:focused:underline = #9a4
label:unfocused = %index%
label:unfocused:padding = 2
label:unfocused:margin = 1
label:unfocused:foreground = #555
label:unfocused:underline = #222
;label:visible = %index%
label:visible:padding = 1
; label:visible:underline = #444
; label:urgent = %icon% %index%
; label:urgent:padding = 1
label:dimmed:underline = ${bar/i3.background}
[module/date]
type = internal/date
; see "man date" for formatting
; if date_detailed is defined, clicking the area will toggle between formats
; if you want to use lemonbar tags here you need to use %%{...}
date = %%{F#888}%Y-%m-%d%%{F-} %%{F#fff} %H:%M%%{F-}
date_detailed = %%{F#aaa}%A, %d %B %Y %%{F#fff} %H:%M%%{F#666}:%%{F#9a4}%S%%{F-}
; Seconds to sleep between updates
;interval = 1.0
; Available tags:
; <date> (default)
format = <date>
format:spacing = 1
format:underline = #333
[module/mpd]
type = internal/mpd
; Seconds to sleep between progressbar/song timer updates
;interval = 0.5
; Available tags:
; <label:song> (default)
; <label:time>
; <bar:progress>
; <toggle> - gets replaced with <icon:(pause|play)>
; <icon:random>
; <icon:repeat>
; <icon:repeatone>
; <icon:prev>
; <icon:stop>
; <icon:play>
; <icon:pause>
; <icon:next>
format:online = <icon:prev> <icon:stop> <toggle> <icon:next> <icon:repeat> <icon:random> <bar:progress> <label:time> <label:song>
; Available tags:
; <label:offline>
format:offline = <label:offline>
format:offline:offset = -8
; Available tokens:
; %artist%
; %album%
; %title%
; Default: %artist% - %title%
;label:song =  %artist% - %title%
;label:song:foreground = ${BAR.foreground}
; Available tokens:
; %elapsed%
; %total%
; Default: %elapsed% / %total%
;label:time = %elapsed% / %total%
label:time:foreground = #66fafafa
; Available tokens:
; None
label:offline =  mpd is off
label:offline:foreground = #66fafafa
icon:play = 
icon:pause = 
icon:stop = 
icon:prev = 
icon:next = 
icon:random = 
icon:repeat = 
;icon:repeatone = 🔂
; Used to display the state of random/repeat/repeatone
toggle_on:foreground =
toggle_off:foreground = #66fafafa
; Required if <bar:progress> is used
bar:progress:width = 25
bar:progress:format = %fill%%indicator%%empty%
bar:progress:indicator = |
bar:progress:indicator:foreground = #ffffffff
bar:progress:indicator:font = 4
bar:progress:fill = ─
bar:progress:fill:foreground = #ddffffff
bar:progress:fill:font = 4
bar:progress:empty = ─
bar:progress:empty:font = 4
bar:progress:empty:foreground = #44ffffff

229
examples/config.test Normal file
View File

@ -0,0 +1,229 @@
; vim:ft=dosini
[bar/test]
;locale = sv_SE.UTF-8
monitor = HDMI1
; monitor = LVDS1
; width = 100%
height = 29
;lineheight = 14
lineheight = 4
background = #222222
foreground = #eefafafa
linecolor = #222222
spacing = 3
module_margin_left = 5
module_margin_right = 5
; font:0 = NotoSans-Regular:size=8;0
font:0 = Termsynu:size=8;1
font:1 = MaterialIcons:size=10;0
font:2 = FontAwesome:size=10;0
; modules:left = counter battery bspwm cpu date memory mpd network volume
; modules:left = counter battery bspwm
; modules:left = counter battery bspwm cpu date memory mpd network network2 volume
;modules:center = i3 network network2
modules:center = i3
; modules:center = counter counter2
; modules:left = network
[module/counter]
type = internal/counter
interval = 1
[module/counter2]
type = internal/counter
interval = 1.25
[module/counter3]
type = internal/counter
interval = 1.5
[module/counter4]
type = internal/counter
interval = 2
[module/backlight]
type = internal/backlight
card = intel_backlight
;interval = 1
; Available tags:
; <label> (default)
; <bar>
format = <bar>
; Available tokens:
; %percentage% (default)
;label = %percentage%
; Required if <bar> is used
bar:width = 25
bar:format = %{+u}%{+o}%fill%%empty%%{-u}%{-o}
bar:fill = █
bar:fill:foreground = #99ffffff
bar:fill:font = 3
bar:empty = █
bar:empty:font = 3
bar:empty:foreground = #44ffffff
[module/battery]
type = internal/battery
full_at = 99
format:charging = <ramp:capacity> <label:charging>
format:discharging = <ramp:capacity> <label:discharging>
format:full = <ramp:capacity> <label:full>
label:charging = Charging %percentage%
label:discharging = Discharging %percentage%
label:full = Fully charged
ramp:capacity:0 = 
ramp:capacity:1 = 
ramp:capacity:2 = 
ramp:capacity:3 = 
ramp:capacity:4 = 
[module/bspwm]
type = internal/bspwm
label:active = %name%
label:occupied = %index%
label:urgent = %index%
label:empty = %index%
[module/cpu]
type = internal/cpu
label = CPU %percentage%
[module/date]
type = internal/date
;date = %Y-%m-%d
date = %A, %d %B %Y %H:%M:%S
[module/i3]
type = internal/i3
workspace_icon:0 = 1;
workspace_icon:1 = 2;
workspace_icon:2 = 3;
workspace_icon:3 = 4;
workspace_icon:4 = 5;
workspace_icon:default = 
label:focused = %icon% %name% %index%
label:focused:padding = 2
label:focused:underline = #ff0
label:unfocused = %icon% %index%
label:unfocused:padding = 2
label:unfocused:foreground = #55ffffff
label:visible = %icon% %index%
label:visible:padding = 2
label:visible:underline = #444
label:urgent = %icon% %index%
label:urgent:padding = 2
[module/memory]
type = internal/memory
label = RAM %percentage_used%
[module/mpd]
type = internal/mpd
format:online = <toggle> <icon:random> <icon:repeatone> <label:time> <label:song>
label:offline = mpd is off
icon:play = ►
icon:pause = ◼
; icon:random = 🔀
; icon:repeatone = 🔂
icon:random = random
icon:repeatone = single
toggle_on:foreground = #f90
[module/network]
type = internal/network
interface = net1
label:connected = [%ifname%] %essid% 
label:packetloss = (!!) [%ifname%] %essid% 
; label:connected = %ifname% %local_ip%
; label:disconnected = not connected
; label:packetloss = %essid%
[module/network2]
type = internal/network
interface = net0
label:connected = [%ifname%] %local_ip% 
label:packetloss = (!!) [%ifname%] %local_ip% 
; label:connected = %ifname% %local_ip%
; label:disconnected = not connected
; label:packetloss = %essid%
; [module/rtorrent]
; type = internal/rtorrent
; script = /home/jaagr/var/github/jaagr/lemonbuddy/scripts/torrents.sh
; rtorrent_session_dir = /home/jaagr/.cache/rtorrent
; display_count = 2
; title_maxlen = 30
[module/volume]
type = internal/volume
speaker_mixer = Speaker
headphone_mixer = Headphone
headphone_control_numid = 9
[module/custom_counter]
type = custom/script
exec = echo %counter%
interval = 1
[module/custom_text]
type = custom/text
; "content" has the same properties as "format:NAME"
; content = 
content = test
content:background = #000
content:foreground = #fff
content:padding = 4
; "click:(left|middle|right)" will be executed using "/usr/bin/env sh -c $COMMAND"
click:left = echo left
click:middle = echo middle
click:right = echo right
; "scroll:(up|down)" will be executed using "/usr/bin/env sh -c $COMMAND"
scroll:up = echo scroll up
scroll:down = echo scroll down
[module/custom_menu]
type = custom/menu
; Available tags:
; <label:toggle> (default) - gets replaced with <label:(open|close)>
; <menu> (default)
;format = <label:toggle> <menu>
format:background = #111111
format:padding = 3
label:open = OPEN
label:close = CLOSE
menu:0:0 = Action 1
menu:0:0:foreground = #fba922
menu:0:0:exec = notify-send Action \#1
menu:0:1 = Action 2
menu:0:1:foreground = #fba922
menu:0:1:exec = menu_open:1
menu:0:2 = Action 3
menu:0:2:foreground = #fba922
menu:0:2:exec = menu_open:2
menu:1:0 = Cancel
menu:1:0:foreground = #fba922
menu:1:0:exec = menu_open:0
menu:1:1 = Confirm
menu:1:1:foreground = #fba922
menu:1:1:exec = notify-send Action \#2
menu:2:0 = Confirm
menu:2:0:foreground = #fba922
menu:2:0:exec = notify-send Action \#3
menu:2:1 = Cancel
menu:2:1:foreground = #fba922
menu:2:1:exec = menu_open:0

870
examples/config.white Normal file
View File

@ -0,0 +1,870 @@
;
; Bar configurations
; ---------------------------------------
;
; Quote the value to keep spaces:
; key = " value"
;
; Values for the current bar can be accessed using:
; ${BAR.foreground}
;
; Other values can be referenced using:
; ${section.key}
;
; format:NAME = <TAG...>
; label:NAME[:(foreground|background|(under|over)line|font|padding)] =
; icon:NAME[:(foreground|background|(under|over)line|font|padding)] =
; ramp:NAME:[0-9]+[:(foreground|background|(under|over)line|font|padding)] =
; animation:NAME:[0-9]+[:(foreground|background|(under|over)line|font|padding)] =
;
; bar:NAME:width =
; bar:NAME:format = (tokens: %fill% %indicator% %empty%)
; bar:NAME:foreground:[0-9]+ =
; bar:NAME:indicator[:(foreground|background|(under|over)line|font|padding)] =
; bar:NAME:fill[:(foreground|background|(under|over)line|font|padding)] =
; bar:NAME:empty[:(foreground|background|(under|over)line|font|padding)] =
;
; These keys can be used to style the module container
; format:NAME:spacing = N (unit: whitespace)
; format:NAME:padding = N (unit: whitespace)
; format:NAME:margin = N (unit: whitespace)
; format:NAME:offset = N (unit: pixels)
; format:NAME:foreground = #hexcolor
; format:NAME:background = #hexcolor
; format:NAME:underline = #hexcolor
; format:NAME:overline = #hexcolor
;
; Module types:
; internal/backlight
; internal/battery
; internal/bspwm
; internal/cpu
; internal/date
; internal/memory
; internal/mpd
; internal/network
; internal/rtorrent
; internal/volume
;
; custom/text
; content
; click:(left|middle|right)
; scroll:(up|down)
; custom/script
; exec
; interval
; format
; click:(left|middle|right)
; scroll:(up|down)
; custom/menu
; format
; label:open
; label:close
; menu:LEVEL:n
; menu:LEVEL:n:exec
;
[bar/top]
monitor = eDP1
width = 100%
height = 30
;clickareas = 25
; background = #222222
; foreground = #eefafafa
background = #eefafafa
foreground = #222222
linecolor = ${bar/top.background}
separator = |
spacing = 3
lineheight = 14
;padding_left = 5
;padding_right = 2
module_margin_left = 3
module_margin_right = 3
font:0 = NotoSans-Regular:size=8;0
font:1 = MaterialIcons:size=10;0
font:2 = Termsynu:size=8;-1
font:3 = FontAwesome:size=10;0
modules:left = powermenu mpd
modules:right = backlight volume wireless-network wired-network battery date
[bar/bottom]
monitor = eDP1
bottom = true
width = 100%
height = 27
;clickareas = 25
background = #111111
foreground = #ccffffff
linecolor = ${bar/bottom.background}
spacing = 3
lineheight = 2
;padding_left = 0
padding_right = 4
module_margin_left = 0
module_margin_right = 6
; font:idx = font:size=N;offsetY
font:0 = NotoSans-Regular:size=8;0
font:1 = Unifont:size=6;-3
;font:1 = Termsynu:size=8;-1
font:2 = FontAwesome:size=8;-2
font:3 = NotoSans-Regular:size=8;-1
font:4 = MaterialIcons:size=10;-1
modules:left = bspwm
; modules:right = rtorrent cpu memory
modules:right = cpu memory
[bar/external_bottom]
monitor = HDMI1
bottom = true
width = 100%
height = 27
;clickareas = 25
background = #111111
foreground = #ccffffff
linecolor = ${bar/external_bottom.background}
spacing = 3
lineheight = 2
;padding_left = 0
padding_right = 3
module_margin_left = 0
module_margin_right = 6
font:0 = NotoSans-Regular:size=8;0
font:1 = Unifont:size=6;-3
font:2 = FontAwesome:size=8;-2
font:3 = NotoSans-Regular:size=8;-1
font:4 = MaterialIcons:size=10;0
modules:left = bspwm
modules:right = clock
[module/backlight]
type = internal/backlight
; Use the following command to list available cards:
; $ ls -1 /sys/class/backlight/
card = intel_backlight
; Available tags:
; <label> (default)
; <bar>
format = <ramp> <bar>
; Available tokens:
; %percentage% (default)
;label = %percentage%
; Required if <ramp> is used
ramp:0 = 
ramp:1 = 
ramp:2 = 
; Required if <bar> is used
bar:width = 10
bar:format = %{+u}%{+o}%fill%%{-u}%{-o}%indicator%%{+u}%{+o}%empty%%{-u}%{-o}
bar:indicator = |
bar:indicator:foreground = #dd000000
bar:indicator:font = 3
bar:fill = █
bar:fill:foreground = #99000000
bar:fill:font = 3
bar:empty = █
bar:empty:font = 3
bar:empty:foreground = #999
[module/battery]
type = internal/battery
;battery = BAT0
;adapter = ADP1
full_at = 99
; Available tags:
; <label:charging> (default)
; <bar:capaity>
; <ramp:capacity>
; <animation:charging>
format:charging = <animation:charging> <label:charging>
; Available tags:
; <label:discharging> (default)
; <bar:capaity>
; <ramp:capacity>
format:discharging = <ramp:capacity> <label:discharging>
; Available tags:
; <label:full> (default)
; <bar:capaity>
; <ramp:capacity>
format:full = <ramp:capacity> <label:full>
; Available tokens:
; %percentage% (default)
;label:charging = Charging %percentage%
; Available tokens:
; %percentage% (default)
;label:discharging = Discharging %percentage%
; Available tokens:
; %percentage% (default)
;label:full = Fully charged
; Required if <ramp:capacity> is used
ramp:capacity:0 = 
ramp:capacity:0:foreground = #f53c3c
ramp:capacity:1 = 
ramp:capacity:1:foreground = #ffa900
ramp:capacity:2 = 
ramp:capacity:2:foreground = #000000
ramp:capacity:3 = 
ramp:capacity:3:foreground = #000000
ramp:capacity:4 = 
ramp:capacity:4:foreground = #000000
; Required if <bar:capacity> is used
bar:capacity:width = 10
bar:capacity:format = %{+u}%{+o}%fill%%empty%%{-u}%{-o}
bar:capacity:fill = █
bar:capacity:fill:foreground = #dd000000
bar:capacity:fill:font = 3
bar:capacity:empty = █
bar:capacity:empty:font = 3
bar:empty:foreground = #999
; Required if <animation:charging> is used
; animation:charging:0 = %{T3}%{F#ddffffff}%{+u}%{+o}█%{F#44ffffff}█████████%{T-}%{F-}%{-u}%{-o}
; animation:charging:1 = %{T3}%{F#ddffffff}%{+u}%{+o}██%{F#44ffffff}████████%{T-}%{F-}%{-u}%{-o}
; animation:charging:2 = %{T3}%{F#ddffffff}%{+u}%{+o}███%{F#44ffffff}███████%{T-}%{F-}%{-u}%{-o}
; animation:charging:3 = %{T3}%{F#ddffffff}%{+u}%{+o}████%{F#44ffffff}██████%{T-}%{F-}%{-u}%{-o}
; animation:charging:4 = %{T3}%{F#ddffffff}%{+u}%{+o}█████%{F#44ffffff}█████%{T-}%{F-}%{-u}%{-o}
; animation:charging:5 = %{T3}%{F#ddffffff}%{+u}%{+o}██████%{F#44ffffff}████%{T-}%{F-}%{-u}%{-o}
; animation:charging:6 = %{T3}%{F#ddffffff}%{+u}%{+o}███████%{F#44ffffff}███%{T-}%{F-}%{-u}%{-o}
; animation:charging:7 = %{T3}%{F#ddffffff}%{+u}%{+o}████████%{F#44ffffff}██%{T-}%{F-}%{-u}%{-o}
; animation:charging:8 = %{T3}%{F#ddffffff}%{+u}%{+o}█████████%{F#44ffffff}█%{T-}%{F-}%{-u}%{-o}
; animation:charging:9 = %{T3}%{F#ddffffff}%{+u}%{+o}██████████%{T-}%{F-}%{-u}%{-o}
animation:charging:0 = 
animation:charging:1 = 
animation:charging:2 = 
animation:charging:3 = 
animation:charging:4 = 
animation:charging:framerate_ms = 750
[module/bspwm]
type = internal/bspwm
; Seconds to sleep between updates
;interval = 1.0
; workspace_icon:[0-9]+ = label;icon
; workspace_icon:default = icon
workspace_icon:0 = term;
workspace_icon:1 = web;
workspace_icon:2 = code;
workspace_icon:3 = music;
workspace_icon:4 = irssi;
workspace_icon:default = 
; Available tags:
; <label:state> (default) - gets replaced with <label:(active|urgent|occupied|empty)>
; <label:mode> - gets replaced with <label:(monocle|tiled|fullscreen|floating|locked|sticky|private)>
format = <label:state> <label:mode>
; If any of these are defined, the workspace/mode colors will get overridden
; with these values if the monitor is out of focus
;label:dimmed:foreground = #555
;label:dimmed:background = ${BAR.background}
label:dimmed:underline = ${BAR.background}
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:active = %icon%
label:active:foreground = #ffffff
label:active:background = #3f3f3f
label:active:underline = #fba922
label:active:font = 4
label:active:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:occupied = %icon%
label:occupied:underline = #555555
label:occupied:font = 4
label:occupied:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:urgent = %icon%
label:urgent:foreground = #000000
label:urgent:background = #bd2c40
label:urgent:underline = #9b0a20
label:urgent:font = 4
label:urgent:padding = 4
; Available tokens:
; %name%
; %icon%
; %index%
; Default: %icon% %name%
label:empty = %icon%
label:empty:foreground = #55ffffff
label:empty:font = 4
label:empty:padding = 4
; Available tokens:
; None
label:monocle = 
label:monocle:underline = ${module/bspwm.label:active:underline}
label:monocle:padding = 2
;label:tiled = 
;label:fullscreen = 
;label:floating = 
label:locked = 
label:locked:foreground = #bd2c40
label:locked:underline = ${module/bspwm.label:monocle:underline}
label:locked:padding = ${module/bspwm.label:monocle:padding}
label:sticky = 
label:sticky:foreground = #fba922
label:sticky:underline = ${module/bspwm.label:monocle:underline}
label:sticky:padding = ${module/bspwm.label:monocle:padding}
label:private = 
label:private:foreground = #bd2c40
label:private:underline = ${module/bspwm.label:monocle:underline}
label:private:padding = ${module/bspwm.label:monocle:padding}
[module/cpu]
type = internal/cpu
; Seconds to sleep between updates
interval = 0.5
; Available tags:
; <label> (default)
; <bar:load>
; <ramp:load>
; <ramp:load_per_core>
format = <label> <ramp:load_per_core>
; Available tokens:
; %percentage% (default)
label = CPU
; Required if <ramp:core_load> is used
ramp:load_per_core:0 = ▁
ramp:load_per_core:0:font = 2
ramp:load_per_core:0:foreground = #55aa55
ramp:load_per_core:1 = ▂
ramp:load_per_core:1:font = 2
ramp:load_per_core:1:foreground = #55aa55
ramp:load_per_core:2 = ▃
ramp:load_per_core:2:font = 2
ramp:load_per_core:2:foreground = #55aa55
ramp:load_per_core:3 = ▄
ramp:load_per_core:3:font = 2
ramp:load_per_core:3:foreground = #55aa55
ramp:load_per_core:4 = ▅
ramp:load_per_core:4:font = 2
ramp:load_per_core:4:foreground = #f5a70a
ramp:load_per_core:5 = ▆
ramp:load_per_core:5:font = 2
ramp:load_per_core:5:foreground = #f5a70a
ramp:load_per_core:6 = ▇
ramp:load_per_core:6:font = 2
ramp:load_per_core:6:foreground = #ff5555
ramp:load_per_core:7 = █
ramp:load_per_core:7:font = 2
ramp:load_per_core:7:foreground = #ff5555
; Required if <bar:total_load> is used
;bar:total_load:width = 10
;bar:total_load:indicator = |
;bar:total_load:fill = =
;bar:total_load:empty = =
[module/date]
type = internal/date
; see "man date" for formatting
; if date_detailed is defined, clicking the area will toggle between formats
; if you want to use lemonbar tags here you need to use %%{...}
date = %%{F#888}%Y-%m-%d%%{F-} %%{F#fff}%H:%M%%{F-}
date_detailed = %%{F#888}%A, %d %B %Y% %%{F#fff}%H:%M%%{F#666}:%%{F#fba922}%S%%{F-}
; Seconds to sleep between updates
;interval = 1.0
; Available tags:
; <date> (default)
format =  <date>
format:background = #111
format:foreground = #eee
format:padding = 5
[module/memory]
type = internal/memory
; Seconds to sleep between updates
;interval = 1.0
; Available tags:
; <label> (default)
; <bar:used>
; <bar:free>
format = <label> <bar:used>
; Available tokens:
; %percentage_used% (default)
; %percentage_free%
; %gb_used%
; %gb_free%
; %gb_total%
; %mb_used%
; %mb_free%
; %mb_total%
label = RAM
; Required if <bar:used> is used
bar:used:width = 50
bar:used:foreground:0 = #55aa55
bar:used:foreground:1 = #557755
bar:used:foreground:2 = #f5a70a
bar:used:foreground:3 = #ff5555
bar:used:indicator = ▐
bar:used:indicator:font = 2
bar:used:indicator:foreground = #ddffffff
bar:used:fill = ▐
bar:used:fill:font = 2
bar:used:empty = ▐
bar:used:empty:font = 2
bar:used:empty:foreground = #444444
; Required if <bar:free> is used
;bar:free:width = 50
;bar:free:foreground:0 = #ff5555
;bar:free:foreground:1 = #f5a70a
;bar:free:foreground:2 = #557755
;bar:free:foreground:3 = #55aa55
;bar:free:indicator = ▐
;bar:free:indicator:font = 2
;bar:free:indicator:foreground = #ddffffff
;bar:free:fill = ▐
;bar:free:fill:font = 2
;bar:free:empty = ▐
;bar:free:empty:font = 2
;bar:free:empty:foreground = #444444
[module/mpd]
type = internal/mpd
; Seconds to sleep between progressbar/song timer updates
;interval = 0.5
; Available tags:
; <label:song> (default)
; <label:time>
; <bar:progress>
; <toggle> - gets replaced with <icon:(pause|play)>
; <icon:random>
; <icon:repeat>
; <icon:repeatone>
; <icon:prev>
; <icon:stop>
; <icon:play>
; <icon:pause>
; <icon:next>
format:online = <icon:prev> <icon:stop> <toggle> <icon:next> <icon:repeat> <icon:random> <bar:progress> <label:time> <label:song>
; Available tags:
; <label:offline>
format:offline = <label:offline>
format:offline:offset = -8
; Available tokens:
; %artist%
; %album%
; %title%
; Default: %artist% - %title%
;label:song = %artist% - %title%
;label:song:foreground = ${BAR.foreground}
; Available tokens:
; %elapsed%
; %total%
; Default: %elapsed% / %total%
;label:time = %elapsed% / %total%
;label:time:foreground = #66fafafa
; Available tokens:
; None
label:offline =  mpd is off
label:offline:foreground = #66fafafa
icon:play = 
icon:pause = 
icon:stop = 
icon:prev = 
icon:next = 
icon:random = 
icon:repeat = 
;icon:repeatone = 🔂
; Used to display the state of random/repeat/repeatone
toggle_on:foreground =
toggle_off:foreground = #aafafafa
; Required if <bar:progress> is used
bar:progress:width = 45
bar:progress:format = %{+u}%{+o}%fill%%{-u}%{-o}%indicator%%{+u}%{+o}%empty%%{-u}%{-o}
bar:progress:indicator = |
bar:progress:indicator:foreground = #dd000000
bar:progress:indicator:font = 3
bar:progress:fill = █
bar:progress:fill:foreground = #aa000000
bar:progress:fill:font = 3
bar:progress:empty = █
bar:progress:empty:font = 3
bar:progress:empty:foreground = #999
[module/wireless-network]
type = internal/network
interface = net1
; Seconds to sleep between updates
interval = 2.0
; Seconds to sleep between connectivity tests
; A value of 0 disables the testing
; Default: 0
connectivity_test_interval = 10
; Available tags:
; <label:connected> (default)
; <ramp:signal>
format:connected = <ramp:signal> <label:connected>
; Available tags:
; <label:disconnected> (default)
;format:disconnected = <label:disconnected>
; Available tags:
; <label:packetloss> (default)
; <animation:packetloss>
; format:packetloss = <animation:packetloss> <label:packetloss>
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
label:connected = %essid%
;label:connected:foreground = #eefafafa
; Available tokens:
; %ifname% [wireless+wired]
; Default: (none)
label:disconnected =  not connected
label:disconnected:foreground = #66000000
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
; ------------------------- NOT ACTIVATED (Needs more testing)
;label:packetloss = %essid%
;label:packetloss:foreground = #eefafafa
; Required if <ramp:signal> is used
ramp:signal:0 = 
ramp:signal:0:foreground = #33000000
ramp:signal:1 = 
ramp:signal:1:foreground = #66000000
ramp:signal:2 = 
ramp:signal:2:foreground = #99000000
ramp:signal:3 = 
ramp:signal:3:foreground = #cc000000
ramp:signal:4 = 
ramp:signal:4:foreground = #000000ff
; Required if <animation:packetloss> is used
animation:packetloss:0 = 
animation:packetloss:0:foreground = #ffa64c
animation:packetloss:1 = 
animation:packetloss:1:foreground = ${bar/top.foreground}
animation:packetloss:framerate_ms = 500
[module/wired-network]
type = internal/network
interface = net0
; Seconds to sleep between updates
interval = 2.0
; Seconds to sleep between connectivity tests
; A value of 0 disables the testing
; Default: 0
;connectivity_test_interval = 0
; Available tags:
; <label:connected> (default)
; <ramp:signal>
;format:connected = <label:connected>
; Available tags:
; <label:disconnected> (default)
;format:disconnected = <label:disconnected>
; Available tags:
; <label:packetloss> (default)
; <animation:packetloss>
; format:packetloss = <animation:packetloss> <label:packetloss>
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
label:connected =  %{T3}%local_ip%%{T-}
;label:connected:foreground = #eefafafa
; Available tokens:
; %ifname% [wireless+wired]
; Default: (none)
;label:disconnected =  not connected
;label:disconnected:foreground = #66ffffff
; Available tokens:
; %ifname% [wireless+wired]
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
; ------------------------- NOT ACTIVATED (Needs more testing)
;label:packetloss = %essid%
;label:packetloss:foreground = #eefafafa
[module/rtorrent]
type = internal/rtorrent
script = /home/jaagr/var/github/jaagr/lemonbuddy/scripts/torrents.sh
rtorrent_session_dir = /home/jaagr/.cache/rtorrent
display_count = 2
title_maxlen = 30
; Available tags:
; <label> (default)
; <bar:progress>
format = <label> <bar:progress>
; Available tokens:
; %title%
; %percentage%
; Default: %label% (%percentage%)
label =  %{F#eefafafa}%title%%{F-}
label:foreground = #fba922
; Required if <bar:progress> is used
bar:progress:width = 10
bar:progress:format = %{+u}%{+o}%fill%%{-u}%{-o}%indicator%%{+u}%{+o}%empty%%{-u}%{-o}
bar:progress:indicator = |
bar:progress:indicator:foreground = ${BAR.foreground}
bar:progress:indicator:font = 2
bar:progress:fill = █
bar:progress:fill:foreground = #5a5
bar:progress:fill:font = 2
bar:progress:empty = █
bar:progress:empty:foreground = #555
bar:progress:empty:font = 2
[module/volume]
type = internal/volume
; Use the following command to list available mixer controls:
; $ amixer scontrols | sed -nr "s/.*'([[:alnum:]]+)'.*/\1/p"
speaker_mixer = Speaker
headphone_mixer = Headphone
; Use the following command to list available device controls
; $ amixer controls | sed -r "/CARD/\!d; s/.*=([0-9]+).*name='([^']+)'.*/printf '%3.0f: %s\n' '\1' '\2'/e" | sort
headphone_control_numid = 9
; Available tags:
; <label:volume> (default)
; <ramp:volume>
; <bar:volume>
format:volume = <ramp:volume> <label:volume>
; Available tags:
; <label:muted> (default)
; <ramp:volume>
; <bar:volume>
;format:muted = <label:muted>
; Available tokens:
; %percentage% (default)
;label:volume = %percentage%
;label:volume:foreground = #ffffff
; Available tokens:
; %percentage% (default)
label:muted =  muted
label:muted:foreground = #66000000
; Required if <ramp:volume> is used
ramp:volume:0 = 
ramp:volume:0:foreground = #99000000
ramp:volume:1 = 
ramp:volume:1:foreground = #bb000000
ramp:volume:2 = 
ramp:volume:2:foreground = #dd000000
ramp:volume:3 = 
ramp:volume:3:foreground = #ff000000
; Required if <bar:capacity> is used
bar:volume:width = 10
bar:volume:format = %{+u}%{+o}%fill%%empty%%{-u}%{-o}
bar:volume:fill = █
bar:volume:fill:foreground = #dd000000
bar:volume:fill:font = 3
bar:volume:empty = █
bar:volume:empty:font = 3
bar:volume:empty:foreground = #44000000
[module/powermenu]
type = custom/menu
; Available tags:
; <label:toggle> (default) - gets replaced with <label:(open|close)>
; <menu> (default)
;format = <label:toggle> <menu>
format:background = #111
format:foreground = #eee
format:padding = 4
label:open = 
label:close = 
; "menu:LEVEL:N" has the same properties as "label:NAME" with
; the additional "exec" property
;
; Available exec commands:
; menu_open:LEVEL
; menu_close
; Other commands will be executed using "/usr/bin/env sh -c $COMMAND"
menu:0:0 = Terminate WM
menu:0:0:foreground = #fba922
menu:0:0:exec = bspc quit -1
menu:0:1 = Reboot
menu:0:1:foreground = #fba922
menu:0:1:exec = menu_open:1
menu:0:2 = Power off
menu:0:2:foreground = #fba922
menu:0:2:exec = menu_open:2
menu:1:0 = Cancel
menu:1:0:foreground = #fba922
menu:1:0:exec = menu_open:0
menu:1:1 = Reboot
menu:1:1:foreground = #fba922
menu:1:1:exec = sudo reboot
menu:2:0 = Power off
menu:2:0:foreground = #fba922
menu:2:0:exec = sudo poweroff
menu:2:1 = Cancel
menu:2:1:foreground = #fba922
menu:2:1:exec = menu_open:0
[module/text-example]
type = custom/text
; "content" has the same properties as "format:NAME"
content = 
content:background = #000
content:foreground = #fff
content:padding = 4
; "click:(left|middle|right)" will be executed using "/usr/bin/env sh -c $COMMAND"
click:left = echo left
click:middle = echo middle
click:right = echo right
; "scroll:(up|down)" will be executed using "/usr/bin/env sh -c $COMMAND"
scroll:up = echo scroll up
scroll:down = echo scroll down
[module/script-example]
type = custom/script
; Available tokens:
; %counter%
;
; The "exec" command will be executed using "/usr/bin/env sh -c [command]"
exec = echo %counter%
; Seconds to sleep between updates
interval = 0.5
; Available tags:
; <output> (default)
;format = <output>
format:background = #111
format:foreground = #eee
format:padding = 4
; Available tokens:
; %counter%
;
; "click:(left|middle|right)" will be executed using "/usr/bin/env sh -c [command]"
click:left = echo left %counter%
click:middle = echo middle %counter%
click:right = echo right %counter%
; Available tokens:
; %counter%
;
; "scroll:(up|down)" will be executed using "/usr/bin/env sh -c [command]"
scroll:up = echo scroll up %counter%
scroll:down = echo scroll down %counter%
[module/clock]
type = internal/date
interval = 5
date = %%{F#999}%Y-%m-%d%%{F-} %%{F#fff}%H:%M%%{F-}
; vim:ft=dosini

187
examples/external_top Normal file
View File

@ -0,0 +1,187 @@
; vim:ft=dosini
[bar/external_top]
monitor = HDMI1
width = 100%
height = 27
background = #111111
foreground = #ccffffff
linecolor = ${bar/external_top.background}
spacing = 3
lineheight = 2
padding_right = 3
module_margin_left = 0
module_margin_right = 6
font:0 = NotoSans-Regular:size=8;0
font:1 = Unifont:size=6;-3
font:2 = FontAwesome:size=8;-2
font:3 = NotoSans-Regular:size=8;-1
font:4 = MaterialIcons:size=10;0
; modules:left = wireless-network wired-network battery cpu
modules:left = cpu
[module/wireless-network]
type = internal/network
interface = net1
interval = 2.0
connectivity_test_interval = 0
format:connected = <ramp:signal> <label:connected>
label:connected = %essid%
label:disconnected =  not connected
label:disconnected:foreground = #66000000
ramp:signal:0 = 
ramp:signal:0:foreground = #33000000
ramp:signal:1 = 
ramp:signal:1:foreground = #66000000
ramp:signal:2 = 
ramp:signal:2:foreground = #99000000
ramp:signal:3 = 
ramp:signal:3:foreground = #cc000000
ramp:signal:4 = 
ramp:signal:4:foreground = #000000ff
[module/wired-network]
type = internal/network
interface = net0
interval = 2.0
label:connected =  %{T3}%local_ip%%{T-}
[module/battery]
type = internal/battery
;battery = BAT0
;adapter = ADP1
full_at = 99
; Available tags:
; <label:charging> (default)
; <bar:capaity>
; <ramp:capacity>
; <animation:charging>
format:charging = <bar:capacity> <label:charging>
; Available tags:
; <label:discharging> (default)
; <bar:capaity>
; <ramp:capacity>
format:discharging = <bar:capacity> <label:discharging>
; Available tags:
; <label:full> (default)
; <bar:capaity>
; <ramp:capacity>
format:full = <bar:capacity> <label:full>
; Available tokens:
; %percentage% (default)
;label:charging = Charging %percentage%
; Available tokens:
; %percentage% (default)
;label:discharging = Discharging %percentage%
; Available tokens:
; %percentage% (default)
;label:full = Fully charged
; Required if <ramp:capacity> is used
; ramp:capacity:0 = 
; ramp:capacity:0:foreground = #f53c3c
; ramp:capacity:1 = 
; ramp:capacity:1:foreground = #ffa900
; ramp:capacity:2 = 
; ramp:capacity:2:foreground = #ffffff
; ramp:capacity:3 = 
; ramp:capacity:3:foreground = #ffffff
; ramp:capacity:4 = 
; ramp:capacity:4:foreground = #ffffff
; Required if <bar:capacity> is used
bar:capacity:width = 10
bar:capacity:format = %{+u}%{+o}%fill%%empty%%{-u}%{-o}
bar:capacity:fill = █
bar:capacity:fill:foreground = #ddffffff
bar:capacity:fill:font = 3
bar:capacity:empty = █
bar:capacity:empty:font = 3
bar:capacity:empty:foreground = #44ffffff
bar:capacity:gradient = false
bar:capacity:foreground:0 = #55aa55
bar:capacity:foreground:1 = #557755
bar:capacity:foreground:2 = #f5a70a
bar:capacity:foreground:3 = #ff5555
; Required if <animation:charging> is used
; animation:charging:0 = %{T3}%{F#ddffffff}%{+u}%{+o}█%{F#44ffffff}█████████%{T-}%{F-}%{-u}%{-o}
; animation:charging:1 = %{T3}%{F#ddffffff}%{+u}%{+o}██%{F#44ffffff}████████%{T-}%{F-}%{-u}%{-o}
; animation:charging:2 = %{T3}%{F#ddffffff}%{+u}%{+o}███%{F#44ffffff}███████%{T-}%{F-}%{-u}%{-o}
; animation:charging:3 = %{T3}%{F#ddffffff}%{+u}%{+o}████%{F#44ffffff}██████%{T-}%{F-}%{-u}%{-o}
; animation:charging:4 = %{T3}%{F#ddffffff}%{+u}%{+o}█████%{F#44ffffff}█████%{T-}%{F-}%{-u}%{-o}
; animation:charging:5 = %{T3}%{F#ddffffff}%{+u}%{+o}██████%{F#44ffffff}████%{T-}%{F-}%{-u}%{-o}
; animation:charging:6 = %{T3}%{F#ddffffff}%{+u}%{+o}███████%{F#44ffffff}███%{T-}%{F-}%{-u}%{-o}
; animation:charging:7 = %{T3}%{F#ddffffff}%{+u}%{+o}████████%{F#44ffffff}██%{T-}%{F-}%{-u}%{-o}
; animation:charging:8 = %{T3}%{F#ddffffff}%{+u}%{+o}█████████%{F#44ffffff}█%{T-}%{F-}%{-u}%{-o}
; animation:charging:9 = %{T3}%{F#ddffffff}%{+u}%{+o}██████████%{T-}%{F-}%{-u}%{-o}
animation:charging:0 = 
animation:charging:1 = 
animation:charging:2 = 
animation:charging:3 = 
animation:charging:4 = 
animation:charging:framerate_ms = 750
[module/cpu]
type = internal/cpu
; Seconds to sleep between updates
interval = 0.5
; Available tags:
; <label> (default)
; <bar:load>
; <ramp:load>
; <ramp:load_per_core>
format = <label> <ramp:load_per_core>
; Available tokens:
; %percentage% (default)
label = CPU
; Required if <ramp:core_load> is used
ramp:load_per_core:0 = ▁
ramp:load_per_core:0:font = 2
ramp:load_per_core:0:foreground = #55aa55
ramp:load_per_core:1 = ▂
ramp:load_per_core:1:font = 2
ramp:load_per_core:1:foreground = #55aa55
ramp:load_per_core:2 = ▃
ramp:load_per_core:2:font = 2
ramp:load_per_core:2:foreground = #55aa55
ramp:load_per_core:3 = ▄
ramp:load_per_core:3:font = 2
ramp:load_per_core:3:foreground = #55aa55
ramp:load_per_core:4 = ▅
ramp:load_per_core:4:font = 2
ramp:load_per_core:4:foreground = #f5a70a
ramp:load_per_core:5 = ▆
ramp:load_per_core:5:font = 2
ramp:load_per_core:5:foreground = #f5a70a
ramp:load_per_core:6 = ▇
ramp:load_per_core:6:font = 2
ramp:load_per_core:6:foreground = #ff5555
ramp:load_per_core:7 = █
ramp:load_per_core:7:font = 2
ramp:load_per_core:7:foreground = #ff5555
; Required if <bar:total_load> is used
;bar:total_load:width = 10
;bar:total_load:indicator = |
;bar:total_load:fill = =
;bar:total_load:empty = =

93
include/bar.hpp Normal file
View File

@ -0,0 +1,93 @@
#ifndef _BAR_HPP_
#define _BAR_HPP_
#include <string>
#include <memory>
#include "exception.hpp"
#include "utils/xlib.hpp"
DefineBaseException(ConfigurationError);
struct Font
{
std::string id;
int offset;
Font(const std::string& id, int offset)
: id(id), offset(offset){}
};
enum Cmd
{
LEFT_CLICK = 1,
MIDDLE_CLICK = 2,
RIGHT_CLICK = 3,
SCROLL_UP = 4,
SCROLL_DOWN = 5,
};
struct Options
{
std::unique_ptr<xlib::Monitor> monitor;
std::string wm_name;
std::string locale;
std::string background = "#ffffff";
std::string foreground = "#000000";
std::string linecolor = "#000000";
int width;
int height;
int offset_x = 0;
int offset_y = 0;
bool bottom = false;
bool dock = true;
int clickareas = 25;
std::string separator;
int spacing = 1;
int lineheight = 1;
int padding_left = 0;
int padding_right = 0;
int module_margin_left = 0;
int module_margin_right = 2;
std::vector<std::unique_ptr<Font>> fonts;
std::string get_geom()
{
std::stringstream ss;
ss << this->width << "x" << this->height << "+";
ss << this->offset_x << "+" << this->offset_y;
return ss.str();
}
};
class Bar
{
std::string config_path;
std::vector<std::string> mod_left;
std::vector<std::string> mod_center;
std::vector<std::string> mod_right;
public:
Bar();
std::unique_ptr<Options> opts;
std::string get_output();
std::string get_exec_line();
void load();
};
std::shared_ptr<Bar> &get_bar();
const Options& bar_opts();
#endif

24
include/config.hpp.cmake Normal file
View File

@ -0,0 +1,24 @@
#ifndef _CONFIG_HPP_
#define _CONFIG_HPP_
#cmakedefine ENABLE_ALSA
#cmakedefine ENABLE_MPD
#cmakedefine ENABLE_I3
#define BUILDER_SPACE_TOKEN "%__"
#define ALSA_SOUNDCARD "@SETTING_ALSA_SOUNDCARD@"
#define MPD_HOST "@SETTING_MPD_HOST@"
#define MPD_PASSWORD "@SETTING_MPD_PASSWORD@"
#define MPD_PORT @SETTING_MPD_PORT@
#define CONNECTION_TEST_IP "@SETTING_CONNECTION_TEST_IP@"
#define PATH_BACKLIGHT_VAL "@SETTING_PATH_BACKLIGHT_VAL@"
#define PATH_BACKLIGHT_MAX "@SETTING_PATH_BACKLIGHT_MAX@"
#define PATH_BATTERY_WATCH "@SETTING_PATH_BATTERY_WATCH@"
#define PATH_BATTERY_CAPACITY "@SETTING_PATH_BATTERY_CAPACITY@"
#define PATH_ADAPTER_STATUS "@SETTING_PATH_ADAPTER_STATUS@"
#define BSPWM_SOCKET_PATH "@SETTING_BSPWM_SOCKET_PATH@"
#define BSPWM_STATUS_PREFIX "@SETTING_BSPWM_STATUS_PREFIX@"
#define PATH_CPU_INFO "@SETTING_PATH_CPU_INFO@"
#define PATH_MEMORY_INFO "@SETTING_PATH_MEMORY_INFO@"
#endif // _CONFIG_HPP_

View File

@ -0,0 +1,41 @@
#ifndef _DRAWTYPES_ANIMATION_HPP_
#define _DRAWTYPES_ANIMATION_HPP_
#include <string>
#include <vector>
#include <chrono>
#include "drawtypes/icon.hpp"
namespace drawtypes
{
class Animation
{
std::vector<std::unique_ptr<Icon>> frames;
int num_frames = 0;
int current_frame= 0;
int framerate_ms = 1000;
std::chrono::system_clock::time_point updated_at;
void tick();
public:
Animation(std::vector<std::unique_ptr<Icon>> &&frames, int framerate_ms = 1);
Animation(int framerate_ms)
: framerate_ms(framerate_ms){}
void add(std::unique_ptr<Icon> &&frame);
std::unique_ptr<Icon> &get();
int get_framerate();
operator bool() {
return !this->frames.empty();
}
};
std::unique_ptr<Animation> get_config_animation(const std::string& config_path, const std::string& animation_name = "animation", bool required = true);
}
#endif

45
include/drawtypes/bar.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef _DRAWTYPES_BAR_HPP_
#define _DRAWTYPES_BAR_HPP_
#include <string>
#include <memory>
#include <vector>
#include "drawtypes/icon.hpp"
class Builder;
namespace drawtypes
{
class Bar
{
protected:
std::unique_ptr<Builder> builder;
std::vector<std::string> colors;
bool gradient;
unsigned int width;
std::string format;
std::unique_ptr<Icon> fill;
std::unique_ptr<Icon> empty;
std::unique_ptr<Icon> indicator;
public:
Bar(int width, const std::string& format, bool lazy_builder_closing = true);
Bar(int width, bool lazy_builder_closing = true)
: Bar(width, "<fill><indicator><empty>", lazy_builder_closing){}
void set_fill(std::unique_ptr<Icon> &&icon);
void set_empty(std::unique_ptr<Icon> &&icon);
void set_indicator(std::unique_ptr<Icon> &&icon);
void set_gradient(bool mode);
void set_colors(std::vector<std::string> &&colors);
std::string get_output(float percentage, bool floor_percentage = false);
};
std::unique_ptr<Bar> get_config_bar(const std::string& config_path, const std::string& bar_name = "bar", bool lazy_builder_closing = true);
}
#endif

View File

@ -0,0 +1,41 @@
#ifndef _DRAWTYPES_ICON_HPP_
#define _DRAWTYPES_ICON_HPP_
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "drawtypes/label.hpp"
namespace drawtypes
{
struct Icon : public Label
{
Icon(const std::string& icon, int font = 0)
: Label(icon, font){}
Icon(const std::string& icon, const std::string& fg, const std::string& bg = "", const std::string& ul = "", const std::string& ol = "", int font = 0, int padding = 0, int margin = 0)
: Label(icon, fg, bg, ul, ol, font, padding, margin){}
std::unique_ptr<Icon> clone();
};
class IconMap
{
std::map<std::string, std::unique_ptr<Icon>> icons;
public:
void add(const std::string& id, std::unique_ptr<Icon> &&icon);
std::unique_ptr<Icon> &get(const std::string& id, const std::string& fallback_id = "");
bool has(const std::string& id);
operator bool() {
return this->icons.size() > 0;
}
};
std::unique_ptr<Icon> get_config_icon(const std::string& module_name, const std::string& icon_name = "icon", bool required = true, const std::string& def = "");
std::unique_ptr<Icon> get_optional_config_icon(const std::string& module_name, const std::string& icon_name = "icon", const std::string& def = "");
}
#endif

View File

@ -0,0 +1,33 @@
#ifndef _DRAWTYPES_LABEL_HPP_
#define _DRAWTYPES_LABEL_HPP_
#include <string>
#include <memory>
namespace drawtypes
{
struct Label
{
std::string text, fg, bg, ul, ol;
int font = 0, padding = 0, margin = 0;
Label(const std::string& text, int font)
: text(text), font(font){}
Label(const std::string& text, const std::string& fg = "", const std::string& bg = "", const std::string& ul = "", const std::string& ol = "", int font = 0, int padding = 0, int margin = 0)
: text(text), fg(fg), bg(bg), ul(ul), ol(ol), font(font), padding(padding), margin(margin){}
operator bool() {
return !this->text.empty();
}
std::unique_ptr<Label> clone();
void replace_token(const std::string& token, const std::string& replacement);
void replace_defined_values(std::unique_ptr<Label> &label);
};
std::unique_ptr<Label> get_config_label(const std::string& module_name, const std::string& label_name = "label", bool required = true, const std::string& def = "");
std::unique_ptr<Label> get_optional_config_label(const std::string& module_name, const std::string& label_name = "label", const std::string& def = "");
}
#endif

View File

@ -0,0 +1,34 @@
#ifndef _DRAWTYPES_RAMP_HPP_
#define _DRAWTYPES_RAMP_HPP_
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "drawtypes/icon.hpp"
namespace drawtypes
{
class Ramp
{
protected:
std::vector<std::unique_ptr<Icon>> icons;
public:
Ramp(){}
Ramp(std::vector<std::unique_ptr<Icon>> icons);
void add(std::unique_ptr<Icon> &&icon);
std::unique_ptr<Icon> &get(int idx);
std::unique_ptr<Icon> &get_by_percentage(float percentage);
operator bool() {
return this->icons.size() > 0;
}
};
std::unique_ptr<Ramp> get_config_ramp(const std::string& module_name, const std::string& ramp_name = "ramp", bool required = true);
}
#endif

60
include/eventloop.hpp Normal file
View File

@ -0,0 +1,60 @@
#ifndef _EVENTLOOP_HPP_
#define _EVENTLOOP_HPP_
#include <map>
#include "bar.hpp"
#include "registry.hpp"
#include "exception.hpp"
#include "modules/base.hpp"
#include "services/logger.hpp"
DefineBaseException(EventLoopTerminate);
DefineBaseException(EventLoopTerminateTimeout);
class EventLoop
{
const int STATE_STOPPED = 1;
const int STATE_STARTED = 2;
std::shared_ptr<Bar> bar;
std::shared_ptr<Registry> registry;
std::shared_ptr<Logger> logger;
concurrency::Atomic<int> state;
std::thread t_write;
std::thread t_read;
int fd_stdin = STDIN_FILENO;
int fd_stdout = STDOUT_FILENO;
std::string pipe_filename;
sigset_t wait_mask;
// <tag, module_name>
// std::map<std::string, std::string> stdin_subs;
std::vector<std::string> stdin_subs;
protected:
void loop_write();
void loop_read();
void read_stdin();
void write_stdout();
bool running();
public:
EventLoop(std::string input_pipe);
void start();
void stop();
void wait();
void cleanup(int timeout_ms = 5000);
void add_stdin_subscriber(const std::string& module_name);
};
#endif

20
include/exception.hpp Normal file
View File

@ -0,0 +1,20 @@
#ifndef _EXCEPTION_HPP_
#define _EXCEPTION_HPP_
#include <string>
#include <stdexcept>
struct Exception : public std::runtime_error
{
Exception(std::string const &error_message = "")
: std::runtime_error(error_message.c_str()) {}
};
#define DefineChildException(ExName, ParentEx) struct ExName : public ParentEx { \
ExName(std::string error_message = "") \
: ParentEx("["+ std::string(__FUNCTION__) +"] => "+ error_message) {} \
};
#define DefineBaseException(ExName) DefineChildException(ExName, Exception);
#endif

View File

@ -0,0 +1,76 @@
#ifndef _INTERFACES_ALSA_HPP_
#define _INTERFACES_ALSA_HPP_
#include <functional>
#include <string>
#include <stdio.h>
#include <alsa/asoundlib.h>
#include <mutex>
#include "exception.hpp"
#define STRSNDERR(s) std::string(snd_strerror(s))
namespace alsa
{
class Exception : public ::Exception
{
public:
Exception(const std::string& msg) : ::Exception("[Alsa] "+ msg){}
};
class ControlInterfaceError : public Exception {
using Exception::Exception;
};
class ControlInterface
{
std::mutex mtx;
snd_hctl_t *hctl;
snd_hctl_elem_t *elem;
snd_ctl_t *ctl;
snd_ctl_elem_info_t *info;
snd_ctl_elem_value_t *value;
snd_ctl_elem_id_t *id;
public:
ControlInterface(int numid) throw(ControlInterfaceError);
~ControlInterface();
bool wait(int timeout = -1) throw(ControlInterfaceError);
bool test_device_plugged() throw(ControlInterfaceError);
};
class MixerError : public Exception {
using Exception::Exception;
};
class Mixer
{
std::mutex mtx;
snd_mixer_t *hardware_mixer = nullptr;
snd_mixer_elem_t *mixer_element = nullptr;
public:
Mixer(const std::string& mixer_control_name) throw(MixerError);
~Mixer();
bool wait(int timeout = -1) throw(MixerError);
int get_volume();
void set_volume(float percentage);
void set_mute(bool mode);
void toggle_mute();
bool is_muted();
protected:
void error_handler(const std::string& message);
};
}
#endif

183
include/interfaces/mpd.hpp Normal file
View File

@ -0,0 +1,183 @@
#ifndef _INTERFACES_MPD_HPP_
#define _INTERFACES_MPD_HPP_
#include <string>
#include <stdlib.h>
#include <memory>
#include <chrono>
#include <mpd/client.h>
#include "config.hpp"
#include "lemonbuddy.hpp"
#include "exception.hpp"
#include "utils/math.hpp"
namespace mpd
{
class Exception : public ::Exception
{
public:
Exception(const std::string& msg, bool clearable)
: ::Exception(msg + (clearable ? " (clearable)" : " (not clearable)")){}
};
class ClientError : public Exception
{
public:
ClientError(const std::string& msg, mpd_error code, bool clearable)
: Exception("[mpd::ClientError::"+ std::to_string(code) +"] "+ msg, clearable){}
};
class ServerError : public Exception
{
public:
ServerError(const std::string& msg, mpd_server_error code, bool clearable)
: Exception("[mpd::ServerError::"+ std::to_string(code) +"] "+ msg, clearable){}
};
enum State
{
UNKNOWN = 1 << 0,
STOPPED = 1 << 1,
PLAYING = 1 << 2,
PAUSED = 1 << 4,
};
struct Song
{
Song(){}
Song(mpd_song *song);
std::shared_ptr<mpd_song> song;
std::string get_artist();
std::string get_album();
std::string get_title();
unsigned get_duration();
operator bool() {
return this->song.get() != nullptr;
}
};
struct Status
{
struct StatusDeleter
{
void operator()(mpd_status *status) {
mpd_status_free(status);
}
};
Status(mpd_status *status);
std::unique_ptr<struct mpd_status, StatusDeleter> status;
std::unique_ptr<Song> song;
std::chrono::system_clock::time_point updated_at;
int state = UNKNOWN;
bool random = false,
repeat = false,
single = false;
int song_id;
unsigned long total_time;
unsigned long elapsed_time;
unsigned long elapsed_time_ms;
void set(std::unique_ptr<struct mpd_status, StatusDeleter> status);
void update(int event);
void update_timer();
unsigned get_total_time();
unsigned get_elapsed_time();
unsigned get_elapsed_percentage();
std::string get_formatted_elapsed();
std::string get_formatted_total();
};
class Connection
{
struct ConnectionDeleter
{
void operator()(mpd_connection *connection)
{
if (connection == nullptr)
return;
//TRACE("Releasing mpd_connection");
mpd_connection_free(connection);
}
};
std::unique_ptr<mpd_connection, ConnectionDeleter> connection;
std::string host = MPD_HOST;
std::string password = MPD_PASSWORD;
int port = MPD_PORT;
int timeout = 15;
bool mpd_command_list_active = false;
bool mpd_idle = false;
int mpd_fd;
void check_connection() throw(ClientError);
void check_prerequisites();
void check_prerequisites_commands_list();
void check_errors() throw(ClientError, ServerError);
public:
static std::shared_ptr<Connection> &get();
void connect() throw (ClientError);
void disconnect();
bool connected();
bool retry_connection(int interval = 1);
void idle();
int noidle();
void set_host(const std::string& host) { this->host = host; }
void set_port(int port) { this->port = port; }
void set_password(const std::string& password) { this->password = password; }
void set_timeout(int timeout) { this->timeout = timeout; }
std::unique_ptr<Status> get_status();
std::unique_ptr<Song> get_song();
void play();
void pause(bool state);
void toggle();
void stop();
void prev();
void next();
void seek(int percentage);
void repeat(bool mode);
void random(bool mode);
void single(bool mode);
};
struct MpdStatus
{
bool random, repeat, single;
std::string artist;
std::string album;
std::string title;
int elapsed_time = 0;
int total_time = 0;
float get_elapsed_percentage();
std::string get_formatted_elapsed();
std::string get_formatted_total();
operator bool() {
return !this->artist.empty() && !this->title.empty();
}
};
}
#endif

View File

@ -0,0 +1,88 @@
#ifndef _INTERFACES_NET_HPP_
#define _INTERFACES_NET_HPP_
#include <string>
#include <memory>
#include <net/if.h>
#include <iwlib.h>
#include <limits.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include "exception.hpp"
#include "services/command.hpp"
namespace net
{
bool is_wireless_interface(const std::string& ifname);
// Network
class NetworkException : public Exception
{
public:
NetworkException(const std::string& msg)
: Exception("[Network] "+ msg){}
};
class Network
{
protected:
std::unique_ptr<Command> ping;
std::string interface;
struct ifreq data;
int fd;
bool test_interface() throw(NetworkException);
bool test_connection() throw(NetworkException);
public:
Network(const std::string& interface) throw(NetworkException);
~Network();
virtual bool connected();
virtual bool test();
std::string get_ip() throw(NetworkException);
};
// WiredNetwork
class WiredNetworkException : public NetworkException {
using NetworkException::NetworkException;
};
class WiredNetwork : public Network
{
int linkspeed = 0;
public:
WiredNetwork(const std::string& interface);
std::string get_link_speed();
};
// WirelessNetwork
class WirelessNetworkException : public NetworkException {
using NetworkException::NetworkException;
};
class WirelessNetwork : public Network
{
struct iwreq iw;
public:
WirelessNetwork(const std::string& interface);
std::string get_essid() throw(WirelessNetworkException);
float get_signal_dbm() throw(WirelessNetworkException);
float get_signal_quality();
};
}
#endif

13
include/lemonbuddy.hpp Normal file
View File

@ -0,0 +1,13 @@
#ifndef _LEMONBUDDY_HPP_
#define _LEMONBUDDY_HPP_
#include "exception.hpp"
DefineBaseException(ApplicationError);
void register_pid(pid_t pid);
void unregister_pid(pid_t pid);
void register_command_handler(const std::string& module_name);
#endif

View File

@ -0,0 +1,35 @@
#ifndef _MODULES_BACKLIGHT_HPP_
#define _MODULES_BACKLIGHT_HPP_
#include "config.hpp"
#include "modules/base.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/label.hpp"
namespace modules
{
DefineModule(BacklightModule, InotifyModule)
{
const char *TAG_LABEL = "<label>";
const char *TAG_BAR = "<bar>";
const char *TAG_RAMP = "<ramp>";
std::unique_ptr<drawtypes::Bar> bar;
std::unique_ptr<drawtypes::Ramp> ramp;
std::unique_ptr<drawtypes::Label> label;
std::unique_ptr<drawtypes::Label> label_tokenized;
std::string path_val, path_max;
float val = 0, max = 0;
std::atomic<int> percentage;
public:
BacklightModule(const std::string& name);
bool on_event(InotifyEvent *event);
bool build(Builder *builder, const std::string& tag);
};
}
#endif

437
include/modules/base.hpp Normal file
View File

@ -0,0 +1,437 @@
#ifndef _MODULES_BASE_HPP_
#define _MODULES_BASE_HPP_
#include <chrono>
#include <memory>
#include <string>
#include <vector>
#include <mutex>
#include <shared_mutex>
#include <condition_variable>
#include <map>
#include <thread>
#include <atomic>
#include <algorithm>
#include "exception.hpp"
#include "services/builder.hpp"
#include "services/inotify.hpp"
#include "utils/config.hpp"
#include "utils/string.hpp"
#include "utils/concurrency.hpp"
using namespace std::chrono_literals;
#define DEFAULT_FORMAT "format"
#define DefineModule(ModuleName, ModuleType) struct ModuleName : public ModuleType<ModuleName>
#define CastModule(ModuleName) static_cast<ModuleName *>(this)
#define ConstCastModule(ModuleName) static_cast<ModuleName const &>(*this)
DefineBaseException(ModuleError);
DefineChildException(UndefinedFormat, ModuleError);
DefineChildException(UndefinedFormatTag, ModuleError);
class ModuleFormatter
{
public:
struct Format
{
std::string value;
std::vector<std::string> tags;
std::string fg, bg, ul, ol;
int spacing, padding, margin, offset;
std::string decorate(Builder *builder, const std::string& output)
{
if (this->offset != 0) builder->offset(this->offset);
if (this->margin > 0) builder->space(this->margin);
if (!this->bg.empty()) builder->background(this->bg);
if (!this->fg.empty()) builder->color(this->fg);
if (!this->ul.empty()) builder->underline(this->ul);
if (!this->ol.empty()) builder->overline(this->ol);
if (this->padding > 0) builder->space(this->padding);
builder->append(output);
if (this->padding > 0) builder->space(this->padding);
if (!this->ol.empty()) builder->overline_close();
if (!this->ul.empty()) builder->underline_close();
if (!this->fg.empty()) builder->color_close();
if (!this->bg.empty()) builder->background_close();
if (this->margin > 0) builder->space(this->margin);
return builder->flush();
}
};
std::string module_name;
std::map<std::string, std::unique_ptr<Format>> formats;
public:
ModuleFormatter(const std::string& module_name)
: module_name(module_name) {}
void add(const std::string& name, const std::string& fallback, std::vector<std::string> &&tags, std::vector<std::string> &&whitelist = {}) throw(UndefinedFormatTag)
{
auto format = std::make_unique<Format>();
format->value = config::get<std::string>(this->module_name, name, fallback);
format->fg = config::get<std::string>(this->module_name, name +":foreground", "");
format->bg = config::get<std::string>(this->module_name, name +":background", "");
format->ul = config::get<std::string>(this->module_name, name +":underline", "");
format->ol = config::get<std::string>(this->module_name, name +":overline", "");
format->spacing = config::get<int>(this->module_name, name +":spacing", DEFAULT_SPACING);
format->padding = config::get<int>(this->module_name, name +":padding", 0);
format->margin = config::get<int>(this->module_name, name +":margin", 0);
format->offset = config::get<int>(this->module_name, name +":offset", 0);
format->tags.swap(tags);
for (auto &&tag : string::split(format->value, ' ')) {
if (tag[0] != '<' || tag[tag.length()-1] != '>')
continue;
if (std::find(format->tags.begin(), format->tags.end(), tag) != format->tags.end())
continue;
if (std::find(whitelist.begin(), whitelist.end(), tag) != whitelist.end())
continue;
throw UndefinedFormatTag("["+ this->module_name +"] Undefined \""+ name +"\" tag: "+ tag);
}
this->formats.insert(std::make_pair(name, std::move(format)));
}
std::unique_ptr<Format>& get(const std::string& format_name) throw(UndefinedFormat)
{
auto format = this->formats.find(format_name);
if (format == this->formats.end())
throw UndefinedFormat("Format \""+ format_name +"\" has not been added");
return format->second;
}
bool has(const std::string& tag, const std::string& format_name) throw(UndefinedFormat)
{
auto format = this->formats.find(format_name);
if (format == this->formats.end())
throw UndefinedFormat(format_name);
return format->second->value.find(tag) != std::string::npos;
}
bool has(const std::string& tag)
{
for (auto &&format : this->formats)
if (format.second->value.find(tag) != std::string::npos)
return true;
return false;
}
};
namespace modules
{
void broadcast_module_update(const std::string& module_name);
std::string get_tag_name(const std::string& tag);
struct ModuleInterface
{
public:
virtual ~ModuleInterface(){}
virtual std::string name() const = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void refresh() = 0;
virtual std::string operator()() = 0;
virtual bool handle_command(const std::string& cmd) = 0;
};
template<typename ModuleImpl>
class Module : public ModuleInterface
{
concurrency::Atomic<bool> enabled_flag;
concurrency::Value<std::string> cache;
protected:
concurrency::SpinLock output_lock;
concurrency::SpinLock broadcast_lock;
std::string name_;
std::unique_ptr<Builder> builder;
std::unique_ptr<ModuleFormatter> formatter;
std::vector<std::thread> threads;
public:
Module(const std::string& name)
{
this->name_ = "module/" + name;
this->enable(false);
this->cache = "";
// this->builder = std::make_unique<Builder>(false);
this->builder = std::make_unique<Builder>();
this->formatter = std::make_unique<ModuleFormatter>(ConstCastModule(ModuleImpl).name());
}
~Module()
{
if (this->enabled())
this->stop();
std::lock_guard<concurrency::SpinLock> lck(this->broadcast_lock);
for (auto &&t : this->threads) {
if (t.joinable())
t.join();
else
log_warning("["+ ConstCastModule(ModuleImpl).name() +"] Runner thread not joinable");
}
log_trace(name());
}
std::string name() const {
return name_;
}
void stop() {
log_trace(name());
std::lock_guard<concurrency::SpinLock> lck(this->broadcast_lock);
this->enable(false);
}
void refresh() {
this->cache = CastModule(ModuleImpl)->get_output();
}
std::string operator()() {
return this->cache();
}
bool handle_command(const std::string& cmd) {
return CastModule(ModuleImpl)->handle_command(cmd);
}
protected:
bool enabled() {
return this->enabled_flag();
}
void enable(bool state) {
this->enabled_flag = state;
}
void broadcast() {
std::lock_guard<concurrency::SpinLock> lck(this->broadcast_lock);
CastModule(ModuleImpl)->refresh();
broadcast_module_update(ConstCastModule(ModuleImpl).name());
}
std::string get_format() {
return DEFAULT_FORMAT;
}
std::string get_output()
{
std::lock_guard<concurrency::SpinLock> lck(this->output_lock);
log_trace(ConstCastModule(ModuleImpl).name());
if (!this->enabled()) {
log_trace(ConstCastModule(ModuleImpl).name() +" is disabled");
return "";
}
auto format_name = CastModule(ModuleImpl)->get_format();
auto &&format = this->formatter->get(format_name);
int i = 0;
for (auto tag : string::split(format->value, ' ')) {
if ((i > 0 && !tag.empty()) || tag.empty()) {
this->builder->space(format->spacing);
}
if (tag[0] == '<' && tag[tag.length()-1] == '>') {
if (!CastModule(ModuleImpl)->build(this->builder.get(), tag)) {
this->builder->remove_trailing_space(format->spacing);
}
} else {
this->builder->node(tag);
}
i++;
}
return format->decorate(this->builder.get(), this->builder->flush());
}
};
template<typename ModuleImpl>
class StaticModule : public Module<ModuleImpl>
{
protected:
std::unique_ptr<Builder> builder;
public:
StaticModule(const std::string& name, bool lazybuilder = true) : Module<ModuleImpl>(name)
{
this->builder = std::make_unique<Builder>(lazybuilder);
}
void start()
{
this->enable(true);
this->threads.emplace_back(std::thread(&StaticModule::broadcast, this));
}
bool build(Builder *builder, const std::string& tag)
{
return true;
}
};
template<typename ModuleImpl>
class TimerModule : public Module<ModuleImpl>
{
protected:
std::chrono::duration<double> interval = 1s;
concurrency::SpinLock update_lock;
void runner()
{
while (this->enabled()) {
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
if (CastModule(ModuleImpl)->update())
CastModule(ModuleImpl)->broadcast();
std::this_thread::sleep_for(this->interval);
}
}
public:
template<typename I>
TimerModule(const std::string& name, I const &interval)
: Module<ModuleImpl>(name), interval(interval) {}
void start()
{
this->enable(true);
this->threads.emplace_back(std::thread(&TimerModule::runner, this));
}
};
template<typename ModuleImpl>
class EventModule : public Module<ModuleImpl>
{
using Module<ModuleImpl>::Module;
protected:
concurrency::SpinLock update_lock;
void runner()
{
// warmup
CastModule(ModuleImpl)->update();
CastModule(ModuleImpl)->broadcast();
while (this->enabled()) {
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
if (!CastModule(ModuleImpl)->has_event())
continue;
if (!CastModule(ModuleImpl)->update())
continue;
CastModule(ModuleImpl)->broadcast();
}
}
public:
void start()
{
this->enable(true);
this->threads.emplace_back(std::thread(&EventModule::runner, this));
}
};
template<typename ModuleImpl>
class InotifyModule : public Module<ModuleImpl>
{
using Module<ModuleImpl>::Module;
protected:
std::map<std::string, int> watch_list;
concurrency::SpinLock update_lock;
void runner()
{
// warmup
if (CastModule(ModuleImpl)->on_event(nullptr))
CastModule(ModuleImpl)->broadcast();
while (this->enabled()) {
try {
this->poll_events();
} catch (InotifyException &e) {
get_logger()->fatal(e.what());
}
}
}
void poll_events()
{
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
std::vector<std::unique_ptr<InotifyWatch>> watches;
for (auto &&w : this->watch_list) {
watches.emplace_back(std::make_unique<InotifyWatch>(w.first, w.second));
}
while (this->enabled()) {
for (auto &&w : watches) {
log_trace("Polling inotify event for watch at "+ (*w)());
if (w->has_event(500 / watches.size())) {
std::unique_ptr<InotifyEvent> event = w->get_event();
watches.clear();
if (CastModule(ModuleImpl)->on_event(event.get()))
CastModule(ModuleImpl)->broadcast();
return;
}
}
}
}
void watch(const std::string& path, int mask = InotifyEvent::ALL)
{
log_trace(path);
this->watch_list.insert(std::make_pair(path, mask));
}
public:
InotifyModule(const std::string& name, const std::string& path, int mask = InotifyEvent::ALL) : Module<ModuleImpl>(name)
{
this->watch(path, mask);
}
InotifyModule(const std::string& name, std::vector<std::string> paths, int mask = InotifyEvent::ALL) : Module<ModuleImpl>(name)
{
for (auto &&path : paths)
this->watch(path, mask);
}
void start()
{
this->enable(true);
this->threads.emplace_back(std::thread(&InotifyModule::runner, this));
}
};
}
#endif

View File

@ -0,0 +1,67 @@
#ifndef _MODULES_BATTERY_HPP_
#define _MODULES_BATTERY_HPP_
#include <memory>
#include <string>
#include <mutex>
#include "modules/base.hpp"
#include "drawtypes/animation.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/ramp.hpp"
namespace modules
{
enum BatteryState
{
UNKNOWN = 1 << 1,
CHARGING = 1 << 2,
DISCHARGING = 1 << 4,
FULL = 1 << 8,
};
DefineModule(BatteryModule, InotifyModule)
{
const char *FORMAT_CHARGING = "format:charging";
const char *FORMAT_DISCHARGING = "format:discharging";
const char *FORMAT_FULL = "format:full";
const char *TAG_ANIMATION_CHARGING = "<animation:charging>";
const char *TAG_BAR_CAPACITY = "<bar:capacity>";
const char *TAG_RAMP_CAPACITY = "<ramp:capacity>";
const char *TAG_LABEL_CHARGING = "<label:charging>";
const char *TAG_LABEL_DISCHARGING = "<label:discharging>";
const char *TAG_LABEL_FULL = "<label:full>";
// std::mutex ev_mtx;
// std::condition_variable cv;
std::unique_ptr<drawtypes::Animation> animation_charging;
std::unique_ptr<drawtypes::Ramp> ramp_capacity;
std::unique_ptr<drawtypes::Bar> bar_capacity;
std::unique_ptr<drawtypes::Label> label_charging;
std::unique_ptr<drawtypes::Label> label_charging_tokenized;
std::unique_ptr<drawtypes::Label> label_discharging;
std::unique_ptr<drawtypes::Label> label_discharging_tokenized;
std::unique_ptr<drawtypes::Label> label_full;
std::unique_ptr<drawtypes::Label> label_full_tokenized;
std::string battery, adapter;
concurrency::Atomic<int> state;
// std::atomic<int> state;
std::atomic<int> percentage;
int full_at;
void animation_thread_runner();
public:
BatteryModule(const std::string& name);
bool on_event(InotifyEvent *event);
std::string get_format();
bool build(Builder *builder, const std::string& tag);
};
}
#endif

81
include/modules/bspwm.hpp Normal file
View File

@ -0,0 +1,81 @@
#ifndef _MODULES_BSPWM_HPP_
#define _MODULES_BSPWM_HPP_
#include <memory>
#include <string>
#include <unistd.h>
#include "modules/base.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
namespace modules
{
namespace Bspwm
{
enum Flag
{
WORKSPACE_NONE,
WORKSPACE_ACTIVE,
WORKSPACE_URGENT,
WORKSPACE_EMPTY,
WORKSPACE_OCCUPIED,
// used when the monitor is unfocused
WORKSPACE_DIMMED,
MODE_NONE,
MODE_LAYOUT_MONOCLE,
MODE_LAYOUT_TILED,
MODE_STATE_FULLSCREEN,
MODE_STATE_FLOATING,
MODE_NODE_LOCKED,
MODE_NODE_STICKY,
MODE_NODE_PRIVATE
};
struct Workspace
{
Flag flag;
std::unique_ptr<drawtypes::Label> label;
Workspace(Flag flag, std::unique_ptr<drawtypes::Label> label) {
this->flag = flag;
this->label.swap(label);
}
operator bool() { return this->label && *this->label; }
};
}
DefineModule(BspwmModule, EventModule)
{
const char *TAG_LABEL_STATE = "<label:state>";
const char *TAG_LABEL_MODE = "<label:mode>";
const char *EVENT_CLICK = "bwm";
std::map<Bspwm::Flag, std::unique_ptr<drawtypes::Label>> mode_labels;
std::map<Bspwm::Flag, std::unique_ptr<drawtypes::Label>> state_labels;
std::vector<std::unique_ptr<Bspwm::Workspace>> workspaces;
std::vector<std::unique_ptr<drawtypes::Label>*> modes;
std::unique_ptr<drawtypes::IconMap> icons;
std::string monitor;
int socket_fd;
std::string prev_data;
public:
BspwmModule(const std::string& name, const std::string& monitor);
~BspwmModule() { close(this->socket_fd); }
void start();
bool has_event();
bool update();
bool build(Builder *builder, const std::string& tag);
bool handle_command(const std::string& cmd);
};
}
#endif

View File

@ -0,0 +1,22 @@
#ifndef _MODULES_COUNTER_HPP_
#define _MODULES_COUNTER_HPP_
#include "modules/base.hpp"
namespace modules
{
DefineModule(CounterModule, TimerModule)
{
const char *TAG_COUNTER = "<counter>";
concurrency::Atomic<int> counter;
public:
CounterModule(const std::string& name);
bool update();
bool build(Builder *builder, const std::string& tag);
};
}
#endif

54
include/modules/cpu.hpp Normal file
View File

@ -0,0 +1,54 @@
#ifndef _MODULES_CPU_HPP_
#define _MODULES_CPU_HPP_
#include <memory>
#include <string>
#include <vector>
#include "modules/base.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/ramp.hpp"
namespace modules
{
struct CpuTime
{
unsigned long long user;
unsigned long long nice;
unsigned long long system;
unsigned long long idle;
unsigned long long total;
};
DefineModule(CpuModule, TimerModule)
{
const char *TAG_LABEL = "<label>";
const char *TAG_BAR_LOAD = "<bar:load>";
const char *TAG_RAMP_LOAD = "<ramp:load>";
const char *TAG_RAMP_LOAD_PER_CORE = "<ramp:load_per_core>";
std::vector<std::unique_ptr<CpuTime>> cpu_times;
std::vector<std::unique_ptr<CpuTime>> prev_cpu_times;
std::unique_ptr<drawtypes::Bar> bar_load;
std::unique_ptr<drawtypes::Ramp> ramp_load;
std::unique_ptr<drawtypes::Ramp> ramp_load_per_core;
std::unique_ptr<drawtypes::Label> label;
std::unique_ptr<drawtypes::Label> label_tokenized;
float current_total_load;
std::vector<float> current_load;
bool read_values();
float get_load(int core);
public:
CpuModule(const std::string& name);
bool update();
bool build(Builder *builder, const std::string& tag);
};
}
#endif

35
include/modules/date.hpp Normal file
View File

@ -0,0 +1,35 @@
#ifndef _MODULES_DATE_HPP_
#define _MODULES_DATE_HPP_
#include <string>
#include "modules/base.hpp"
namespace modules
{
DefineModule(DateModule, TimerModule)
{
const char *TAG_DATE = "<date>";
const char *EVENT_TOGGLE = "datetoggle";
std::unique_ptr<Builder> builder;
std::string date;
std::string date_detailed;
char date_str[256];
bool detailed = false;
public:
DateModule(const std::string& name);
bool update();
bool build(Builder *builder, const std::string& tag);
std::string get_output();
bool handle_command(const std::string& cmd);
};
}
#endif

85
include/modules/i3.hpp Normal file
View File

@ -0,0 +1,85 @@
#ifndef _MODULES_I3_HPP_
#define _MODULES_I3_HPP_
#include "config.hpp"
#include <memory>
#include <string>
#include <unistd.h>
#include <i3ipc++/ipc.hpp>
#include "modules/base.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
namespace modules
{
namespace i3
{
enum Flag
{
WORKSPACE_NONE,
WORKSPACE_FOCUSED,
WORKSPACE_UNFOCUSED,
WORKSPACE_VISIBLE,
WORKSPACE_URGENT,
// used when the monitor is unfocused
WORKSPACE_DIMMED,
};
struct Workspace
{
int idx;
Flag flag;
std::unique_ptr<drawtypes::Label> label;
Workspace(int idx, Flag flag, std::unique_ptr<drawtypes::Label> label) {
this->idx = idx;
this->flag = flag;
this->label.swap(label);
}
operator bool() { return this->label && *this->label; }
};
}
DefineModule(i3Module, EventModule)
{
const char *TAG_LABEL_STATE = "<label:state>";
const char *EVENT_CLICK = "i3";
concurrency::SpinLock update_lock;
std::unique_ptr<i3ipc::connection> ipc;
// std::map<i3::Flag, std::unique_ptr<drawtypes::Label>> mode_labels;
std::map<i3::Flag, std::unique_ptr<drawtypes::Label>> state_labels;
std::vector<std::unique_ptr<i3::Workspace>> workspaces;
// std::vector<std::unique_ptr<drawtypes::Label>*> modes;
std::unique_ptr<drawtypes::IconMap> icons;
std::string monitor;
bool local_workspaces = true;
std::size_t workspace_name_strip_nchars = 0;
int ipc_fd;
public:
i3Module(const std::string& name, const std::string& monitor);
void start();
void stop();
bool has_event();
bool update();
bool build(Builder *builder, const std::string& tag);
bool handle_command(const std::string& cmd);
};
}
#endif

View File

@ -0,0 +1,35 @@
#ifndef _MODULES_MEMORY_HPP_
#define _MODULES_MEMORY_HPP_
#include <atomic>
#include "modules/base.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
namespace modules
{
DefineModule(MemoryModule, TimerModule)
{
const char *TAG_LABEL = "<label>";
const char *TAG_BAR_USED = "<bar:used>";
const char *TAG_BAR_FREE = "<bar:free>";
std::unique_ptr<drawtypes::Bar> bar_used;
std::unique_ptr<drawtypes::Bar> bar_free;
std::unique_ptr<drawtypes::Label> label;
std::unique_ptr<drawtypes::Label> label_tokenized;
std::atomic<int> percentage_used;
std::atomic<int> percentage_free;
public:
MemoryModule(const std::string& name);
bool update();
bool build(Builder *builder, const std::string& tag);
};
}
#endif

47
include/modules/menu.hpp Normal file
View File

@ -0,0 +1,47 @@
#ifndef _MODULES_MENU_HPP_
#define _MODULES_MENU_HPP_
#include <mutex>
#include "modules/base.hpp"
namespace modules
{
struct MenuTreeItem {
std::string exec;
std::unique_ptr<drawtypes::Label> label;
};
struct MenuTree {
std::vector<std::unique_ptr<MenuTreeItem>> items;
};
DefineModule(MenuModule, StaticModule)
{
const char *TAG_LABEL_TOGGLE = "<label:toggle>";
const char *TAG_MENU = "<menu>";
const char *EVENT_MENU_OPEN = "menu_open:";
const char *EVENT_MENU_CLOSE = "menu_close";
std::mutex output_mtx;
std::mutex cmd_mtx;
int current_level = -1;
std::vector<std::unique_ptr<MenuTree>> levels;
std::unique_ptr<drawtypes::Label> label_open;
std::unique_ptr<drawtypes::Label> label_close;
public:
MenuModule(const std::string& name);
std::string get_output() throw(UndefinedFormat);
bool build(Builder *builder, const std::string& tag);
bool handle_command(const std::string& cmd);
};
}
#endif

78
include/modules/mpd.hpp Normal file
View File

@ -0,0 +1,78 @@
#ifndef _MODULES_MPD_HPP_
#define _MODULES_MPD_HPP_
#include "modules/base.hpp"
#include "interfaces/mpd.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
namespace modules
{
DefineModule(MpdModule, EventModule)
{
static const int PROGRESSBAR_THREAD_SYNC_COUNT = 10;
const std::chrono::duration<double> PROGRESSBAR_THREAD_INTERVAL = 1s;
const char *FORMAT_ONLINE = "format:online";
const char *TAG_BAR_PROGRESS = "<bar:progress>";
const char *TAG_TOGGLE = "<toggle>";
const char *TAG_LABEL_SONG = "<label:song>";
const char *TAG_LABEL_TIME = "<label:time>";
const char *TAG_ICON_RANDOM = "<icon:random>";
const char *TAG_ICON_REPEAT = "<icon:repeat>";
const char *TAG_ICON_REPEAT_ONE = "<icon:repeatone>";
const char *TAG_ICON_PREV = "<icon:prev>";
const char *TAG_ICON_STOP = "<icon:stop>";
const char *TAG_ICON_PLAY = "<icon:play>";
const char *TAG_ICON_PAUSE = "<icon:pause>";
const char *TAG_ICON_NEXT = "<icon:next>";
const char *FORMAT_OFFLINE = "format:offline";
const char *TAG_LABEL_OFFLINE = "<label:offline>";
const char *EVENT_PLAY = "mpdplay";
const char *EVENT_PAUSE = "mpdpause";
const char *EVENT_STOP = "mpdstop";
const char *EVENT_PREV = "mpdprev";
const char *EVENT_NEXT = "mpdnext";
const char *EVENT_REPEAT = "mpdrepeat";
const char *EVENT_REPEAT_ONE = "mpdrepeatone";
const char *EVENT_RANDOM = "mpdrandom";
const char *EVENT_SEEK = "mpdseek";
std::unique_ptr<drawtypes::Bar> bar_progress;
std::unique_ptr<drawtypes::IconMap> icons;
std::unique_ptr<drawtypes::Label> label_song;
std::unique_ptr<drawtypes::Label> label_song_tokenized;
std::unique_ptr<drawtypes::Label> label_time;
std::unique_ptr<drawtypes::Label> label_time_tokenized;
std::unique_ptr<drawtypes::Label> label_offline;
std::unique_ptr<mpd::Status> status;
std::string toggle_on_color;
std::string toggle_off_color;
std::shared_ptr<mpd::Connection> mpd;
std::chrono::system_clock::time_point synced_at;
float sync_interval;
bool clickable_progress = false;
std::string progress_fill, progress_empty, progress_indicator;
public:
MpdModule(const std::string& name);
~MpdModule();
void start();
bool has_event();
bool update();
std::string get_format();
bool build(Builder *builder, const std::string& tag);
bool handle_command(const std::string& cmd);
};
}
#endif

View File

@ -0,0 +1,68 @@
#ifndef _MODULES_NETWORK_HPP_
#define _MODULES_NETWORK_HPP_
#include <atomic>
#include <chrono>
#include <memory>
#include <string>
#include "modules/base.hpp"
#include "interfaces/net.hpp"
#include "services/logger.hpp"
#include "services/store.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/animation.hpp"
#include "drawtypes/ramp.hpp"
namespace modules
{
DefineModule(NetworkModule, TimerModule)
{
const char *FORMAT_CONNECTED = "format:connected";
const char *FORMAT_PACKETLOSS = "format:packetloss";
const char *FORMAT_DISCONNECTED = "format:disconnected";
const char *TAG_RAMP_SIGNAL = "<ramp:signal>";
const char *TAG_LABEL_CONNECTED = "<label:connected>";
const char *TAG_LABEL_DISCONNECTED = "<label:disconnected>";
const char *TAG_LABEL_PACKETLOSS = "<label:packetloss>";
const char *TAG_ANIMATION_PACKETLOSS = "<animation:packetloss>";
const int PING_EVERY_NTH_UPDATE = 10;
std::unique_ptr<net::WiredNetwork> wired_network;
std::unique_ptr<net::WirelessNetwork> wireless_network;
std::unique_ptr<drawtypes::Ramp> ramp_signal;
std::unique_ptr<drawtypes::Animation> animation_packetloss;
std::unique_ptr<drawtypes::Label> label_connected;
std::unique_ptr<drawtypes::Label> label_connected_tokenized;
std::unique_ptr<drawtypes::Label> label_disconnected;
std::unique_ptr<drawtypes::Label> label_packetloss;
std::unique_ptr<drawtypes::Label> label_packetloss_tokenized;
std::string interface;
bool connected = false;
int signal_quality = 0;
bool conseq_packetloss = false;
int counter = -1; // -1 to avoid ping the first run
int connectivity_test_interval;
// std::thread t_animation;
// void animation_thread_runner();
public:
NetworkModule(const std::string& name);
~NetworkModule();
bool update();
std::string get_format();
bool build(Builder *builder, const std::string& tag);
};
}
#endif

View File

@ -0,0 +1,36 @@
#ifndef _MODULES_SCRIPT_HPP_
#define _MODULES_SCRIPT_HPP_
#include "modules/base.hpp"
#include "services/command.hpp"
namespace modules
{
DefineModule(ScriptModule, TimerModule)
{
const char *TAG_OUTPUT = "<output>";
std::unique_ptr<Builder> builder;
std::string exec;
std::string click_left;
std::string click_middle;
std::string click_right;
std::string scroll_up;
std::string scroll_down;
std::string output;
std::atomic<int> counter;
protected:
public:
ScriptModule(const std::string& name);
bool update();
bool build(Builder *builder, const std::string& tag);
std::string get_output();
};
}
#endif

20
include/modules/text.hpp Normal file
View File

@ -0,0 +1,20 @@
#ifndef _MODULES_TEXT_HPP_
#define _MODULES_TEXT_HPP_
#include "modules/base.hpp"
namespace modules
{
DefineModule(TextModule, StaticModule)
{
const char *FORMAT = "content";
public:
TextModule(const std::string& name) throw(UndefinedFormat);
std::string get_format();
std::string get_output();
};
}
#endif

View File

@ -0,0 +1,42 @@
#ifndef _MODULES_TORRENT_HPP_
#define _MODULES_TORRENT_HPP_
#include "modules/base.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/label.hpp"
namespace modules
{
struct Torrent
{
std::string title;
unsigned long data_total;
unsigned long data_downloaded;
unsigned long data_remaining;
float perc_downloaded;
std::unique_ptr<drawtypes::Label> label_tokenized;
};
DefineModule(TorrentModule, InotifyModule)
{
const char *TAG_LABEL = "<label>";
const char *TAG_BAR_PROGRESS = "<bar:progress>";
std::vector<std::unique_ptr<Torrent>> torrents;
std::unique_ptr<drawtypes::Label> label;
std::unique_ptr<drawtypes::Label> label_tokenized;
std::unique_ptr<drawtypes::Bar> bar;
std::string pipe_cmd;
std::vector<std::unique_ptr<Torrent>> &read_data_into(std::vector<std::unique_ptr<Torrent>> &container);
public:
TorrentModule(const std::string& name);
void start();
bool on_event(InotifyEvent *event);
bool build(Builder *builder, const std::string& tag);
};
}
#endif

View File

@ -0,0 +1,60 @@
#ifndef _MODULES_VOLUME_HPP_
#define _MODULES_VOLUME_HPP_
#include "modules/base.hpp"
#include "interfaces/alsa.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/ramp.hpp"
#include "drawtypes/bar.hpp"
namespace modules
{
DefineModule(VolumeModule, EventModule)
{
const char *FORMAT_VOLUME = "format:volume";
const char *FORMAT_MUTED = "format:muted";
const char *TAG_RAMP_VOLUME = "<ramp:volume>";
const char *TAG_BAR_VOLUME = "<bar:volume>";
const char *TAG_LABEL_VOLUME = "<label:volume>";
const char *TAG_LABEL_MUTED = "<label:muted>";
const char *EVENT_VOLUME_UP = "volup";
const char *EVENT_VOLUME_DOWN = "voldown";
const char *EVENT_TOGGLE_MUTE = "volmute";
std::unique_ptr<alsa::Mixer> master_mixer;
std::unique_ptr<alsa::Mixer> speaker_mixer;
std::unique_ptr<alsa::Mixer> headphone_mixer;
std::unique_ptr<alsa::ControlInterface> headphone_ctrl;
int headphone_ctrl_numid;
std::unique_ptr<Builder> builder;
std::unique_ptr<drawtypes::Bar> bar_volume;
std::unique_ptr<drawtypes::Ramp> ramp_volume;
std::unique_ptr<drawtypes::Label> label_volume;
std::unique_ptr<drawtypes::Label> label_volume_tokenized;
std::unique_ptr<drawtypes::Label> label_muted;
std::unique_ptr<drawtypes::Label> label_muted_tokenized;
int volume = 0;
bool muted = false;
public:
VolumeModule(const std::string& name) throw(ModuleError);
~VolumeModule();
bool has_event();
bool update();
std::string get_format();
bool build(Builder *builder, const std::string& tag);
std::string get_output();
bool handle_command(const std::string& cmd);
};
}
#endif

57
include/registry.hpp Normal file
View File

@ -0,0 +1,57 @@
#ifndef _REGISTRY_HPP_
#define _REGISTRY_HPP_
#include <condition_variable>
#include "exception.hpp"
#include "modules/base.hpp"
DefineBaseException(RegistryError);
DefineChildException(ModuleNotFound, RegistryError);
struct ModuleEntry
{
concurrency::Atomic<bool> warmedup;
std::unique_ptr<modules::ModuleInterface> module;
ModuleEntry(std::unique_ptr<modules::ModuleInterface> &&module)
{
this->warmedup = false;
this->module.swap(module);
}
};
class Registry
{
// Stopped and no loaded modules
const int STAGE_1 = 1;
// Modules loaded but waiting for initial broadcast
const int STAGE_2 = 2;
// Running
const int STAGE_3 = 3;
// Unloading modules
const int STAGE_4 = 4;
concurrency::Atomic<int> stage;
std::vector<std::unique_ptr<ModuleEntry>> modules;
std::mutex wait_mtx;
std::condition_variable wait_cv;
public:
Registry();
bool ready();
void insert(std::unique_ptr<modules::ModuleInterface> &&module);
void load();
void unload();
bool wait();
void notify(const std::string& module_name);
std::string get(const std::string& module_name);
std::unique_ptr<ModuleEntry>& find(const std::string& module_name) throw(ModuleNotFound);
};
std::shared_ptr<Registry> &get_registry();
#endif

View File

@ -0,0 +1,102 @@
#ifndef _SERVICES_BUILDER_HPP_
#define _SERVICES_BUILDER_HPP_
#include <string>
#include <memory>
#include "drawtypes/animation.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/ramp.hpp"
#define DEFAULT_SPACING -1
class Lemonbuddy;
class Builder
{
public:
enum Alignment {
ALIGN_NONE,
ALIGN_LEFT,
ALIGN_CENTER,
ALIGN_RIGHT,
};
private:
std::string output;
bool lazy_closing = true;
Alignment alignment = ALIGN_NONE;
// counters
int A = 0, B = 0, F = 0, T = 0, U = 0, o = 0, u = 0;
int T_value = 1;
std::string B_value = "", F_value = "", U_value = "";
void tag_open(char tag, const std::string& value);
void tag_close(char tag);
void align_left();
void align_center();
void align_right();
public:
Builder(bool lazy_closing = true) : lazy_closing(lazy_closing){}
void set_lazy_closing(bool mode) { this->lazy_closing = mode; }
std::string flush();
void append(const std::string& node);
void append_module_output(Alignment alignment, const std::string& module_output, bool margin_left = true, bool margin_right = true);
void node(const std::string& str, bool add_space = false);
void node(const std::string& str, int font_index, bool add_space = false);
void node(drawtypes::Bar *bar, float percentage, bool add_space = false);
void node(std::unique_ptr<drawtypes::Bar> &bar, float percentage, bool add_space = false);
void node(drawtypes::Label *label, bool add_space = false);
void node(std::unique_ptr<drawtypes::Label> &label, bool add_space = false);
void node(drawtypes::Icon *icon, bool add_space = false);
void node(std::unique_ptr<drawtypes::Icon> &icon, bool add_space = false);
void node(drawtypes::Ramp *ramp, float percentage, bool add_space = false);
void node(std::unique_ptr<drawtypes::Ramp> &ramp, float percentage, bool add_space = false);
void node(drawtypes::Animation *animation, bool add_space = false);
void node(std::unique_ptr<drawtypes::Animation> &animation, bool add_space = false);
void offset(int pixels = 0);
void space(int width = DEFAULT_SPACING);
void remove_trailing_space(int width = DEFAULT_SPACING);
void font(int index);
void font_close(bool force = false);
void background(const std::string& color);
void background_close(bool force = false);
void color(const std::string& color);
void color_alpha(const std::string& alpha);
void color_close(bool force = false);
void line_color(const std::string& color);
void line_color_close(bool force = false);
void overline(const std::string& color = "");
void overline_close(bool force = false);
void underline(const std::string& color = "");
void underline_close(bool force = false);
void invert();
void cmd(int button, std::string action, bool condition = true);
void cmd_close(bool force = false);
};
#endif

View File

@ -0,0 +1,45 @@
#ifndef _SERVICES_COMMAND_HPP_
#define _SERVICES_COMMAND_HPP_
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include "exception.hpp"
#include "utils/proc.hpp"
class CommandException : public Exception {
using Exception::Exception;
};
class Command
{
protected:
std::string cmd;
int stdout[2];
int stdin[2];
pid_t fork_pid;
int fork_status;
public:
Command(const std::string& cmd, int stdout[2] = nullptr, int stdin[2] = nullptr)
throw(CommandException);
~Command() throw(CommandException);
int exec(bool wait_for_completion = true) throw(CommandException);
int wait() throw(CommandException);
void tail(std::function<void(std::string)> callback);
int writeline(const std::string& data);
int get_stdout(int);
int get_stdin(int);
pid_t get_pid();
int get_exit_status();
};
#endif

View File

@ -0,0 +1,63 @@
#ifndef _SERVICES_INOTIFY_HPP_
#define _SERVICES_INOTIFY_HPP_
#include <memory>
#include <sys/inotify.h>
#include "exception.hpp"
class InotifyException : public Exception
{
public:
InotifyException(const std::string& msg)
: Exception("[Inotify] "+ msg){}
};
struct InotifyEvent
{
static const auto ACCESSED = IN_ACCESS;
static const auto MODIFIED = IN_MODIFY;
static const auto ATTRIBUTES_CHANGED = IN_ATTRIB;
static const auto FILE_ADDED = IN_CREATE;
static const auto FILE_REMOVED = IN_DELETE;
static const auto DELETED_SELF = IN_DELETE_SELF;
static const auto MOVED_SELF = IN_MOVE_SELF;
static const auto MOVED_FROM = IN_MOVED_FROM;
static const auto MOVED_TO = IN_MOVED_TO;
static const auto OPENED = IN_OPEN;
static const auto CLOSED = IN_CLOSE;
static const auto MOVED = IN_MOVE;
static const auto ALL = (ACCESSED | MODIFIED | ATTRIBUTES_CHANGED \
| FILE_ADDED | FILE_REMOVED \
| DELETED_SELF | MOVED_SELF \
| MOVED_FROM | MOVED_TO \
| OPENED | CLOSED | MOVED);
bool is_dir;
std::string filename;
int wd, cookie, mask = 0;
};
class InotifyWatch
{
std::string path;
int fd = -1, wd = -1, mask;
public:
InotifyWatch(const std::string& path, int mask = InotifyEvent::ALL) throw (InotifyException);
~InotifyWatch();
std::string operator()() {
return this->path;
}
bool has_event(int timeout_ms = 1000);
std::unique_ptr<InotifyEvent> get_event();
};
#endif

View File

@ -0,0 +1,69 @@
#ifndef _SERVICES_LOGGER_HPP_
#define _SERVICES_LOGGER_HPP_
#include <memory>
#include <string>
#include <mutex>
#include "exception.hpp"
#include "utils/streams.hpp"
#define LOGGER_FD STDERR_FILENO
#define LOGGER_TAG_FATAL isatty(LOGGER_FD) ? "\033[1;31mfatal\033[37m ** \033[0m" : "fatal ** "
#define LOGGER_TAG_ERR isatty(LOGGER_FD) ? "\033[1;31m err\033[37m ** \033[0m" : "error ** "
#define LOGGER_TAG_WARN isatty(LOGGER_FD) ? "\033[1;33m warn\033[37m ** \033[0m" : "warn ** "
#define LOGGER_TAG_INFO isatty(LOGGER_FD) ? "\033[1;36m info\033[37m ** \033[0m" : "info ** "
#define LOGGER_TAG_DEBUG isatty(LOGGER_FD) ? "\033[1;35mdebug\033[37m ** \033[0m" : "debug ** "
#define LOGGER_TAG_TRACE isatty(LOGGER_FD) ? "\033[1;32mtrace\033[37m ** \033[0m" : "trace ** "
#define log_error(s) get_logger()->error(s)
#define log_warning(s) get_logger()->warning(s)
#define log_info(s) get_logger()->info(s)
#define log_debug(s) get_logger()->debug(s)
#define log_trace(s) get_logger()->trace(__FILE__, __FUNCTION__, __LINE__, s)
enum LogLevel
{
LEVEL_NONE = 1 << 0,
LEVEL_ERROR = 1 << 1,
LEVEL_WARNING = 1 << 2,
LEVEL_INFO = 1 << 4,
LEVEL_DEBUG = 1 << 8,
LEVEL_TRACE = 1 << 16,
LEVEL_ALL = LEVEL_ERROR | LEVEL_WARNING | LEVEL_INFO | LEVEL_DEBUG | LEVEL_TRACE
};
class Logger
{
std::mutex mtx;
int level = LogLevel::LEVEL_ERROR | LogLevel::LEVEL_WARNING;
int fd = LOGGER_FD;
public:
Logger();
void set_level(int level);
void add_level(int level);
void fatal(const std::string& msg);
void error(const std::string& msg);
void warning(const std::string& msg);
void info(const std::string& msg);
void debug(const std::string& msg);
void trace(const char *file, const char *fn, int lineno, const std::string& msg);
void fatal(int msg) { fatal(std::to_string(msg)); }
void error(int msg) { error(std::to_string(msg)); }
void warning(int msg) { warning(std::to_string(msg)); }
void info(int msg) { info(std::to_string(msg)); }
void debug(int msg) { debug(std::to_string(msg)); }
void trace(const char *file, const char *fn, int lineno, int msg) {
trace(file, fn, lineno, std::to_string(msg));
}
};
std::shared_ptr<Logger> &get_logger();
#endif

View File

@ -0,0 +1,36 @@
#ifndef _SERVICES_STORE_HPP_
#define _SERVICES_STORE_HPP_
#include <string>
#include <memory>
#include <boost/interprocess/anonymous_shared_memory.hpp>
#include <boost/interprocess/mapped_region.hpp>
#define PIPE_READ 0
#define PIPE_WRITE 1
struct Store
{
const char FLAG_NO_DATA = '1';
const char FLAG_DATA = '2';
boost::interprocess::mapped_region region;
boost::interprocess::mapped_region state_region;
void flag();
void unflag();
bool check();
char &get(char &d);
void set(char val);
std::string get_string();
std::string &get_string(std::string& s);
void set_string(const std::string& s);
Store(int size);
~Store() {}
};
#endif

44
include/utils/cli.hpp Normal file
View File

@ -0,0 +1,44 @@
#ifndef _UTILS_CLI_HPP_
#define _UTILS_CLI_HPP_
#include <string>
#include <vector>
#include "exception.hpp"
namespace cli
{
class CommandLineError : public Exception {
using Exception::Exception;
};
struct Option
{
std::string flag_short;
std::string flag_long;
std::string placeholder;
std::string help;
std::vector<std::string> accept;
Option(const std::string& flag_short, const std::string& flag_long, const std::string& placeholder, std::vector<std::string> accept, std::string help)
: flag_short(flag_short), flag_long(flag_long), placeholder(placeholder), help(help), accept(accept){}
};
void add_option(const std::string& opt_short, const std::string& opt_long, const std::string& help);
void add_option(const std::string& opt_short, const std::string& opt_long, const std::string& placeholder, const std::string& help, std::vector<std::string> accept = {});
bool is_option(char *opt, const std::string& opt_short, const std::string& opt_long);
bool is_option_short(char *opt, const std::string& opt_short);
bool is_option_long(char *opt, const std::string& opt_long);
std::string parse_option_value(int &i, int argc, char **argv, std::vector<std::string> accept = {});
bool has_option(const std::string& opt);
std::string get_option_value(const std::string& opt);
bool match_option_value(const std::string& opt, const std::string& val);
void parse(int i, int argc, char **argv);
void usage(const std::string& usage, bool exit_success = true);
}
#endif

View File

@ -0,0 +1,115 @@
#include <atomic>
#include <mutex>
#include <thread>
// Based on https://github.com/ademakov/Evenk
namespace concurrency
{
struct NoBackoff
{
bool operator()()
{
return true;
}
};
struct YieldBackoff
{
bool operator()()
{
std::this_thread::yield();
return false;
}
};
class SpinLock
{
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
public:
SpinLock() = default;
SpinLock(const SpinLock &) = delete;
SpinLock &operator=(const SpinLock &) = delete;
void lock() noexcept
{
lock(NoBackoff {});
}
template <typename Backoff>
void lock(Backoff backoff) noexcept
{
while (lock_flag.test_and_set(std::memory_order_acquire)) {
backoff();
}
}
void unlock() noexcept
{
lock_flag.clear(std::memory_order_release);
}
};
template<typename T>
class Atomic
{
concurrency::SpinLock lock;
std::atomic<T> value;
public:
Atomic() = default;
Atomic(T init) {
this->operator=(init);
}
void operator=(T value)
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
this->value = value;
}
T operator()()
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value;
}
operator bool()
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value;
}
};
template<typename T>
class Value
{
concurrency::SpinLock lock;
T value;
public:
Value() = default;
Value(T init) {
this->operator=(init);
}
void operator=(T value)
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
this->value = value;
}
T operator()()
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value;
}
operator bool()
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value;
}
};
}

152
include/utils/config.hpp Normal file
View File

@ -0,0 +1,152 @@
#ifndef _UTILS_CONFIG_HPP_
#define _UTILS_CONFIG_HPP_
#include <string>
#include <vector>
#include <mutex>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "exception.hpp"
#include "services/logger.hpp"
#include "utils/string.hpp"
namespace config
{
class ConfigException : public Exception {
using Exception::Exception;
};
class UnexistingFileError : public ConfigException {
using ConfigException::ConfigException;
};
class ParseError : public ConfigException {
using ConfigException::ConfigException;
};
class MissingValueException : public ConfigException {
using ConfigException::ConfigException;
};
class MissingListValueException : public ConfigException {
using ConfigException::ConfigException;
};
class InvalidVariableException : public ConfigException {
using ConfigException::ConfigException;
};
class InvalidReferenceException : public ConfigException {
using ConfigException::ConfigException;
};
static std::recursive_mutex mtx;
std::string get_bar_path();
void set_bar_path(const std::string& path);
void load(const std::string& path) throw(UnexistingFileError, ParseError);
void load(char *dir, const std::string& path);
void reload() throw(ParseError);
boost::property_tree::ptree get_tree();
std::string build_path(const std::string& section, const std::string& key);
template<typename T>
T dereference_var(const std::string& ref_section, const std::string& ref_key, const std::string& var, const T ref_val) throw (InvalidVariableException, InvalidReferenceException)
{
std::lock_guard<std::recursive_mutex> lck(config::mtx);
std::string::size_type n, m;
if ((n = var.find("${")) != 0) return ref_val;
if ((m = var.find("}")) != var.length()-1) return ref_val;
auto path = var.substr(2, m-2);
auto ref_path = build_path(ref_section, ref_key);
if ((n = path.find(".")) == std::string::npos)
throw InvalidVariableException("Invalid variable "+ ref_path +" => ${"+ path +"}");
auto section = string::replace(path.substr(0, n), "BAR", get_bar_path());
auto key = path.substr(n+1, path.length()-n-1);
auto val = get_tree().get_optional<T>(build_path(section, key));
if (val == boost::none)
throw InvalidReferenceException("Variable defined at ["+ ref_path +"] points to a non existing parameter: ["+ build_path(section, key) +"]");
auto str_val = get_tree().get<std::string>(build_path(section, key));
return dereference_var<T>(section, key, str_val, val.get());
}
template<typename T>
T get(const std::string& section, const std::string& key) throw (MissingValueException)
{
std::lock_guard<std::recursive_mutex> lck(config::mtx);
auto val = get_tree().get_optional<T>(build_path(section, key));
if (val == boost::none)
throw MissingValueException("Missing property \""+ key +"\" in section ["+ section +"]");
auto str_val = get_tree().get<std::string>(build_path(section, key));
return dereference_var<T>(section, key, str_val, val.get());
}
template<typename T>
T get(const std::string& section, const std::string& key, T default_value)
{
std::lock_guard<std::recursive_mutex> lck(config::mtx);
auto val = get_tree().get_optional<T>(build_path(section, key));
auto str_val = get_tree().get_optional<std::string>(build_path(section, key));
return dereference_var<T>(section, key, str_val.get_value_or(""), val.get_value_or(default_value));
}
template<typename T>
std::vector<T> get_list(const std::string& section, const std::string& key) throw (MissingListValueException)
{
std::lock_guard<std::recursive_mutex> lck(config::mtx);
std::vector<T> vec;
boost::optional<T> value;
while ((value = get_tree().get_optional<T>(build_path(section, key) + ":"+ std::to_string(vec.size()))) != boost::none) {
auto str_val = get_tree().get<std::string>(build_path(section, key) + ":"+ std::to_string(vec.size()));
vec.emplace_back(dereference_var<T>(section, key, str_val, value.get()));
}
if (vec.empty())
throw MissingListValueException("Missing property \""+ key + ":0\" in section ["+ section +"]");
return vec;
}
template<typename T>
std::vector<T> get_list(const std::string& section, const std::string& key, std::vector<T> default_value)
{
std::lock_guard<std::recursive_mutex> lck(config::mtx);
std::vector<T> vec;
boost::optional<T> value;
while ((value = get_tree().get_optional<T>(build_path(section, key) + ":"+ std::to_string(vec.size()))) != boost::none) {
auto str_val = get_tree().get<std::string>(build_path(section, key) + ":"+ std::to_string(vec.size()));
vec.emplace_back(dereference_var<T>(section, key, str_val, value.get()));
}
if (vec.empty())
return default_value;
else
return vec;
}
}
#endif

80
include/utils/io.hpp Normal file
View File

@ -0,0 +1,80 @@
#ifndef _UTILS_IO_HPP_
#define _UTILS_IO_HPP_
#include <string>
#include <functional>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
namespace io
{
namespace socket
{
int open(const std::string& path);
int send(int fd, const std::string& data, int flags = 0);
int recv(int fd, char *buffer, int recv_bytes, int flags = 0);
}
namespace file
{
class FilePtr
{
FILE *fptr = nullptr;
public:
std::string path;
std::string mode;
FilePtr(const std::string& path, const std::string& mode = "a+")
{
this->path = std::string(path);
this->mode = std::string(mode);
this->fptr = fopen(this->path.c_str(), this->mode.c_str());
}
~FilePtr()
{
if (this->fptr != nullptr)
fclose(this->fptr);
}
operator bool() {
return this->fptr != nullptr;
}
FILE *operator()() {
return this->fptr;
}
};
bool exists(const std::string& fname);
std::string get_contents(const std::string& fname);
bool is_fifo(const std::string& fname);
std::size_t write(FilePtr *fptr, const std::string& data);
std::size_t write(const std::string& fpath, const std::string& data);
}
std::string read(int read_fd, int bytes_to_read = -1);
std::string read(int read_fd, int bytes_to_read, int &bytes_read_loc, int &status_loc);
std::string readline(int read_fd);
int write(int write_fd, const std::string& data);
int writeline(int write_fd, const std::string& data);
void tail(int read_fd, std::function<void(std::string)> callback);
void tail(int read_fd, int writeback_fd);
bool poll_read(int fd, int timeout_ms = 1);
bool poll_write(int fd, int timeout_ms = 1);
bool poll(int fd, short int events, int timeout_ms = 1);
int get_flags(int fd);
int set_blocking(int fd);
int set_non_blocking(int fd);
}
#endif

18
include/utils/math.hpp Normal file
View File

@ -0,0 +1,18 @@
#ifndef _UTILS_MATH_HPP_
#define _UTILS_MATH_HPP_
#include <string>
#include <algorithm>
namespace math
{
template<typename T>
T cap(T value, T min_value, T max_value)
{
value = std::min<T>(value, max_value);
value = std::max<T>(value, min_value);
return value;
}
}
#endif

16
include/utils/memory.hpp Normal file
View File

@ -0,0 +1,16 @@
#ifndef _UTILS_MEMORY_HPP
#define _UTILS_MEMORY_HPP
// Swap the two ints without the need of creating another tmp variable
#define int_memswap(one, two) one += two; \
two = one ? two; \
one -= two;
#define _repeat(n, var_name) for (int var_name = n; var_name--;)
#define repeat(n) _repeat(n, i)
#define repeat_(n) _repeat(n, i_)
#define repeat_with(n, m) for (m = n; m--;)
#define repeat_i i
#define repeat_i_rev(n) (n - i - 1)
#endif

47
include/utils/proc.hpp Normal file
View File

@ -0,0 +1,47 @@
#ifndef _UTILS_PROC_HPP_
#define _UTILS_PROC_HPP_
#include <string>
#include <errno.h>
#include <cstring>
#include <signal.h>
#include "exception.hpp"
#define PIPE_READ 0
#define PIPE_WRITE 1
#define STRERRNO std::string(std::strerror(errno))
#define STRERRNO_VAL(s) std::string(std::strerror(s))
#define SIGCSTR(sig) strsignal(sig)
#define SIGNSTR(sig) std::to_string(sig)
#define SIGSTR(sig) std::string(SIGCSTR(sig))
namespace proc
{
class ExecFailure : public Exception {
using Exception::Exception;
};
pid_t get_process_id();
pid_t get_parent_process_id();
bool in_parent_process(pid_t pid);
bool in_forked_process(pid_t pid);
pid_t fork();
bool pipe(int fds[2]);
void exec(const std::string& cmd) throw(ExecFailure);
bool kill(pid_t pid, int sig = SIGTERM);
pid_t wait(int *status);
pid_t wait_for_completion(pid_t pid, int *status, int options = 0);
pid_t wait_for_completion(int *status, int options = 0);
pid_t wait_for_completion_nohang(pid_t pid, int *status);
pid_t wait_for_completion_nohang(int *status);
pid_t wait_for_completion_nohang();
}
#endif

47
include/utils/streams.hpp Normal file
View File

@ -0,0 +1,47 @@
#ifndef _UTILS_STREAMS_HPP_
#define _UTILS_STREAMS_HPP_
#include <iostream>
#include <streambuf>
#include <cstdio>
#include <unistd.h>
namespace streams
{
class fdoutbuf : public std::streambuf
{
protected:
int fd;
virtual int_type overflow(int_type c)
{
if (c != EOF) {
char z = c;
if (write (fd, &z, 1) != 1)
return EOF;
}
return c;
}
virtual std::streamsize xsputn(const char* s, std::streamsize num) {
return write(fd,s,num);
}
public:
fdoutbuf(int _fd) : fd(_fd) {}
};
class fdout : public std::ostream
{
protected:
fdoutbuf buf;
public:
fdout(int fd) : std::ostream(0), buf(fd) {
rdbuf(&buf);
}
};
}
#endif

37
include/utils/string.hpp Normal file
View File

@ -0,0 +1,37 @@
#ifndef _UTILS_STRING_HPP_
#define _UTILS_STRING_HPP_
#include <string>
#include <vector>
#define STR(s) std::string(s)
#define STRI(s) std::to_string(s)
namespace string
{
bool compare(const std::string& s1, const std::string& s2);
std::string upper(const std::string& s);
std::string lower(const std::string& s);
std::string replace(const std::string& haystack, const std::string& needle, const std::string& replacement);
std::string replace_all(const std::string& haystack, const std::string& needle, const std::string& replacement);
std::string squeeze(const std::string& haystack, const char &needle);
std::string strip(const std::string& haystack, const char &needle);
std::string strip_trailing_newline(const std::string& s);
std::string trim(const std::string& haystack, const char &needle);
std::string ltrim(const std::string& haystack, const char &needle);
std::string rtrim(const std::string& haystack, const char &needle);
std::string join(const std::vector<std::string> &strs, const std::string& delim);
std::vector<std::string> split(const std::string& s, const char &delim);
std::vector<std::string> &split_into(const std::string& s, const char &delim, std::vector<std::string> &elems);
std::size_t find_nth(const std::string& haystack, std::size_t pos, const std::string& needle, std::size_t nth);
}
#endif

10
include/utils/timer.hpp Normal file
View File

@ -0,0 +1,10 @@
#ifndef _UTILS_TIMER_HPP_
#define _UTILS_TIMER_HPP_
namespace timer
{
unsigned int seconds_to_microseconds(float seconds);
void sleep(float seconds);
}
#endif

34
include/utils/xlib.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef _UTILS_XLIB_HPP_
#define _UTILS_XLIB_HPP_
#include <sstream>
#include <memory>
#include <vector>
namespace xlib
{
struct Monitor
{
std::string name;
int index = 0;
int width = 0;
int height = 0;
int x = 0;
int y = 0;
std::string str() {
return this->name + ": "+ this->geom();
}
std::string geom() {
return std::to_string(width) +"x"+ std::to_string(height) +"+"+
std::to_string(x) +"+"+ std::to_string(y);
}
};
std::unique_ptr<Monitor> get_monitor(const std::string& monitor_name);
std::vector<std::unique_ptr<Monitor>> get_sorted_monitorlist();
}
#endif

38
scripts/lemonbuddy_wrapper.sh Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env sh
[ $# -eq 0 ] && {
echo "No bar specified" ; exit 1
}
command -v lemonbar >/dev/null || {
echo "Lemonbar is not installed" ; exit 1
}
command -v lemonbuddy >/dev/null || {
echo "Lemonbuddy is not installed" ; exit 1
}
lemonbar="$(lemonbuddy "$@" -x)"
wmname="$(lemonbuddy "$@" -w)"
logfile="${XDG_CACHE_HOME:-$HOME/.cache}/lemonbuddy/${wmname}.log"
logdir="$(dirname "$logfile")"
pipe="$(mktemp -u /tmp/lemonbuddy.in.XXXXX)"
[ -d "$logdir" ] || mkdir -p "$logdir"
mkfifo "$pipe"
# shellcheck disable=SC2094
lemonbuddy "$@" -p "$pipe" 2>"$logfile" | $lemonbar >"$pipe" & pid=$!
sigaction() {
printf "\r"
kill -TERM $pid 2>/dev/null
echo "Waiting for processes to terminate..."
}
trap sigaction TERM INT PIPE CHLD
while true; do
wait || break
done

48
scripts/torrents.sh Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env bash
#
# Creates a summary of active torrents
#
main() {
local rtorrent_session_dir=${1:-"${HOME}/.cache/rtorrent"} ; shift
local max_count=${1:-3} ; shift
local cap=${1:-40} ; shift
local file target_dir chunks_wanted chunks_done chunks_total i
for file in $(find "$rtorrent_session_dir" -name '*.rtorrent' | sed -nr 's/^(.*)\.rtorrent$/\0/p'); do
target_dir=$(sed -nr 's/.*directory[0-9]+:(.*)7:hashing.*/\1/p' "$file")
state=$(egrep -ro "statei([0-9]+)e13" "$file")
state=${state##*i}
state=${state%%e*}
chunks_done=$(egrep -ro "chunks_donei([0-9]+)e13" "$file")
chunks_wanted=$(egrep -ro "chunks_wantedi([0-9]+)e8" "$file")
chunks_done=${chunks_done##*i}
chunks_done=${chunks_done%%e*}
chunks_wanted=${chunks_wanted##*i}
chunks_wanted=${chunks_wanted%%e*}
chunks_total=$(( chunks_done + chunks_wanted ))
if (( $(sed -nr 's/.*statei([0-9]+)e13.*/\1/p' "$file") )); then
[[ "$chunks_total" == "$chunks_wanted" ]] && [[ $chunks_done -eq 0 ]] && continue;
num_files=$(( num_files + 1 ))
label=$(echo "$target_dir" | sed -nr 's/\//\n/gp' | tail -1)
if [[ ${#label} -gt $cap ]]; then
label=${label:0:$cap}
label="${label% *}..."
fi
echo "${label}:${chunks_total:-0}:${chunks_done:-0}:${chunks_wanted:-0}"
i=$(( i + 1 ))
[[ $i -ge $max_count ]] && break
fi
done
}
main "$@"

57
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,57 @@
if(ENABLE_ALSA)
set(SOURCE_FILES_ALSA
"src/interfaces/alsa.cpp"
"src/modules/volume.cpp")
endif()
if(ENABLE_MPD)
set(SOURCE_FILES_MPD
"src/interfaces/mpd.cpp"
"src/modules/mpd.cpp")
endif()
if(ENABLE_I3)
set(SOURCE_FILES_I3 "src/modules/i3.cpp")
endif()
set(SOURCE_FILES
${SOURCE_FILES_ALSA}
${SOURCE_FILES_MPD}
${SOURCE_FILES_I3}
"src/drawtypes/animation.cpp"
"src/drawtypes/bar.cpp"
"src/drawtypes/icon.cpp"
"src/drawtypes/label.cpp"
"src/drawtypes/ramp.cpp"
"src/interfaces/net.cpp"
"src/modules/backlight.cpp"
"src/modules/base.cpp"
"src/modules/battery.cpp"
"src/modules/bspwm.cpp"
"src/modules/counter.cpp"
"src/modules/cpu.cpp"
"src/modules/date.cpp"
"src/modules/memory.cpp"
"src/modules/menu.cpp"
"src/modules/network.cpp"
"src/modules/script.cpp"
"src/modules/text.cpp"
# "src/modules/torrent.cpp"
"src/services/builder.cpp"
"src/services/command.cpp"
"src/services/inotify.cpp"
"src/services/logger.cpp"
"src/services/store.cpp"
"src/utils/cli.cpp"
"src/utils/config.cpp"
"src/utils/io.cpp"
"src/utils/proc.cpp"
"src/utils/string.cpp"
"src/utils/timer.cpp"
"src/utils/xlib.cpp"
"src/bar.cpp"
"src/eventloop.cpp"
"src/lemonbuddy.cpp"
"src/registry.cpp"
PARENT_SCOPE
)

259
src/bar.cpp Normal file
View File

@ -0,0 +1,259 @@
#include "bar.hpp"
#include "registry.hpp"
#include "services/builder.hpp"
#include "utils/config.hpp"
#include "modules/backlight.hpp"
#include "modules/battery.hpp"
#include "modules/bspwm.hpp"
#include "modules/counter.hpp"
#include "modules/cpu.hpp"
#include "modules/date.hpp"
#include "modules/memory.hpp"
#include "modules/menu.hpp"
#include "modules/network.hpp"
#include "modules/script.hpp"
#include "modules/text.hpp"
#include "modules/torrent.hpp"
#ifdef ENABLE_I3
#include "modules/i3.hpp"
#endif
#ifdef ENABLE_MPD
#include "modules/mpd.hpp"
#endif
#ifdef ENABLE_ALSA
#include "modules/volume.hpp"
#endif
std::shared_ptr<Bar> bar;
std::shared_ptr<Bar> &get_bar()
{
if (bar == nullptr)
bar = std::make_shared<Bar>();
return bar;
}
std::vector<std::unique_ptr<xlib::Monitor>> monitors;
const Options& bar_opts() {
return *bar->opts.get();
}
/**
* Bar constructor
*/
Bar::Bar()
{
this->config_path = config::get_bar_path();
struct Options defaults;
this->opts = std::make_unique<Options>();
try {
this->opts->locale = config::get<std::string>(this->config_path, "locale");
std::locale::global(std::locale(this->opts->locale.c_str()));
} catch (config::MissingValueException &e) {}
auto monitor_name = config::get<std::string>(this->config_path, "monitor", "");
this->opts->monitor = std::make_unique<xlib::Monitor>();
if (monitors.empty())
monitors = xlib::get_sorted_monitorlist();
// In case we're not connected to X, we'll just ignore the monitor
if (!monitors.empty()) {
if (monitor_name.empty() && !monitors.empty())
monitor_name = monitors[0]->name;
for (auto &&monitor : monitors) {
if (monitor_name.compare(monitor->name) == 0) {
this->opts->monitor->name = monitor->name;
this->opts->monitor->index = monitor->index;
this->opts->monitor->width = monitor->width;
this->opts->monitor->height = monitor->height;
this->opts->monitor->x = monitor->x;
this->opts->monitor->y = monitor->y;
}
}
if (this->opts->monitor->name.empty())
throw ConfigurationError("Could not find monitor: "+ monitor_name);
}
this->opts->wm_name = "lemonbuddy-"+ this->config_path.substr(4);
if (!this->opts->monitor->name.empty())
this->opts->wm_name += "_"+ this->opts->monitor->name;
this->opts->wm_name = config::get<std::string>(this->config_path, "wm_name", this->opts->wm_name);
this->opts->offset_x = config::get<int>(this->config_path, "offset_x", defaults.offset_x);
this->opts->offset_y = config::get<int>(this->config_path, "offset_y", defaults.offset_y);
auto width = config::get<std::string>(this->config_path, "width", "100%");
auto height = config::get<std::string>(this->config_path, "height", "30");
if (width.find("%") != std::string::npos) {
this->opts->width = this->opts->monitor->width * (std::atoi(width.c_str()) / 100.0) + 0.5;
this->opts->width -= this->opts->offset_x * 2;
} else {
this->opts->width = std::atoi(width.c_str());
}
if (height.find("%") != std::string::npos) {
this->opts->height = this->opts->monitor->height * (std::atoi(height.c_str()) / 100.0) + 0.5;
this->opts->width -= this->opts->offset_y * 2;
} else {
this->opts->height = std::atoi(height.c_str());
}
this->opts->background = config::get<std::string>(this->config_path, "background", defaults.background);
this->opts->foreground = config::get<std::string>(this->config_path, "foreground", defaults.foreground);
this->opts->linecolor = config::get<std::string>(this->config_path, "linecolor", defaults.linecolor);
this->opts->bottom = config::get<bool>(this->config_path, "bottom", defaults.bottom);
this->opts->dock = config::get<bool>(this->config_path, "dock", defaults.dock);
this->opts->clickareas = config::get<int>(this->config_path, "clickareas", defaults.clickareas);
this->opts->separator = config::get<std::string>(this->config_path, "separator", defaults.separator);
this->opts->spacing = config::get<int>(this->config_path, "spacing", defaults.spacing);
this->opts->lineheight = config::get<int>(this->config_path, "lineheight", defaults.lineheight);
this->opts->padding_left = config::get<int>(this->config_path, "padding_left", defaults.padding_left);
this->opts->padding_right = config::get<int>(this->config_path, "padding_right", defaults.padding_right);
this->opts->module_margin_left = config::get<int>(this->config_path, "module_margin_left", defaults.module_margin_left);
this->opts->module_margin_right = config::get<int>(this->config_path, "module_margin_right", defaults.module_margin_right);
for (auto f : config::get_list<std::string>(this->config_path, "font")) {
std::vector<std::string> font;
string::split_into(f, ';', font);
this->opts->fonts.emplace_back(std::make_unique<Font>(font[0], std::stoi(font[1])));
}
}
/**
* Loads all modules configured for the current bar
*/
void Bar::load()
{
auto add_modules = [&](std::string modlist, std::vector<std::string> &vec){
std::vector<std::string> modules;
string::split_into(modlist, ' ', modules);
for (auto &&mod : modules) {
std::unique_ptr<modules::ModuleInterface> module;
auto type = config::get<std::string>("module/"+ mod, "type");
if (type == "internal/counter") module = std::make_unique<modules::CounterModule>(mod);
else if (type == "internal/backlight") module = std::make_unique<modules::BacklightModule>(mod);
else if (type == "internal/battery") module = std::make_unique<modules::BatteryModule>(mod);
else if (type == "internal/bspwm") module = std::make_unique<modules::BspwmModule>(mod, this->opts->monitor->name);
else if (type == "internal/cpu") module = std::make_unique<modules::CpuModule>(mod);
else if (type == "internal/date") module = std::make_unique<modules::DateModule>(mod);
else if (type == "internal/memory") module = std::make_unique<modules::MemoryModule>(mod);
else if (type == "internal/network") module = std::make_unique<modules::NetworkModule>(mod);
#ifdef ENABLE_I3
else if (type == "internal/i3") module = std::make_unique<modules::i3Module>(mod, this->opts->monitor->name);
#endif
#ifdef ENABLE_MPD
else if (type == "internal/mpd") module = std::make_unique<modules::MpdModule>(mod);
#endif
#ifdef ENABLE_ALSA
else if (type == "internal/volume") module = std::make_unique<modules::VolumeModule>(mod);
#endif
#if 0
else if (type == "internal/rtorrent") module = std::make_unique<modules::TorrentModule>(mod);
#endif
else if (type == "custom/text") module = std::make_unique<modules::TextModule>(mod);
else if (type == "custom/script") module = std::make_unique<modules::ScriptModule>(mod);
else if (type == "custom/menu") module = std::make_unique<modules::MenuModule>(mod);
else throw ConfigurationError("Unknown module: "+ mod);
vec.emplace_back(module->name());
get_registry()->insert(std::move(module));
}
};
add_modules(config::get<std::string>(this->config_path, "modules:left", ""), this->mod_left);
add_modules(config::get<std::string>(this->config_path, "modules:center", ""), this->mod_center);
add_modules(config::get<std::string>(this->config_path, "modules:right", ""), this->mod_right);
if (this->mod_left.empty() && this->mod_center.empty() && this->mod_right.empty())
throw ConfigurationError("The bar does not contain any modules...");
}
/**
* Builds the output string by combining the output
* of all added modules
*/
std::string Bar::get_output()
{
auto builder = std::make_unique<Builder>();
auto pad_left = std::string(this->opts->padding_left, ' ');
auto pad_right = std::string(this->opts->padding_right, ' ');
auto output_modules = [&](Builder::Alignment align, std::vector<std::string> &mods){
if (!pad_left.empty() && !mods.empty() && align == Builder::ALIGN_LEFT)
builder->append_module_output(align, pad_left, false, false);
for (auto &mod_name : mods) {
auto mod_output = get_registry()->get(mod_name);
builder->append_module_output(align, mod_output,
!(align == Builder::ALIGN_LEFT && mod_name == mods.front()),
!(align == Builder::ALIGN_RIGHT && mod_name == mods.back()));
if (!mod_output.empty() && !this->opts->separator.empty() && mod_name != mods.back())
builder->append(this->opts->separator);
}
if (!pad_right.empty() && !mods.empty() && align == Builder::ALIGN_RIGHT)
builder->append_module_output(align, pad_right, false, false);
};
output_modules(Builder::ALIGN_LEFT, this->mod_left);
output_modules(Builder::ALIGN_CENTER, this->mod_center);
output_modules(Builder::ALIGN_RIGHT, this->mod_right);
auto data = builder->flush();
return data;
}
/**
* Builds the command line string used to execute
* lemonbar using the specified bar
*/
std::string Bar::get_exec_line()
{
std::stringstream buffer;
buffer << "lemonbar -p";
if (!this->opts->wm_name.empty())
buffer << " -n " << this->opts->wm_name;
if (!this->opts->background.empty())
buffer << " -B " << this->opts->background;
if (!this->opts->foreground.empty())
buffer << " -F " << this->opts->foreground;
if (!this->opts->linecolor.empty())
buffer << " -U " << this->opts->linecolor;
if (this->opts->bottom)
buffer << " -b ";
if (this->opts->dock)
buffer << " -d ";
for (auto &&font : this->opts->fonts) {
buffer << " -f " << font->id;
buffer << " -o " << font->offset;
}
buffer << " -u " << this->opts->lineheight;
buffer << " -a " << this->opts->clickareas;
buffer << " -g " << this->opts->get_geom();
buffer << " " << this->opts->monitor->name;
buffer.flush();
return string::squeeze(buffer.str(), ' ');
}

View File

@ -0,0 +1,70 @@
#include <string>
#include <vector>
#include "drawtypes/animation.hpp"
#include "utils/config.hpp"
#include "utils/memory.hpp"
namespace drawtypes
{
std::unique_ptr<Animation> get_config_animation(const std::string& config_path, const std::string& animation_name, bool required)
{
std::vector<std::unique_ptr<Icon>> vec;
std::vector<std::string> frames;
if (required)
frames = config::get_list<std::string>(config_path, animation_name);
else
frames = config::get_list<std::string>(config_path, animation_name, {});
auto n_frames = frames.size();
repeat(n_frames)
{
auto anim = animation_name +":"+ std::to_string(repeat_i_rev(n_frames));
vec.emplace_back(std::unique_ptr<Icon> { get_optional_config_icon(config_path, anim, frames[n_frames - repeat_i - 1]) });
}
auto framerate = config::get<int>(config_path, animation_name +":framerate_ms", 1000);
return std::unique_ptr<Animation> { new Animation(std::move(vec), framerate) };
}
Animation::Animation(std::vector<std::unique_ptr<Icon>> &&frames, int framerate_ms)
{
this->framerate_ms = framerate_ms;
this->frames = std::move(frames);
this->num_frames = this->frames.size();
this->updated_at = std::chrono::system_clock::now();
}
void Animation::tick()
{
auto now = std::chrono::system_clock::now();
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - this->updated_at);
if (diff.count() < this->framerate_ms)
return;
if (++this->current_frame >= this->num_frames)
this->current_frame = 0;
this->updated_at = now;
}
void Animation::add(std::unique_ptr<Icon> &&frame)
{
this->frames.emplace_back(std::move(frame));
this->num_frames = this->frames.size();
}
std::unique_ptr<Icon> &Animation::get()
{
this->tick();
return this->frames[this->current_frame];
}
int Animation::get_framerate() {
return this->framerate_ms;
}
}

113
src/drawtypes/bar.cpp Normal file
View File

@ -0,0 +1,113 @@
#include "services/builder.hpp"
#include "lemonbuddy.hpp"
#include "drawtypes/bar.hpp"
#include "utils/math.hpp"
#include "utils/string.hpp"
#include "utils/config.hpp"
namespace drawtypes
{
std::unique_ptr<Bar> get_config_bar(const std::string& config_path, const std::string& bar_name, bool lazy_builder_closing)
{
std::unique_ptr<Bar> bar;
auto width = config::get<int>(config_path, bar_name +":width");
auto format = config::get<std::string>(config_path, bar_name +":format", "%fill%%indicator%%empty%");
if (format.empty())
bar = std::make_unique<Bar>(width, lazy_builder_closing);
else
bar = std::make_unique<Bar>(width, format, lazy_builder_closing);
bar->set_gradient(config::get<bool>(config_path, bar_name +":gradient", true));
bar->set_colors(config::get_list<std::string>(config_path, bar_name +":foreground", {}));
bar->set_indicator(get_config_icon(config_path, bar_name +":indicator", format.find("%indicator%") != std::string::npos, ""));
bar->set_fill(get_config_icon(config_path, bar_name +":fill", format.find("%fill%") != std::string::npos, ""));
bar->set_empty(get_config_icon(config_path, bar_name +":empty", format.find("%empty%") != std::string::npos, ""));
return bar;
}
Bar::Bar(int width, const std::string& format, bool lazy_builder_closing)
{
this->width = width;
this->format = format;
this->builder = std::make_unique<Builder>(lazy_builder_closing);
}
void Bar::set_fill(std::unique_ptr<Icon> &&fill) {
this->fill.swap(fill);
}
void Bar::set_empty(std::unique_ptr<Icon> &&empty) {
this->empty.swap(empty);
}
void Bar::set_indicator(std::unique_ptr<Icon> &&indicator) {
this->indicator.swap(indicator);
}
void Bar::set_gradient(bool mode) {
this->gradient = mode;
}
void Bar::set_colors(std::vector<std::string> &&colors) {
this->colors.swap(colors);
}
std::string Bar::get_output(float percentage, bool floor_percentage)
{
if (this->colors.empty()) this->colors.emplace_back(this->fill->fg);
float add = floor_percentage ? 0 : 0.5;
int fill_width = (int) this->width * percentage / 100 + add;
int empty_width = this->width - fill_width;
int color_step = this->width / this->colors.size() + 0.5f;
int i = 0, j = 0;
auto output = std::string(this->format);
if (*this->indicator) {
if (empty_width == 1)
empty_width = 0;
else if (fill_width == 0)
empty_width--;
else
fill_width--;
}
if (!this->gradient) {
auto color = this->colors.size() * percentage / 100;
if (color >= this->colors.size())
color = 0;
this->fill->fg = this->colors[color-1];
while (fill_width--) this->builder->node(this->fill);
} else {
for (auto color : this->colors) {
i += 1;
j = 0;
if ((i-1) * color_step >= fill_width) break;
this->fill->fg = color;
while (j++ < color_step && (i-1) * color_step + j <= fill_width)
this->builder->node(this->fill);
}
}
auto fill = this->builder->flush();
this->builder->node(this->indicator);
auto indicator = this->builder->flush();
while (empty_width--) this->builder->node(this->empty);
auto empty = this->builder->flush();
output = string::replace_all(output, "%fill%", fill);
output = string::replace_all(output, "%indicator%", indicator);
output = string::replace_all(output, "%empty%", empty);
return output;
}
}

40
src/drawtypes/icon.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "drawtypes/icon.hpp"
#include "utils/config.hpp"
namespace drawtypes
{
std::unique_ptr<Icon> Icon::clone()
{
return std::unique_ptr<Icon> { new Icon(
this->text, this->fg, this->bg, this->ul, this->ol, this->font, this->padding, this->margin) };
}
std::unique_ptr<Icon> get_config_icon(const std::string& config_path, const std::string& icon_name, bool required, const std::string& def)
{
auto label = get_config_label(config_path, icon_name, required, def);
return std::unique_ptr<Icon> { new Icon(label->text, label->fg, label->bg, label->ul, label->ol, label->font) };
}
std::unique_ptr<Icon> get_optional_config_icon(const std::string& config_path, const std::string& icon_name, const std::string& def) {
return get_config_icon(config_path, icon_name, false, def);
}
// IconMap
void IconMap::add(const std::string& id, std::unique_ptr<Icon> &&icon) {
this->icons.insert(std::make_pair(id, std::move(icon)));
}
std::unique_ptr<Icon> &IconMap::get(const std::string& id, const std::string& fallback_id)
{
auto icon = this->icons.find(id);
if (icon == this->icons.end())
return this->icons.find(fallback_id)->second;
return icon->second;
}
bool IconMap::has(const std::string& id) {
return this->icons.find(id) != this->icons.end();
}
}

46
src/drawtypes/label.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "drawtypes/label.hpp"
#include "utils/config.hpp"
#include "utils/string.hpp"
namespace drawtypes
{
std::unique_ptr<Label> Label::clone() {
return std::unique_ptr<Label> { new Label(
this->text, this->fg, this->bg, this->ul, this->ol, this->font, this->padding, this->margin) };
}
void Label::replace_token(const std::string& token, const std::string& replacement) {
this->text = string::replace_all(this->text, token, replacement);
}
void Label::replace_defined_values(std::unique_ptr<Label> &label)
{
if (!label->fg.empty()) this->fg = label->fg;
if (!label->bg.empty()) this->bg = label->bg;
if (!label->ul.empty()) this->ul = label->ul;
if (!label->ol.empty()) this->ol = label->ol;
}
std::unique_ptr<Label> get_config_label(const std::string& config_path, const std::string& label_name, bool required, const std::string& def)
{
std::string label;
if (required)
label = config::get<std::string>(config_path, label_name);
else
label = config::get<std::string>(config_path, label_name, def);
return std::unique_ptr<Label> { new Label(label,
config::get<std::string>(config_path, label_name +":foreground", ""),
config::get<std::string>(config_path, label_name +":background", ""),
config::get<std::string>(config_path, label_name +":underline", ""),
config::get<std::string>(config_path, label_name +":overline", ""),
config::get<int>(config_path, label_name +":font", 0),
config::get<int>(config_path, label_name +":padding", 0),
config::get<int>(config_path, label_name +":margin", 0)) };
}
std::unique_ptr<Label> get_optional_config_label(const std::string& config_path, const std::string& label_name, const std::string& def) {
return get_config_label(config_path, label_name, false, def);
}
}

42
src/drawtypes/ramp.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "drawtypes/ramp.hpp"
#include "utils/config.hpp"
#include "utils/memory.hpp"
namespace drawtypes
{
std::unique_ptr<Ramp> get_config_ramp(const std::string& config_path, const std::string& ramp_name, bool required)
{
std::vector<std::unique_ptr<Icon>> vec;
std::vector<std::string> icons;
if (required)
icons = config::get_list<std::string>(config_path, ramp_name);
else
icons = config::get_list<std::string>(config_path, ramp_name, {});
auto n_icons = icons.size();
repeat(n_icons)
{
auto ramp = ramp_name +":"+ std::to_string(repeat_i_rev(n_icons));
vec.emplace_back(std::unique_ptr<Icon> { get_optional_config_icon(config_path, ramp, icons[repeat_i_rev(n_icons)]) });
}
return std::unique_ptr<Ramp> { new Ramp(std::move(vec)) };
}
Ramp::Ramp(std::vector<std::unique_ptr<Icon>> icons) {
this->icons.swap(icons);
}
void Ramp::add(std::unique_ptr<Icon> &&icon) {
this->icons.emplace_back(std::move(icon));
}
std::unique_ptr<Icon> &Ramp::get(int idx) {
return this->icons[idx];
}
std::unique_ptr<Icon> &Ramp::get_by_percentage(float percentage) {
return this->icons[(int)(percentage * (this->icons.size() - 1) / 100.0 + 0.5)];
}
}

229
src/eventloop.cpp Normal file
View File

@ -0,0 +1,229 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "eventloop.hpp"
#include "services/command.hpp"
#include "utils/io.hpp"
EventLoop::EventLoop(std::string input_pipe)
{
this->bar = get_bar();
this->registry = get_registry();
this->logger = get_logger();
this->state = STATE_STOPPED;
this->pipe_filename = input_pipe;
if (!this->pipe_filename.empty()) {
if (!io::file::is_fifo(this->pipe_filename)) {
if (io::file::exists(this->pipe_filename))
unlink(this->pipe_filename.c_str());
if (mkfifo(this->pipe_filename.c_str(), 0600) == -1)
throw Exception(STRERRNO);
}
}
}
bool EventLoop::running()
{
return this->state() == STATE_STARTED;
}
void EventLoop::start()
{
if (this->state() == STATE_STARTED)
return;
this->logger->info("Starting event loop...");
this->bar->load();
this->registry->load();
this->state = STATE_STARTED;
this->t_write = std::thread(&EventLoop::loop_write, this);
this->t_read = std::thread(&EventLoop::loop_read, this);
}
void EventLoop::stop()
{
if (this->state() == STATE_STOPPED)
return;
this->state = STATE_STOPPED;
// break the input read block - totally how it's meant to be done!
if (!this->pipe_filename.empty())
std::system(("echo >"+this->pipe_filename).c_str());
this->registry->unload();
this->logger->info("Event loop stopped...");
}
void EventLoop::wait()
{
if (!this->running())
return;
while (!this->registry->ready())
std::this_thread::sleep_for(100ms);
int sig = 0;
sigemptyset(&this->wait_mask);
sigaddset(&this->wait_mask, SIGINT);
sigaddset(&this->wait_mask, SIGQUIT);
sigaddset(&this->wait_mask, SIGTERM);
if (pthread_sigmask(SIG_BLOCK, &this->wait_mask, nullptr) == -1)
logger->fatal(STRERRNO);
// Wait for termination signal
sigwait(&this->wait_mask, &sig);
this->logger->info("Termination signal received... Shutting down");
}
void EventLoop::add_stdin_subscriber(const std::string& module_name)
{
// this->stdin_subs.insert(std::make_pair("TAG", module_name));
this->stdin_subs.emplace_back(module_name);
}
void EventLoop::loop_write()
{
while (this->running()) {
try {
if (this->registry->wait())
this->write_stdout();
} catch (Exception &e) {
this->logger->error(e.what());
return;
}
}
}
void EventLoop::loop_read()
{
while (!this->pipe_filename.empty() && this->running()) {
try {
if ((this->fd_stdin = open(this->pipe_filename.c_str(), O_RDONLY)) == -1)
throw Exception(STRERRNO);
this->read_stdin();
} catch (Exception &e) {
this->logger->error(e.what());
return;
}
}
if (!this->pipe_filename.empty()) {
close(this->fd_stdin);
unlink(this->pipe_filename.c_str());
}
}
void EventLoop::read_stdin()
{
std::string input;
while ((input = io::readline(this->fd_stdin)).empty() == false) {
this->logger->debug("Input value: \'"+ input +"\"");
bool input_processed = false;
for (auto &module_name : this->stdin_subs) {
if (this->registry->find(module_name)->module->handle_command(input)) {
input_processed = true;
break;
}
}
if (!input_processed) {
this->logger->debug("Unrecognized input value");
this->logger->debug("Forwarding input to shell");
auto command = std::make_unique<Command>("/usr/bin/env\nsh\n-c\n"+ input);
try {
command->exec(false);
command->tail([](std::string cmd_output){
get_logger()->debug("| "+ cmd_output);
});
command->wait();
} catch (CommandException &e) {
this->logger->error(e.what());
}
}
return;
}
}
void EventLoop::write_stdout()
{
if (!this->registry->ready())
return;
try {
auto data = this->bar->get_output();
if (!this->running())
return;
// dprintf(this->fd_stdout, "\033[2J\033[1;1H\033[0mCleared! \033[35;1m %s\n", data.c_str());
dprintf(this->fd_stdout, "%s\n", data.c_str());
} catch (RegistryError &e) {
this->logger->error(e.what());
return;
}
}
void EventLoop::cleanup(int timeout_ms)
{
log_info("Cleaning up...");
std::atomic<bool> t_read_joined(false);
std::atomic<bool> t_write_joined(false);
std::thread t_timeout([&]{
auto start = std::chrono::system_clock::now();
while (true) {
std::this_thread::sleep_for(20ms);
if (t_read_joined && t_write_joined)
break;
auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - start);
if (dur.count() > timeout_ms)
throw EventLoopTerminateTimeout();
}
});
this->logger->debug("Joining input thread");
if (this->t_read.joinable())
this->t_read.join();
else
this->logger->debug("Input thread not joinable");
t_read_joined = true;
this->logger->debug("Joining output thread");
if (this->t_write.joinable())
this->t_write.join();
else
this->logger->debug("Output thread not joinable");
t_write_joined = true;
this->logger->debug("Joining timeout thread");
if (t_timeout.joinable())
t_timeout.join();
else
this->logger->debug("Timeout thread not joinable");
}

183
src/interfaces/alsa.cpp Normal file
View File

@ -0,0 +1,183 @@
#include "config.hpp"
#include "interfaces/alsa.hpp"
#include "services/logger.hpp"
#include "utils/config.hpp"
#include "utils/memory.hpp"
#include "utils/proc.hpp"
namespace alsa
{
ControlInterface::ControlInterface(int numid) throw(ControlInterfaceError)
{
int err;
snd_ctl_elem_info_alloca(&this->info);
snd_ctl_elem_value_alloca(&this->value);
snd_ctl_elem_id_alloca(&this->id);
snd_ctl_elem_id_set_numid(this->id, numid);
snd_ctl_elem_info_set_id(this->info, this->id);
if ((err = snd_ctl_open(&this->ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) < 0)
throw ControlInterfaceError("Could not open control \""+ STR(ALSA_SOUNDCARD) +"\": "+ STRSNDERR(err));
if ((err = snd_ctl_elem_info(this->ctl, this->info)) < 0)
throw ControlInterfaceError("Could not get control data: "+ STRSNDERR(err));
snd_ctl_elem_info_get_id(this->info, this->id);
if ((err = snd_hctl_open(&this->hctl, ALSA_SOUNDCARD, 0)) < 0)
throw ControlInterfaceError(STRSNDERR(err));
if ((err = snd_hctl_load(this->hctl)) < 0)
throw ControlInterfaceError(STRSNDERR(err));
if ((elem = snd_hctl_find_elem(this->hctl, this->id)) == nullptr)
throw ControlInterfaceError("Could not find control with id "+ STRI(snd_ctl_elem_id_get_numid(this->id)));
if ((err = snd_ctl_subscribe_events(this->ctl, 1)) < 0)
throw ControlInterfaceError("Could not subscribe to events: "+ STRI(snd_ctl_elem_id_get_numid(this->id)));
log_trace("Successfully initialized control interface");
}
ControlInterface::~ControlInterface() {
std::lock_guard<std::mutex> lck(this->mtx);
snd_ctl_close(this->ctl);
snd_hctl_close(this->hctl);
}
bool ControlInterface::wait(int timeout) throw(ControlInterfaceError)
{
std::lock_guard<std::mutex> lck(this->mtx);
int err;
if ((err = snd_ctl_wait(this->ctl, timeout)) < 0)
throw ControlInterfaceError("Failed to wait for events: "+ STRSNDERR(err));
snd_ctl_event_t *event;
snd_ctl_event_alloca(&event);
if ((err = snd_ctl_read(this->ctl, event)) < 0)
return false;
if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM)
return false;
auto mask = snd_ctl_event_elem_get_mask(event);
return mask & SND_CTL_EVENT_MASK_VALUE;
}
bool ControlInterface::test_device_plugged() throw(ControlInterfaceError)
{
int err;
if (!this->wait(0))
return false;
if ((err = snd_hctl_elem_read(this->elem, this->value)) < 0)
throw ControlInterfaceError("Could not read control value: "+ STRSNDERR(err));
return snd_ctl_elem_value_get_boolean(this->value, 0);
}
Mixer::Mixer(const std::string& mixer_control_name) throw(MixerError)
{
snd_mixer_selem_id_t *mixer_id;
snd_mixer_selem_id_alloca(&mixer_id);
if (snd_mixer_open(&this->hardware_mixer, 1) < 0)
throw MixerError("Failed to open hardware mixer");
if (snd_mixer_attach(this->hardware_mixer, ALSA_SOUNDCARD) < 0)
throw MixerError("Failed to attach hardware mixer control");
if (snd_mixer_selem_register(this->hardware_mixer, nullptr, nullptr) < 0)
throw MixerError("Failed to register simple mixer element");
if (snd_mixer_load(this->hardware_mixer) < 0)
throw MixerError("Failed to load mixer");
snd_mixer_selem_id_set_index(mixer_id, 0);
snd_mixer_selem_id_set_name(mixer_id, mixer_control_name.c_str());
if ((this->mixer_element = snd_mixer_find_selem(this->hardware_mixer, mixer_id)) == nullptr)
throw MixerError("Cannot find simple element");
log_trace("Successfully initialized mixer");
}
Mixer::~Mixer() {
std::lock_guard<std::mutex> lck(this->mtx);
snd_mixer_elem_remove(this->mixer_element);
snd_mixer_detach(this->hardware_mixer, ALSA_SOUNDCARD);
snd_mixer_close(this->hardware_mixer);
}
bool Mixer::wait(int timeout) throw(MixerError)
{
std::lock_guard<std::mutex> lck(this->mtx);
int err, pend_n = 0;
if (this->hardware_mixer != nullptr && (err = snd_mixer_wait(this->hardware_mixer, timeout)) < 0)
throw MixerError("Failed to wait for events: "+ STRSNDERR(err));
if (this->hardware_mixer != nullptr && (pend_n = snd_mixer_handle_events(this->hardware_mixer)) < 0)
throw MixerError("Failed to process pending events: "+ STRSNDERR(err));
return pend_n > 0;
}
int Mixer::get_volume()
{
long chan_n = 0, vol_total = 0, vol, vol_min, vol_max;
snd_mixer_selem_get_playback_volume_range(this->mixer_element, &vol_min, &vol_max);
repeat(SND_MIXER_SCHN_LAST)
{
if (snd_mixer_selem_has_playback_channel(this->mixer_element, (snd_mixer_selem_channel_id_t) repeat_i)) {
snd_mixer_selem_get_playback_volume(this->mixer_element, (snd_mixer_selem_channel_id_t) repeat_i, &vol);
vol_total += vol;
chan_n++;
}
}
return (int) 100 * (vol_total / chan_n) / vol_max + 0.5f;
}
void Mixer::set_volume(float percentage)
{
if (this->is_muted())
return;
long vol_min, vol_max;
snd_mixer_selem_get_playback_volume_range(this->mixer_element, &vol_min, &vol_max);
snd_mixer_selem_set_playback_volume_all(this->mixer_element, vol_max * percentage / 100);
}
void Mixer::set_mute(bool mode) {
snd_mixer_selem_set_playback_switch_all(this->mixer_element, mode);
}
void Mixer::toggle_mute()
{
int state;
snd_mixer_selem_get_playback_switch(this->mixer_element, SND_MIXER_SCHN_FRONT_LEFT, &state);
snd_mixer_selem_set_playback_switch_all(this->mixer_element, !state);
}
bool Mixer::is_muted()
{
int state = 0;
repeat(SND_MIXER_SCHN_LAST)
{
if (snd_mixer_selem_has_playback_channel(this->mixer_element, (snd_mixer_selem_channel_id_t) repeat_i)) {
snd_mixer_selem_get_playback_switch(this->mixer_element, SND_MIXER_SCHN_FRONT_LEFT, &state);
if (state == 0)
return true;
}
}
return false;
}
}

386
src/interfaces/mpd.cpp Normal file
View File

@ -0,0 +1,386 @@
#include <cstring>
#include <string>
#include <assert.h>
#include <thread>
#include "lemonbuddy.hpp"
#include "services/logger.hpp"
#include "interfaces/mpd.hpp"
#include "utils/math.hpp"
namespace mpd
{
std::shared_ptr<Connection> conn;
std::shared_ptr<Connection> &Connection::get()
{
if (!conn)
conn = std::make_shared<Connection>();
return conn;
}
// Base
void Connection::connect() throw (ClientError)
{
assert(!this->connection);
try {
this->connection.reset(mpd_connection_new(this->host.c_str(), this->port, this->timeout * 1000));
this->check_errors();
if (!this->password.empty()) {
this->noidle();
assert(!this->mpd_command_list_active);
mpd_run_password(this->connection.get(), this->password.c_str());
this->check_errors();
}
this->mpd_fd = mpd_connection_get_fd(this->connection.get());
this->check_errors();
} catch(ClientError &e) {
this->disconnect();
throw e;
}
}
void Connection::disconnect()
{
this->connection.reset();
this->mpd_idle = false;
this->mpd_command_list_active = false;
}
bool Connection::connected()
{
return this->connection.get() != nullptr;
}
bool Connection::retry_connection(int interval)
{
if (this->connected())
return true;
while (true) {
try {
this->connect();
return true;
} catch (Exception &e) {
get_logger()->debug(e.what());
}
std::this_thread::sleep_for(
std::chrono::duration<double>(interval));
}
}
void Connection::idle()
{
this->check_connection();
if (!this->mpd_idle) {
mpd_send_idle(this->connection.get());
this->check_errors();
}
this->mpd_idle = true;
}
int Connection::noidle()
{
this->check_connection();
int flags = 0;
if (this->mpd_idle && mpd_send_noidle(this->connection.get())) {
this->mpd_idle = false;
flags = mpd_recv_idle(this->connection.get(), true);
mpd_response_finish(this->connection.get());
this->check_errors();
}
return flags;
}
void Connection::check_connection() throw(ClientError)
{
if (!this->connection)
throw ClientError("Not connected to MPD server", MPD_ERROR_STATE, false);
}
void Connection::check_prerequisites()
{
this->check_connection();
this->noidle();
}
void Connection::check_prerequisites_commands_list()
{
this->noidle();
assert(!this->mpd_command_list_active);
this->check_prerequisites();
}
void Connection::check_errors() throw(ClientError, ServerError)
{
auto connection = this->connection.get();
mpd_error code = mpd_connection_get_error(connection);
if (code == MPD_ERROR_SUCCESS)
return;
std::string msg = mpd_connection_get_error_message(connection);
if (code == MPD_ERROR_SERVER)
throw ServerError(msg,
mpd_connection_get_server_error(connection),
mpd_connection_clear_error(connection));
else
throw ClientError(msg, code, mpd_connection_clear_error(connection));
}
// Commands
void Connection::play()
{
try {
this->check_prerequisites_commands_list();
mpd_run_play(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::pause(bool state)
{
try {
this->check_prerequisites_commands_list();
mpd_run_pause(this->connection.get(), state);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::toggle()
{
try {
this->check_prerequisites_commands_list();
mpd_run_toggle_pause(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::stop()
{
try {
this->check_prerequisites_commands_list();
mpd_run_stop(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::prev()
{
try {
this->check_prerequisites_commands_list();
mpd_run_previous(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::next()
{
try {
this->check_prerequisites_commands_list();
mpd_run_next(this->connection.get());
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::seek(int percentage)
{
try {
auto status = this->get_status();
if (status->total_time == 0)
return;
int pos = float(status->total_time) * percentage / 100.0f + 0.5f;
this->check_prerequisites_commands_list();
mpd_run_seek_id(this->connection.get(), status->song_id, pos);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::repeat(bool mode)
{
try {
this->check_prerequisites_commands_list();
mpd_run_repeat(this->connection.get(), mode);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::random(bool mode)
{
try {
this->check_prerequisites_commands_list();
mpd_run_random(this->connection.get(), mode);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
void Connection::single(bool mode)
{
try {
this->check_prerequisites_commands_list();
mpd_run_single(this->connection.get(), mode);
this->check_errors();
} catch (Exception &e) {
log_error(e.what());
}
}
// Status
std::unique_ptr<Status> Connection::get_status()
{
this->check_prerequisites();
mpd_status *status = mpd_run_status(this->connection.get());
this->check_errors();
return std::make_unique<Status>(status);
}
Status::Status(struct mpd_status *status) {
this->set(std::unique_ptr<struct mpd_status, StatusDeleter> {status});
}
void Status::set(std::unique_ptr<struct mpd_status, StatusDeleter> status)
{
this->status.swap(status);
this->song_id = mpd_status_get_song_id(this->status.get());
this->random = mpd_status_get_random(this->status.get());
this->repeat = mpd_status_get_repeat(this->status.get());
this->single = mpd_status_get_single(this->status.get());
this->elapsed_time = mpd_status_get_elapsed_time(this->status.get());
this->total_time = mpd_status_get_total_time(this->status.get());
this->updated_at = std::chrono::system_clock::now();
}
void Status::update(int event)
{
auto status = Connection::get()->get_status();
if (event & (MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS)) {
this->set(std::move(status->status));
this->elapsed_time_ms = this->elapsed_time * 1000;
this->song = Connection::get()->get_song();
auto mpd_state = mpd_status_get_state(this->status.get());
switch (mpd_state) {
case MPD_STATE_PAUSE: this->state = PAUSED; break;
case MPD_STATE_PLAY: this->state = PLAYING; break;
case MPD_STATE_STOP: this->state = STOPPED; break;
default: this->state = UNKNOWN;
}
}
}
void Status::update_timer()
{
auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - this->updated_at);
this->elapsed_time_ms += dur.count();
this->elapsed_time = this->elapsed_time_ms / 1000 + 0.5f;
this->updated_at = std::chrono::system_clock::now();
}
unsigned Status::get_total_time() {
return this->total_time;
}
unsigned Status::get_elapsed_time() {
return this->elapsed_time;
}
unsigned Status::get_elapsed_percentage()
{
if (this->total_time == 0) return 0;
return (int) float(this->elapsed_time) / float(this->total_time) * 100 + 0.5f;
}
std::string Status::get_formatted_elapsed()
{
char buffer[32];
snprintf(buffer, sizeof(buffer), "%lu:%02lu", this->elapsed_time / 60, this->elapsed_time % 60);
return std::string(buffer);
}
std::string Status::get_formatted_total()
{
char buffer[32];
snprintf(buffer, sizeof(buffer), "%lu:%02lu", this->total_time / 60, this->total_time % 60);
return std::string(buffer);
}
// Song
std::unique_ptr<Song> Connection::get_song()
{
this->check_prerequisites_commands_list();
mpd_send_current_song(this->connection.get());
mpd_song *song = mpd_recv_song(this->connection.get());
mpd_response_finish(this->connection.get());
this->check_errors();
if (song == nullptr)
return std::make_unique<Song>();
else
return std::make_unique<Song>(song);
}
Song::Song(struct mpd_song *song) {
this->song = std::shared_ptr<struct mpd_song>(song, mpd_song_free);
}
std::string Song::get_artist()
{
assert(this->song);
return mpd_song_get_tag(this->song.get(), MPD_TAG_ARTIST, 0);
}
std::string Song::get_album()
{
assert(this->song);
return mpd_song_get_tag(this->song.get(), MPD_TAG_ALBUM, 0);
}
std::string Song::get_title()
{
assert(this->song);
return mpd_song_get_tag(this->song.get(), MPD_TAG_TITLE, 0);
}
unsigned Song::get_duration()
{
assert(this->song);
return mpd_song_get_duration(this->song.get());
}
}

167
src/interfaces/net.cpp Normal file
View File

@ -0,0 +1,167 @@
#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <netinet/in.h>
#include <sstream>
#include <string>
#include <sys/socket.h>
#include <signal.h>
#include "config.hpp"
#include "services/logger.hpp"
#include "interfaces/net.hpp"
#include "utils/io.hpp"
#include "utils/string.hpp"
using namespace net;
bool net::is_wireless_interface(const std::string& ifname) {
return io::file::exists("/sys/class/net/"+ ifname +"/wireless");
}
// Network
Network::Network(const std::string& interface) throw(NetworkException)
{
this->interface = interface;
if (if_nametoindex(this->interface.c_str()) == 0)
throw NetworkException("Invalid network interface \""+ this->interface +"\"");
if ((this->fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
throw NetworkException("Failed to open socket: "+ STRERRNO);
std::memset(&this->data, 0, sizeof(this->data));
std::strncpy(this->data.ifr_name, this->interface.data(), IFNAMSIZ-1);
}
Network::~Network()
{
if (close(this->fd) == -1)
log_error("Failed to close Network socket FD: "+ STRERRNO);
}
bool Network::test_interface() throw(NetworkException)
{
if ((ioctl(this->fd, SIOCGIFFLAGS, &this->data)) == -1)
throw NetworkException(STRERRNO);
return this->data.ifr_flags & IFF_UP;
}
bool Network::test_connection() throw(NetworkException) {
int status = EXIT_FAILURE;
try {
this->ping = std::make_unique<Command>(
"ping -c 2 -W 2 -I "+ this->interface +" " + std::string(CONNECTION_TEST_IP));
status = this->ping->exec(true);
this->ping.reset();
} catch (CommandException &e) {
log_error(e.what());
} catch (proc::ExecFailure &e) {
log_error(e.what());
}
return (status == EXIT_SUCCESS);
}
bool Network::test()
{
try {
return this->test_interface() && this->test_connection();
} catch (NetworkException &e) {
return false;
}
}
bool Network::connected()
{
try {
return this->test_interface() &&
io::file::get_contents("/sys/class/net/"+ this->interface +"/carrier")[0] == '1';
} catch (NetworkException &e) {
return false;
}
}
std::string Network::get_ip() throw(NetworkException)
{
if (!this->test_interface())
throw NetworkException("Interface is not up");
this->data.ifr_addr.sa_family = AF_INET;
if (ioctl(this->fd, SIOCGIFADDR, &this->data) == -1)
throw NetworkException(STRERRNO);
return inet_ntoa(((struct sockaddr_in *) &this->data.ifr_addr)->sin_addr);
}
// WiredNetwork
WiredNetwork::WiredNetwork(const std::string& interface) : Network(interface)
{
struct ethtool_cmd e;
e.cmd = ETHTOOL_GSET;
this->data.ifr_data = (caddr_t) &e;
if (ioctl(this->fd, SIOCETHTOOL, &this->data) == 0)
this->linkspeed = (e.speed == USHRT_MAX ? 0 : e.speed);
}
std::string WiredNetwork::get_link_speed() {
return std::string((this->linkspeed == 0 ? "???" : std::to_string(this->linkspeed)) +" Mbit/s");
}
// WirelessNetwork
WirelessNetwork::WirelessNetwork(const std::string& interface) : Network(interface) {
std::strcpy((char *) &this->iw.ifr_ifrn.ifrn_name, this->interface.c_str());
}
std::string WirelessNetwork::get_essid() throw(WirelessNetworkException)
{
char essid[IW_ESSID_MAX_SIZE+1];
std::memset(&essid, 0, IW_ESSID_MAX_SIZE + 1);
this->iw.u.essid.pointer = &essid;
this->iw.u.essid.length = sizeof(essid);
if (ioctl(this->fd, SIOCGIWESSID, &this->iw) == -1)
throw WirelessNetworkException(STRERRNO);
return string::trim(essid, ' ');
}
float WirelessNetwork::get_signal_quality()
{
auto dbm = this->get_signal_dbm();
return 2 * (dbm + 100);
}
float WirelessNetwork::get_signal_dbm() throw(WirelessNetworkException)
{
this->iw.u.data.pointer = (iw_statistics *) std::malloc(sizeof(iw_statistics));
this->iw.u.data.length = sizeof(iw_statistics);
if (ioctl(this->fd, SIOCGIWSTATS, &this->iw) == -1)
throw WirelessNetworkException(STRERRNO);
auto signal = ((iw_statistics *) this->iw.u.data.pointer)->qual.level - 256;
std::free(this->iw.u.data.pointer);
return signal;
}

202
src/lemonbuddy.cpp Normal file
View File

@ -0,0 +1,202 @@
#include <iostream>
#include <signal.h>
#include <sys/stat.h>
#include <thread>
#include "bar.hpp"
#include "config.hpp"
#include "eventloop.hpp"
#include "lemonbuddy.hpp"
#include "registry.hpp"
#include "modules/base.hpp"
#include "services/builder.hpp"
#include "utils/cli.hpp"
#include "utils/config.hpp"
#include "utils/io.hpp"
#include "utils/proc.hpp"
#include "utils/string.hpp"
#include "utils/timer.hpp"
#include "utils/xlib.hpp"
/**
* TODO: Reload config on USR1
* TODO: Add more documentation
* TODO: Simplify overall flow
*/
#define writeln(s) std::cout << s << std::endl;
std::unique_ptr<EventLoop> eventloop;
std::mutex pid_mtx;
std::vector<pid_t> pids;
void register_pid(pid_t pid) {
std::lock_guard<std::mutex> lck(pid_mtx);
pids.emplace_back(pid);
}
void unregister_pid(pid_t pid) {
std::lock_guard<std::mutex> lck(pid_mtx);
pids.erase(std::remove(pids.begin(), pids.end(), pid), pids.end());
}
void register_command_handler(const std::string& module_name) {
eventloop->add_stdin_subscriber(module_name);
}
/**
* Main entry point woop!
*/
int main(int argc, char **argv)
{
int retval = EXIT_SUCCESS;
auto logger = get_logger();
sigset_t pipe_mask;
sigemptyset(&pipe_mask);
sigaddset(&pipe_mask, SIGPIPE);
if (pthread_sigmask(SIG_BLOCK, &pipe_mask, nullptr) == -1)
logger->fatal(STRERRNO);
try {
auto usage = "Usage: "+ std::string(argv[0]) + " bar_name [OPTION...]";
cli::add_option("-h", "--help", "Show help options");
cli::add_option("-c", "--config", "FILE", "Path to the configuration file");
cli::add_option("-p", "--pipe", "FILE", "Path to the input pipe");
cli::add_option("-l", "--log", "LEVEL", "Set the logging verbosity", {"info","debug","trace"});
cli::add_option("-d", "--dump", "PARAM", "Show value of PARAM in section [bar_name]");
cli::add_option("-x", "--print-exec", "Print the generated command line string used to start the lemonbar process");
cli::add_option("-w", "--print-wmname", "Print the generated WM_NAME");
/**
* Parse command line arguments
*/
if (argc < 2 || cli::is_option(argv[1], "-h", "--help") || argv[1][0] == '-')
cli::usage(usage, argc > 1);
cli::parse(2, argc, argv);
if (cli::has_option("help"))
cli::usage(usage);
/**
* Set logging verbosity
*/
if (cli::has_option("log")) {
if (cli::match_option_value("log", "info"))
logger->add_level(LogLevel::LEVEL_INFO);
else if (cli::match_option_value("log", "debug"))
logger->add_level(LogLevel::LEVEL_INFO | LogLevel::LEVEL_DEBUG);
else if (cli::match_option_value("log", "trace"))
logger->add_level(LogLevel::LEVEL_INFO | LogLevel::LEVEL_DEBUG | LogLevel::LEVEL_TRACE);
}
/**
* Load configuration file
*/
if (cli::has_option("config")) {
config::load(cli::get_option_value("config"));
} else {
auto xdg_config_home = std::getenv("XDG_CONFIG_HOME");
auto home = std::getenv("HOME");
if (xdg_config_home != nullptr)
config::load(xdg_config_home, "lemonbuddy/config");
else if (home != nullptr)
config::load(home, ".config/lemonbuddy/config");
else
throw ApplicationError("Could not find config file. Specify the location using --config=PATH");
}
/**
* Check if the specified bar exist
*/
std::vector<std::string> defined_bars;
for (auto &section : config::get_tree()) {
if (section.first.find("bar/") == 0)
defined_bars.emplace_back(section.first);
}
if (defined_bars.empty())
logger->fatal("There are no bars defined in the config");
auto config_path = "bar/"+ std::string(argv[1]);
config::set_bar_path(config_path);
if (std::find(defined_bars.begin(), defined_bars.end(), config_path) == defined_bars.end()) {
logger->error("The bar \""+ config_path.substr(4) +"\" is not defined in the config");
logger->info("Available bars:");
for (auto &bar : defined_bars) logger->info(" "+ bar.substr(4));
std::exit(EXIT_FAILURE);
}
if (config::get_tree().get_child_optional(config_path) == boost::none) {
logger->fatal("Bar \""+ std::string(argv[1]) +"\" does not exist");
}
/**
* Dump specified config value
*/
if (cli::has_option("dump")) {
std::cout << config::get<std::string>(config_path, cli::get_option_value("dump")) << std::endl;
return EXIT_SUCCESS;
}
if (cli::has_option("print-exec")) {
std::cout << get_bar()->get_exec_line() << std::endl;
return EXIT_SUCCESS;
}
if (cli::has_option("print-wmname")) {
std::cout << get_bar()->opts->wm_name << std::endl;
return EXIT_SUCCESS;
}
/**
* Set path to input pipe file
*/
std::string pipe_file;
if (!isatty(STDOUT_FILENO)) {
if (cli::has_option("pipe")) {
pipe_file = cli::get_option_value("pipe");
} else {
pipe_file = "/tmp/lemonbuddy.pipe."
+ get_bar()->opts->wm_name
+ "."
+ std::to_string(proc::get_process_id());
auto fptr = std::make_unique<io::file::FilePtr>(pipe_file, "a+");
if (!*fptr)
throw Exception(STRERRNO);
}
}
/**
* Create and start the main event loop
*/
eventloop = std::make_unique<EventLoop>(pipe_file);
eventloop->start();
eventloop->wait();
} catch (Exception &e) {
logger->fatal(e.what());
}
eventloop->stop();
/**
* Terminate forked sub processes
*/
if (!pids.empty()) {
logger->info("Terminating "+ STRI(pids.size()) +" spawned process"+ (pids.size() > 1 ? "es" : ""));
for (auto &&pid : pids)
proc::kill(pid, SIGKILL);
}
eventloop->cleanup();
while (proc::wait_for_completion_nohang() > 0);
return retval;
}

63
src/modules/backlight.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "modules/backlight.hpp"
#include "utils/config.hpp"
#include "utils/io.hpp"
using namespace modules;
BacklightModule::BacklightModule(const std::string& name_) : InotifyModule(name_)
{
this->formatter->add(DEFAULT_FORMAT, TAG_LABEL, { TAG_LABEL, TAG_BAR, TAG_RAMP });
if (this->formatter->has(TAG_LABEL))
this->label = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL), "%percentage%");
if (this->formatter->has(TAG_BAR))
this->bar = drawtypes::get_config_bar(name(), get_tag_name(TAG_BAR));
if (this->formatter->has(TAG_RAMP))
this->ramp = drawtypes::get_config_ramp(name(), get_tag_name(TAG_RAMP));
if (this->label)
this->label_tokenized = this->label->clone();
auto card = config::get<std::string>(name(), "card");
this->path_val = string::replace(PATH_BACKLIGHT_VAL, "%card%", card);
this->path_max = string::replace(PATH_BACKLIGHT_MAX, "%card%", card);
this->watch(string::replace(PATH_BACKLIGHT_VAL, "%card%", card));
}
bool BacklightModule::on_event(InotifyEvent *event)
{
if (event != nullptr)
log_trace(event->filename);
auto val = io::file::get_contents(this->path_val);
this->val = std::stoull(val.c_str(), 0, 10);
auto max = io::file::get_contents(this->path_max);
this->max = std::stoull(max.c_str(), 0, 10);
this->percentage = (int) float(this->val) / float(this->max) * 100.f + 0.5f;
if (!this->label)
return true;
this->label_tokenized->text = this->label->text;
this->label_tokenized->replace_token("%percentage%", std::to_string(this->percentage)+"%");
return true;
}
bool BacklightModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_BAR)
builder->node(this->bar, this->percentage);
else if (tag == TAG_RAMP)
builder->node(this->ramp, this->percentage);
else if (tag == TAG_LABEL)
builder->node(this->label_tokenized);
else
return false;
return true;
}

17
src/modules/base.cpp Normal file
View File

@ -0,0 +1,17 @@
#include "lemonbuddy.hpp"
#include "registry.hpp"
#include "modules/base.hpp"
#include "utils/config.hpp"
#include "utils/string.hpp"
namespace modules
{
void broadcast_module_update(const std::string& module_name) {
log_trace("Broadcasting module update for => "+ module_name);
get_registry()->notify(module_name);
}
std::string get_tag_name(const std::string& tag) {
return tag.length() < 2 ? "" : tag.substr(1, tag.length()-2);
}
}

176
src/modules/battery.cpp Normal file
View File

@ -0,0 +1,176 @@
#include <thread>
#include "config.hpp"
#include "lemonbuddy.hpp"
#include "modules/battery.hpp"
#include "services/logger.hpp"
#include "utils/config.hpp"
#include "utils/io.hpp"
#include "utils/math.hpp"
#include "utils/string.hpp"
using namespace modules;
BatteryModule::BatteryModule(const std::string& name_) : InotifyModule(name_)
{
this->battery = config::get<std::string>(name(), "battery", "BAT0");
this->adapter = config::get<std::string>(name(), "adapter", "ADP1");
this->full_at = config::get<int>(name(), "full_at", 100);
this->formatter->add(FORMAT_CHARGING, TAG_LABEL_CHARGING,
{ TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_ANIMATION_CHARGING, TAG_LABEL_CHARGING });
this->formatter->add(FORMAT_DISCHARGING, TAG_LABEL_DISCHARGING,
{ TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_LABEL_DISCHARGING });
this->formatter->add(FORMAT_FULL, TAG_LABEL_FULL,
{ TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_LABEL_FULL });
if (this->formatter->has(TAG_ANIMATION_CHARGING, FORMAT_CHARGING))
this->animation_charging = drawtypes::get_config_animation(
name(), get_tag_name(TAG_ANIMATION_CHARGING));
if (this->formatter->has(TAG_BAR_CAPACITY))
this->bar_capacity = drawtypes::get_config_bar(
name(), get_tag_name(TAG_BAR_CAPACITY));
if (this->formatter->has(TAG_RAMP_CAPACITY))
this->ramp_capacity = drawtypes::get_config_ramp(
name(), get_tag_name(TAG_RAMP_CAPACITY));
if (this->formatter->has(TAG_LABEL_CHARGING, FORMAT_CHARGING))
this->label_charging = drawtypes::get_optional_config_label(
name(), get_tag_name(TAG_LABEL_CHARGING), "%percentage%");
if (this->formatter->has(TAG_LABEL_DISCHARGING, FORMAT_DISCHARGING))
this->label_discharging = drawtypes::get_optional_config_label(
name(), get_tag_name(TAG_LABEL_DISCHARGING), "%percentage%");
if (this->formatter->has(TAG_LABEL_FULL, FORMAT_FULL))
this->label_full = drawtypes::get_optional_config_label(
name(), get_tag_name(TAG_LABEL_FULL), "%percentage%");
this->watch(string::replace(PATH_BATTERY_CAPACITY, "%battery%", this->battery));
this->watch(string::replace(PATH_ADAPTER_STATUS, "%adapter%", this->adapter));
if (this->animation_charging) {
this->threads.emplace_back(std::thread(&BatteryModule::animation_thread_runner, this));
}
}
void BatteryModule::animation_thread_runner()
{
while (this->enabled()) {
// std::unique_lock<std::mutex> lck(this->ev_mtx);
auto state = this->state();
if (state & CHARGING && !(state & FULL)) {
this->broadcast();
// else
// this->cv.wait(lck, [&]{ return this->state & CHARGING && ~this->state & FULL; });
std::this_thread::sleep_for(std::chrono::duration<double>(
float(this->animation_charging->get_framerate()) / 1000));
} else {
std::this_thread::sleep_for(1s);
}
}
}
bool BatteryModule::on_event(InotifyEvent *event)
{
// std::unique_lock<std::mutex> lck(this->ev_mtx);
if (event != nullptr)
log_trace(event->filename);
auto path_capacity = string::replace(PATH_BATTERY_CAPACITY, "%battery%", this->battery);
auto path_status = string::replace(PATH_ADAPTER_STATUS, "%adapter%", this->adapter);
auto status = io::file::get_contents(path_status);
int state = UNKNOWN;
if (status.empty()) {
log_error("Failed to read "+ path_status);
return false;
}
auto capacity = io::file::get_contents(path_capacity);
if (capacity.empty()) {
log_error("Failed to read "+ path_capacity);
return false;
}
this->percentage = (int) math::cap<float>(std::atof(capacity.c_str()), 0, 100) + 0.5;
switch (status[0]) {
case '0': state = DISCHARGING; break;
case '1': state = CHARGING; break;
}
if (this->state() & CHARGING && this->percentage >= this->full_at)
this->percentage = 100;
if (this->percentage == 100)
state |= FULL;
this->state = state;
if (!this->label_charging_tokenized)
this->label_charging_tokenized = this->label_charging->clone();
if (!this->label_discharging_tokenized)
this->label_discharging_tokenized = this->label_discharging->clone();
if (!this->label_full_tokenized)
this->label_full_tokenized = this->label_full->clone();
auto percentage_str = std::to_string(this->percentage) + "%";
this->label_charging_tokenized->text = this->label_charging->text;
this->label_charging_tokenized->replace_token("%percentage%", percentage_str);
this->label_discharging_tokenized->text = this->label_discharging->text;
this->label_discharging_tokenized->replace_token("%percentage%", std::to_string(this->percentage) +"%");
this->label_full_tokenized->text = this->label_full->text;
this->label_full_tokenized->replace_token("%percentage%", percentage_str);
// lck.unlock();
//
// this->cv.notify_all();
return true;
}
std::string BatteryModule::get_format()
{
auto state = this->state();
if (state & FULL)
return FORMAT_FULL;
else if (state & CHARGING)
return FORMAT_CHARGING;
else
return FORMAT_DISCHARGING;
}
bool BatteryModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_ANIMATION_CHARGING)
builder->node(this->animation_charging);
else if (tag == TAG_BAR_CAPACITY) {
builder->node(this->bar_capacity, this->percentage);
// builder->node(this->bar_capacity, 10);
// builder->space(5);
// builder->node(this->bar_capacity, 50);
// builder->space(5);
// builder->node(this->bar_capacity, 90);
// builder->space(5);
// builder->node(this->bar_capacity, 100);
} else if (tag == TAG_RAMP_CAPACITY)
builder->node(this->ramp_capacity, this->percentage);
else if (tag == TAG_LABEL_CHARGING)
builder->node(this->label_charging_tokenized);
else if (tag == TAG_LABEL_DISCHARGING)
builder->node(this->label_discharging_tokenized);
else if (tag == TAG_LABEL_FULL)
builder->node(this->label_full_tokenized);
else
return false;
return true;
}

223
src/modules/bspwm.cpp Normal file
View File

@ -0,0 +1,223 @@
#include <thread>
#include <vector>
#include <sstream>
#include "config.hpp"
#include "lemonbuddy.hpp"
#include "bar.hpp"
#include "modules/bspwm.hpp"
#include "utils/config.hpp"
#include "utils/io.hpp"
#include "utils/memory.hpp"
#include "utils/string.hpp"
using namespace modules;
using namespace Bspwm;
#define DEFAULT_WS_ICON "workspace_icon:default"
#define DEFAULT_WS_LABEL "%icon% %name%"
BspwmModule::BspwmModule(const std::string& name_, const std::string& monitor) : EventModule(name_)
{
this->monitor = monitor;
this->formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, { TAG_LABEL_STATE }, { TAG_LABEL_MODE });
if (this->formatter->has(TAG_LABEL_STATE)) {
this->state_labels.insert(std::make_pair(WORKSPACE_ACTIVE, drawtypes::get_optional_config_label(name(), "label:active", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(WORKSPACE_OCCUPIED, drawtypes::get_optional_config_label(name(), "label:occupied", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(WORKSPACE_URGENT, drawtypes::get_optional_config_label(name(), "label:urgent", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(WORKSPACE_EMPTY, drawtypes::get_optional_config_label(name(), "label:empty", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(WORKSPACE_DIMMED, drawtypes::get_optional_config_label(name(), "label:dimmed")));
}
if (this->formatter->has(TAG_LABEL_MODE)) {
this->mode_labels.insert(std::make_pair(MODE_LAYOUT_MONOCLE, drawtypes::get_optional_config_label(name(), "label:monocle")));
this->mode_labels.insert(std::make_pair(MODE_LAYOUT_TILED, drawtypes::get_optional_config_label(name(), "label:tiled")));
this->mode_labels.insert(std::make_pair(MODE_STATE_FULLSCREEN, drawtypes::get_optional_config_label(name(), "label:fullscreen")));
this->mode_labels.insert(std::make_pair(MODE_STATE_FLOATING, drawtypes::get_optional_config_label(name(), "label:floating")));
this->mode_labels.insert(std::make_pair(MODE_NODE_LOCKED, drawtypes::get_optional_config_label(name(), "label:locked")));
this->mode_labels.insert(std::make_pair(MODE_NODE_STICKY, drawtypes::get_optional_config_label(name(), "label:sticky")));
this->mode_labels.insert(std::make_pair(MODE_NODE_PRIVATE, drawtypes::get_optional_config_label(name(), "label:private")));
}
this->icons = std::make_unique<drawtypes::IconMap>();
this->icons->add(DEFAULT_WS_ICON, std::make_unique<drawtypes::Icon>(config::get<std::string>(name(), DEFAULT_WS_ICON, "")));
for (auto workspace : config::get_list<std::string>(name(), "workspace_icon", {})) {
auto vec = string::split(workspace, ';');
if (vec.size() == 2) this->icons->add(vec[0], std::make_unique<drawtypes::Icon>(vec[1]));
}
register_command_handler(name());
}
void BspwmModule::start()
{
this->socket_fd = io::socket::open(BSPWM_SOCKET_PATH);
if (io::socket::send(this->socket_fd, "subscribe\x00report") == 0)
throw ModuleError("Failed to subscribe to bspwm changes");
this->EventModule<BspwmModule>::start();
}
bool BspwmModule::has_event() {
return io::poll_read(this->socket_fd, 100);
}
bool BspwmModule::update()
{
std::string data;
if ((data = io::readline(this->socket_fd)).empty())
return false;
if (data == this->prev_data)
return false;
this->prev_data = data;
unsigned long n, m;
while ((n = data.find("\n")) != std::string::npos)
data.erase(n);
if (data.empty())
return false;
auto prefix = std::string(BSPWM_STATUS_PREFIX);
if (data.find(prefix) != 0) {
log_error("Received unknown status -> "+ data);
return false;
}
// Cut out the relevant section for the current monitor
if ((n = data.find(prefix +"M"+ this->monitor +":")) != std::string::npos) {
if ((m = data.find(":m")) != std::string::npos) data = data.substr(n, m);
} else if ((n = data.find(prefix +"m"+ this->monitor +":")) != std::string::npos) {
if ((m = data.find(":M")) != std::string::npos) data = data.substr(n, m);
} else if ((n = data.find("M"+ this->monitor +":")) != std::string::npos) {
data.erase(0, n);
} else if ((n = data.find("m"+ this->monitor +":")) != std::string::npos) {
data.erase(0, n);
}
if (data.find(prefix) == 0) data.erase(0, 1);
log_trace(data);
this->modes.clear();
this->workspaces.clear();
bool monitor_focused = true;
int workspace_n = 0;
for (auto &&tag : string::split(data, ':')) {
if (tag.empty()) continue;
auto value = tag.size() > 0 ? tag.substr(1) : "";
auto workspace_flag = WORKSPACE_NONE;
auto mode_flag = MODE_NONE;
switch (tag[0]) {
case 'm': monitor_focused = false; break;
case 'M': monitor_focused = true; break;
case 'F': workspace_flag = WORKSPACE_ACTIVE; break;
case 'O': workspace_flag = WORKSPACE_ACTIVE; break;
case 'o': workspace_flag = WORKSPACE_OCCUPIED; break;
case 'U': workspace_flag = WORKSPACE_URGENT; break;
case 'u': workspace_flag = WORKSPACE_URGENT; break;
case 'f': workspace_flag = WORKSPACE_EMPTY; break;
case 'L':
switch (value[0]) {
case 0: break;
case 'M': mode_flag = MODE_LAYOUT_MONOCLE; break;
case 'T': mode_flag = MODE_LAYOUT_TILED; break;
default: log_warning("[modules::Bspwm] Undefined L => "+ value);
}
break;
case 'T':
switch (value[0]) {
case 0: break;
case 'T': break;
case '=': mode_flag = MODE_STATE_FULLSCREEN; break;
case 'F': mode_flag = MODE_STATE_FLOATING; break;
default: log_warning("[modules::Bspwm] Undefined T => "+ value);
}
break;
case 'G':
repeat(value.length())
{
switch (value[repeat_i]) {
case 0: break;
case 'L': mode_flag = MODE_NODE_LOCKED; break;
case 'S': mode_flag = MODE_NODE_STICKY; break;
case 'P': mode_flag = MODE_NODE_PRIVATE; break;
default: log_warning("[modules::Bspwm] Undefined G => "+ value.substr(repeat_i, 1));
}
if (mode_flag != MODE_NONE && !this->mode_labels.empty())
this->modes.emplace_back(&this->mode_labels.find(mode_flag)->second);
}
continue;
default: log_warning("[modules::Bspwm] Undefined tag => "+ tag.substr(0, 1));
}
if (workspace_flag != WORKSPACE_NONE && this->formatter->has(TAG_LABEL_STATE)) {
std::unique_ptr<drawtypes::Icon> &icon = this->icons->get(value, DEFAULT_WS_ICON);
std::unique_ptr<drawtypes::Label> label = this->state_labels.find(workspace_flag)->second->clone();
if (!monitor_focused)
label->replace_defined_values(this->state_labels.find(WORKSPACE_DIMMED)->second);
label->replace_token("%name%", value);
label->replace_token("%icon%", icon->text);
label->replace_token("%index%", std::to_string(++workspace_n));
this->workspaces.emplace_back(std::make_unique<Workspace>(workspace_flag, std::move(label)));
}
if (mode_flag != MODE_NONE && !this->mode_labels.empty())
this->modes.emplace_back(&this->mode_labels.find(mode_flag)->second);
}
if (!monitor_focused) this->modes.clear();
return true;
}
bool BspwmModule::build(Builder *builder, const std::string& tag)
{
if (tag != TAG_LABEL_STATE)
return false;
int workspace_n = 0;
for (auto &&ws : this->workspaces) {
builder->cmd(Cmd::LEFT_CLICK, std::string(EVENT_CLICK) + std::to_string(++workspace_n));
builder->node(ws.get()->label);
if (ws->flag == WORKSPACE_ACTIVE && this->formatter->has(TAG_LABEL_MODE)) {
for (auto &&mode : this->modes)
builder->node(mode->get());
}
builder->cmd_close(true);
}
return true;
}
bool BspwmModule::handle_command(const std::string& cmd)
{
if (cmd.find(EVENT_CLICK) == std::string::npos || cmd.length() <= std::strlen(EVENT_CLICK))
return false;
std::system(("bspc desktop -f "+ this->monitor +":^"+ cmd.substr(std::strlen(EVENT_CLICK))).c_str());
return true;
}

33
src/modules/counter.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "config.hpp"
#include "modules/counter.hpp"
#include "utils/config.hpp"
#include "utils/math.hpp"
#include "utils/string.hpp"
using namespace modules;
CounterModule::CounterModule(const std::string& module_name) : TimerModule(module_name, 1s)
{
this->interval = std::chrono::duration<double>(
config::get<float>(name(), "interval", 1));
this->formatter->add(DEFAULT_FORMAT, TAG_COUNTER, { TAG_COUNTER });
this->counter = 0;
}
bool CounterModule::update()
{
this->counter = this->counter() + 1;
return true;
}
bool CounterModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_COUNTER) {
builder->node(std::to_string(this->counter()));
return true;
}
return false;
}

138
src/modules/cpu.cpp Normal file
View File

@ -0,0 +1,138 @@
#include "config.hpp"
#include "modules/cpu.hpp"
#include "utils/config.hpp"
#include "utils/math.hpp"
#include "utils/memory.hpp"
#include "utils/string.hpp"
using namespace modules;
CpuModule::CpuModule(const std::string& name_) : TimerModule(name_, 1s)
{
this->interval = std::chrono::duration<double>(
config::get<float>(name(), "interval", 1));
this->formatter->add(DEFAULT_FORMAT, TAG_LABEL, {
TAG_LABEL, TAG_BAR_LOAD, TAG_RAMP_LOAD, TAG_RAMP_LOAD_PER_CORE });
if (this->formatter->has(TAG_LABEL))
this->label = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL), "%percentage%");
if (this->formatter->has(TAG_BAR_LOAD))
this->bar_load = drawtypes::get_config_bar(name(), get_tag_name(TAG_BAR_LOAD));
if (this->formatter->has(TAG_RAMP_LOAD))
this->ramp_load = drawtypes::get_config_ramp(name(), get_tag_name(TAG_RAMP_LOAD));
if (this->formatter->has(TAG_RAMP_LOAD_PER_CORE))
this->ramp_load_per_core = drawtypes::get_config_ramp(name(), get_tag_name(TAG_RAMP_LOAD_PER_CORE));
if (this->label)
this->label_tokenized = this->label->clone();
// warmup cpu_time and prev_cpu_time
this->read_values();
this->read_values();
}
bool CpuModule::update()
{
if (!this->read_values())
return false;
this->current_total_load = 0;
this->current_load.clear();
int cores_n = this->cpu_times.size();
repeat(cores_n)
{
auto load = this->get_load(repeat_i_rev(cores_n));
this->current_total_load += load;
this->current_load.emplace_back(load);
}
this->label_tokenized->text = this->label->text;
this->label_tokenized->replace_token("%percentage%",
std::to_string((int) this->current_total_load / cores_n)+"%");
return true;
}
bool CpuModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_LABEL)
builder->node(this->label_tokenized);
else if (tag == TAG_BAR_LOAD)
builder->node(this->bar_load, (int) this->current_total_load);
else if (tag == TAG_RAMP_LOAD)
builder->node(this->ramp_load, (int) this->current_total_load);
else if (tag == TAG_RAMP_LOAD_PER_CORE) {
int i = 0;
for (auto &&load : this->current_load) {
if (i++ > 0) builder->space(1);
builder->node(this->ramp_load_per_core, (int) load);
}
builder->node(builder->flush());
} else return false;
return true;
}
bool CpuModule::read_values()
{
std::vector<std::unique_ptr<CpuTime>> cpu_times;
try {
std::ifstream in(PATH_CPU_INFO);
std::string str;
while (std::getline(in, str) && str.find("cpu") == 0) {
// skip the accumulated line
if (str.find("cpu ") == 0) continue;
auto values = string::split(str, ' ');
auto cpu = std::make_unique<CpuTime>();
cpu->user = std::stoull(values[1].c_str(), 0, 10);
cpu->nice = std::stoull(values[2].c_str(), 0, 10);
cpu->system = std::stoull(values[3].c_str(), 0, 10);
cpu->idle = std::stoull(values[4].c_str(), 0, 10);
cpu->total = cpu->user + cpu->nice + cpu->system + cpu->idle;
cpu_times.emplace_back(std::move(cpu));
}
} catch (std::ios_base::failure &e) {
log_error(e.what());
}
this->prev_cpu_times.swap(this->cpu_times);
this->cpu_times.swap(cpu_times);
if (this->cpu_times.empty())
log_error("Failed to read CPU values");
return !this->cpu_times.empty();
}
float CpuModule::get_load(int core)
{
if (this->cpu_times.size() == 0) return 0;
if (this->prev_cpu_times.size() == 0) return 0;
if (core < 0) return 0;
if (core > (int) this->cpu_times.size() - 1) return 0;
if (core > (int) this->prev_cpu_times.size() - 1) return 0;
auto &last = this->cpu_times[core];
auto &prev = this->prev_cpu_times[core];
auto last_idle = last->idle;
auto prev_idle = prev->idle;
auto diff = last->total - prev->total;
if (diff == 0) return 0;
float load_percentage = 100.0f * (diff - (last_idle - prev_idle)) / diff;
return math::cap<float>(load_percentage, 0, 100);
}

60
src/modules/date.cpp Normal file
View File

@ -0,0 +1,60 @@
#include "bar.hpp"
#include "lemonbuddy.hpp"
#include "modules/date.hpp"
#include "utils/config.hpp"
#include "utils/string.hpp"
using namespace modules;
DateModule::DateModule(const std::string& name_) : TimerModule(name_, 1s)
{
this->builder = std::make_unique<Builder>();
this->interval = std::chrono::duration<double>(
config::get<float>(name(), "interval", 1));
this->formatter->add(DEFAULT_FORMAT, TAG_DATE, { TAG_DATE });
this->date = config::get<std::string>(name(), "date");
this->date_detailed = config::get<std::string>(name(), "date_detailed", "");
if (!this->date_detailed.empty())
register_command_handler(name());
}
bool DateModule::update()
{
auto date_format = this->detailed ? this->date_detailed : this->date;
auto time = std::time(nullptr);
if (this->formatter->has(TAG_DATE))
std::strftime(this->date_str, sizeof(this->date_str), date_format.c_str(), std::localtime(&time));
return true;
}
std::string DateModule::get_output()
{
if (!this->date_detailed.empty())
this->builder->cmd(Cmd::LEFT_CLICK, EVENT_TOGGLE);
this->builder->node(this->Module::get_output());
return this->builder->flush();
}
bool DateModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_DATE)
builder->node(this->date_str);
return tag == TAG_DATE;
}
bool DateModule::handle_command(const std::string& cmd)
{
if (cmd == EVENT_TOGGLE) {
this->detailed = !this->detailed;
this->broadcast();
}
return cmd == EVENT_TOGGLE;
}

176
src/modules/i3.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "config.hpp"
#include <thread>
#include <vector>
#include <sstream>
#include <i3ipc++/ipc-util.hpp>
#include "lemonbuddy.hpp"
#include "bar.hpp"
#include "modules/i3.hpp"
#include "utils/config.hpp"
#include "utils/io.hpp"
#include "utils/string.hpp"
using namespace modules;
#define DEFAULT_WS_ICON "workspace_icon:default"
#define DEFAULT_WS_LABEL "%icon% %name%"
// TODO: Needs more testing
// TODO: Add mode indicators
i3Module::i3Module(const std::string& name_, const std::string& monitor) : EventModule(name_)
{
try {
this->ipc = std::make_unique<i3ipc::connection>();
} catch (std::runtime_error &e) {
throw ModuleError(e.what());
}
this->monitor = monitor;
this->local_workspaces
= config::get<bool>(name(), "local_workspaces", this->local_workspaces);
this->workspace_name_strip_nchars
= config::get<std::size_t>(name(), "workspace_name_strip_nchars", this->workspace_name_strip_nchars);
this->formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, { TAG_LABEL_STATE });
if (this->formatter->has(TAG_LABEL_STATE)) {
this->state_labels.insert(std::make_pair(i3::WORKSPACE_FOCUSED, drawtypes::get_optional_config_label(name(), "label:focused", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(i3::WORKSPACE_UNFOCUSED, drawtypes::get_optional_config_label(name(), "label:unfocused", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(i3::WORKSPACE_VISIBLE, drawtypes::get_optional_config_label(name(), "label:visible", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(i3::WORKSPACE_URGENT, drawtypes::get_optional_config_label(name(), "label:urgent", DEFAULT_WS_LABEL)));
this->state_labels.insert(std::make_pair(i3::WORKSPACE_DIMMED, drawtypes::get_optional_config_label(name(), "label:dimmed")));
}
this->icons = std::make_unique<drawtypes::IconMap>();
this->icons->add(DEFAULT_WS_ICON, std::make_unique<drawtypes::Icon>(config::get<std::string>(name(), DEFAULT_WS_ICON, "")));
for (auto workspace : config::get_list<std::string>(name(), "workspace_icon", {})) {
auto vec = string::split(workspace, ';');
if (vec.size() == 2) this->icons->add(vec[0], std::make_unique<drawtypes::Icon>(vec[1]));
}
register_command_handler(name());
}
void i3Module::start()
{
this->ipc = std::make_unique<i3ipc::connection>();
// this->ipc->subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_OUTPUT | i3ipc::ET_WINDOW);
this->ipc->subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_OUTPUT);
this->ipc->prepare_to_event_handling();
this->EventModule<i3Module>::start();
}
void i3Module::stop()
{
std::lock_guard<concurrency::SpinLock> lock(this->update_lock);
this->ipc->send_command("workspace back_and_forth");
this->ipc->send_command("workspace back_and_forth");
this->EventModule<i3Module>::stop();
}
bool i3Module::has_event()
{
this->ipc->handle_event();
return true;
}
bool i3Module::update()
{
if (!this->enabled())
return false;
i3ipc::connection connection;
try {
// for (auto &&m : connection.get_outputs()) {
// if (m->name == this->monitor) {
// monitor_focused = m->active;
// break;
// }
// }
this->workspaces.clear();
auto workspaces = connection.get_workspaces();
std::string focused_monitor;
for (auto &&ws : workspaces) {
if (ws->focused) {
focused_monitor = ws->output;
break;
}
}
bool monitor_focused = (focused_monitor == this->monitor);
for (auto &&ws : connection.get_workspaces()) {
if (this->local_workspaces && ws->output != this->monitor)
continue;
i3::Flag flag = i3::WORKSPACE_NONE;
if (ws->focused)
flag = i3::WORKSPACE_FOCUSED;
else if (ws->urgent)
flag = i3::WORKSPACE_URGENT;
else if (ws->visible)
flag = i3::WORKSPACE_VISIBLE;
else
flag = i3::WORKSPACE_UNFOCUSED;
// if (!monitor_focused)
// flag = i3::WORKSPACE_DIMMED;
auto workspace_name = STR(ws->name);
if (this->workspace_name_strip_nchars > 0 && workspace_name.length() > this->workspace_name_strip_nchars)
workspace_name.erase(0, this->workspace_name_strip_nchars);
std::unique_ptr<drawtypes::Icon> &icon = this->icons->get(workspace_name, DEFAULT_WS_ICON);
std::unique_ptr<drawtypes::Label> label = this->state_labels.find(flag)->second->clone();
if (!monitor_focused)
label->replace_defined_values(this->state_labels.find(i3::WORKSPACE_DIMMED)->second);
label->replace_token("%name%", workspace_name);
label->replace_token("%icon%", icon->text);
label->replace_token("%index%", std::to_string(ws->num));
this->workspaces.emplace_back(std::make_unique<i3::Workspace>(ws->num, flag, std::move(label)));
}
} catch (std::runtime_error &e) {
get_logger()->error(e.what());
}
return true;
}
bool i3Module::build(Builder *builder, const std::string& tag)
{
if (tag != TAG_LABEL_STATE)
return false;
for (auto &&ws : this->workspaces) {
builder->cmd(Cmd::LEFT_CLICK, std::string(EVENT_CLICK) + std::to_string(ws.get()->idx));
builder->node(ws.get()->label);
builder->cmd_close(true);
}
return true;
}
bool i3Module::handle_command(const std::string& cmd)
{
if (cmd.find(EVENT_CLICK) == std::string::npos || cmd.length() < std::strlen(EVENT_CLICK))
return false;
this->ipc->send_command("workspace number "+ cmd.substr(std::strlen(EVENT_CLICK)));
return true;
}

94
src/modules/memory.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "config.hpp"
#include "modules/memory.hpp"
#include "utils/config.hpp"
using namespace modules;
MemoryModule::MemoryModule(const std::string& name_) : TimerModule(name_, 1s)
{
this->interval = std::chrono::duration<double>(
config::get<float>(name(), "interval", 1));
this->formatter->add(DEFAULT_FORMAT, TAG_LABEL, { TAG_LABEL, TAG_BAR_USED, TAG_BAR_FREE });
if (this->formatter->has(TAG_LABEL))
this->label = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL), "%percentage_used%");
if (this->formatter->has(TAG_BAR_USED))
this->bar_used = drawtypes::get_config_bar(name(), get_tag_name(TAG_BAR_USED));
if (this->formatter->has(TAG_BAR_FREE))
this->bar_free = drawtypes::get_config_bar(name(), get_tag_name(TAG_BAR_FREE));
if (this->label)
this->label_tokenized = this->label->clone();
}
bool MemoryModule::update()
{
long kbytes_total, kbytes_available/*, kbytes_free*/;
try {
std::string str, rdbuf;
std::ifstream in(PATH_MEMORY_INFO);
std::stringstream buffer;
int i = 0;
in.exceptions(in.failbit);
buffer.imbue(std::locale::classic());
while (std::getline(in, str) && i++ < 3) {
size_t off = str.find_first_of("1234567890", str.find(':'));
buffer << std::strtol(&str[off], 0, 10) << std::endl;
}
buffer >> rdbuf; kbytes_total = std::atol(rdbuf.c_str());
buffer >> rdbuf; //kbytes_free = std::atol(rdbuf.c_str());
buffer >> rdbuf; kbytes_available = std::atol(rdbuf.c_str());
} catch (std::ios_base::failure &e) {
log_error("Failed to read memory values: "+ STR(e.what()));
}
if (kbytes_total > 0)
this->percentage_free = ((float) kbytes_available) / kbytes_total * 100.0f + 0.5f;
else
this->percentage_free = 0;
this->percentage_used = 100 - this->percentage_free;
this->label_tokenized->text = this->label->text;
this->label_tokenized->replace_token("%percentage_used%", std::to_string(this->percentage_used)+"%");
this->label_tokenized->replace_token("%percentage_free%", std::to_string(this->percentage_free)+"%");
auto replace_unit = [](drawtypes::Label *label, const std::string& token, float value, const std::string& unit){
if (label->text.find(token) != std::string::npos) {
std::stringstream ss;
ss.precision(2);
ss << std::fixed << value;
label->replace_token(token, ss.str() +" "+ unit);
}
};
replace_unit(this->label_tokenized.get(), "%gb_used%", (float) (kbytes_total - kbytes_available) / 1024 / 1024, "GB");
replace_unit(this->label_tokenized.get(), "%gb_free%", (float) kbytes_available / 1024 / 1024, "GB");
replace_unit(this->label_tokenized.get(), "%gb_total%", (float) kbytes_total / 1024 / 1024, "GB");
replace_unit(this->label_tokenized.get(), "%mb_used%", (float) (kbytes_total - kbytes_available) / 1024, "MB");
replace_unit(this->label_tokenized.get(), "%mb_free%", (float) kbytes_available / 1024, "MB");
replace_unit(this->label_tokenized.get(), "%mb_total%", (float) kbytes_total / 1024, "MB");
return true;
}
bool MemoryModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_BAR_USED)
builder->node(this->bar_used, this->percentage_used);
else if (tag == TAG_BAR_FREE)
builder->node(this->bar_free, this->percentage_free);
else if (tag == TAG_LABEL)
builder->node(this->label_tokenized);
else
return false;
return true;
}

125
src/modules/menu.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "bar.hpp"
#include "lemonbuddy.hpp"
#include "modules/menu.hpp"
#include "utils/config.hpp"
using namespace modules;
MenuModule::MenuModule(const std::string& name_) : StaticModule(name_)
{
auto default_format_string = std::string(TAG_LABEL_TOGGLE) +" "+ std::string(TAG_MENU);
this->formatter->add(DEFAULT_FORMAT, default_format_string, { TAG_LABEL_TOGGLE, TAG_MENU });
if (this->formatter->has(TAG_LABEL_TOGGLE)) {
this->label_open = drawtypes::get_config_label(name(), "label:open");
this->label_close = drawtypes::get_optional_config_label(name(), "label:close", "x");
}
if (this->formatter->has(TAG_MENU)) {
int level_n = 0;
while (true) {
auto level_path = "menu:"+ std::to_string(level_n);
if (config::get<std::string>(name(), level_path +":0", "") == "")
break;
this->levels.emplace_back(std::make_unique<MenuTree>());
int item_n = 0;
while (true) {
auto item_path = level_path +":"+ std::to_string(item_n);
if (config::get<std::string>(name(), item_path, "") == "")
break;
auto item = std::make_unique<MenuTreeItem>();
item->label = drawtypes::get_config_label(name(), item_path);
item->exec = config::get<std::string>(name(), item_path +":exec", EVENT_MENU_CLOSE);
this->levels.back()->items.emplace_back(std::move(item));
item_n++;
}
level_n++;
}
}
register_command_handler(name());
}
std::string MenuModule::get_output() throw(UndefinedFormat)
{
this->builder->node(this->Module::get_output());
return this->builder->flush();
}
bool MenuModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_LABEL_TOGGLE && this->current_level == -1) {
builder->cmd(Cmd::LEFT_CLICK, std::string(EVENT_MENU_OPEN) +"0");
builder->node(this->label_open);
builder->cmd_close(true);
} else if (tag == TAG_LABEL_TOGGLE && this->current_level > -1) {
builder->cmd(Cmd::LEFT_CLICK, EVENT_MENU_CLOSE);
builder->node(this->label_close);
builder->cmd_close(true);
} else if (tag == TAG_MENU && this->current_level > -1) {
int i = 0;
for (auto &&m : this->levels[this->current_level]->items) {
if (i++ > 0)
builder->space();
builder->color_alpha("77");
builder->node("/");
builder->color_close(true);
builder->space();
builder->cmd(Cmd::LEFT_CLICK, m->exec);
builder->node(m->label);
builder->cmd_close(true);
}
} else {
return false;
}
return true;
}
bool MenuModule::handle_command(const std::string& cmd)
{
std::lock_guard<std::mutex> lck(this->cmd_mtx);
if (cmd.find(EVENT_MENU_OPEN) == 0) {
auto level = cmd.substr(std::strlen(EVENT_MENU_OPEN));
if (level.empty())
level = "0";
this->current_level = std::atoi(level.c_str());
if (this->current_level >= (int) this->levels.size()) {
log_error("Cannot open unexisting menu level: "+ level);
this->current_level = -1;
}
} else if (cmd == EVENT_MENU_CLOSE) {
this->current_level = -1;
} else {
this->current_level = -1;
this->broadcast();
return false;
}
this->broadcast();
return true;
}

303
src/modules/mpd.cpp Normal file
View File

@ -0,0 +1,303 @@
#include <thread>
#include "bar.hpp"
#include "lemonbuddy.hpp"
#include "modules/mpd.hpp"
#include "utils/config.hpp"
#include "utils/string.hpp"
#include "utils/memory.hpp"
using namespace modules;
using namespace mpd;
MpdModule::MpdModule(const std::string& name_) : EventModule(name_)
{
this->icons = std::make_unique<drawtypes::IconMap>();
this->formatter->add(FORMAT_ONLINE, TAG_LABEL_SONG, {
TAG_BAR_PROGRESS, TAG_TOGGLE, TAG_LABEL_SONG, TAG_LABEL_TIME,
TAG_ICON_RANDOM, TAG_ICON_REPEAT, TAG_ICON_REPEAT_ONE, TAG_ICON_PREV,
TAG_ICON_STOP, TAG_ICON_PLAY, TAG_ICON_PAUSE, TAG_ICON_NEXT });
this->formatter->add(FORMAT_OFFLINE, "", { TAG_LABEL_OFFLINE });
if (this->formatter->has(TAG_ICON_PLAY) || this->formatter->has(TAG_TOGGLE))
this->icons->add("play", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_PLAY)));
if (this->formatter->has(TAG_ICON_PAUSE) || this->formatter->has(TAG_TOGGLE))
this->icons->add("pause", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_PAUSE)));
if (this->formatter->has(TAG_ICON_STOP))
this->icons->add("stop", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_STOP)));
if (this->formatter->has(TAG_ICON_PREV))
this->icons->add("prev", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_PREV)));
if (this->formatter->has(TAG_ICON_NEXT))
this->icons->add("next", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_NEXT)));
if (this->formatter->has(TAG_ICON_RANDOM))
this->icons->add("random", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_RANDOM)));
if (this->formatter->has(TAG_ICON_REPEAT))
this->icons->add("repeat", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_REPEAT)));
if (this->formatter->has(TAG_ICON_REPEAT_ONE))
this->icons->add("repeat_one", drawtypes::get_config_icon(name(), get_tag_name(TAG_ICON_REPEAT_ONE)));
if (this->formatter->has(TAG_LABEL_SONG)) {
this->label_song = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_SONG), "%artist% - %title%");
this->label_song_tokenized = this->label_song->clone();
}
if (this->formatter->has(TAG_LABEL_TIME)) {
this->label_time = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_TIME), "%elapsed% / %total%");
this->label_time_tokenized = this->label_time->clone();
}
if (this->formatter->has(TAG_ICON_RANDOM) || this->formatter->has(TAG_ICON_REPEAT) || this->formatter->has(TAG_ICON_REPEAT_ONE)) {
this->toggle_on_color = config::get<std::string>(name(), "toggle_on:foreground", "");
this->toggle_off_color = config::get<std::string>(name(), "toggle_off:foreground", "");
}
if (this->formatter->has(TAG_LABEL_OFFLINE, FORMAT_OFFLINE))
this->label_offline = drawtypes::get_config_label(name(), get_tag_name(TAG_LABEL_OFFLINE));
if (this->formatter->has(TAG_BAR_PROGRESS)) {
this->bar_progress = drawtypes::get_config_bar(name(), get_tag_name(TAG_BAR_PROGRESS));
}
register_command_handler(name());
}
MpdModule::~MpdModule()
{
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
this->status.reset();
}
void MpdModule::start()
{
this->mpd = mpd::Connection::get();
this->synced_at = std::chrono::system_clock::now();
this->sync_interval = config::get<float>(name(), "interval", 0.5) * 1000;
try {
mpd->connect();
this->status = mpd->get_status();
this->status->update(-1);
} catch (mpd::Exception &e) {
log_error(e.what());
mpd->disconnect();
}
this->EventModule::start();
}
bool MpdModule::has_event()
{
auto &mpd = mpd::Connection::get();
bool has_event = false;
if (!mpd->connected()) {
try {
mpd->connect();
} catch (mpd::Exception &e) {
get_logger()->debug(e.what());
}
if (!mpd->connected()) {
std::this_thread::sleep_for(3s);
return false;
}
}
if (!this->status) {
this->status = mpd->get_status();
this->status->update(-1);
}
try {
mpd->idle();
int idle_flags;
if ((idle_flags = mpd->noidle()) != 0) {
this->status->update(idle_flags);
has_event = true;
} else if (this->status->state & mpd::PLAYING) {
this->status->update_timer();
}
} catch (mpd::Exception &e) {
log_error(e.what());
mpd->disconnect();
has_event = true;
}
if (this->label_time || this->bar_progress) {
auto now = std::chrono::system_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->synced_at).count() > this->sync_interval) {
has_event = true;
this->synced_at = now;
}
}
return has_event;
}
bool MpdModule::update()
{
if (!mpd::Connection::get()->connected())
return true;
if (!this->status)
try {
this->status = mpd::Connection::get()->get_status();
} catch (mpd::Exception &e) {
log_trace(e.what());
}
if (!this->status)
return true;
std::unique_ptr<mpd::Song> song;
std::string artist, album, title, elapsed_str, total_str;
try {
elapsed_str = this->status->get_formatted_elapsed();
total_str = this->status->get_formatted_total();
song = mpd::Connection::get()->get_song();
if (*song) {
artist = song->get_artist();
album = song->get_album();
title = song->get_title();
}
} catch (mpd::Exception &e) {
log_error(e.what());
mpd::Connection::get()->disconnect();
return true;
}
if (this->label_song) {
this->label_song_tokenized->text = this->label_song->text;
this->label_song_tokenized->replace_token("%artist%", artist);
this->label_song_tokenized->replace_token("%album%", album);
this->label_song_tokenized->replace_token("%title%", title);
}
if (this->label_time) {
this->label_time_tokenized->text = this->label_time->text;
this->label_time_tokenized->replace_token("%elapsed%", elapsed_str);
this->label_time_tokenized->replace_token("%total%", total_str);
}
if (this->icons->has("random"))
this->icons->get("random")->fg = this->status->random ? this->toggle_on_color : this->toggle_off_color;
if (this->icons->has("repeat"))
this->icons->get("repeat")->fg = this->status->repeat ? this->toggle_on_color : this->toggle_off_color;
if (this->icons->has("repeat_one"))
this->icons->get("repeat_one")->fg = this->status->single ? this->toggle_on_color : this->toggle_off_color;
return true;
}
std::string MpdModule::get_format() {
return mpd::Connection::get()->connected() ? FORMAT_ONLINE : FORMAT_OFFLINE;
}
bool MpdModule::build(Builder *builder, const std::string& tag)
{
auto icon_cmd = [](Builder *builder, std::string cmd, std::unique_ptr<drawtypes::Icon> &icon){
builder->cmd(Cmd::LEFT_CLICK, cmd);
builder->node(icon);
builder->cmd_close();
};
bool is_playing = false;
bool is_stopped = true;
int elapsed_percentage = 0;
if (this->status) {
elapsed_percentage = this->status->get_elapsed_percentage();
if (this->status->state & mpd::State::PLAYING)
is_playing = true;
if (!(this->status->state & mpd::State::STOPPED))
is_stopped = false;
}
if (tag == TAG_LABEL_SONG && !is_stopped)
builder->node(this->label_song_tokenized);
else if (tag == TAG_LABEL_TIME && !is_stopped)
builder->node(this->label_time_tokenized);
else if (tag == TAG_BAR_PROGRESS && !is_stopped)
builder->node(this->bar_progress, elapsed_percentage);
else if (tag == TAG_LABEL_OFFLINE)
builder->node(this->label_offline);
else if (tag == TAG_ICON_RANDOM)
icon_cmd(builder, EVENT_RANDOM, this->icons->get("random"));
else if (tag == TAG_ICON_REPEAT)
icon_cmd(builder, EVENT_REPEAT, this->icons->get("repeat"));
else if (tag == TAG_ICON_REPEAT_ONE)
icon_cmd(builder, EVENT_REPEAT_ONE, this->icons->get("repeat_one"));
else if (tag == TAG_ICON_PREV)
icon_cmd(builder, EVENT_PREV, this->icons->get("prev"));
else if (tag == TAG_ICON_STOP)
icon_cmd(builder, EVENT_STOP, this->icons->get("stop"));
else if (tag == TAG_ICON_PAUSE || (tag == TAG_TOGGLE && is_playing))
icon_cmd(builder, EVENT_PAUSE, this->icons->get("pause"));
else if (tag == TAG_ICON_PLAY || (tag == TAG_TOGGLE && !is_playing))
icon_cmd(builder, EVENT_PLAY, this->icons->get("play"));
else if (tag == TAG_ICON_NEXT)
icon_cmd(builder, EVENT_NEXT, this->icons->get("next"));
else
return false;
return true;
}
bool MpdModule::handle_command(const std::string& cmd)
{
if (cmd.length() < 3 || cmd.substr(0, 3) != "mpd")
return false;
try {
auto mpd = std::make_shared<mpd::Connection>();
mpd->connect();
if (cmd == EVENT_PLAY)
mpd->play();
else if (cmd == EVENT_PAUSE)
if (this->status && this->status.get())
mpd->pause(!(this->status->state & mpd::State::PAUSED));
else
mpd->pause(true);
else if (cmd == EVENT_STOP)
mpd->stop();
else if (cmd == EVENT_PREV)
mpd->prev();
else if (cmd == EVENT_NEXT)
mpd->next();
else if (cmd == EVENT_REPEAT_ONE)
if (this->status)
mpd->single(!this->status->single);
else
mpd->single(true);
else if (cmd == EVENT_REPEAT)
if (this->status)
#undef repeat
mpd->repeat(!this->status->repeat);
else
mpd->repeat(true);
#define repeat _repeat(n)
else if (cmd == EVENT_RANDOM)
if (this->status)
mpd->random(!this->status->random);
else
mpd->random(true);
else if (cmd.find(EVENT_SEEK) == 0) {
auto s = cmd.substr(std::strlen(EVENT_SEEK));
if (s.empty())
return false;
mpd->seek(std::atoi(s.c_str()));
} else
return false;
} catch (mpd::Exception &e) {
log_error(e.what());
}
return true;
}

195
src/modules/network.cpp Normal file
View File

@ -0,0 +1,195 @@
#include <thread>
#include "lemonbuddy.hpp"
#include "modules/network.hpp"
#include "services/logger.hpp"
#include "utils/config.hpp"
#include "utils/io.hpp"
#include "utils/proc.hpp"
#include "utils/timer.hpp"
using namespace modules;
// TODO: Add up-/download speed (check how ifconfig read the bytes)
NetworkModule::NetworkModule(const std::string& name_) : TimerModule(name_, 1s)
{
this->interval = std::chrono::duration<double>(
config::get<float>(name(), "interval", 1));
this->connectivity_test_interval = config::get<int>(
name(), "connectivity_test_interval", 0);
this->interface = config::get<std::string>(name(), "interface");
this->connected = false;
this->formatter->add(FORMAT_CONNECTED, TAG_LABEL_CONNECTED, { TAG_RAMP_SIGNAL, TAG_LABEL_CONNECTED });
this->formatter->add(FORMAT_DISCONNECTED, TAG_LABEL_DISCONNECTED, { TAG_LABEL_DISCONNECTED });
if (this->formatter->has(TAG_RAMP_SIGNAL, FORMAT_CONNECTED))
this->ramp_signal = drawtypes::get_config_ramp(name(), get_tag_name(TAG_RAMP_SIGNAL));
if (this->formatter->has(TAG_LABEL_CONNECTED, FORMAT_CONNECTED))
this->label_connected = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_CONNECTED), "%ifname% %local_ip%");
if (this->formatter->has(TAG_LABEL_DISCONNECTED, FORMAT_DISCONNECTED)) {
this->label_disconnected = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_DISCONNECTED), "");
this->label_disconnected->replace_token("%ifname%", this->interface);
}
if (this->connectivity_test_interval > 0) {
this->formatter->add(FORMAT_PACKETLOSS, "", { TAG_ANIMATION_PACKETLOSS, TAG_LABEL_PACKETLOSS });
if (this->formatter->has(TAG_LABEL_PACKETLOSS, FORMAT_PACKETLOSS))
this->label_packetloss = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_PACKETLOSS), "%ifname% %local_ip%");
if (this->formatter->has(TAG_ANIMATION_PACKETLOSS, FORMAT_PACKETLOSS))
this->animation_packetloss = drawtypes::get_config_animation(name(), get_tag_name(TAG_ANIMATION_PACKETLOSS));
}
try {
if (net::is_wireless_interface(this->interface)) {
this->wireless_network = std::make_unique<net::WirelessNetwork>(this->interface);
} else {
this->wired_network = std::make_unique<net::WiredNetwork>(this->interface);
}
} catch (net::NetworkException &e) {
get_logger()->fatal(e.what());
}
}
NetworkModule::~NetworkModule()
{
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
// if (this->t_animation.joinable())
// this->t_animation.join();
}
// void NetworkModule::dispatch()
// {
// this->EventModule::dispatch();
//
// // if (this->animation_packetloss)
// // this->t_animation = std::thread(&NetworkModule::animation_thread_runner, this);
// }
// bool NetworkModule::has_event()
// {
// std::this_thread::sleep_for(this->interval);
// return true;
// }
// void NetworkModule::animation_thread_runner()
// {
// while (this->enabled()) {
// std::unique_lock<std::mutex> lck(this->mtx);
//
// if (this->connected && this->conseq_packetloss)
// this->Module::notify_change();
// else
// this->cv.wait(lck, [&]{
// return this->connected && this->conseq_packetloss; });
//
// std::this_thread::sleep_for(std::chrono::duration<double>(
// float(this->animation_packetloss->get_framerate()) / 1000));
// }
// }
bool NetworkModule::update()
{
std::string ip, essid, linkspeed;
int signal_quality = 0;
if (this->wireless_network) {
try {
ip = this->wireless_network->get_ip();
} catch (net::NetworkException &e) {
get_logger()->debug(e.what());
}
try {
essid = this->wireless_network->get_essid();
signal_quality = this->wireless_network->get_signal_quality();
} catch (net::WirelessNetworkException &e) {
get_logger()->debug(e.what());
}
this->connected = this->wireless_network->connected();
this->signal_quality = signal_quality;
if (this->connectivity_test_interval > 0 && this->connected && this->counter++ % PING_EVERY_NTH_UPDATE == 0)
this->conseq_packetloss = !this->wireless_network->test();
} else if (this->wired_network) {
try {
ip = this->wired_network->get_ip();
} catch (net::NetworkException &e) {
get_logger()->debug(e.what());
}
linkspeed = this->wired_network->get_link_speed();
this->connected = this->wired_network->connected();
if (this->connectivity_test_interval > 0 && this->connected && this->counter++ % PING_EVERY_NTH_UPDATE == 0)
this->conseq_packetloss = !this->wired_network->test();
}
if (this->label_connected || this->label_packetloss) {
auto replace_tokens = [&](std::unique_ptr<drawtypes::Label> &label){
label->replace_token("%ifname%", this->interface);
label->replace_token("%local_ip%", ip);
if (this->wired_network) {
label->replace_token("%linkspeed%", linkspeed);
} else if (this->wireless_network) {
// label->replace_token("%essid%", essid);
label->replace_token("%essid%", !essid.empty() ? essid : "No network");
label->replace_token("%signal%", std::to_string(signal_quality)+"%");
}
};
if (this->label_connected) {
if (!this->label_connected_tokenized)
this->label_connected_tokenized = this->label_connected->clone();
this->label_connected_tokenized->text = this->label_connected->text;
replace_tokens(this->label_connected_tokenized);
}
if (this->label_packetloss) {
if (!this->label_packetloss_tokenized)
this->label_packetloss_tokenized = this->label_packetloss->clone();
this->label_packetloss_tokenized->text = this->label_packetloss->text;
replace_tokens(this->label_packetloss_tokenized);
}
}
// this->cv.notify_all();
return true;
}
std::string NetworkModule::get_format()
{
if (!this->connected)
return FORMAT_DISCONNECTED;
else if (this->conseq_packetloss)
return FORMAT_PACKETLOSS;
else
return FORMAT_CONNECTED;
}
bool NetworkModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_LABEL_CONNECTED)
builder->node(this->label_connected_tokenized);
else if (tag == TAG_LABEL_DISCONNECTED)
builder->node(this->label_disconnected);
else if (tag == TAG_LABEL_PACKETLOSS)
builder->node(this->label_packetloss_tokenized);
else if (tag == TAG_ANIMATION_PACKETLOSS)
builder->node(this->animation_packetloss);
else if (tag == TAG_RAMP_SIGNAL)
builder->node(this->ramp_signal, signal_quality);
else
return false;
return true;
}

83
src/modules/script.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "bar.hpp"
#include "modules/script.hpp"
#include "utils/config.hpp"
#include "utils/io.hpp"
#include "utils/string.hpp"
using namespace modules;
ScriptModule::ScriptModule(const std::string& name_) : TimerModule(name_, 1s)
{
this->counter = 0;
this->builder = std::make_unique<Builder>(true);
this->exec = config::get<std::string>(name(), "exec");
this->interval = std::chrono::duration<double>(
config::get<float>(name(), "interval", 1));
this->click_left = config::get<std::string>(name(), "click:left", "");
this->click_middle = config::get<std::string>(name(), "click:middle", "");
this->click_right = config::get<std::string>(name(), "click:right", "");
this->scroll_up = config::get<std::string>(name(), "scroll:up", "");
this->scroll_down = config::get<std::string>(name(), "scroll:down", "");
this->formatter->add(DEFAULT_FORMAT, TAG_OUTPUT, { TAG_OUTPUT });
}
bool ScriptModule::update()
{
this->counter++;
this->output.clear();
try {
std::string buf;
auto execline = string::replace_all(this->exec, "%counter%", std::to_string(this->counter));
auto command = std::make_unique<Command>("/usr/bin/env\nsh\n-c\n"+ execline);
command->exec(false);
while (!(buf = io::readline(command->get_stdout(PIPE_READ))).empty()) {
this->output.append(buf + "\n");
}
command->wait();
} catch (CommandException &e) {
log_error(e.what());
} catch (proc::ExecFailure &e) {
log_error(e.what());
}
return true;
}
std::string ScriptModule::get_output()
{
if (!this->click_left.empty())
this->builder->cmd(Cmd::LEFT_CLICK, string::replace_all(this->click_left, "%counter%", std::to_string(this->counter)));
if (!this->click_middle.empty())
this->builder->cmd(Cmd::MIDDLE_CLICK, string::replace_all(this->click_middle, "%counter%", std::to_string(this->counter)));
if (!this->click_right.empty())
this->builder->cmd(Cmd::RIGHT_CLICK, string::replace_all(this->click_right, "%counter%", std::to_string(this->counter)));
if (!this->scroll_up.empty())
this->builder->cmd(Cmd::SCROLL_UP, string::replace_all(this->scroll_up, "%counter%", std::to_string(this->counter)));
if (!scroll_down.empty())
this->builder->cmd(Cmd::SCROLL_DOWN, string::replace_all(this->scroll_down, "%counter%", std::to_string(this->counter)));
this->builder->node(this->Module::get_output());
return this->builder->flush();
}
bool ScriptModule::build(Builder *builder, const std::string& tag)
{
if (tag == TAG_OUTPUT)
builder->node(string::replace_all(this->output, "\n", ""));
else
return false;
return true;
}

42
src/modules/text.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "lemonbuddy.hpp"
#include "bar.hpp"
#include "modules/text.hpp"
#include "utils/config.hpp"
using namespace modules;
TextModule::TextModule(const std::string& name_) throw(UndefinedFormat) : StaticModule(name_) {
this->formatter->add(FORMAT, "", {});
if (this->formatter->get(FORMAT)->value.empty())
throw UndefinedFormat(FORMAT);
}
std::string TextModule::get_format() {
return FORMAT;
}
std::string TextModule::get_output()
{
auto click_left = config::get<std::string>(name(), "click:left", "");
auto click_middle = config::get<std::string>(name(), "click:middle", "");
auto click_right = config::get<std::string>(name(), "click:right", "");
auto scroll_up = config::get<std::string>(name(), "scroll:up", "");
auto scroll_down = config::get<std::string>(name(), "scroll:down", "");
if (!click_left.empty())
this->builder->cmd(Cmd::LEFT_CLICK, click_left);
if (!click_middle.empty())
this->builder->cmd(Cmd::MIDDLE_CLICK, click_middle);
if (!click_right.empty())
this->builder->cmd(Cmd::RIGHT_CLICK, click_right);
if (!scroll_up.empty())
this->builder->cmd(Cmd::SCROLL_UP, scroll_up);
if (!scroll_down.empty())
this->builder->cmd(Cmd::SCROLL_DOWN, scroll_down);
this->builder->node(this->Module::get_output());
return this->builder->flush();
}

123
src/modules/torrent.cpp Normal file
View File

@ -0,0 +1,123 @@
#include <string>
#include <iostream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include "lemonbuddy.hpp"
#include "services/command.hpp"
#include "services/builder.hpp"
#include "utils/io.hpp"
#include "utils/proc.hpp"
#include "utils/string.hpp"
#include "utils/config.hpp"
#include "modules/torrent.hpp"
using namespace modules;
// TODO: Parse the torrent files internally instead of using the bash script
TorrentModule::TorrentModule(const std::string& name_) : InotifyModule(name_)
{
this->formatter->add(DEFAULT_FORMAT, TAG_LABEL, { TAG_LABEL, TAG_BAR_PROGRESS });
if (this->formatter->has(TAG_LABEL))
this->label = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL), "%title% (%percentage%)");
if (this->formatter->has(TAG_BAR_PROGRESS))
this->bar = drawtypes::get_config_bar(name(), get_tag_name(TAG_BAR_PROGRESS));
auto rtorrent_session_dir = config::get<std::string>(name(), "rtorrent_session_dir");
this->watch(rtorrent_session_dir);
this->pipe_cmd = config::get<std::string>(name(), "script")
+ " " + rtorrent_session_dir
+ " " + std::to_string(config::get<int>(name(), "display_count", 2))
+ " " + std::to_string(config::get<int>(name(), "title_maxlen", 30));
}
void TorrentModule::start()
{
this->InotifyModule<TorrentModule>::start();
std::thread manual_updater([&]{
while (this->enabled()) {
std::this_thread::sleep_for(5s);
this->on_event(nullptr);
}
});
this->threads.emplace_back(std::move(manual_updater));
}
bool TorrentModule::on_event(InotifyEvent *event)
{
if (EXIT_SUCCESS != std::system("pgrep rtorrent >/dev/null")) {
get_logger()->debug("[modules::Torrent] rtorrent is not running... ");
return true;
}
if (event != nullptr) {
log_trace(event->filename);
}
this->torrents.clear();
this->read_data_into(this->torrents);
return true;
}
bool TorrentModule::build(Builder *builder, const std::string& tag)
{
if (tag != TAG_LABEL && tag != TAG_BAR_PROGRESS)
return false;
for (auto &torrent : this->torrents) {
if (tag == TAG_LABEL)
builder->node(torrent->label_tokenized);
else if (tag == TAG_BAR_PROGRESS)
builder->node(this->bar, torrent->perc_downloaded);
}
return true;
}
std::vector<std::unique_ptr<Torrent>> &TorrentModule::read_data_into(std::vector<std::unique_ptr<Torrent>> &container)
{
try {
std::string buf;
auto command = std::make_unique<Command>("/usr/bin/env\nsh\n-c\n"+ this->pipe_cmd);
command->exec(false);
while (!(buf = io::readline(command->get_stdout(PIPE_READ))).empty()) {
auto values = string::split(buf, ':');
if (values.size() != 4) {
log_error("Bad value received from torrent script:\n"+ buf);
continue;
}
auto torrent = std::make_unique<Torrent>();
torrent->title = values[0];
torrent->data_total = std::strtoul(values[1].c_str(), 0, 10);
torrent->data_downloaded = std::strtoul(values[2].c_str(), 0, 10);
torrent->data_remaining = std::strtoul(values[3].c_str(), 0, 10);
torrent->perc_downloaded = (float) torrent->data_downloaded / torrent->data_total * 100.0 + 0.5f;
torrent->label_tokenized = this->label->clone();
torrent->label_tokenized->replace_token("%title%", torrent->title);
torrent->label_tokenized->replace_token("%percentage%", std::to_string((int) torrent->perc_downloaded)+"%");
container.emplace_back(std::move(torrent));
}
command->wait();
} catch (CommandException &e) {
log_error(e.what());
} catch (proc::ExecFailure &e) {
log_error(e.what());
}
return container;
}

230
src/modules/volume.cpp Normal file
View File

@ -0,0 +1,230 @@
#include <thread>
#include "lemonbuddy.hpp"
#include "bar.hpp"
#include "services/builder.hpp"
#include "utils/config.hpp"
#include "utils/math.hpp"
#include "modules/volume.hpp"
using namespace modules;
VolumeModule::VolumeModule(const std::string& name_) throw(ModuleError) : EventModule(name_)
{
auto speaker_mixer = config::get<std::string>(name(), "speaker_mixer", "");
auto headphone_mixer = config::get<std::string>(name(), "headphone_mixer", "");
this->headphone_ctrl_numid = config::get<int>(name(), "headphone_control_numid", -1);
if (!headphone_mixer.empty() && this->headphone_ctrl_numid == -1)
throw ModuleError("[VolumeModule] Missing required property value for \"headphone_control_numid\"...");
else if (headphone_mixer.empty())
throw ModuleError("[VolumeModule] Missing required property value for \"headphone_mixer\"...");
if (string::lower(speaker_mixer) == "master")
throw ModuleError("[VolumeModule] The \"Master\" mixer is already processed internally. Specify another mixer or comment out the \"speaker_mixer\" parameter...");
if (string::lower(headphone_mixer) == "master")
throw ModuleError("[VolumeModule] The \"Master\" mixer is already processed internally. Specify another mixer or comment out the \"headphone_mixer\" parameter...");
auto create_mixer = [](std::string mixer_name)
{
std::unique_ptr<alsa::Mixer> mixer;
try {
mixer = std::make_unique<alsa::Mixer>(mixer_name);
} catch (alsa::MixerError &e) {
log_error("Failed to open \""+ mixer_name +"\" mixer => "+ STR(e.what()));
mixer.reset();
}
return mixer;
};
this->master_mixer = create_mixer("Master");
if (!speaker_mixer.empty())
this->speaker_mixer = create_mixer(speaker_mixer);
if (!headphone_mixer.empty())
this->headphone_mixer = create_mixer(headphone_mixer);
if (!this->master_mixer && !this->speaker_mixer && !this->headphone_mixer) {
this->stop();
return;
}
if (this->headphone_mixer && this->headphone_ctrl_numid > -1) {
try {
this->headphone_ctrl = std::make_unique<alsa::ControlInterface>(this->headphone_ctrl_numid);
} catch (alsa::ControlInterfaceError &e) {
log_error("Failed to open headphone control interface => "+ STR(e.what()));
this->headphone_ctrl.reset();
}
}
this->builder = std::make_unique<Builder>();
this->formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME,
{ TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME });
this->formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED,
{ TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME });
if (this->formatter->has(TAG_BAR_VOLUME))
this->bar_volume = drawtypes::get_config_bar(name(), get_tag_name(TAG_BAR_VOLUME));
if (this->formatter->has(TAG_RAMP_VOLUME))
this->ramp_volume = drawtypes::get_config_ramp(name(), get_tag_name(TAG_RAMP_VOLUME));
if (this->formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME))
this->label_volume = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_VOLUME), "%percentage%");
if (this->formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED))
this->label_muted = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_MUTED), "%percentage%");
register_command_handler(name());
}
VolumeModule::~VolumeModule()
{
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
this->master_mixer.reset();
this->speaker_mixer.reset();
this->headphone_mixer.reset();
this->headphone_ctrl.reset();
}
bool VolumeModule::has_event()
{
bool has_event = false;
try {
if (this->master_mixer)
has_event |= this->master_mixer->wait(25);
if (this->speaker_mixer)
has_event |= this->speaker_mixer->wait(25);
if (this->headphone_mixer)
has_event |= this->headphone_mixer->wait(25);
if (this->headphone_ctrl)
has_event |= this->headphone_ctrl->wait(25);
} catch (alsa::Exception &e) {
log_error(e.what());
}
return has_event;
}
bool VolumeModule::update()
{
int volume = 0;
bool muted = false;
auto headphones_connected = false;
if (this->master_mixer) {
volume = this->master_mixer->get_volume();
muted |= this->master_mixer->is_muted();
} else {
volume = 100;
}
if (this->headphone_ctrl && this->headphone_mixer)
headphones_connected = this->headphone_ctrl->test_device_plugged();
if (headphones_connected) {
volume *= this->headphone_mixer->get_volume() / 100.0f;
muted |= this->headphone_mixer->is_muted();
} else if (this->speaker_mixer) {
volume *= this->speaker_mixer->get_volume() / 100.0f;
muted |= this->speaker_mixer->is_muted();
}
this->volume = volume;
this->muted = muted;
if (!this->label_volume_tokenized)
this->label_volume_tokenized = this->label_volume->clone();
if (!this->label_muted_tokenized)
this->label_muted_tokenized = this->label_muted->clone();
this->label_volume_tokenized->text = this->label_volume->text;
this->label_volume_tokenized->replace_token("%percentage%", std::to_string(this->volume) +"%");
this->label_muted_tokenized->text = this->label_muted->text;
this->label_muted_tokenized->replace_token("%percentage%", std::to_string(this->volume) +"%");
return true;
}
std::string VolumeModule::get_format() {
return this->muted ? FORMAT_MUTED : FORMAT_VOLUME;
}
std::string VolumeModule::get_output()
{
this->builder->cmd(Cmd::LEFT_CLICK, EVENT_TOGGLE_MUTE);
if (volume < 100)
this->builder->cmd(Cmd::SCROLL_UP, EVENT_VOLUME_UP, volume < 100);
if (volume > 0)
this->builder->cmd(Cmd::SCROLL_DOWN, EVENT_VOLUME_DOWN);
this->builder->node(this->Module::get_output());
return this->builder->flush();
}
bool VolumeModule::build(Builder *builder, const std::string& tag)
{
bool built = true;
if (tag == TAG_BAR_VOLUME)
builder->node(this->bar_volume, volume);
else if (tag == TAG_RAMP_VOLUME)
builder->node(this->ramp_volume, volume);
else if (tag == TAG_LABEL_VOLUME)
builder->node(this->label_volume_tokenized);
else if (tag == TAG_LABEL_MUTED)
builder->node(this->label_muted_tokenized);
else
built = false;
return built;
}
bool VolumeModule::handle_command(const std::string& cmd)
{
if (cmd.length() < 3 || cmd.substr(0, 3) != "vol")
return false;
alsa::Mixer *master_mixer = nullptr;
alsa::Mixer *other_mixer = nullptr;
bool headphones_connected = false;
if (this->headphone_ctrl && this->headphone_mixer)
headphones_connected = this->headphone_ctrl->test_device_plugged();
if (headphones_connected)
other_mixer = this->headphone_mixer.get();
else if (this->speaker_mixer)
other_mixer = this->speaker_mixer.get();
if (this->master_mixer)
master_mixer = this->master_mixer.get();
if (cmd == EVENT_VOLUME_UP) {
auto vol = math::cap<float>(this->master_mixer->get_volume() + 5, 0, 100);
if (master_mixer != nullptr)
master_mixer->set_volume(vol);
} else if (cmd == EVENT_VOLUME_DOWN) {
auto vol = math::cap<float>(this->master_mixer->get_volume() - 5, 0, 100);
if (master_mixer != nullptr)
master_mixer->set_volume(vol);
} else if (cmd == EVENT_TOGGLE_MUTE) {
bool mute = master_mixer->is_muted();
if (master_mixer != nullptr)
master_mixer->toggle_mute();
if (other_mixer != nullptr)
other_mixer->set_mute(mute);
} else {
return false;
}
return true;
}

158
src/registry.cpp Normal file
View File

@ -0,0 +1,158 @@
#include "registry.hpp"
#include "services/logger.hpp"
#include "utils/string.hpp"
std::shared_ptr<Registry> registry;
std::shared_ptr<Registry> &get_registry()
{
if (registry == nullptr)
registry = std::make_shared<Registry>();
return registry;
}
Registry::Registry()
{
get_logger()->debug("Entering STAGE 1");
this->stage = STAGE_1;
}
bool Registry::ready()
{
auto stage = this->stage();
if (stage == STAGE_2)
for (auto &&entry : this->modules)
if (!entry->warmedup) get_logger()->debug("Waiting for: "+ entry->module->name());
return stage == STAGE_3;
}
void Registry::insert(std::unique_ptr<modules::ModuleInterface> &&module)
{
log_trace("Inserting module: "+ module->name());
this->modules.emplace_back(std::make_unique<ModuleEntry>(std::move(module)));
}
void Registry::load()
{
if (this->stage() != STAGE_1)
return;
get_logger()->debug("Entering STAGE 2");
this->stage = STAGE_2;
get_logger()->debug("Loading modules");
for (auto &&entry : this->modules) {
std::lock_guard<std::mutex> wait_lck(this->wait_mtx);
entry->module->start();
}
}
void Registry::unload()
{
if (this->stage() != STAGE_3)
return;
get_logger()->debug("Entering STAGE 4");
this->stage = STAGE_4;
get_logger()->debug("Unloading modules");
// Release wait lock
{
std::lock_guard<std::mutex> wait_lck(this->wait_mtx);
this->wait_cv.notify_one();
}
for (auto &&entry : this->modules)
entry->module->stop();
}
bool Registry::wait()
{
log_trace("STAGE "+ std::to_string(this->stage()));
std::unique_lock<std::mutex> wait_lck(this->wait_mtx);
auto stage = this->stage();
if (stage < STAGE_2)
return false;
else if (stage == STAGE_2)
while (stage == STAGE_2) {
bool ready = true;
for (auto &&entry : this->modules)
if (!entry->warmedup) ready = false;
if (!ready) {
this->wait_cv.wait(wait_lck);
continue;
}
get_logger()->info("Received initial broadcast from all modules");
get_logger()->debug("Entering STAGE 3");
this->stage = STAGE_3;
break;
}
else if (stage == STAGE_3)
this->wait_cv.wait(wait_lck);
else if (stage == STAGE_4)
this->modules.clear();
return true;
}
void Registry::notify(const std::string& module_name)
{
log_trace(module_name +" - STAGE "+ std::to_string(this->stage()));
auto stage = this->stage();
if (stage == STAGE_4)
return;
auto &mod_entry = this->find(module_name);
if (stage == STAGE_2) {
if (mod_entry->warmedup())
while (this->stage() == STAGE_2)
std::this_thread::sleep_for(100ms);
else
mod_entry->warmedup = true;
}
std::unique_lock<std::mutex> wait_lck(this->wait_mtx);
try {
mod_entry->module->refresh();
} catch (Exception &e) {
log_trace("Exception occurred in runner thread for: "+ module_name);
get_logger()->error(e.what());
}
wait_lck.unlock();
this->wait_cv.notify_one();
}
std::string Registry::get(const std::string& module_name)
{
return (*this->find(module_name)->module)();
}
std::unique_ptr<ModuleEntry>& Registry::find(const std::string& module_name) throw(ModuleNotFound)
{
for (auto &&entry : this->modules)
if (entry->module->name() == module_name)
return entry;
throw ModuleNotFound(module_name);
}

472
src/services/builder.cpp Normal file
View File

@ -0,0 +1,472 @@
#include <string>
#include <memory>
#include <vector>
#include <regex>
#include <boost/algorithm/string/replace.hpp>
#include "config.hpp"
#include "exception.hpp"
#include "bar.hpp"
#include "services/builder.hpp"
#include "utils/string.hpp"
// Private
void Builder::tag_open(char tag, const std::string& value) {
this->append("%{"+ std::string({tag}) + value +"}");
}
void Builder::tag_close(char tag) {
this->append("%{"+ std::string({tag}) +"-}");
}
void Builder::align_left() {
this->tag_open('l', "");
}
void Builder::align_center() {
this->tag_open('c', "");
}
void Builder::align_right() {
this->tag_open('r', "");
}
// Public
std::string Builder::flush()
{
if (this->lazy_closing) {
while (this->A > 0) this->cmd_close(true);
while (this->B > 0) this->background_close(true);
while (this->F > 0) this->color_close(true);
while (this->T > 0) this->font_close(true);
while (this->U > 0) this->line_color_close(true);
while (this->u > 0) this->underline_close(true);
while (this->o > 0) this->overline_close(true);
}
std::string output = this->output.data();
this->output.clear();
this->alignment = ALIGN_NONE;
this->A = 0;
this->B = 0;
this->F = 0;
this->T = 0;
this->U = 0;
this->o = 0;
this->u = 0;
this->B_value = "";
this->F_value = "";
this->U_value = "";
this->T_value = 1;
return string::replace_all(output, std::string(BUILDER_SPACE_TOKEN), " ");
}
void Builder::append(const std::string& text)
{
std::string str(text);
auto len = str.length();
if (len > 2 && str[0] == '"' && str[len-1] == '"')
this->output += str.substr(1, len-2);
else
this->output += str;
}
void Builder::append_module_output(Alignment alignment, const std::string& module_output, bool margin_left, bool margin_right)
{
if (module_output.empty()) return;
if (alignment != this->alignment) {
this->alignment = alignment;
switch (alignment) {
case ALIGN_NONE: return;
case ALIGN_LEFT: this->align_left(); break;
case ALIGN_CENTER: this->align_center(); break;
case ALIGN_RIGHT: this->align_right(); break;
}
}
int margin;
if (margin_left && (margin= bar_opts().module_margin_left) > 0)
this->output += std::string(margin, ' ');
this->append(module_output);
if (margin_right && (margin = bar_opts().module_margin_right) > 0)
this->output += std::string(margin, ' ');
}
void Builder::node(const std::string& str, bool add_space)
{
std::string::size_type n, m;
std::string s(str);
// Parse raw tags
while (true) {
if (s.empty()) {
break;
} else if ((n = s.find("%{F-}")) == 0) {
this->color_close(!this->lazy_closing);
s.erase(0, 5);
} else if ((n = s.find("%{F#")) == 0 && (m = s.find("}")) != std::string::npos) {
if (m-n-4 == 2)
this->color_alpha(s.substr(n+3, m-3));
else
this->color(s.substr(n+3, m-3));
s.erase(n, m+1);
} else if ((n = s.find("%{B-}")) == 0) {
this->background_close(!this->lazy_closing);
s.erase(0, 5);
} else if ((n = s.find("%{B#")) == 0 && (m = s.find("}")) != std::string::npos) {
this->background(s.substr(n+3, m-3));
s.erase(n, m+1);
} else if ((n = s.find("%{T-}")) == 0) {
this->font_close(!this->lazy_closing);
s.erase(0, 5);
} else if ((n = s.find("%{T")) == 0 && (m = s.find("}")) != std::string::npos) {
this->font(std::atoi(s.substr(n+3, m-3).c_str()));
s.erase(n, m+1);
} else if ((n = s.find("%{U-}")) == 0) {
this->line_color_close(!this->lazy_closing);
s.erase(0, 5);
} else if ((n = s.find("%{U#")) == 0 && (m = s.find("}")) != std::string::npos) {
this->line_color(s.substr(n+3, m-3));
s.erase(n, m+1);
} else if ((n = s.find("%{+u}")) == 0) {
this->underline();
s.erase(0, 5);
} else if ((n = s.find("%{+o}")) == 0) {
this->overline();
s.erase(0, 5);
} else if ((n = s.find("%{-u}")) == 0) {
this->underline_close(true);
s.erase(0, 5);
} else if ((n = s.find("%{-o}")) == 0) {
this->overline_close(true);
s.erase(0, 5);
} else if ((n = s.find("%{A}")) == 0) {
this->cmd_close(true);
s.erase(0, 4);
} else if ((n = s.find("%{")) == 0 && (m = s.find("}")) != std::string::npos) {
this->append(s.substr(n, m+1));
s.erase(n, m+1);
} else if ((n = s.find("%{")) > 0) {
this->append(s.substr(0, n));
s.erase(0, n);
} else break;
}
if (!s.empty()) this->append(s);
if (add_space) this->space();
}
void Builder::node(const std::string& str, int font_index, bool add_space)
{
this->font(font_index);
this->node(str, add_space);
this->font_close();
}
void Builder::node(drawtypes::Bar *bar, float percentage, bool add_space)
{
this->node(bar->get_output(percentage), add_space);
}
void Builder::node(std::unique_ptr<drawtypes::Bar> &bar, float percentage, bool add_space) {
this->node(bar.get(), percentage, add_space);
}
void Builder::node(drawtypes::Label *label, bool add_space)
{
if (!*label) return;
if ((label->ol.empty() && this->o > 0) || (this->o > 0 && label->margin > 0))
this->overline_close(true);
if ((label->ul.empty() && this->u > 0) || (this->u > 0 && label->margin > 0))
this->underline_close(true);
if (label->margin > 0)
this->space(label->margin);
if (!label->ol.empty())
this->overline(label->ol);
if (!label->ul.empty())
this->underline(label->ul);
this->background(label->bg);
this->color(label->fg);
if (label->padding > 0)
this->space(label->padding);
this->node(label->text, label->font, add_space);
if (label->padding > 0)
this->space(label->padding);
this->color_close(lazy_closing && label->margin > 0);
this->background_close(lazy_closing && label->margin > 0);
if (!label->ul.empty() || (label->margin > 0 && this->u > 0))
this->underline_close(lazy_closing && label->margin > 0);
if (!label->ol.empty() || (label->margin > 0 && this->o > 0))
this->overline_close(lazy_closing && label->margin > 0);
if (label->margin > 0)
this->space(label->margin);
}
void Builder::node(std::unique_ptr<drawtypes::Label> &label, bool add_space) {
this->node(label.get(), add_space);
}
void Builder::node(drawtypes::Icon *icon, bool add_space)
{
this->node((drawtypes::Label*) icon, add_space);
}
void Builder::node(std::unique_ptr<drawtypes::Icon> &icon, bool add_space) {
this->node(icon.get(), add_space);
}
void Builder::node(drawtypes::Ramp *ramp, float percentage, bool add_space) {
if (*ramp) this->node(ramp->get_by_percentage(percentage), add_space);
}
void Builder::node(std::unique_ptr<drawtypes::Ramp> &ramp, float percentage, bool add_space) {
this->node(ramp.get(), percentage, add_space);
}
void Builder::node(drawtypes::Animation *animation, bool add_space) {
if (*animation) this->node(animation->get(), add_space);
}
void Builder::node(std::unique_ptr<drawtypes::Animation> &animation, bool add_space) {
this->node(animation.get(), add_space);
}
void Builder::offset(int pixels)
{
if (pixels != 0)
this->tag_open('O', std::to_string(pixels));
}
void Builder::space(int width)
{
if (width == DEFAULT_SPACING) width = bar_opts().spacing;
if (width <= 0) return;
std::string str(width, ' ');
this->append(str);
}
void Builder::remove_trailing_space(int width)
{
if (width == DEFAULT_SPACING) width = bar_opts().spacing;
if (width <= 0) return;
std::string::size_type spacing = width;
std::string str(spacing, ' ');
if (this->output.length() >= spacing && this->output.substr(this->output.length()-spacing) == str)
this->output = this->output.substr(0, this->output.length()-spacing);
}
void Builder::invert() {
this->tag_open('R', "");
}
// Fonts
void Builder::font(int index)
{
if (index <= 0 && this->T > 0) this->font_close(true);
if (index <= 0 || index == this->T_value) return;
if (this->lazy_closing && this->T > 0) this->font_close(true);
this->T++;
this->T_value = index;
this->tag_open('T', std::to_string(index));
}
void Builder::font_close(bool force)
{
if ((!force && this->lazy_closing) || this->T <= 0) return;
this->T--;
this->T_value = 1;
this->tag_close('T');
}
// Back- and foreground
void Builder::background(const std::string& color_)
{
auto color(color_);
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
color = "#"+ color.substr(color.length()-2);
auto bg = bar_opts().background;
color += bg.substr(bg.length()-(bg.length() < 6 ? 3 : 6));
} else if (color.length() >= 7 && color == "#"+ std::string(color.length()-1, color[1])) {
color = color.substr(0, 4);
}
if (color.empty() && this->B > 0) this->background_close(true);
if (color.empty() || color == this->B_value) return;
if (this->lazy_closing && this->B > 0) this->background_close(true);
this->B++;
this->B_value = color;
this->tag_open('B', color);
}
void Builder::background_close(bool force)
{
if ((!force && this->lazy_closing) || this->B <= 0) return;
this->B--;
this->B_value = "";
this->tag_close('B');
}
void Builder::color(const std::string& color_)
{
auto color(color_);
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
color = "#"+ color.substr(color.length()-2);
auto bg = bar_opts().foreground;
color += bg.substr(bg.length()-(bg.length() < 6 ? 3 : 6));
} else if (color.length() >= 7 && color == "#"+ std::string(color.length()-1, color[1])) {
color = color.substr(0, 4);
}
if (color.empty() && this->F > 0) this->color_close(true);
if (color.empty() || color == this->F_value) return;
if (this->lazy_closing && this->F > 0) this->color_close(true);
this->F++;
this->F_value = color;
this->tag_open('F', color);
}
void Builder::color_alpha(const std::string& alpha_)
{
auto alpha(alpha_);
std::string val = bar_opts().foreground;
if (val.size() < 6 && val.size() > 2) {
val.append(val.substr(val.size() - 3));
} else if (val.length() > 6) {
val = "#" + val.substr(3);
}
if (alpha.find("#") == std::string::npos) {
alpha = "#"+ alpha;
}
this->color(alpha.substr(0, 3) + val.substr(val.size() - 6));
}
void Builder::color_close(bool force)
{
if ((!force && this->lazy_closing) || this->F <= 0) return;
this->F--;
this->F_value = "";
this->tag_close('F');
}
// Under- and overline
void Builder::line_color(const std::string& color)
{
if (color.empty() && this->U > 0) this->line_color_close(true);
if (color.empty() || color == this->U_value) return;
if (this->lazy_closing && this->U > 0) this->line_color_close(true);
this->U++;
this->U_value = color;
this->tag_open('U', color);
}
void Builder::line_color_close(bool force)
{
if ((!force && this->lazy_closing) || this->U <= 0) return;
this->U--;
this->U_value = "";
this->tag_close('U');
}
void Builder::overline(const std::string& color)
{
if (!color.empty()) this->line_color(color);
if (this->o > 0) return;
this->o++;
this->append("%{+o}");
}
void Builder::overline_close(bool force)
{
if ((!force && this->lazy_closing) || this->o <= 0) return;
this->o--;
this->append("%{-o}");
}
void Builder::underline(const std::string& color)
{
if (!color.empty()) this->line_color(color);
if (this->u > 0) return;
this->u++;
this->append("%{+u}");
}
void Builder::underline_close(bool force)
{
if ((!force && this->lazy_closing) || this->u <= 0) return;
this->u--;
this->append("%{-u}");
}
// Command
void Builder::cmd(int button, std::string action, bool condition)
{
if (!condition || action.empty()) return;
boost::replace_all(action, ":", "\\:");
boost::replace_all(action, "$", "\\$");
boost::replace_all(action, "}", "\\}");
boost::replace_all(action, "{", "\\{");
boost::replace_all(action, "%", "\x0025");
this->append("%{A"+ std::to_string(button) + ":"+ action +":}");
this->A++;
}
void Builder::cmd_close(bool force)
{
if (this->A > 0 || force) this->append("%{A}");
if (this->A > 0) this->A--;
}

155
src/services/command.cpp Normal file
View File

@ -0,0 +1,155 @@
#include <errno.h>
#include <sstream>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include "lemonbuddy.hpp"
#include "services/command.hpp"
#include "services/logger.hpp"
#include "utils/string.hpp"
#include "utils/io.hpp"
/**
* auto cmd = std::make_unique<Command>("cat /etc/rc.local");
* cmd->exec();
* cmd->tail(callback); //---> the contents of /etc/rc.local is sent to callback()
*
* auto cmd = std::make_unique<Command>("/bin/sh\n-c\n while read -r line; do echo data from parent process: $line; done");
* cmd->exec();
* cmd->writeline("Test");
* std::cout << cmd->readline(); //---> data from parent process: Test
*
* auto cmd = std::make_unique<Command>("/bin/sh\n-c\nfor i in 1 2 3; do echo $i; done");
* std::cout << cmd->readline(); //---> 1
* std::cout << cmd->readline() << cmd->readline(); //---> 23
*/
Command::Command(const std::string& cmd, int stdout[2], int stdin[2]) throw(CommandException)
{
this->cmd = cmd;
if (stdin != nullptr) {
this->stdin[PIPE_READ] = stdin[PIPE_READ];
this->stdin[PIPE_WRITE] = stdin[PIPE_WRITE];
} else if (false == proc::pipe(this->stdin))
throw CommandException("Failed to allocate pipe");
if (stdout != nullptr) {
this->stdout[PIPE_READ] = stdout[PIPE_READ];
this->stdout[PIPE_WRITE] = stdout[PIPE_WRITE];
} else if (false == proc::pipe(this->stdout)) {
if ((this->stdin[PIPE_READ] = close(this->stdin[PIPE_READ])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
if ((this->stdin[PIPE_WRITE] = close(this->stdin[PIPE_WRITE])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
throw CommandException("Failed to allocate pipe");
}
}
Command::~Command() throw(CommandException)
{
if (this->stdin[PIPE_READ] > 0 && (close(this->stdin[PIPE_READ]) == -1))
throw CommandException("Failed to close fd: "+ STRERRNO);
if (this->stdin[PIPE_WRITE] > 0 && (close(this->stdin[PIPE_WRITE]) == -1))
throw CommandException("Failed to close fd: "+ STRERRNO);
if (this->stdout[PIPE_READ] > 0 && (close(this->stdout[PIPE_READ]) == -1))
throw CommandException("Failed to close fd: "+ STRERRNO);
if (this->stdout[PIPE_WRITE] > 0 && (close(this->stdout[PIPE_WRITE]) == -1))
throw CommandException("Failed to close fd: "+ STRERRNO);
}
int Command::exec(bool wait_for_completion) throw(CommandException)
{
if ((this->fork_pid = proc::fork()) == -1)
throw CommandException("Failed to fork process: "+ STRERRNO);
if (proc::in_forked_process(this->fork_pid)) {
if (dup2(this->stdin[PIPE_READ], STDIN_FILENO) == -1)
throw CommandException("Failed to redirect stdin in child process");
if (dup2(this->stdout[PIPE_WRITE], STDOUT_FILENO) == -1)
throw CommandException("Failed to redirect stdout in child process");
if (dup2(this->stdout[PIPE_WRITE], STDERR_FILENO) == -1)
throw CommandException("Failed to redirect stderr in child process");
// Close file descriptors that won't be used by forked process
if ((this->stdin[PIPE_READ] = close(this->stdin[PIPE_READ])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
if ((this->stdin[PIPE_WRITE] = close(this->stdin[PIPE_WRITE])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
if ((this->stdout[PIPE_READ] = close(this->stdout[PIPE_READ])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
if ((this->stdout[PIPE_WRITE] = close(this->stdout[PIPE_WRITE])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
// Replace the forked process with the given command
proc::exec(cmd);
std::exit(0);
} else {
register_pid(this->fork_pid);
// Close file descriptors that won't be used by parent process
if ((this->stdin[PIPE_READ] = close(this->stdin[PIPE_READ])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
if ((this->stdout[PIPE_WRITE] = close(this->stdout[PIPE_WRITE])) == -1)
throw CommandException("Failed to close fd: "+ STRERRNO);
if (wait_for_completion)
return this->wait();
}
return EXIT_SUCCESS;
}
int Command::wait() throw(CommandException)
{
// Wait for the child processs to finish
do {
pid_t pid;
char msg[64];
if ((pid = proc::wait_for_completion(this->fork_pid, &this->fork_status, WCONTINUED | WUNTRACED)) == -1) {
unregister_pid(this->fork_pid);
throw CommandException("Process did not finish successfully ("+ STRI(this->fork_status) +")");
}
if WIFEXITED(this->fork_status)
sprintf(msg, "exited with status %d", WEXITSTATUS(this->fork_status));
else if WIFSIGNALED(this->fork_status)
sprintf(msg, "got killed by signal %d (%s)", WTERMSIG(this->fork_status), SIGCSTR(WTERMSIG(this->fork_status)));
else if WIFSTOPPED(this->fork_status)
sprintf(msg, "stopped by signal %d (%s)", WSTOPSIG(this->fork_status), SIGCSTR(WSTOPSIG(this->fork_status)));
else if WIFCONTINUED(this->fork_status)
sprintf(msg, "continued");
get_logger()->debug("Command "+ STR(msg));
} while (!WIFEXITED(this->fork_status) && !WIFSIGNALED(this->fork_status));
unregister_pid(this->fork_pid);
return this->fork_status;
}
void Command::tail(std::function<void(std::string)> callback) {
io::tail(this->stdout[PIPE_READ], callback);
}
int Command::writeline(const std::string& data) {
return io::writeline(this->stdin[PIPE_WRITE], data);
}
int Command::get_stdout(int c) {
return this->stdout[c];
}
int Command::get_stdin(int c) {
return this->stdin[c];
}
pid_t Command::get_pid() {
return this->fork_pid;
}
int Command::get_exit_status() {
return this->fork_status;
}

62
src/services/inotify.cpp Normal file
View File

@ -0,0 +1,62 @@
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "services/inotify.hpp"
#include "services/logger.hpp"
#include "utils/io.hpp"
#include "utils/proc.hpp"
InotifyWatch::InotifyWatch(const std::string& path, int mask) throw (InotifyException)
{
log_trace("Installing watch at: "+ path);
if ((this->fd = inotify_init()) < 0)
throw InotifyException(STRERRNO);
if ((this->wd = inotify_add_watch(this->fd, path.c_str(), mask)) < 0)
throw InotifyException(STRERRNO);
this->path = path;
this->mask = mask;
}
InotifyWatch::~InotifyWatch()
{
log_trace("Uninstalling watch at: "+ this->path);
if ((this->fd > 0 || this->wd > 0) && inotify_rm_watch(this->fd, this->wd) == -1)
log_error("Failed to remove inotify watch: "+ STRERRNO);
if (this->fd > 0 && close(this->fd) == -1)
log_error("Failed to close inotify watch fd: "+ STRERRNO);
}
bool InotifyWatch::has_event(int timeout_ms) {
return io::poll_read(this->fd, timeout_ms);
}
std::unique_ptr<InotifyEvent> InotifyWatch::get_event()
{
char buffer[1024];
std::size_t bytes = read(this->fd, buffer, 1024), len = 0;
auto event = std::make_unique<InotifyEvent>();
while (len < bytes) {
struct inotify_event *e = (struct inotify_event *) &buffer[len];
if (e->len) {
event->filename = e->name;
} else {
event->filename = this->path;
}
event->wd = e->wd;
event->cookie = e->cookie;
event->is_dir = e->mask & IN_ISDIR;
event->mask |= e->mask;
len += sizeof(struct inotify_event) + e->len;
}
return event;
}

70
src/services/logger.cpp Normal file
View File

@ -0,0 +1,70 @@
#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include "services/logger.hpp"
#include "utils/string.hpp"
#include "utils/proc.hpp"
std::shared_ptr<Logger> logger;
std::shared_ptr<Logger> &get_logger()
{
if (logger == nullptr)
logger = std::make_unique<Logger>();
return logger;
}
Logger::Logger()
{
if (isatty(LOGGER_FD)) {
dup2(LOGGER_FD, this->fd);
}
}
void Logger::set_level(int mask)
{
this->level = mask;
}
void Logger::add_level(int mask)
{
this->level |= mask;
}
void Logger::fatal(const std::string& msg)
{
dprintf(this->fd, "%s%s\n", LOGGER_TAG_FATAL, msg.c_str());
std::exit(EXIT_FAILURE);
}
void Logger::error(const std::string& msg)
{
if (this->level & LogLevel::LEVEL_ERROR)
dprintf(this->fd, "%s%s\n", LOGGER_TAG_ERR, msg.c_str());
}
void Logger::warning(const std::string& msg)
{
if (this->level & LogLevel::LEVEL_WARNING)
dprintf(this->fd, "%s%s\n", LOGGER_TAG_WARN, msg.c_str());
}
void Logger::info(const std::string& msg)
{
if (this->level & LogLevel::LEVEL_INFO)
dprintf(this->fd, "%s%s\n", LOGGER_TAG_INFO, msg.c_str());
}
void Logger::debug(const std::string& msg)
{
if (this->level & LogLevel::LEVEL_DEBUG)
dprintf(this->fd, "%s%s\n", LOGGER_TAG_DEBUG, msg.c_str());
}
void Logger::trace(const char *file, const char *fn, int lineno, const std::string& msg)
{
if (this->level & LogLevel::LEVEL_TRACE && !msg.empty())
dprintf(this->fd, "%s%s (%s:%d) -> %s\n", LOGGER_TAG_TRACE, &file[0], fn, lineno, msg.c_str());
else if (msg.empty())
dprintf(this->fd, "%s%s (%s:%d)\n", LOGGER_TAG_TRACE, &file[0], fn, lineno);
}

65
src/services/store.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "services/logger.hpp"
#include "services/store.hpp"
Store::Store(int size)
{
log_trace("Creating shared memory container");
this->region = boost::interprocess::anonymous_shared_memory(size);
this->state_region = boost::interprocess::anonymous_shared_memory(sizeof(char));
}
void Store::flag()
{
std::memset(this->state_region.get_address(), this->FLAG_DATA, this->state_region.get_size());
}
void Store::unflag()
{
std::memset(this->state_region.get_address(), this->FLAG_NO_DATA, this->state_region.get_size());
}
bool Store::check()
{
char test;
std::memcpy(&test, this->state_region.get_address(), this->state_region.get_size());
return this->FLAG_DATA == test;
}
char &Store::get(char &d)
{
std::memcpy(&d, this->region.get_address(), this->region.get_size());
this->unflag();
log_trace("Fetched: \""+ std::to_string(d) +"\"");
return d;
}
void Store::set(char val)
{
log_trace("Storing: \""+ std::to_string(val) +"\"");
std::memset(this->region.get_address(), val, this->region.get_size());
this->flag();
}
std::string Store::get_string()
{
std::string s((char *) this->region.get_address());
this->unflag();
log_trace("Fetched: \""+ s +"\"");
return s;
}
std::string &Store::get_string(std::string& s)
{
s.clear();
s.append((char *) this->region.get_address());
this->unflag();
log_trace("Fetched: \""+ s +"\"");
return s;
}
void Store::set_string(const std::string& s)
{
log_trace("Storing: \""+ s +"\"");
std::memcpy(this->region.get_address(), s.c_str(), this->region.get_size());
this->flag();
}

Some files were not shown because too many files have changed in this diff Show More