init(git): Base commit
This commit is contained in:
commit
eeefb3c610
5
.exrc
Normal file
5
.exrc
Normal 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
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
build
|
||||
tags
|
||||
*.bak
|
||||
*.pyc
|
||||
*.tmp
|
||||
include/config.hpp
|
8
.gitmodules
vendored
Normal file
8
.gitmodules
vendored
Normal 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
130
.ycm_extra_conf.py
Normal 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
145
CMakeLists.txt
Normal 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
19
LICENSE
Normal 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
11
README.md
Normal 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
|
31
cmake/modules/FindLibMPDClient.cmake
Normal file
31
cmake/modules/FindLibMPDClient.cmake
Normal 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
26
cmake/uninstall.cmake.in
Normal 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
947
config
Normal 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
11
contrib/dosini.vim
Normal 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
1
contrib/i3ipcpp
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ec2d88919d5037360e61932492eb3f98f9b9df28
|
1
contrib/lemonbar-sm-git
Submodule
1
contrib/lemonbar-sm-git
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8ed285ec2289761e6585090724f73093d62f290f
|
60
examples/config.i3.edp1
Normal file
60
examples/config.i3.edp1
Normal 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
172
examples/config.i3.hdmi1
Normal 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
229
examples/config.test
Normal 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
870
examples/config.white
Normal 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
187
examples/external_top
Normal 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
93
include/bar.hpp
Normal 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
24
include/config.hpp.cmake
Normal 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_
|
41
include/drawtypes/animation.hpp
Normal file
41
include/drawtypes/animation.hpp
Normal 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
45
include/drawtypes/bar.hpp
Normal 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
|
41
include/drawtypes/icon.hpp
Normal file
41
include/drawtypes/icon.hpp
Normal 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
|
33
include/drawtypes/label.hpp
Normal file
33
include/drawtypes/label.hpp
Normal 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
|
34
include/drawtypes/ramp.hpp
Normal file
34
include/drawtypes/ramp.hpp
Normal 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
60
include/eventloop.hpp
Normal 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
20
include/exception.hpp
Normal 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
|
||||
|
76
include/interfaces/alsa.hpp
Normal file
76
include/interfaces/alsa.hpp
Normal 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
183
include/interfaces/mpd.hpp
Normal 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
|
88
include/interfaces/net.hpp
Normal file
88
include/interfaces/net.hpp
Normal 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
13
include/lemonbuddy.hpp
Normal 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
|
35
include/modules/backlight.hpp
Normal file
35
include/modules/backlight.hpp
Normal 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
437
include/modules/base.hpp
Normal 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
|
67
include/modules/battery.hpp
Normal file
67
include/modules/battery.hpp
Normal 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
81
include/modules/bspwm.hpp
Normal 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
|
22
include/modules/counter.hpp
Normal file
22
include/modules/counter.hpp
Normal 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
54
include/modules/cpu.hpp
Normal 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
35
include/modules/date.hpp
Normal 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
85
include/modules/i3.hpp
Normal 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
|
35
include/modules/memory.hpp
Normal file
35
include/modules/memory.hpp
Normal 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
47
include/modules/menu.hpp
Normal 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
78
include/modules/mpd.hpp
Normal 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
|
68
include/modules/network.hpp
Normal file
68
include/modules/network.hpp
Normal 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
|
36
include/modules/script.hpp
Normal file
36
include/modules/script.hpp
Normal 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
20
include/modules/text.hpp
Normal 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
|
42
include/modules/torrent.hpp
Normal file
42
include/modules/torrent.hpp
Normal 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
|
60
include/modules/volume.hpp
Normal file
60
include/modules/volume.hpp
Normal 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
57
include/registry.hpp
Normal 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
|
102
include/services/builder.hpp
Normal file
102
include/services/builder.hpp
Normal 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
|
45
include/services/command.hpp
Normal file
45
include/services/command.hpp
Normal 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
|
63
include/services/inotify.hpp
Normal file
63
include/services/inotify.hpp
Normal 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
|
69
include/services/logger.hpp
Normal file
69
include/services/logger.hpp
Normal 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
|
36
include/services/store.hpp
Normal file
36
include/services/store.hpp
Normal 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
44
include/utils/cli.hpp
Normal 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
|
115
include/utils/concurrency.hpp
Normal file
115
include/utils/concurrency.hpp
Normal 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
152
include/utils/config.hpp
Normal 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
80
include/utils/io.hpp
Normal 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
18
include/utils/math.hpp
Normal 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
16
include/utils/memory.hpp
Normal 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
47
include/utils/proc.hpp
Normal 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
47
include/utils/streams.hpp
Normal 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
37
include/utils/string.hpp
Normal 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
10
include/utils/timer.hpp
Normal 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
34
include/utils/xlib.hpp
Normal 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
38
scripts/lemonbuddy_wrapper.sh
Executable 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
48
scripts/torrents.sh
Executable 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
57
src/CMakeLists.txt
Normal 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
259
src/bar.cpp
Normal 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(), ' ');
|
||||
}
|
70
src/drawtypes/animation.cpp
Normal file
70
src/drawtypes/animation.cpp
Normal 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
113
src/drawtypes/bar.cpp
Normal 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
40
src/drawtypes/icon.cpp
Normal 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
46
src/drawtypes/label.cpp
Normal 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
42
src/drawtypes/ramp.cpp
Normal 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
229
src/eventloop.cpp
Normal 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
183
src/interfaces/alsa.cpp
Normal 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
386
src/interfaces/mpd.cpp
Normal 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
167
src/interfaces/net.cpp
Normal 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
202
src/lemonbuddy.cpp
Normal 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 §ion : 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
63
src/modules/backlight.cpp
Normal 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
17
src/modules/base.cpp
Normal 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
176
src/modules/battery.cpp
Normal 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
223
src/modules/bspwm.cpp
Normal 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
33
src/modules/counter.cpp
Normal 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
138
src/modules/cpu.cpp
Normal 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
60
src/modules/date.cpp
Normal 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
176
src/modules/i3.cpp
Normal 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
94
src/modules/memory.cpp
Normal 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
125
src/modules/menu.cpp
Normal 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
303
src/modules/mpd.cpp
Normal 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
195
src/modules/network.cpp
Normal 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
83
src/modules/script.cpp
Normal 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
42
src/modules/text.cpp
Normal 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
123
src/modules/torrent.cpp
Normal 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
230
src/modules/volume.cpp
Normal 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
158
src/registry.cpp
Normal 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
472
src/services/builder.cpp
Normal 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
155
src/services/command.cpp
Normal 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
62
src/services/inotify.cpp
Normal 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
70
src/services/logger.cpp
Normal 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
65
src/services/store.cpp
Normal 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
Loading…
Reference in New Issue
Block a user