refactor: Application rewrite

This commit is contained in:
Michael Carlberg 2016-06-15 05:32:35 +02:00
parent d72ff7334e
commit d359ab6057
167 changed files with 17368 additions and 8891 deletions

13
.clang-format Normal file
View File

@ -0,0 +1,13 @@
---
Language: Cpp
Standard: Cpp11
BasedOnStyle: Google
ColumnLimit: 100
NamespaceIndentation: All
AlignAfterOpenBracket: DontAlign
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
BreakConstructorInitializersBeforeComma: true
DerivePointerAlignment: false
PointerAlignment: Left
---

27
.exrc
View File

@ -1,5 +1,24 @@
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 &path.='include,src,'
let g:alternateSearchPath = ''
\ . 'sfr:../src'
\ . ',sfr:../../src/adapters'
\ . ',sfr:../../src/components'
\ . ',sfr:../../src/drawtypes'
\ . ',sfr:../../src/interfaces'
\ . ',sfr:../../src/modules'
\ . ',sfr:../../src/services'
\ . ',sfr:../../src/utils'
\ . ',sfr:../../src/x11'
\ . ',sfr:../include'
\ . ',sfr:../../include/adapters'
\ . ',sfr:../../include/components'
\ . ',sfr:../../include/drawtypes'
\ . ',sfr:../../include/interfaces'
\ . ',sfr:../../include/modules'
\ . ',sfr:../../include/services'
\ . ',sfr:../../include/utils'
\ . ',sfr:../../include/x11'
let g:alternateExtensions_cpp = 'hpp'
" let tag_path = expand("%:p:h") . "/tags"
set tags+=/home/jaagr/var/github/jaagr/lemonbuddy/tags
let tag_path='/home/jaagr/.local/src/c++/lemonbuddy/.tags'
set tags+=/home/jaagr/.local/src/c++/lemonbuddy/.tags

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ tags
*.pyc
*.tmp
include/config.hpp
.tags

9
.gitmodules vendored
View File

@ -1,7 +1,3 @@
[submodule "contrib/lemonbar-sm-git"]
path = contrib/lemonbar-sm-git
url = https://github.com/jaagr/bar
branch = v1.1
[submodule "lib/i3ipcpp"]
path = lib/i3ipcpp
url = https://github.com/jaagr/i3ipcpp
@ -9,4 +5,7 @@
[submodule "lib/xpp"]
path = lib/xpp
url = https://github.com/jaagr/xpp
branch = 1.0.0
branch = 1.1.1
[submodule "lib/gsl"]
path = lib/gsl
url = https://github.com/Microsoft/GSL

View File

@ -1,6 +1,16 @@
sudo: required
dist: trusty
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.8
packages:
- gcc-5
- g++-5
- clang-3.8
language: cpp
compiler:
- clang
@ -9,27 +19,50 @@ cache: ccache
env:
global:
- LLVM_VERSION=3.8.0
- LLVM_ARCHIVE_PATH=$HOME/clang+llvm.tar.xz
- BUILD_TYPE="Release"
- LLVM_VERSION="3.8.1"
- LLVM_URL="http://llvm.org/releases/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz"
- LIBCXX_URL="http://llvm.org/releases/${LLVM_VERSION}/libcxx-${LLVM_VERSION}.src.tar.xz"
- LIBCXXABI_URL="http://llvm.org/releases/${LLVM_VERSION}/libcxxabi-${LLVM_VERSION}.src.tar.xz"
- CMAKE_URL="https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz"
before_install:
- wget http://llvm.org/releases/$LLVM_VERSION/clang+llvm-$LLVM_VERSION-x86_64-linux-gnu-ubuntu-14.04.tar.xz -O $LLVM_ARCHIVE_PATH
- mkdir $HOME/clang-$LLVM_VERSION
- tar xf $LLVM_ARCHIVE_PATH -C $HOME/clang-$LLVM_VERSION --strip-components 1
- export PATH=$HOME/clang-$LLVM_VERSION/bin:$PATH
- sudo apt-add-repository -y "ppa:george-edison55/george-edison"
- sudo sed -i "s/trusty/wily/g" /etc/apt/sources.list
- sudo apt-get -qq update
- sudo apt-get install -y cmake cmake-data libxcb1-dev python-xcbgen xcb-proto libboost-dev libiw-dev libasound2-dev libmpdclient-dev
- sudo apt-get install -y cmake cmake-data libxcb1-dev libxcb-util0-dev libxcb-randr0-dev libxcb-ewmh-dev libxcb-icccm4-dev xcb-proto libboost-dev libiw-dev libasound2-dev libmpdclient-dev libjsoncpp-dev libsigc++-2.0-0c2a libsigc++-2.0-dev libfreetype6-dev
install:
- DEPS_DIR="${TRAVIS_BUILD_DIR}/deps"
- LLVM_ROOT="${DEPS_DIR}/llvm-${LLVM_VERSION}"
- |
mkdir -p "${DEPS_DIR}" && cd "${DEPS_DIR}"
if [[ -z "$(ls -A ${DEPS_DIR}/cmake/bin 2>/dev/null)" ]]; then
mkdir -p cmake && travis_retry wget --no-check-certificate --quiet -O - "${CMAKE_URL}" | tar --strip-components=1 -xz -C cmake
fi
export PATH="${DEPS_DIR}/cmake/bin:${PATH}"
if [[ -z "$(ls -A ${LLVM_ROOT}/install/include 2>/dev/null)" ]]; then
mkdir -p "${LLVM_ROOT}" "${LLVM_ROOT}/build" "${LLVM_ROOT}/projects/libcxx" "${LLVM_ROOT}/projects/libcxxabi"
travis_retry wget --quiet -O - "${LLVM_URL}" | tar --strip-components=1 -xJ -C "${LLVM_ROOT}"
travis_retry wget --quiet -O - "${LIBCXX_URL}" | tar --strip-components=1 -xJ -C "${LLVM_ROOT}/projects/libcxx"
travis_retry wget --quiet -O - "${LIBCXXABI_URL}" | tar --strip-components=1 -xJ -C "${LLVM_ROOT}/projects/libcxxabi"
(cd "${LLVM_ROOT}/build" && cmake .. -DCMAKE_CXX_COMPILER="${CXX}" -DCMAKE_C_COMPILER="${CC}" -DCMAKE_INSTALL_PREFIX="${LLVM_ROOT}/install" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}")
(cd "${LLVM_ROOT}/build/projects/libcxx" && make install)
(cd "${LLVM_ROOT}/build/projects/libcxxabi" && make install)
export CXXFLAGS="-I ${LLVM_ROOT}/install/include/c++/v1"
export LDFLAGS="-L ${LLVM_ROOT}/install/lib -lc++ -lc++abi"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${LLVM_ROOT}/install/lib"
fi
before_script:
- if [ "$CXX" = "clang++" ]; then export CPPFLAGS="-I $HOME/clang-$LLVM_VERSION/include/c++/v1" CXXFLAGS="-Qunused-arguments"; fi
- export PYTHONPATH="/usr/lib/python2.7/dist-packages:${PYTHONPATH}"
- if [ "${CXX}" = "clang++" ]; then export CXX="clang++-3.8" CC="clang-3.8" CXXFLAGS="${CXXFLAGS} -Qunused-arguments"; fi
- if [ "${CXX}" = "g++" ]; then export CXX="g++-5" CC="gcc-5"; fi
- eval "${CXX} --version"
- eval "${CC} --version"
- cmake --version
- eval "$CXX --version"
- eval "$CC --version"
- mkdir build
- cd build
- cmake -DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" -DCMAKE_CXX_FLAGS="$CXXFLAGS" ..
- mkdir -p "${TRAVIS_BUILD_DIR}/build"
- cd "${TRAVIS_BUILD_DIR}/build"
- cmake -DCMAKE_C_COMPILER="${CC}" -DCMAKE_CXX_COMPILER="${CXX}" -DCMAKE_CXX_FLAGS="${CXXFLAGS}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" ..
script: make

View File

@ -48,8 +48,21 @@ def DirectoryOfThisScript():
flags.append('-I'+ DirectoryOfThisScript() +'/src')
flags.append('-I'+ DirectoryOfThisScript() +'/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/gsl')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/cpp_freetype/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/i3ipcpp/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/xpp/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/lemonbar/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/fastdelegate/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/boost/include')
flags.append('-I'+ DirectoryOfThisScript() +'/tests')
flags.append('-I/usr/include/freetype2')
flags.append('-I/usr/include/pango-1.0')
flags.append('-I/usr/include/cairomm-1.0')
flags.append('-I/usr/include/pangomm-1.4')
flags.append('-I/usr/include/glibmm-2.4')
flags.append('-I/usr/lib/cairomm-1.0/include')
flags.append('-I/usr/include')
def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
if not working_directory:

View File

@ -1,39 +1,57 @@
#
# Build configuration
#
cmake_minimum_required(VERSION 3.1)
project(lemonbuddy C CXX)
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(lemonbuddy CXX)
# Include the local cmake modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake/modules)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wno-unused-parameter -Wno-unused-local-typedefs -Wno-c99-extensions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG -O0 -g2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
SET(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(NOT CMAKE_BUILD_TYPE)
message(STATUS "No build type specified; using Release")
set(CMAKE_BUILD_TYPE "Release")
endif()
string(ASCII 27 ANSI)
include(cmake/utils.cmake)
include(cmake/clang-cpp-tools.cmake)
#
# Internal values and switches
#
# Figure out default option values {{{
# Keep track if the i3 option is explicitly defined
if(ENABLE_I3)
set(ENABLE_I3_NODEF ON)
find_package(ALSA QUIET)
find_package(Libiw QUIET)
find_package(LibMPDClient QUIET)
find_program(CCACHE_BINARY ccache)
find_program(I3_BINARY i3)
if(CCACHE_BINARY)
set(CCACHE_FOUND ON)
endif()
if(I3_BINARY)
set(I3_FOUND ON)
endif()
option(ENABLE_CCACHE "Enable ccache support" ON)
option(ENABLE_ALSA "Enable alsa support" ON)
option(ENABLE_I3 "Enable i3 support" ON)
option(ENABLE_MPD "Enable mpd support" ON)
option(ENABLE_NETWORK "Enable network support" ON)
# }}}
# Project settings {{{
option(ENABLE_CCACHE "Enable ccache support" ${CCACHE_FOUND})
option(ENABLE_CCACHE "Enable ccache support" ${CCACHE_FOUND})
option(ENABLE_ALSA "Enable alsa support" ${ALSA_FOUND})
option(ENABLE_I3 "Enable i3 support" ${I3_FOUND})
option(ENABLE_MPD "Enable mpd support" ${LIBMPDCLIENT_FOUND})
option(ENABLE_NETWORK "Enable network support" ${LIBIW_FOUND})
if(ENABLE_ALSA)
set(SETTING_ALSA_SOUNDCARD "default"
CACHE STRING "Name of the ALSA soundcard driver")
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"
@ -54,154 +72,105 @@ set(SETTING_PATH_MEMORY_INFO "/proc/meminfo"
CACHE STRING "Path to file containing memory info")
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")
endif()
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "ccache")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "ccache")
endif()
#
# Locate and insert libs
#
find_package("Boost" REQUIRED)
find_package("Threads" REQUIRED)
# }}}
# Locate dependencies {{{
set(PROJECT_INCL_DIRS "${PROJECT_SOURCE_DIR}/include"
${BOOST_INCLUDE_DIR})
set(PROJECT_LINK_LIBS
${BOOST_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT})
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Boost REQUIRED)
find_package(Threads REQUIRED)
find_package(Freetype REQUIRED Freetype2)
find_package(PkgConfig)
find_package(X11 REQUIRED COMPONENTS Xft)
find_package(X11_XCB REQUIRED)
if(ENABLE_I3)
find_program(I3_EXECUTABLE "i3")
if(NOT I3_EXECUTABLE)
if(NOT ENABLE_I3_NODEF)
message(WARNING "${ANSI}[41;1mDisabling \"i3 module\" support (prerequisites failed)${ANSI}[0m")
set(ENABLE_I3 OFF)
endif()
endif()
if(ENABLE_I3)
add_subdirectory("${PROJECT_SOURCE_DIR}/lib/i3ipcpp" EXCLUDE_FROM_ALL)
set(PROJECT_INCL_DIRS
${PROJECT_INCL_DIRS}
${SIGCPP_INCLUDE_DIRS}
${I3IPCPP_INCLUDE_DIRS})
set(PROJECT_LINK_LIBS
${PROJECT_LINK_LIBS}
${I3IPCPP_LIBRARIES})
endif()
endif()
pkg_check_modules(FONTCONFIG REQUIRED fontconfig)
link_libraries(${X11_Xft_LIB})
link_libraries(${X11_XCB_LIB})
link_libraries(${BOOST_LIBRARIES})
link_libraries(${CMAKE_THREAD_LIBS_INIT})
if(ENABLE_ALSA)
find_package("ALSA")
if(ALSA_FOUND)
set(PROJECT_INCL_DIRS ${PROJECT_INCL_DIRS} ${ALSA_INCLUDE_DIR})
set(PROJECT_LINK_LIBS ${PROJECT_LINK_LIBS} ${ALSA_LIBRARY})
else(ALSA_FOUND)
message(WARNING "${ANSI}[41;1mDisabling \"volume module\" support (prerequisites failed)${ANSI}[0m")
set(ENABLE_ALSA OFF)
endif()
find_package(ALSA REQUIRED)
endif()
if(ENABLE_MPD)
find_package("LibMPDClient")
if(LIBMPDCLIENT_FOUND)
set(PROJECT_INCL_DIRS ${PROJECT_INCL_DIRS} ${LIBMPDCLIENT_INCLUDE_DIR})
set(PROJECT_LINK_LIBS ${PROJECT_LINK_LIBS} ${LIBMPDCLIENT_LIBRARY})
else(LIBMPDCLIENT_FOUND)
message(WARNING "${ANSI}[41;1mDisabling \"mpd module\" support (prerequisites failed)${ANSI}[0m")
set(ENABLE_MPD OFF)
endif()
find_package(LibMPDClient REQUIRED)
endif()
if(ENABLE_NETWORK)
find_package("Libiw")
if(LIBIW_FOUND)
set(PROJECT_INCL_DIRS ${PROJECT_INCL_DIRS} ${LIBIW_INCLUDE_DIR})
set(PROJECT_LINK_LIBS ${PROJECT_LINK_LIBS} ${LIBIW_LIBRARY})
else(LIBIW_FOUND)
message(WARNING "${ANSI}[41;1mDisabling \"network module\" support (prerequisites failed)${ANSI}[0m")
set(ENABLE_NETWORK OFF)
endif()
find_package(Libiw REQUIRED)
endif()
if(ENABLE_I3)
add_subdirectory(${PROJECT_SOURCE_DIR}/lib/i3ipcpp EXCLUDE_FROM_ALL)
endif()
#
# Load the xpp library
#
include_directories(
${BOOST_INCLUDE_DIR}
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/lib/boost/include
${PROJECT_SOURCE_DIR}/lib/fastdelegate/include)
set(XCB_PROTOS xproto randr)
add_subdirectory("${PROJECT_SOURCE_DIR}/lib/xpp")
set(PROJECT_INCL_DIRS ${PROJECT_INCL_DIRS} ${XPP_INCLUDE_DIRS})
set(PROJECT_LINK_LIBS ${PROJECT_LINK_LIBS} ${XPP_LIBRARIES})
add_subdirectory(${PROJECT_SOURCE_DIR}/lib/xpp)
#
# Execute versioning script
#
execute_process(COMMAND ./version.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_QUIET ERROR_QUIET)
# }}}
# Build source tree {{{
add_subdirectory(${PROJECT_SOURCE_DIR}/man)
add_subdirectory(${PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/bin)
add_subdirectory(${PROJECT_SOURCE_DIR}/examples ${PROJECT_BINARY_DIR}/examples)
add_subdirectory(${PROJECT_SOURCE_DIR}/tests ${PROJECT_BINARY_DIR}/tests EXCLUDE_FROM_ALL)
# }}}
# Build summary {{{
#
# Install executable and wrapper
#
message(STATUS "---------------------------")
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS " Compiler C: ${CMAKE_C_COMPILER}")
message(STATUS " Compiler C++: ${CMAKE_CXX_COMPILER}")
message(STATUS " Compiler flags: ${CMAKE_CXX_FLAGS}")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS " + debug flags:: ${CMAKE_CXX_FLAGS_DEBUG}")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
message(STATUS " + release flags:: ${CMAKE_CXX_FLAGS_RELEASE}")
elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
message(STATUS " + minsizerel flags:: ${CMAKE_CXX_FLAGS_MINSIZEREL}")
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
message(STATUS " + relwithdebinfo flags:: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
endif()
message(STATUS "---------------------------")
message(STATUS " Enable ccache support ${ENABLE_CCACHE}")
message(STATUS " Enable alsa support ${ENABLE_ALSA}")
message(STATUS " Enable i3 support ${ENABLE_I3}")
message(STATUS " Enable mpd support ${ENABLE_MPD}")
message(STATUS " Enable network support ${ENABLE_NETWORK}")
if(DISABLE_MODULES)
message(STATUS " Disable modules ON")
endif()
if(DISABLE_TRAY)
message(STATUS " Disable systray ON")
endif()
if(DISABLE_DRAW)
message(STATUS " Disable drawing ON")
endif()
message(STATUS "---------------------------")
add_subdirectory("${PROJECT_SOURCE_DIR}/man")
add_subdirectory("${PROJECT_SOURCE_DIR}/src" EXCLUDE_FROM_ALL)
link_directories(${PROJECT_LINK_DIRS})
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${PROJECT_INCL_DIRS})
link_libraries(${PROJECT_LINK_LIBS})
# }}}
# Uninstall target {{{
add_executable(${PROJECT_NAME} ${FILES}
"examples/config"
"examples/config.bspwm"
"examples/config.i3")
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall -Wextra -Wpedantic -Wno-unused-parameter
$<$<CONFIG:Debug>:-g3 -DDEBUG>
$<$<CONFIG:Release>:-O3 -Wno-unused-variable>)
target_link_libraries(${PROJECT_NAME} ${PROJECT_LINK_LIBS})
configure_file("${CMAKE_SOURCE_DIR}/include/config.hpp.cmake" "${CMAKE_SOURCE_DIR}/include/config.hpp" ESCAPE_QUOTES @ONLY)
install(TARGETS ${PROJECT_NAME}
DESTINATION "bin"
COMPONENT "binaries")
install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/lemonbuddy_wrapper"
DESTINATION "bin" COMPONENT "binaries")
install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/lemonbuddy_terminate"
DESTINATION "bin" COMPONENT "binaries")
install(FILES "examples/config"
DESTINATION "share/examples/${PROJECT_NAME}"
COMPONENT "config")
install(FILES "examples/config.bspwm"
DESTINATION "share/examples/${PROJECT_NAME}"
COMPONENT "config")
install(FILES "examples/config.i3"
DESTINATION "share/examples/${PROJECT_NAME}"
COMPONENT "config")
#
# Uninstall target
#
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/cmake/uninstall.cmake"
${PROJECT_SOURCE_DIR}/cmake/uninstall.cmake.in
${PROJECT_BINARY_DIR}/cmake/uninstall.cmake
IMMEDIATE @ONLY)
add_custom_target(uninstall COMMAND ${CMAKE_COMMAND}
-P "${CMAKE_CURRENT_BINARY_DIR}/cmake/uninstall.cmake")
-P ${PROJECT_BINARY_DIR}/cmake/uninstall.cmake)
# }}}

View File

@ -100,13 +100,13 @@ available for more people.
### Dependencies
A C++ compiler with C++14 support. For example [`clang`](http://clang.llvm.org/get_started.html).
A compiler with c++14 support. For example [`clang`](http://clang.llvm.org/get_started.html).
- lemonbar with xft support _(personally I use [this fork](https://github.com/osense/bar))_
- cmake
- boost
- libxcb
- xcb-proto
- freetype2
Optional dependencies for module support:
@ -117,8 +117,8 @@ Optional dependencies for module support:
~~~ sh
$ pacman -S cmake boost libxcb xcb-proto wireless_tools alsa-lib libmpdclient jsoncpp libsigc++
$ xbps-install cmake boost-devel libxcb-devel alsa-lib-devel i3-devel libmpdclient-devel jsoncpp-devel libsigc++-devel wireless_tools-devel
$ apt-get install cmake libxcb1-dev xcb-proto python-xcbgen libboost-dev libiw-dev libasound2-dev libmpdclient-dev libjsoncpp-dev libsigc++-dev
$ xbps-install cmake boost-devel libxcb-devel alsa-lib-devel i3-devel libmpdclient-devel jsoncpp-devel freetype-devel libsigc++-devel wireless_tools-devel
$ apt-get install cmake libxcb1-dev xcb-proto python-xcbgen libboost-dev libiw-dev libasound2-dev libmpdclient-dev libjsoncpp-dev libsigc++-2.0-dev libfreetype6-dev
~~~
@ -127,7 +127,7 @@ $ apt-get install cmake libxcb1-dev xcb-proto python-xcbgen libboost-dev libiw-d
Please [report any problems](https://github.com/jaagr/lemonbuddy/issues/new) you run into when building the project. It helps alot.
~~~ sh
$ git clone --branch 1.4.4 --recursive https://github.com/jaagr/lemonbuddy
$ git clone --branch 1.4.6 --recursive https://github.com/jaagr/lemonbuddy
$ mkdir lemonbuddy/build
$ cd lemonbuddy/build
$ cmake ..
@ -281,7 +281,7 @@ The configuration syntax is based on the `ini` file format.
;
; The rest of the drawtypes follow the same pattern.
;
; label-NAME[-(foreground|background|(under|over)line|font|padding)] = ?
; label-NAME[-(foreground|background|(under|over)line|font|padding|maxlen|ellipsis)] = ?
; 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)] = ?
@ -300,6 +300,10 @@ The configuration syntax is based on the `ini` file format.
format-offline = <label-offline>
format-offline-offset = -8
; Cap the song label without trailing ellipsis
label-song-maxlen = 30
label-song-ellipsis = false
; By only specifying alpha value, it will be applied to the bar's default foreground
label-time-foreground = #66
@ -324,8 +328,8 @@ The configuration syntax is based on the `ini` file format.
; Limit the amount of events sent to lemonbar within a set timeframe:
; - "Allow <throttle_limit> updates within <throttle_ms> of time"
; Default values:
throttle_limit = 5
throttle_ms = 50
throttle_limit = 3
throttle_ms = 60
~~~
@ -361,6 +365,19 @@ The configuration syntax is based on the `ini` file format.
foreground = #eefafafa
linecolor = ${bar/example.background}
; Borders
; Size to be used for all borders
border-size = 2
; Color to be used for all borders
border-color = #ff9900
; Per-border values
;border-top = 1
;border-top-color = #ff9900
;border-bottom = 2
;border-bottom-color = #5d00ff
;border-left = 3
;border-right-color = #ff0059
; Amount of spaces to add at the start/end of the whole bar
padding_left = 5
padding_right = 2
@ -378,10 +395,6 @@ The configuration syntax is based on the `ini` file format.
; The separator will be inserted between the output of each module
separator = |
; This value is used by Lemonbar and it specifies the clickable
; areas available -> %{A:action:}...%{A}
clickareas = 30
; Value to be used to set the WM_NAME atom
; This defaults to "lemonbuddy-[BAR]_[MONITOR]"
wm_name = mybar
@ -393,6 +406,14 @@ The configuration syntax is based on the `ini` file format.
modules-left = cpu ram
modules-center = label
modules-right = clock
; Position of the tray container
; If undefined, tray support will be disabled
;
; Available positions:
; left
; right
tray-position = right
~~~
### Modules
@ -958,6 +979,12 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
ramp-volume-0 = 🔈
ramp-volume-1 = 🔉
ramp-volume-2 = 🔊
; If defined, it will replace <ramp-volume> when
; headphones are plugged in to `headphone_control_numid`
; If undefined, <ramp-volume> will be used for both
ramp-headphones-0 = 
ramp-headphones-1 = 
~~~
@ -1026,6 +1053,14 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
; Will be ignored if `tail = true`
; Default: 1
interval = 90
; Limit the length of the output string
; Default: 0
maxlen = 20
; Add trailing ellipsis when truncating the string
; Default: true
ellipsis = true
~~~
##### Extra formatting (example)
@ -1061,6 +1096,7 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
type = custom/script
exec = xtitle -s
tail = true
maxlen = 25
~~~

6
TODO Normal file
View File

@ -0,0 +1,6 @@
task: sandbox modules
task: rewrite README for 2.0
task: rewrite i3 module
fix: hide mpd controls if playlist is empty
bug: allow empty formats
bug: ntpd update crash

View File

@ -0,0 +1,22 @@
#
# Additional targets to perform clang-format/clang-tidy
#
file(GLOB_RECURSE SOURCE_FILES *.[chi]pp)
# Add clang-format target if executable is found
# --------------------------------------------------
find_program(CLANG_FORMAT "clang-format")
if(CLANG_FORMAT)
add_custom_target(clang-format COMMAND
${CLANG_FORMAT} -i -style=file ${SOURCE_FILES})
endif()
# Add clang-tidy target if executable is found
# --------------------------------------------------
find_program(CLANG_TIDY "clang-tidy")
if(CLANG_TIDY)
add_custom_target(clang-tidy COMMAND ${CLANG_TIDY}
${SOURCE_FILES} -config='' -- -std=c++11
${INCLUDE_DIRECTORIES})
endif()

View File

@ -0,0 +1,54 @@
#
# Find the CppUnit includes and library
#
# This module defines
# CPPUNIT_INCLUDE_DIR, where to find tiff.h, etc.
# CPPUNIT_LIBRARIES, the libraries to link against to use CppUnit.
# CPPUNIT_FOUND, If false, do not try to use CppUnit.
# also defined, but not for general use are
# CPPUNIT_LIBRARY, where to find the CppUnit library.
# CPPUNIT_DEBUG_LIBRARY, where to find the CppUnit library in debug
# mode.
SET(CPPUNIT_FOUND "NO")
FIND_PATH(CPPUNIT_INCLUDE_DIR cppunit/TestCase.h /usr/local/include /usr/include)
# With Win32, important to have both
IF(WIN32)
FIND_LIBRARY(CPPUNIT_LIBRARY cppunit
${CPPUNIT_INCLUDE_DIR}/../lib
/usr/local/lib
/usr/lib)
FIND_LIBRARY(CPPUNIT_DEBUG_LIBRARY cppunitd
${CPPUNIT_INCLUDE_DIR}/../lib
/usr/local/lib
/usr/lib)
ELSE(WIN32)
# On unix system, debug and release have the same name
FIND_LIBRARY(CPPUNIT_LIBRARY cppunit
${CPPUNIT_INCLUDE_DIR}/../lib
/usr/local/lib
/usr/lib)
FIND_LIBRARY(CPPUNIT_DEBUG_LIBRARY cppunit
${CPPUNIT_INCLUDE_DIR}/../lib
/usr/local/lib
/usr/lib)
ENDIF(WIN32)
IF(CPPUNIT_INCLUDE_DIR)
IF(CPPUNIT_LIBRARY)
SET(CPPUNIT_FOUND "YES")
SET(CPPUNIT_LIBRARIES ${CPPUNIT_LIBRARY} ${CMAKE_DL_LIBS})
SET(CPPUNIT_DEBUG_LIBRARIES ${CPPUNIT_DEBUG_LIBRARY} ${CMAKE_DL_LIBS})
ELSE (CPPUNIT_LIBRARY)
IF (CPPUNIT_FIND_REQUIRED)
MESSAGE(SEND_ERROR "Could not find library CppUnit.")
ENDIF (CPPUNIT_FIND_REQUIRED)
ENDIF(CPPUNIT_LIBRARY)
ELSE(CPPUNIT_INCLUDE_DIR)
IF (CPPUNIT_FIND_REQUIRED)
MESSAGE(SEND_ERROR "Could not find library CppUnit.")
ENDIF(CPPUNIT_FIND_REQUIRED)
ENDIF(CPPUNIT_INCLUDE_DIR)

152
cmake/utils.cmake Normal file
View File

@ -0,0 +1,152 @@
#
# Collection of cmake utility functions
#
# message_colored : Outputs a colorized message {{{
function(message_colored message_level text color)
string(ASCII 27 esc)
message(${message_level} "${esc}[${color}m${text}${esc}[0m")
endfunction()
# }}}
# make_executable : Builds an executable target {{{
function(make_executable target_name)
set(zero_value_args)
set(one_value_args PACKAGE)
set(multi_value_args SOURCES INCLUDE_DIRS PKG_DEPENDS CMAKE_DEPENDS TARGET_DEPENDS RAW_DEPENDS)
cmake_parse_arguments(BIN
"${zero_value_args}" "${one_value_args}"
"${multi_value_args}" ${ARGN})
# add defined INCLUDE_DIRS
include_directories(${BIN_INCLUDE_DIRS})
# add INCLUDE_DIRS for all external dependencies
foreach(DEP ${BIN_TARGET_DEPENDS} ${BIN_PKG_DEPENDS} ${BIN_CMAKE_DEPENDS})
string(TOUPPER ${DEP} DEP)
include_directories(${${DEP}_INCLUDE_DIRS})
include_directories(${${DEP}_INCLUDEDIR})
endforeach()
# create target
add_executable(${target_name} ${BIN_SOURCES})
# set the output file basename the same for static and shared
set_target_properties(${target_name}
PROPERTIES OUTPUT_NAME ${target_name})
# link libraries from pkg-config imports
foreach(DEP ${BIN_PKG_DEPENDS})
string(TOUPPER ${DEP} DEP)
target_link_libraries(${target_name} ${${DEP}_LDFLAGS})
endforeach()
# link libraries from cmake imports
foreach(DEP ${BIN_CMAKE_DEPENDS})
string(TOUPPER ${DEP} DEP)
target_link_libraries(${target_name} ${${DEP}_LIB}
${${DEP}_LIBRARY}
${${DEP}_LIBRARIES})
endforeach()
# link libraries that are build as part of this project
target_link_libraries(${target_name} ${BIN_TARGET_DEPENDS}
${BIN_RAW_DEPENDS})
# install targets
install(TARGETS ${target_name}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
endfunction()
# }}}
# make_library : Builds a library target {{{
function(make_library target_name)
set(zero_value_args SHARED STATIC)
set(one_value_args PACKAGE HEADER_INSTALL_DIR)
set(multi_value_args SOURCES HEADERS INCLUDE_DIRS PKG_DEPENDS CMAKE_DEPENDS TARGET_DEPENDS RAW_DEPENDS)
cmake_parse_arguments(LIB
"${zero_value_args}" "${one_value_args}"
"${multi_value_args}" ${ARGN})
# make the header paths absolute
foreach(HEADER ${LIB_HEADERS})
set(LIB_HEADERS_ABS ${LIB_HEADERS_ABS} ${PROJECT_SOURCE_DIR}/include/${HEADER})
endforeach()
# add defined INCLUDE_DIRS
foreach(DIR ${LIB_INCLUDE_DIRS})
string(TOUPPER ${DIR} DIR)
include_directories(${DIR})
include_directories(${${DIR}_INCLUDE_DIRS})
endforeach()
# add INCLUDE_DIRS for all external dependencies
foreach(DEP ${LIB_TARGET_DEPENDS} ${LIB_PKG_DEPENDS} ${LIB_CMAKE_DEPENDS})
string(TOUPPER ${DEP} DEP)
include_directories(${${DEP}_INCLUDE_DIRS} ${${DEP}_INCLUDEDIRS})
endforeach()
if(LIB_SHARED)
list(APPEND library_targets ${target_name}_shared)
endif()
if(LIB_STATIC)
list(APPEND library_targets ${target_name}_static)
endif()
foreach(library_target_name ${library_targets})
message(STATUS "${library_target_name}")
add_library(${library_target_name} ${LIB_HEADERS_ABS} ${LIB_SOURCES})
# link libraries from pkg-config imports
foreach(DEP ${LIB_PKG_DEPENDS})
string(TOUPPER ${DEP} DEP)
target_link_libraries(${library_target_name} ${${DEP}_LDFLAGS})
endforeach()
# link libraries from cmake imports
foreach(DEP ${LIB_CMAKE_DEPENDS})
string(TOUPPER ${DEP} DEP)
target_link_libraries(${library_target_name} ${${DEP}_LIB}
${${DEP}_LIBRARY}
${${DEP}_LIBRARIES})
endforeach()
# link libraries that are build as part of this project
foreach(DEP ${LIB_TARGET_DEPENDS})
string(TOUPPER ${DEP} DEP)
if(LIB_BUILD_SHARED)
target_link_libraries(${library_target_name} ${DEP}_shared)
endif()
if(LIB_BUILD_STATIC)
target_link_libraries(${library_target_name} ${DEP}_static)
endif()
endforeach()
if(${LIB_RAW_DEPENDS})
if(LIB_BUILD_STATIC)
target_link_libraries(${library_target_name} ${LIB_RAW_DEPENDS})
endif()
endif()
# set the output file basename
set_target_properties(${library_target_name} PROPERTIES OUTPUT_NAME ${target_name})
# install headers
install(FILES ${LIBRARY_HEADERS} DESTINATION include/${LIB_HEADERS_ABS})
# install targets
install(TARGETS ${LIBRARY_TARGETS}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
endforeach()
endfunction()
# }}}

6
config
View File

@ -203,19 +203,19 @@ full_at = 99
; <bar-capaity>
; <ramp-capacity>
; <animation-charging>
format-charging = Charging <animation-charging> <label-charging>
format-charging = <animation-charging> <label-charging>
; Available tags:
; <label-discharging> (default)
; <bar-capaity>
; <ramp-capacity>
format-discharging = Discharging <ramp-capacity> <label-discharging>
format-discharging = <ramp-capacity> <label-discharging>
; Available tags:
; <label-full> (default)
; <bar-capaity>
; <ramp-capacity>
format-full = Fully charged <ramp-capacity> <label-full>
format-full = <ramp-capacity> <label-full>
; Available tokens:
; %percentage% (default)

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

13
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,13 @@
install(FILES config
DESTINATION share/examples/lemonbuddy
COMPONENT config)
install(FILES config.bspwm
DESTINATION share/examples/lemonbuddy
COMPONENT config)
if(ENABLE_I3)
install(FILES config.i3
DESTINATION share/examples/lemonbuddy
COMPONENT config)
endif()

View File

@ -15,7 +15,7 @@
;throttle_ms = 50
[bar/example]
;monitor = eDP1
monitor = eDP-1
bottom = true
dock = false
@ -45,7 +45,9 @@ font-0 = sans:size=8;0
font-1 = font awesome:size=10:weight=heavy;0
modules-left = label
modules-right = volume cpu ram clock
;modules-center = counter
;modules-right = volume cpu ram clock
modules-right = volume clock
[module/label]
type = custom/text
@ -97,4 +99,14 @@ format-muted-padding = 2
label-volume = Volume: %percentage%
label-muted = Sound is muted
; [module/counter]
; type = custom/script
; exec = echo %counter%
; interval = 1
; format-background = #393484
; format-underline = #69d294
; format-overline = #69d294
; format-padding = 2
; vim:ft=dosini

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

247
include/adapters/alsa.hpp Normal file
View File

@ -0,0 +1,247 @@
#pragma once
#include <stdio.h>
#include <functional>
#include <string>
#include <alsa/asoundlib.h>
#include "common.hpp"
#include "config.hpp"
// #include "utils/threading.hpp"
LEMONBUDDY_NS
DEFINE_ERROR(alsa_exception);
DEFINE_CHILD_ERROR(alsa_ctl_interface_error, alsa_exception);
DEFINE_CHILD_ERROR(alsa_mixer_error, alsa_exception);
// class definition : alsa_ctl_interface {{{
template<typename T>
void throw_exception(string&& message, int error_code) {
const char* snd_error = snd_strerror(error_code);
if (snd_error != nullptr)
message += ": "+ string{snd_error};
throw T(message.c_str());
}
class alsa_ctl_interface {
public:
explicit alsa_ctl_interface(int numid) {
int err = 0;
snd_ctl_elem_info_alloca(&m_info);
snd_ctl_elem_value_alloca(&m_value);
snd_ctl_elem_id_alloca(&m_id);
snd_ctl_elem_id_set_numid(m_id, numid);
snd_ctl_elem_info_set_id(m_info, m_id);
if ((err = snd_ctl_open(&m_ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) < 0)
throw_exception<alsa_ctl_interface_error>("Could not open control '"+ string{ALSA_SOUNDCARD} +"'", err);
if ((err = snd_ctl_elem_info(m_ctl, m_info)) < 0)
throw_exception<alsa_ctl_interface_error>("Could not get control datal", err);
snd_ctl_elem_info_get_id(m_info, m_id);
if ((err = snd_hctl_open(&m_hctl, ALSA_SOUNDCARD, 0)) < 0)
throw_exception<alsa_ctl_interface_error>("Failed to open hctl", err);
if ((err = snd_hctl_load(m_hctl)) < 0)
throw_exception<alsa_ctl_interface_error>("Failed to load hctl", err);
if ((m_elem = snd_hctl_find_elem(m_hctl, m_id)) == nullptr)
throw alsa_ctl_interface_error(
"Could not find control with id " + to_string(snd_ctl_elem_id_get_numid(m_id)));
if ((err = snd_ctl_subscribe_events(m_ctl, 1)) < 0)
throw alsa_ctl_interface_error(
"Could not subscribe to events: " + to_string(snd_ctl_elem_id_get_numid(m_id)));
// log_trace("Successfully initialized control interface with ID: "+ Intstring(numid));
}
~alsa_ctl_interface() {
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
snd_ctl_close(m_ctl);
snd_hctl_close(m_hctl);
}
bool wait(int timeout = -1) {
assert(m_ctl);
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
int err = 0;
if ((err = snd_ctl_wait(m_ctl, timeout)) < 0)
throw_exception<alsa_ctl_interface_error>("Failed to wait for events", err);
snd_ctl_event_t* event;
snd_ctl_event_alloca(&event);
if ((err = snd_ctl_read(m_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 test_device_plugged() {
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
// if (!wait(0))
// return false;
assert(m_elem);
assert(m_value);
int err = 0;
if ((err = snd_hctl_elem_read(m_elem, m_value)) < 0)
throw_exception<alsa_ctl_interface_error>("Could not read control value", err);
return snd_ctl_elem_value_get_boolean(m_value, 0);
}
void process_events() {}
private:
// threading_util::spin_lock m_lock;
snd_hctl_t* m_hctl = nullptr;
snd_hctl_elem_t* m_elem = nullptr;
snd_ctl_t* m_ctl = nullptr;
snd_ctl_elem_info_t* m_info = nullptr;
snd_ctl_elem_value_t* m_value = nullptr;
snd_ctl_elem_id_t* m_id = nullptr;
};
// }}}
// class definition : alsa_mixer {{{
class alsa_mixer {
public:
explicit alsa_mixer(string mixer_control_name) {
snd_mixer_selem_id_t* mixer_id;
snd_mixer_selem_id_alloca(&mixer_id);
int err = 0;
if ((err = snd_mixer_open(&m_hardwaremixer, 1)) < 0)
throw_exception<alsa_mixer_error>("Failed to open hardware mixer", err);
if ((err = snd_mixer_attach(m_hardwaremixer, ALSA_SOUNDCARD)) < 0)
throw_exception<alsa_mixer_error>("Failed to attach hardware mixer control", err);
if ((err = snd_mixer_selem_register(m_hardwaremixer, nullptr, nullptr)) < 0)
throw_exception<alsa_mixer_error>("Failed to register simple mixer element", err);
if ((err = snd_mixer_load(m_hardwaremixer)) < 0)
throw_exception<alsa_mixer_error>("Failed to load mixer", err);
snd_mixer_selem_id_set_index(mixer_id, 0);
snd_mixer_selem_id_set_name(mixer_id, mixer_control_name.c_str());
if ((m_mixerelement = snd_mixer_find_selem(m_hardwaremixer, mixer_id)) == nullptr)
throw alsa_mixer_error("Cannot find simple element");
// log_trace("Successfully initialized mixer: "+ mixer_control_name);
}
~alsa_mixer() {
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
snd_mixer_elem_remove(m_mixerelement);
snd_mixer_detach(m_hardwaremixer, ALSA_SOUNDCARD);
snd_mixer_close(m_hardwaremixer);
}
bool wait(int timeout = -1) {
assert(m_hardwaremixer);
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
int err = 0;
if ((err = snd_mixer_wait(m_hardwaremixer, timeout)) < 0)
throw_exception<alsa_mixer_error>("Failed to wait for events", err);
return process_events() > 0;
}
int process_events() {
int num_events = snd_mixer_handle_events(m_hardwaremixer);
if (num_events < 0)
throw_exception<alsa_mixer_error>("Failed to process pending events", num_events);
return num_events;
}
int get_volume() {
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
long chan_n = 0, vol_total = 0, vol, vol_min, vol_max;
snd_mixer_selem_get_playback_volume_range(m_mixerelement, &vol_min, &vol_max);
for (int i = 0; i < SND_MIXER_SCHN_LAST; i++) {
if (snd_mixer_selem_has_playback_channel(m_mixerelement, (snd_mixer_selem_channel_id_t)i)) {
snd_mixer_selem_get_playback_volume(m_mixerelement, (snd_mixer_selem_channel_id_t)i, &vol);
vol_total += vol;
chan_n++;
}
}
return 100.0f * (vol_total / chan_n) / vol_max + 0.5f;
}
void set_volume(float percentage) {
if (is_muted())
return;
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
long vol_min, vol_max;
snd_mixer_selem_get_playback_volume_range(m_mixerelement, &vol_min, &vol_max);
snd_mixer_selem_set_playback_volume_all(m_mixerelement, vol_max * percentage / 100);
}
void set_mute(bool mode) {
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
snd_mixer_selem_set_playback_switch_all(m_mixerelement, mode);
}
void toggle_mute() {
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
int state;
snd_mixer_selem_get_playback_switch(m_mixerelement, SND_MIXER_SCHN_FRONT_LEFT, &state);
snd_mixer_selem_set_playback_switch_all(m_mixerelement, !state);
}
bool is_muted() {
// std::lock_guard<threading_util::spin_lock> lck(m_lock);
int state = 0;
for (int i = 0; i < SND_MIXER_SCHN_LAST; i++) {
if (snd_mixer_selem_has_playback_channel(m_mixerelement, (snd_mixer_selem_channel_id_t)i)) {
snd_mixer_selem_get_playback_switch(m_mixerelement, SND_MIXER_SCHN_FRONT_LEFT, &state);
if (state == 0)
return true;
}
}
return false;
}
protected:
void error_handler(string message) {}
private:
// threading_util::spin_lock m_lock;
snd_mixer_t* m_hardwaremixer = nullptr;
snd_mixer_elem_t* m_mixerelement = nullptr;
};
// }}}
LEMONBUDDY_NS_END

491
include/adapters/mpd.hpp Normal file
View File

@ -0,0 +1,491 @@
#pragma once
#include <mpd/client.h>
#include <stdlib.h>
#include <chrono>
#include <memory>
#include <string>
#include "common.hpp"
#include "components/logger.hpp"
#include "utils/math.hpp"
LEMONBUDDY_NS
namespace mpd {
DEFINE_ERROR(mpd_exception);
DEFINE_CHILD_ERROR(client_error, mpd_exception);
DEFINE_CHILD_ERROR(server_error, mpd_exception);
// type details {{{
namespace details {
struct mpd_connection_deleter {
void operator()(mpd_connection* conn) {
if (conn != nullptr)
mpd_connection_free(conn);
}
};
struct mpd_status_deleter {
void operator()(mpd_status* status) {
mpd_status_free(status);
}
};
struct mpd_song_deleter {
void operator()(mpd_song* song) {
mpd_song_free(song);
}
};
using mpd_connection_t = unique_ptr<mpd_connection, mpd_connection_deleter>;
using mpd_status_t = unique_ptr<mpd_status, mpd_status_deleter>;
using mpd_song_t = unique_ptr<mpd_song, mpd_song_deleter>;
}
inline void check_connection(mpd_connection* conn) {
if (conn == nullptr)
throw client_error("Not connected to MPD server", MPD_ERROR_STATE);
}
inline void check_errors(mpd_connection* conn) {
mpd_error code = mpd_connection_get_error(conn);
if (code == MPD_ERROR_SUCCESS)
return;
auto msg = mpd_connection_get_error_message(conn);
if (code == MPD_ERROR_SERVER) {
mpd_connection_clear_error(conn);
throw server_error(msg, mpd_connection_get_server_error(conn));
} else {
mpd_connection_clear_error(conn);
throw client_error(msg, code);
}
}
enum class mpdstate {
UNKNOWN = 1 << 0,
STOPPED = 1 << 1,
PLAYING = 1 << 2,
PAUSED = 1 << 4,
};
// }}}
// class: mpdsong {{{
class mpdsong {
public:
explicit mpdsong(details::mpd_song_t&& song) : m_song(forward<decltype(song)>(song)) {}
operator bool() {
return m_song.get() != nullptr;
}
string get_artist() {
assert(m_song);
auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ARTIST, 0);
if (tag == nullptr)
return "";
return string{tag};
}
string get_album() {
assert(m_song);
auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ALBUM, 0);
if (tag == nullptr)
return "";
return string{tag};
}
string get_title() {
assert(m_song);
auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_TITLE, 0);
if (tag == nullptr)
return "";
return string{tag};
}
unsigned get_duration() {
assert(m_song);
return mpd_song_get_duration(m_song.get());
}
private:
details::mpd_song_t m_song;
};
// }}}
// class: mpdconnection {{{
class mpdstatus;
class mpdconnection {
public:
explicit mpdconnection(const logger& logger, string host, unsigned int port = 6600, string password = "", unsigned int timeout = 15)
: m_log(logger), m_host(host), m_port(port), m_password(password), m_timeout(timeout) {}
void connect() {
try {
m_log.trace("mpdconnection.connect: %s, %i, \"%s\", timeout: %i", m_host, m_port, m_password, m_timeout);
m_connection.reset(mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000));
check_errors(m_connection.get());
if (!m_password.empty()) {
noidle();
assert(!m_listactive);
mpd_run_password(m_connection.get(), m_password.c_str());
check_errors(m_connection.get());
}
m_fd = mpd_connection_get_fd(m_connection.get());
check_errors(m_connection.get());
} catch (const client_error& e) {
disconnect();
throw e;
}
}
void disconnect() {
m_connection.reset();
m_idle = false;
m_listactive = false;
}
bool connected() {
if (!m_connection)
return false;
return m_connection.get() != nullptr;
}
bool retry_connection(int interval = 1) {
if (connected())
return true;
while (true) {
try {
connect();
return true;
} catch (const mpd_exception& e) {
}
this_thread::sleep_for(chrono::duration<double>(interval));
}
return false;
}
int get_fd() {
return m_fd;
}
void idle() {
check_connection(m_connection.get());
if (m_idle)
return;
mpd_send_idle(m_connection.get());
check_errors(m_connection.get());
m_idle = true;
}
int noidle() {
check_connection(m_connection.get());
int flags = 0;
if (m_idle && mpd_send_noidle(m_connection.get())) {
m_idle = false;
flags = mpd_recv_idle(m_connection.get(), true);
mpd_response_finish(m_connection.get());
check_errors(m_connection.get());
}
return flags;
}
unique_ptr<mpdstatus> get_status() {
check_prerequisites();
auto status = make_unique<mpdstatus>(this);
check_errors(m_connection.get());
// if (update)
// status->update(-1, this);
return status;
}
unique_ptr<mpdstatus> get_status_safe() {
try {
return get_status();
} catch (const mpd_exception& e) {
return {};
}
}
unique_ptr<mpdsong> get_song() {
check_prerequisites_commands_list();
mpd_send_current_song(m_connection.get());
details::mpd_song_t song{mpd_recv_song(m_connection.get()), details::mpd_song_deleter{}};
mpd_response_finish(m_connection.get());
check_errors(m_connection.get());
if (song.get() != nullptr) {
return make_unique<mpdsong>(std::move(song));
}
return unique_ptr<mpdsong>{};
}
void play() {
try {
check_prerequisites_commands_list();
mpd_run_play(m_connection.get());
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.play: %s", e.what());
}
}
void pause(bool state) {
try {
check_prerequisites_commands_list();
mpd_run_pause(m_connection.get(), state);
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.pause: %s", e.what());
}
}
void toggle() {
try {
check_prerequisites_commands_list();
mpd_run_toggle_pause(m_connection.get());
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.toggle: %s", e.what());
}
}
void stop() {
try {
check_prerequisites_commands_list();
mpd_run_stop(m_connection.get());
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.stop: %s", e.what());
}
}
void prev() {
try {
check_prerequisites_commands_list();
mpd_run_previous(m_connection.get());
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.prev: %s", e.what());
}
}
void next() {
try {
check_prerequisites_commands_list();
mpd_run_next(m_connection.get());
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.next: %s", e.what());
}
}
void seek(int songid, int pos) {
try {
check_prerequisites_commands_list();
mpd_run_seek_id(m_connection.get(), songid, pos);
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.seek: %s", e.what());
}
}
void set_repeat(bool mode) {
try {
check_prerequisites_commands_list();
mpd_run_repeat(m_connection.get(), mode);
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.set_repeat: %s", e.what());
}
}
void set_random(bool mode) {
try {
check_prerequisites_commands_list();
mpd_run_random(m_connection.get(), mode);
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.set_random: %s", e.what());
}
}
void set_single(bool mode) {
try {
check_prerequisites_commands_list();
mpd_run_single(m_connection.get(), mode);
check_errors(m_connection.get());
} catch (const mpd_exception& e) {
m_log.err("mpdconnection.set_single: %s", e.what());
}
}
operator details::mpd_connection_t::element_type*() {
return m_connection.get();
}
protected:
void check_prerequisites() {
check_connection(m_connection.get());
noidle();
}
void check_prerequisites_commands_list() {
noidle();
assert(!m_listactive);
check_prerequisites();
}
private:
const logger& m_log;
details::mpd_connection_t m_connection;
bool m_listactive = false;
bool m_idle = false;
int m_fd = -1;
string m_host;
unsigned int m_port;
string m_password;
unsigned int m_timeout;
};
// }}}
// class: mpdstatus {{{
class mpdstatus {
public:
explicit mpdstatus(mpdconnection* conn, bool autoupdate = true) {
fetch_data(conn);
if (autoupdate)
update(-1, conn);
}
void fetch_data(mpdconnection* conn) {
m_status.reset(mpd_run_status(*conn));
m_updated_at = chrono::system_clock::now();
m_songid = mpd_status_get_song_id(m_status.get());
m_random = mpd_status_get_random(m_status.get());
m_repeat = mpd_status_get_repeat(m_status.get());
m_single = mpd_status_get_single(m_status.get());
m_elapsed_time = mpd_status_get_elapsed_time(m_status.get());
m_total_time = mpd_status_get_total_time(m_status.get());
}
void update(int event, mpdconnection* connection) {
if (connection == nullptr || (event & (MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS)) == false)
return;
fetch_data(connection);
m_elapsed_time_ms = m_elapsed_time * 1000;
auto state = mpd_status_get_state(m_status.get());
switch (state) {
case MPD_STATE_PAUSE:
m_state = mpdstate::PAUSED;
break;
case MPD_STATE_PLAY:
m_state = mpdstate::PLAYING;
break;
case MPD_STATE_STOP:
m_state = mpdstate::STOPPED;
break;
default:
m_state = mpdstate::UNKNOWN;
}
}
void update_timer() {
auto diff = chrono::system_clock::now() - m_updated_at;
auto dur = chrono::duration_cast<chrono::milliseconds>(diff);
m_elapsed_time_ms += dur.count();
m_elapsed_time = m_elapsed_time_ms / 1000 + 0.5f;
m_updated_at = chrono::system_clock::now();
}
bool random() const {
return m_random;
}
bool repeat() const {
return m_repeat;
}
bool single() const {
return m_single;
}
bool match_state(mpdstate state) const {
return state == m_state;
}
int get_songid() const {
return m_songid;
}
unsigned get_total_time() const {
return m_total_time;
}
unsigned get_elapsed_time() const {
return m_elapsed_time;
}
unsigned get_elapsed_percentage() {
if (m_total_time == 0)
return 0;
return static_cast<int>(float(m_elapsed_time) / float(m_total_time) * 100.0 + 0.5f);
}
string get_formatted_elapsed() {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%lu:%02lu", m_elapsed_time / 60, m_elapsed_time % 60);
return {buffer};
}
string get_formatted_total() {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%lu:%02lu", m_total_time / 60, m_total_time % 60);
return {buffer};
}
int get_seek_position(int percentage) {
if (m_total_time == 0)
return 0;
math_util::cap<int>(0, 100, percentage);
return float(m_total_time) * percentage / 100.0f + 0.5f;
}
private:
details::mpd_status_t m_status;
unique_ptr<mpdsong> m_song;
mpdstate m_state = mpdstate::UNKNOWN;
chrono::system_clock::time_point m_updated_at;
bool m_random = false;
bool m_repeat = false;
bool m_single = false;
int m_songid;
unsigned long m_total_time;
unsigned long m_elapsed_time;
unsigned long m_elapsed_time_ms;
};
// }}}
}
LEMONBUDDY_NS_END

433
include/adapters/net.hpp Normal file
View File

@ -0,0 +1,433 @@
#pragma once
#include <bitset>
#include <iomanip>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <iwlib.h>
#include <limits.h>
#include <linux/ethtool.h>
#include <linux/if_link.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <memory>
#include <sstream>
#include <string>
#include <string>
#ifdef inline
#undef inline
#endif
#include "common.hpp"
#include "config.hpp"
#include "utils/command.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
namespace net {
DEFINE_ERROR(network_error);
DEFINE_ERROR(wired_network_error);
DEFINE_ERROR(wireless_network_error);
// types {{{
struct bytes_t {
uint32_t transmitted = 0;
uint32_t received = 0;
std::chrono::system_clock::time_point time;
};
struct linkdata_t {
string ip_address;
bytes_t previous;
bytes_t current;
};
// }}}
// class: network {{{
class network {
public:
explicit network(string interface) : m_interface(interface) {
if (if_nametoindex(m_interface.c_str()) == 0)
throw network_error("Invalid network interface \"" + m_interface + "\"");
if ((m_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
throw network_error("Failed to open socket");
std::memset(&m_data, 0, sizeof(m_data));
std::strncpy(m_data.ifr_name, m_interface.data(), IFNAMSIZ - 1);
}
~network() {
if (m_fd != -1)
close(m_fd);
}
bool test_interface() {
if ((ioctl(m_fd, SIOCGIFFLAGS, &m_data)) == -1)
throw network_error("Failed to get flags");
if ((m_data.ifr_flags & IFF_UP) == 0)
return false;
if ((m_data.ifr_flags & IFF_RUNNING) == 0)
return false;
return true;
}
bool test_connection() {
int status = EXIT_FAILURE;
try {
m_ping = command_util::make_command(
"ping -c 2 -W 2 -I " + m_interface + " " + string(CONNECTION_TEST_IP));
status = m_ping->exec(true);
m_ping.reset();
} catch (std::exception& e) {
}
return (status == EXIT_SUCCESS);
}
bool test() {
try {
return test_interface() && test_connection();
} catch (network_error& e) {
return false;
}
}
bool connected() {
try {
if (!test_interface())
return false;
return file_util::get_contents("/sys/class/net/" + m_interface + "/carrier")[0] == '1';
} catch (network_error& e) {
return false;
}
}
bool query_interface() {
auto now = chrono::system_clock::now();
if ((now - m_last_query) < chrono::seconds(1))
return true;
m_last_query = now;
struct ifaddrs* ifaddr;
getifaddrs(&ifaddr);
bool match = false;
for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
if (m_interface.compare(0, m_interface.length(), ifa->ifa_name) != 0)
continue;
match = true;
switch (ifa->ifa_addr->sa_family) {
case AF_INET:
char ip_buffer[NI_MAXHOST];
getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), ip_buffer, NI_MAXHOST, nullptr, 0,
NI_NUMERICHOST);
m_linkdata.ip_address = string(ip_buffer);
break;
case AF_PACKET:
if (ifa->ifa_data == nullptr)
continue;
struct rtnl_link_stats* link_state =
reinterpret_cast<struct rtnl_link_stats*>(ifa->ifa_data);
m_linkdata.previous = m_linkdata.current;
m_linkdata.current.transmitted = link_state->tx_bytes;
m_linkdata.current.received = link_state->rx_bytes;
m_linkdata.current.time = chrono::system_clock::now();
break;
}
}
freeifaddrs(ifaddr);
return match;
}
string ip() {
if (!test_interface())
throw network_error("Interface is not up");
if (!query_interface())
throw network_error("Failed to query interface");
return m_linkdata.ip_address;
}
string downspeed() {
if (!query_interface())
throw network_error("Failed to query interface");
float bytes_diff = m_linkdata.current.received - m_linkdata.previous.received;
float time_diff =
chrono::duration_cast<chrono::seconds>(m_linkdata.current.time - m_linkdata.previous.time)
.count();
float speed = bytes_diff / time_diff;
speed /= 1000; // convert to KB
int suffix_n = 0;
vector<string> suffixes{"KB", "MB", "GB"};
while (speed >= 1000 && suffix_n < (int)suffixes.size() - 1) {
suffix_n++;
speed /= 1000;
}
return string_util::from_stream(stringstream() << std::setw(3) << std::setfill(' ')
<< std::setprecision(0) << std::fixed << speed
<< " " << suffixes[suffix_n] << "/s");
}
string upspeed() {
if (!query_interface())
throw network_error("Failed to query interface");
float bytes_diff = m_linkdata.current.transmitted - m_linkdata.previous.transmitted;
float time_diff =
chrono::duration_cast<chrono::seconds>(m_linkdata.current.time - m_linkdata.previous.time)
.count();
float speed = bytes_diff / time_diff;
speed /= 1000; // convert to KB
int suffix_n = 0;
vector<string> suffixes{"KB", "MB", "GB"};
while (speed >= 1000 && suffix_n < (int)suffixes.size() - 1) {
suffix_n++;
speed /= 1000;
}
return string_util::from_stream(stringstream() << std::setw(3) << std::setfill(' ')
<< std::setprecision(0) << std::fixed << speed
<< " " << suffixes[suffix_n] << "/s");
}
protected:
unique_ptr<command_util::command> m_ping;
string m_interface;
string m_ip;
struct ifreq m_data;
int m_fd = 0;
linkdata_t m_linkdata;
chrono::system_clock::time_point m_last_query;
};
// }}}
// class: wired_network {{{
class wired_network : public network {
public:
explicit wired_network(string interface) : network(interface) {
struct ethtool_cmd e;
e.cmd = ETHTOOL_GSET;
m_data.ifr_data = (caddr_t)&e;
if (ioctl(m_fd, SIOCETHTOOL, &m_data) == 0)
m_linkspeed = (e.speed == USHRT_MAX ? 0 : e.speed);
}
string link_speed() {
return string((m_linkspeed == 0 ? "???" : to_string(m_linkspeed)) + " Mbit/s");
}
private:
int m_linkspeed = 0;
};
// }}}
// class: wireless_network {{{
struct wireless_info {
std::bitset<5> flags;
string essid{IW_ESSID_MAX_SIZE + 1};
int quality = 0;
int quality_max = 0;
int quality_avg = 0;
int signal = 0;
int signal_max = 0;
int noise = 0;
int noise_max = 0;
int bitrate = 0;
double frequency = 0;
};
enum wireless_flags {
ESSID = 0,
QUALITY = 1,
SIGNAL = 2,
NOISE = 3,
FREQUENCY = 4,
};
class wireless_network : public network {
public:
wireless_network(string interface) : network(interface) {
std::strcpy((char*)&m_iw.ifr_ifrn.ifrn_name, m_interface.c_str());
if (!m_info)
m_info.reset(new wireless_info());
}
string essid() {
if (!query_interface())
return "";
if (!m_info->flags.test(wireless_flags::ESSID))
return "";
return m_info->essid;
}
float signal_quality() {
if (!query_interface())
return 0;
if (m_info->flags.test(wireless_flags::QUALITY))
return 2 * (signal_dbm() + 100);
return 0;
}
float signal_dbm() {
if (!query_interface())
return 0;
if (m_info->flags.test(wireless_flags::QUALITY))
return m_info->quality + m_info->noise - 256;
return 0;
}
protected:
bool query_interface() {
if ((chrono::system_clock::now() - m_last_query) < chrono::seconds(1))
return true;
network::query_interface();
auto ifname = m_interface.c_str();
auto socket_fd = iw_sockets_open();
if (socket_fd == -1)
return false;
auto on_exit = scope_util::make_exit_handler<>([&]() { iw_sockets_close(socket_fd); });
{
wireless_config wcfg;
if (iw_get_basic_config(socket_fd, ifname, &wcfg) == -1)
return false;
// reset flags
m_info->flags.none();
if (wcfg.has_essid && wcfg.essid_on) {
m_info->essid = {wcfg.essid, 0, IW_ESSID_MAX_SIZE};
m_info->flags |= wireless_flags::ESSID;
}
if (wcfg.has_freq) {
m_info->frequency = wcfg.freq;
m_info->flags |= wireless_flags::FREQUENCY;
}
if (wcfg.mode == IW_MODE_ADHOC)
return true;
iwrange range;
if (iw_get_range_info(socket_fd, ifname, &range) == -1)
return false;
iwstats stats;
if (iw_get_stats(socket_fd, ifname, &stats, &range, 1) == -1)
return false;
if (stats.qual.updated & IW_QUAL_RCPI) {
if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
m_info->quality = stats.qual.qual;
m_info->quality_max = range.max_qual.qual;
m_info->quality_avg = range.avg_qual.qual;
m_info->flags |= wireless_flags::QUALITY;
}
if (stats.qual.updated & IW_QUAL_RCPI) {
if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
m_info->signal = stats.qual.level / 2.0 - 110 + 0.5;
m_info->flags |= wireless_flags::SIGNAL;
}
if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
m_info->noise = stats.qual.noise / 2.0 - 110 + 0.5;
m_info->flags |= wireless_flags::NOISE;
}
} else {
if ((stats.qual.updated & IW_QUAL_DBM) || stats.qual.level > range.max_qual.level) {
if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
m_info->signal = stats.qual.level;
if (m_info->signal > 63)
m_info->signal -= 256;
m_info->flags |= wireless_flags::SIGNAL;
}
if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
m_info->noise = stats.qual.noise;
if (m_info->noise > 63)
m_info->noise -= 256;
m_info->flags |= wireless_flags::NOISE;
}
} else {
if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
m_info->signal = stats.qual.level;
m_info->signal_max = range.max_qual.level;
m_info->flags |= wireless_flags::SIGNAL;
}
if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
m_info->noise = stats.qual.noise;
m_info->noise_max = range.max_qual.noise;
m_info->flags |= wireless_flags::NOISE;
}
}
}
} else {
if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
m_info->quality = stats.qual.qual;
m_info->flags |= wireless_flags::QUALITY;
}
if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
m_info->quality = stats.qual.level;
m_info->flags |= wireless_flags::SIGNAL;
}
if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
m_info->quality = stats.qual.noise;
m_info->flags |= wireless_flags::NOISE;
}
}
// struct iwreq wrq;
// if (iw_get_ext(socket_fd, ifname, SIOCGIWRATE, &wrq) != -1)
// m_info->bitrate = wrq.u.bitrate.value;
return true;
}
}
private:
struct iwreq m_iw;
shared_ptr<wireless_info> m_info;
};
// }}}
inline bool is_wireless_interface(string ifname) {
return file_util::exists("/sys/class/net/" + ifname + "/wireless");
}
}
LEMONBUDDY_NS_END

View File

@ -1,102 +0,0 @@
#pragma once
#include <string>
#include <memory>
#include <vector>
#include <sstream>
#include "config.hpp"
#include "exception.hpp"
#include "utils/xcb.hpp"
DefineBaseException(ConfigurationError);
class Registry;
struct CompiledWithoutModuleSupport : public ConfigurationError
{
explicit CompiledWithoutModuleSupport(std::string module_name)
: ConfigurationError(std::string(APP_NAME) + " was not compiled with support for module \""+ module_name +"\"") {}
};
struct Font
{
std::string id;
int offset = 0;
Font(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::shared_ptr<xcb::monitor_t> monitor;
std::string wm_name;
std::string locale;
std::string background = "#ffffff";
std::string foreground = "#000000";
std::string linecolor = "#000000";
int width = 0;
int height = 0;
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::shared_ptr<Options> opts;
std::shared_ptr<Registry> registry;
void load(std::shared_ptr<Registry> registry);
std::string get_output();
std::string get_exec_line();
};
std::shared_ptr<Bar> get_bar();
std::shared_ptr<Options> bar_opts();

125
include/common.hpp Normal file
View File

@ -0,0 +1,125 @@
#pragma once
#ifdef DEBUG
#define BOOST_DI_CFG_DIAGNOSTICS_LEVEL 2
#endif
#include <atomic>
#include <boost/di.hpp>
#include <boost/optional.hpp>
#include <cassert>
#include <cerrno>
#include <chrono>
#include <cstring>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#define LEMONBUDDY_NS \
namespace lemonbuddy { \
inline namespace v2_0_0 {
#define LEMONBUDDY_NS_END \
} \
}
#define LEMONBUDDY_NS_PATH "lemonbuddy::v2_0_0"
#define PIPE_READ 0
#define PIPE_WRITE 1
#define LOG(m) std::cout << m << std::endl
#ifdef DEBUG
#include "debug.hpp"
#endif
LEMONBUDDY_NS
//==================================================
// Include common types (i.e, unclutter editor!)
//==================================================
namespace di = boost::di;
namespace chrono = std::chrono;
namespace this_thread = std::this_thread;
using namespace std::chrono_literals;
using std::string;
using std::stringstream;
using std::size_t;
using std::bind;
using std::forward;
using std::function;
using std::shared_ptr;
using std::unique_ptr;
using std::make_unique;
using std::make_shared;
using std::make_pair;
using std::array;
using std::map;
using std::vector;
using std::to_string;
using std::strerror;
using std::getenv;
using std::thread;
using boost::optional;
using stateflag = std::atomic<bool>;
//==================================================
// Errors and exceptions
//==================================================
namespace factory {
template <class InstanceType, class... Deps>
unique_ptr<InstanceType> generic_instance(Deps... deps) {
return make_unique<InstanceType>(deps...);
}
template <class InstanceType, class... Deps>
shared_ptr<InstanceType> generic_singleton(Deps... deps) {
static auto instance = make_shared<InstanceType>(deps...);
return instance;
}
}
//==================================================
// Errors and exceptions
//==================================================
class application_error : public std::runtime_error {
public:
int m_code;
explicit application_error(string&& message, int code = 0)
: std::runtime_error(forward<string>(message)), m_code(code) {}
};
class system_error : public application_error {
public:
explicit system_error() : application_error(strerror(errno), errno) {}
explicit system_error(string&& message)
: application_error(forward<string>(message) + " (reason: " + strerror(errno) + ")", errno) {}
};
#define DEFINE_CHILD_ERROR(error, parent) \
class error : public parent { \
using parent::parent; \
}
#define DEFINE_ERROR(error) DEFINE_CHILD_ERROR(error, application_error)
//==================================================
// Various tools and helpers functions
//==================================================
auto has_env = [](const char* var) { return getenv(var) != nullptr; };
auto read_env = [](const char* var, string&& fallback = "") {
const char* value{getenv(var)};
return value != nullptr ? value : fallback;
};
LEMONBUDDY_NS_END

767
include/components/bar.hpp Normal file
View File

@ -0,0 +1,767 @@
#pragma once
#include <xcb/xcb_icccm.h>
#include <mutex>
#include "common.hpp"
#include "components/config.hpp"
#include "components/logger.hpp"
#include "components/parser.hpp"
#include "components/types.hpp"
#include "components/x11/connection.hpp"
#include "components/x11/draw.hpp"
#include "components/x11/fontmanager.hpp"
#include "components/x11/randr.hpp"
#include "components/x11/tray.hpp"
#include "components/x11/types.hpp"
#include "components/x11/window.hpp"
#include "components/x11/xlib.hpp"
#include "components/x11/xutils.hpp"
#include "utils/math.hpp"
#include "utils/string.hpp"
#include "utils/threading.hpp"
LEMONBUDDY_NS
class bar {
public:
/**
* Construct bar
*/
explicit bar(connection& conn, const config& config, const logger& logger,
unique_ptr<fontmanager> fontmanager)
: m_connection(conn)
, m_conf(config)
, m_log(logger)
, m_fontmanager(forward<decltype(fontmanager)>(fontmanager)) {}
/**
* Cleanup signal handlers and destroy the bar window
*/
~bar() {
std::lock_guard<threading_util::spin_lock> lck(m_lock);
parser_signals::alignment_change.disconnect(this, &bar::on_alignment_change);
parser_signals::attribute_set.disconnect(this, &bar::on_attribute_set);
parser_signals::attribute_unset.disconnect(this, &bar::on_attribute_unset);
parser_signals::attribute_toggle.disconnect(this, &bar::on_attribute_toggle);
parser_signals::action_block_open.disconnect(this, &bar::on_action_block_open);
parser_signals::action_block_close.disconnect(this, &bar::on_action_block_close);
parser_signals::color_change.disconnect(this, &bar::on_color_change);
parser_signals::font_change.disconnect(this, &bar::on_font_change);
parser_signals::pixel_offset.disconnect(this, &bar::on_pixel_offset);
parser_signals::ascii_text_write.disconnect(this, &bar::draw_character);
parser_signals::unicode_text_write.disconnect(this, &bar::draw_character);
if (m_tray.align != alignment::NONE)
tray_signals::report_slotcount.disconnect(this, &bar::on_tray_report);
m_window.destroy();
}
/**
* Configure injection module
*/
template <typename T = unique_ptr<bar>>
static di::injector<T> configure() {
// clang-format off
return di::make_injector(
connection::configure(),
config::configure(),
logger::configure(),
fontmanager::configure());
// clang-format on
}
/**
* Create required components
*
* This is done outside the constructor due to boost::di noexcept
*/
void bootstrap(bool nodraw = false) { //{{{
m_screen = m_connection.screen();
m_visual = m_connection.visual_type(m_screen, 32).get();
auto monitors = randr_util::get_monitors(m_connection, m_connection.screen()->root);
auto bs = m_conf.bar_section();
// Look for the defined monitor {{{
if (monitors.empty())
throw application_error("No monitors found");
auto monitor_name = m_conf.get<std::string>(bs, "monitor", "");
if (monitor_name.empty())
monitor_name = monitors[0]->name;
for (auto&& monitor : monitors) {
if (monitor_name.compare(monitor->name) == 0) {
m_bar.monitor = std::move(monitor);
break;
}
}
if (m_bar.monitor)
m_log.trace("bar: Found matching monitor %s (%ix%i+%i+%i)", m_bar.monitor->name,
m_bar.monitor->w, m_bar.monitor->h, m_bar.monitor->x, m_bar.monitor->y);
else
throw application_error("Could not find monitor: " + monitor_name);
// }}}
// Set bar colors {{{
m_bar.background = color::parse(m_conf.get<string>(bs, "background", m_bar.background.hex()));
m_bar.foreground = color::parse(m_conf.get<string>(bs, "foreground", m_bar.foreground.hex()));
m_bar.linecolor = color::parse(m_conf.get<string>(bs, "linecolor", m_bar.linecolor.hex()));
// }}}
// Set border values {{{
auto bsize = m_conf.get<int>(bs, "border-size", 0);
auto bcolor = m_conf.get<string>(bs, "border-color", "");
m_borders.emplace(border::TOP, border_settings{});
m_borders[border::TOP].size = m_conf.get<int>(bs, "border-top", bsize);
m_borders[border::TOP].color = color::parse(m_conf.get<string>(bs, "border-top-color", bcolor));
m_borders.emplace(border::BOTTOM, border_settings{});
m_borders[border::BOTTOM].size = m_conf.get<int>(bs, "border-bottom", bsize);
m_borders[border::BOTTOM].color =
color::parse(m_conf.get<string>(bs, "border-bottom-color", bcolor));
m_borders.emplace(border::LEFT, border_settings{});
m_borders[border::LEFT].size = m_conf.get<int>(bs, "border-left", bsize);
m_borders[border::LEFT].color =
color::parse(m_conf.get<string>(bs, "border-left-color", bcolor));
m_borders.emplace(border::RIGHT, border_settings{});
m_borders[border::RIGHT].size = m_conf.get<int>(bs, "border-right", bsize);
m_borders[border::RIGHT].color =
color::parse(m_conf.get<string>(bs, "border-right-color", bcolor));
// }}}
// Set size and position {{{
m_bar.dock = m_conf.get<decltype(m_bar.dock)>(bs, "dock", true);
m_bar.bottom = m_conf.get<decltype(m_bar.bottom)>(bs, "bottom", false);
m_bar.lineheight = m_conf.get<decltype(m_bar.lineheight)>(bs, "lineheight", 0);
m_bar.offset_x = m_conf.get<decltype(m_bar.offset_x)>(bs, "offset_x", 0);
m_bar.offset_y = m_conf.get<decltype(m_bar.offset_y)>(bs, "offset_y", 0);
m_bar.padding_left = m_conf.get<decltype(m_bar.padding_left)>(bs, "padding_left", 0);
m_bar.padding_right = m_conf.get<decltype(m_bar.padding_right)>(bs, "padding_right", 0);
m_bar.module_margin_left =
m_conf.get<decltype(m_bar.module_margin_left)>(bs, "module_margin_left", 0);
m_bar.module_margin_right =
m_conf.get<decltype(m_bar.module_margin_right)>(bs, "module_margin_right", 0);
auto w = m_conf.get<string>(bs, "width", "100%");
auto h = m_conf.get<string>(bs, "height", "24");
m_bar.width = std::atoi(w.c_str());
if (w.find("%") != string::npos)
m_bar.width = m_bar.monitor->w * (m_bar.width / 100.0) + 0.5f;
m_bar.height = std::atoi(h.c_str());
if (h.find("%") != string::npos)
m_bar.height = m_bar.monitor->h * (m_bar.height / 100.0) + 0.5f;
// apply offsets
m_bar.width -= m_bar.offset_x * 2;
m_bar.x = m_bar.offset_x + m_bar.monitor->x;
m_bar.y = m_bar.offset_y + m_bar.monitor->y;
// apply borders
m_bar.height += m_borders[border::TOP].size;
m_bar.height += m_borders[border::BOTTOM].size;
if (m_bar.bottom)
m_bar.y = m_bar.monitor->y + m_bar.monitor->h - m_bar.height - m_bar.offset_y;
if (m_bar.width <= 0 || m_bar.width > m_bar.monitor->w)
throw application_error("Resulting bar width is out of bounds");
if (m_bar.height <= 0 || m_bar.height > m_bar.monitor->h)
throw application_error("Resulting bar height is out of bounds");
m_bar.width = math_util::cap<int>(m_bar.width, 0, m_bar.monitor->w);
m_bar.height = math_util::cap<int>(m_bar.height, 0, m_bar.monitor->h);
m_bar.vertical_mid =
(m_bar.height + m_borders[border::TOP].size - m_borders[border::BOTTOM].size) / 2;
m_log.trace("bar: Resulting bar geom %ix%i+%i+%i", m_bar.width, m_bar.height, m_bar.x, m_bar.y);
// }}}
// Set the WM_NAME value {{{
m_bar.wmname = "lemonbuddy-" + bs.substr(4) + "_" + m_bar.monitor->name;
m_bar.wmname = m_conf.get<string>(bs, "wm_name", m_bar.wmname);
m_bar.wmname = string_util::replace(m_bar.wmname, " ", "-");
// }}}
// Checking nodraw {{{
if (nodraw) {
m_log.trace("bar: Abort bootstrap routine (reason: nodraw)");
return;
}
// }}}
// Setup graphic components and create the window {{{
m_log.trace("bar: Create colormap");
{
m_connection.create_colormap_checked(
XCB_COLORMAP_ALLOC_NONE, m_colormap, m_screen->root, m_visual->visual_id);
}
m_log.trace("bar: Create window %s", m_connection.id(m_window));
{
uint32_t mask = 0;
xcb_params_cw_t params;
// clang-format off
XCB_AUX_ADD_PARAM(&mask, &params, back_pixel, m_bar.background.value());
XCB_AUX_ADD_PARAM(&mask, &params, border_pixel, m_bar.background.value());
XCB_AUX_ADD_PARAM(&mask, &params, colormap, m_colormap);
XCB_AUX_ADD_PARAM(&mask, &params, override_redirect, m_bar.dock);
XCB_AUX_ADD_PARAM(&mask, &params, event_mask, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS);
// clang-format on
m_window.create_checked(m_bar.x, m_bar.y, m_bar.width, m_bar.height, mask, &params);
m_window.map_checked();
}
m_log.trace("bar: Set WM_NAME");
{
xcb_icccm_set_wm_name(
m_connection, m_window, XCB_ATOM_STRING, 8, m_bar.wmname.length(), m_bar.wmname.c_str());
xcb_icccm_set_wm_class(m_connection, m_window, 21, "lemonbuddy\0Lemonbuddy");
}
m_log.trace("bar: Set _NET_WM_WINDOW_TYPE");
{
xcb_atom_t win_types[2] = {_NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_NORMAL};
m_connection.change_property_checked(
XCB_PROP_MODE_REPLACE, m_window, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, 2, &win_types);
}
m_log.trace("bar: Set _NET_WM_STATE");
{
xcb_atom_t win_states[2] = {_NET_WM_STATE_STICKY, _NET_WM_STATE_SKIP_TASKBAR};
m_connection.change_property_checked(
XCB_PROP_MODE_REPLACE, m_window, _NET_WM_STATE, XCB_ATOM_ATOM, 32, 2, &win_states);
}
m_log.trace("bar: Set _NET_WM_PID");
{
int pid = getpid();
m_connection.change_property_checked(
XCB_PROP_MODE_REPLACE, m_window, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
}
m_log.trace("bar: Create pixmap");
{
m_connection.create_pixmap_checked(
m_visual->visual_id == m_screen->root_visual ? XCB_COPY_FROM_PARENT : 32, m_pixmap,
m_window, m_bar.width, m_bar.height);
}
// }}}
// Create graphic contexts {{{
// XCB_GC_LINE_WIDTH
// XCB_GC_LINE_STYLE
// -- XCB_LINE_STYLE_SOLID
// xcb_poly_line (connection, XCB_COORD_MODE_PREVIOUS, window, foreground, 4, polyline);
// xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, (xcb_point_t[]) { {10, 10}, {100,
// 10} });
m_log.trace("bar: Create graphic contexts");
{
// clang-format off
vector<uint32_t> colors {
m_bar.background.value(),
m_bar.foreground.value(),
m_bar.linecolor.value(),
m_bar.linecolor.value(),
m_borders[border::TOP].color.value(),
m_borders[border::BOTTOM].color.value(),
m_borders[border::LEFT].color.value(),
m_borders[border::RIGHT].color.value(),
};
// clang-format on
for (int i = 1; i <= 8; i++) {
uint32_t mask = 0;
uint32_t value_list[32];
xcb_params_gc_t params;
XCB_AUX_ADD_PARAM(&mask, &params, foreground, colors[i - 1]);
XCB_AUX_ADD_PARAM(&mask, &params, graphics_exposures, 0);
xutils::pack_values(mask, &params, value_list);
m_gcontexts.emplace(gc(i), gcontext{m_connection, m_connection.generate_id()});
m_connection.create_gc_checked(m_gcontexts.at(gc(i)), m_pixmap, mask, &value_list);
}
}
// }}}
// Load fonts {{{
auto fonts_loaded = false;
auto fontindex = 0;
auto fonts = m_conf.get_list<string>(bs, "font");
for (auto f : fonts) {
fontindex++;
vector<string> fd = string_util::split(f, ';');
string pattern{fd[0]};
int offset{0};
if (fd.size() > 1)
offset = std::stoi(fd[1], 0, 10);
if (m_fontmanager->load(pattern, fontindex, offset))
fonts_loaded = true;
else
m_log.warn("Unable to load font '%s'", fd[0]);
}
if (!fonts_loaded) {
m_log.warn("Loading fallback font");
if (!m_fontmanager->load("fixed"))
throw application_error("Unable to load fonts");
}
m_fontmanager->allocate_color(m_bar.foreground);
// }}}
// Set tray settings {{{
try {
auto tray_position = m_conf.get<string>(bs, "tray-position");
if (tray_position == "left")
m_tray.align = alignment::LEFT;
else if (tray_position == "right")
m_tray.align = alignment::RIGHT;
else
m_tray.align = alignment::NONE;
} catch (const key_error& err) {
m_tray.align = alignment::NONE;
}
if (m_tray.align != alignment::NONE) {
m_tray.background = m_bar.background.value();
m_tray.height = m_bar.height;
m_tray.height -= m_borders.at(border::BOTTOM).size;
m_tray.height -= m_borders.at(border::TOP).size;
if (m_tray.height % 2 != 0) {
m_tray.height--;
}
if (m_tray.height > 24) {
m_tray.spacing = (m_tray.height - 24) / 2;
m_tray.height = 24;
}
m_tray.width = m_tray.height;
m_tray.orig_y = m_bar.y + m_borders.at(border::TOP).size;
if (m_tray.align == alignment::RIGHT)
m_tray.orig_x = m_bar.x + m_bar.width - m_borders.at(border::RIGHT).size;
else
m_tray.orig_x = m_bar.x + m_borders.at(border::LEFT).size;
}
// }}}
// Connect signal handlers {{{
parser_signals::alignment_change.connect(this, &bar::on_alignment_change);
parser_signals::attribute_set.connect(this, &bar::on_attribute_set);
parser_signals::attribute_unset.connect(this, &bar::on_attribute_unset);
parser_signals::attribute_toggle.connect(this, &bar::on_attribute_toggle);
parser_signals::action_block_open.connect(this, &bar::on_action_block_open);
parser_signals::action_block_close.connect(this, &bar::on_action_block_close);
parser_signals::color_change.connect(this, &bar::on_color_change);
parser_signals::font_change.connect(this, &bar::on_font_change);
parser_signals::pixel_offset.connect(this, &bar::on_pixel_offset);
parser_signals::ascii_text_write.connect(this, &bar::draw_character);
parser_signals::unicode_text_write.connect(this, &bar::draw_character);
if (m_tray.align != alignment::NONE)
tray_signals::report_slotcount.connect(this, &bar::on_tray_report);
// }}}
m_connection.flush();
} //}}}
/**
* Parse input string and redraw the bar window
*
* @param data Input string
* @param force Unless true, do not parse unchanged data
*/
void parse(string data, bool force = false) { //{{{
std::lock_guard<threading_util::spin_lock> lck(m_lock);
{
if (data == m_prevdata && !force)
return;
m_prevdata = data;
// TODO: move to fontmanager
m_xftdraw = XftDrawCreate(xlib::get_display(), m_pixmap, xlib::get_visual(), m_colormap);
m_bar.align = alignment::LEFT;
m_xpos = m_borders[border::LEFT].size;
m_attributes = 0;
m_actions.clear();
draw_background();
if (m_tray.align == alignment::LEFT && m_tray.slots)
m_xpos += ((m_tray.width + m_tray.spacing) * m_tray.slots) + m_tray.spacing;
try {
parser parser(m_bar);
parser(data);
} catch (const unrecognized_token& err) {
m_log.err("Unrecognized syntax token '%s'", err.what());
}
if (m_tray.align == alignment::RIGHT && m_tray.slots)
draw_shift(m_xpos, ((m_tray.width + m_tray.spacing) * m_tray.slots) + m_tray.spacing);
draw_border(border::ALL);
flush();
XftDrawDestroy(m_xftdraw);
}
} //}}}
/**
* Copy the contents of the pixmap's onto the bar window
*/
void flush() { //{{{
m_connection.copy_area(
m_pixmap, m_window, m_gcontexts.at(gc::FG), 0, 0, 0, 0, m_bar.width, m_bar.height);
m_connection.copy_area(
m_pixmap, m_window, m_gcontexts.at(gc::BT), 0, 0, 0, 0, m_bar.width, m_bar.height);
m_connection.copy_area(
m_pixmap, m_window, m_gcontexts.at(gc::BB), 0, 0, 0, 0, m_bar.width, m_bar.height);
m_connection.copy_area(
m_pixmap, m_window, m_gcontexts.at(gc::BL), 0, 0, 0, 0, m_bar.width, m_bar.height);
m_connection.copy_area(
m_pixmap, m_window, m_gcontexts.at(gc::BR), 0, 0, 0, 0, m_bar.width, m_bar.height);
} //}}}
/**
* Get the bar settings container
*/
const bar_settings settings() const { // {{{
return m_bar;
} // }}}
/**
* Get the tray settings container
*/
const tray_settings tray() const { // {{{
return m_tray;
} // }}}
protected:
/**
* Handle alignment update
*/
void on_alignment_change(alignment align) { //{{{
if (align == m_bar.align)
return;
m_log.trace("bar: alignment_change(%i)", static_cast<int>(align));
m_bar.align = align;
m_xpos = 0;
} //}}}
/**
* Handle attribute on state
*/
void on_attribute_set(attribute attr) { //{{{
int val{static_cast<int>(attr)};
if ((m_attributes & val) != 0)
return;
m_log.trace("bar: attribute_set(%i)", val);
m_attributes |= val;
} //}}}
/**
* Handle attribute off state
*/
void on_attribute_unset(attribute attr) { //{{{
int val{static_cast<int>(attr)};
if ((m_attributes & val) == 0)
return;
m_log.trace("bar: attribute_unset(%i)", val);
m_attributes ^= val;
} //}}}
/**
* Handle attribute toggle state
*/
void on_attribute_toggle(attribute attr) { //{{{
int val{static_cast<int>(attr)};
m_log.trace("bar: attribute_toggle(%i)", val);
m_attributes ^= val;
} //}}}
/**
* Handle action block start
*/
void on_action_block_open(mousebtn btn, string cmd) { //{{{
if (btn == mousebtn::NONE)
btn = mousebtn::LEFT;
m_log.trace("bar: action_block_open(%i, %s)", static_cast<int>(btn), cmd);
action_block action;
action.active = true;
action.mousebtn = btn;
action.start_x = m_xpos;
action.command = cmd;
action.command = string_util::replace_all(action.command, ":", "\\:");
m_actions.emplace_back(action);
} //}}}
/**
* Handle action block end
*/
void on_action_block_close(mousebtn btn) { //{{{
m_log.trace("bar: action_block_close(%i)", static_cast<int>(btn));
auto n_actions = m_actions.size();
while (n_actions--) {
action_block& action = m_actions[n_actions];
if (!action.active || action.mousebtn != btn)
continue;
action.end_x = m_xpos;
action.active = false;
}
} //}}}
/**
* Handle color change
*/
void on_color_change(gc gc_, color color_) { //{{{
m_log.trace(
"bar: color_change(%i, %s -> %s)", static_cast<int>(gc_), color_.hex(), color_.rgb());
const uint32_t value_list[32]{color_.value()};
m_connection.change_gc(m_gcontexts.at(gc_), XCB_GC_FOREGROUND, &value_list);
if (gc_ == gc::FG)
m_fontmanager->allocate_color(color_);
} //}}}
/**
* Handle font change
*/
void on_font_change(int index) { //{{{
m_log.trace("bar: font_change(%i)", index);
m_fontmanager->set_preferred_font(index);
} //}}}
/**
* Handle pixel offsetting
*/
void on_pixel_offset(int px) { //{{{
m_log.trace("bar: pixel_offset(%i)", px);
draw_shift(m_xpos, px);
m_xpos += px;
} //}}}
/**
* Proess systray report
*/
void on_tray_report(uint16_t slots) { // {{{
if (m_tray.slots == slots)
return;
m_log.trace("bar: tray_report(%lu)", slots);
m_tray.slots = slots;
if (!m_prevdata.empty())
parse(m_prevdata, true);
} // }}}
/**
* Draw background onto the pixmap
*/
void draw_background() { //{{{
draw_util::fill(
m_connection, m_pixmap, m_gcontexts.at(gc::BG), 0, 0, m_bar.width, m_bar.height);
} //}}}
/**
* Draw borders onto the pixmap
*/
void draw_border(border border_) { //{{{
switch (border_) {
case border::NONE:
break;
case border::TOP:
if (m_borders[border::TOP].size > 0) {
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BT),
m_borders[border::LEFT].size, 0,
m_bar.width - m_borders[border::LEFT].size - m_borders[border::RIGHT].size,
m_borders[border::TOP].size);
}
break;
case border::BOTTOM:
if (m_borders[border::BOTTOM].size > 0) {
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BB),
m_borders[border::LEFT].size, m_bar.height - m_borders[border::BOTTOM].size,
m_bar.width - m_borders[border::LEFT].size - m_borders[border::RIGHT].size,
m_borders[border::BOTTOM].size);
}
break;
case border::LEFT:
if (m_borders[border::LEFT].size > 0) {
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BL), 0, 0,
m_borders[border::LEFT].size, m_bar.height);
}
break;
case border::RIGHT:
if (m_borders[border::RIGHT].size > 0) {
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BR),
m_bar.width - m_borders[border::RIGHT].size, 0, m_borders[border::RIGHT].size,
m_bar.height);
}
break;
case border::ALL:
draw_border(border::TOP);
draw_border(border::BOTTOM);
draw_border(border::LEFT);
draw_border(border::RIGHT);
break;
}
} //}}}
/**
* Draw over- and underline onto the pixmap
*/
void draw_lines(int x, int w) { //{{{
if (!m_bar.lineheight)
return;
if (m_attributes & static_cast<int>(attribute::o))
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::OL), x,
m_borders[border::TOP].size, w, m_bar.lineheight);
if (m_attributes & static_cast<int>(attribute::u))
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::UL), x,
m_bar.height - m_borders[border::BOTTOM].size - m_bar.lineheight, w, m_bar.lineheight);
} //}}}
/**
* Shift the contents of the pixmap horizontally
*/
int draw_shift(int x, int chr_width) { //{{{
int delta = 0;
switch (m_bar.align) {
case alignment::CENTER:
m_connection.copy_area(m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), m_bar.width / 2 - x / 2,
0, m_bar.width / 2 - (x + chr_width) / 2, 0, x, m_bar.height);
x = m_bar.width / 2 - (x + chr_width) / 2 + x;
delta = chr_width / 2;
break;
case alignment::RIGHT:
m_connection.copy_area(m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), m_bar.width - x, 0,
m_bar.width - x - chr_width, 0, x, m_bar.height);
x = m_bar.width - chr_width - m_borders[border::RIGHT].size;
delta = chr_width;
break;
default:
break;
}
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BG), x, 0, chr_width, m_bar.height);
if (delta != 0 && !m_actions.empty()) {
for (auto&& action : m_actions) {
if (action.active || action.align != m_bar.align)
continue;
action.start_x -= delta;
action.end_x -= delta;
}
}
return x;
} //}}}
/**
* Draw text contents
*/
void draw_character(uint16_t character) { // {{{
// TODO: cache
auto& font = m_fontmanager->match_char(character);
if (!font) {
// m_log.warn("No suitable font found");
return;
}
if (font->ptr && font->ptr != m_gcfont) {
m_gcfont = font->ptr;
m_fontmanager->set_gcontext_font(m_gcontexts.at(gc::FG), m_gcfont);
}
// TODO: cache
auto chr_width = m_fontmanager->char_width(font, character);
auto x = draw_shift(m_xpos, chr_width);
auto y = m_bar.vertical_mid + font->height / 2 - font->descent + font->offset_y;
// m_log.trace("Draw char(%c, width: %i) at pos(%i,%i)", character, chr_width, x, y);
if (font->xft != nullptr) {
auto color = m_fontmanager->xftcolor();
XftDrawString16(m_xftdraw, &color, font->xft, x, y, &character, 1);
} else {
character = (character >> 8) | (character << 8);
draw_util::xcb_poly_text_16_patched(
m_connection, m_pixmap, m_gcontexts.at(gc::FG), x, y, 1, &character);
}
draw_lines(x, chr_width);
m_xpos += chr_width;
} // }}}
private:
connection& m_connection;
const config& m_conf;
const logger& m_log;
unique_ptr<fontmanager> m_fontmanager;
threading_util::spin_lock m_lock;
xcb_screen_t* m_screen;
xcb_visualtype_t* m_visual;
window m_window{m_connection};
colormap m_colormap{m_connection, m_connection.generate_id()};
pixmap m_pixmap{m_connection, m_connection.generate_id()};
bar_settings m_bar;
tray_settings m_tray;
map<border, border_settings> m_borders;
map<gc, gcontext> m_gcontexts;
vector<action_block> m_actions;
string m_prevdata;
int m_xpos{0};
int m_attributes{0};
xcb_font_t m_gcfont{0};
XftDraw* m_xftdraw;
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,463 @@
#pragma once
#include "common.hpp"
#include "components/config.hpp"
#include "components/types.hpp"
#include "config.hpp"
#include "drawtypes/label.hpp"
#include "utils/math.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
#define DEFAULT_SPACING -1
#ifndef BUILDER_SPACE_TOKEN
#define BUILDER_SPACE_TOKEN "%__"
#endif
using namespace drawtypes;
class builder {
public:
explicit builder(const bar_settings bar, bool lazy = true) : m_bar(bar), m_lazy(lazy) {}
void set_lazy(bool mode) {
m_lazy = mode;
}
string flush() {
if (m_lazy) {
while (m_counters[syntaxtag::A] > 0) cmd_close(true);
while (m_counters[syntaxtag::B] > 0) background_close(true);
while (m_counters[syntaxtag::F] > 0) color_close(true);
while (m_counters[syntaxtag::T] > 0) font_close(true);
while (m_counters[syntaxtag::U] > 0) line_color_close(true);
while (m_counters[syntaxtag::u] > 0) underline_close(true);
while (m_counters[syntaxtag::o] > 0) overline_close(true);
}
string output = m_output.data();
// reset values
m_output.clear();
for (auto& counter : m_counters) counter.second = 0;
for (auto& value : m_colors) value.second = "";
m_fontindex = 1;
return string_util::replace_all(output, string{BUILDER_SPACE_TOKEN}, " ");
}
void append(string text) {
string str(text);
auto len = str.length();
if (len > 2 && str[0] == '"' && str[len - 1] == '"')
m_output += str.substr(1, len - 2);
else
m_output += str;
}
void node(string str, bool add_space = false) {
string::size_type n, m;
string s(str);
while (true) {
if (s.empty()) {
break;
} else if ((n = s.find("%{F-}")) == 0) {
color_close(!m_lazy);
s.erase(0, 5);
} else if ((n = s.find("%{F#")) == 0 && (m = s.find("}")) != string::npos) {
if (m - n - 4 == 2)
color_alpha(s.substr(n + 3, m - 3));
else
color(s.substr(n + 3, m - 3));
s.erase(n, m + 1);
} else if ((n = s.find("%{B-}")) == 0) {
background_close(!m_lazy);
s.erase(0, 5);
} else if ((n = s.find("%{B#")) == 0 && (m = s.find("}")) != string::npos) {
background(s.substr(n + 3, m - 3));
s.erase(n, m + 1);
} else if ((n = s.find("%{T-}")) == 0) {
font_close(!m_lazy);
s.erase(0, 5);
} else if ((n = s.find("%{T")) == 0 && (m = s.find("}")) != string::npos) {
font(std::atoi(s.substr(n + 3, m - 3).c_str()));
s.erase(n, m + 1);
} else if ((n = s.find("%{U-}")) == 0) {
line_color_close(!m_lazy);
s.erase(0, 5);
} else if ((n = s.find("%{U#")) == 0 && (m = s.find("}")) != string::npos) {
line_color(s.substr(n + 3, m - 3));
s.erase(n, m + 1);
} else if ((n = s.find("%{+u}")) == 0) {
underline();
s.erase(0, 5);
} else if ((n = s.find("%{+o}")) == 0) {
overline();
s.erase(0, 5);
} else if ((n = s.find("%{-u}")) == 0) {
underline_close(true);
s.erase(0, 5);
} else if ((n = s.find("%{-o}")) == 0) {
overline_close(true);
s.erase(0, 5);
} else if ((n = s.find("%{A}")) == 0) {
cmd_close(true);
s.erase(0, 4);
} else if ((n = s.find("%{")) == 0 && (m = s.find("}")) != string::npos) {
append(s.substr(n, m + 1));
s.erase(n, m + 1);
} else if ((n = s.find("%{")) > 0) {
append(s.substr(0, n));
s.erase(0, n);
} else
break;
}
if (!s.empty())
append(s);
if (add_space)
space();
}
void node(string str, int font_index, bool add_space = false) {
font(font_index);
node(str, add_space);
font_close();
}
// void node(progressbar_t bar, float perc, bool add_space = false) {
// if (!bar)
// return;
// node(bar->get_output(math_util::cap<float>(0, 100, perc)), add_space);
// }
void node(label_t label, bool add_space = false) {
if (!label || !*label)
return;
auto text = label->m_text;
if (label->m_maxlen > 0 && text.length() > label->m_maxlen) {
text = text.substr(0, label->m_maxlen) + "...";
}
if ((label->m_overline.empty() && m_counters[syntaxtag::o] > 0) ||
(m_counters[syntaxtag::o] > 0 && label->m_margin > 0))
overline_close(true);
if ((label->m_underline.empty() && m_counters[syntaxtag::u] > 0) ||
(m_counters[syntaxtag::u] > 0 && label->m_margin > 0))
underline_close(true);
if (label->m_margin > 0)
space(label->m_margin);
if (!label->m_overline.empty())
overline(label->m_overline);
if (!label->m_underline.empty())
underline(label->m_underline);
background(label->m_background);
color(label->m_foreground);
if (label->m_padding > 0)
space(label->m_padding);
node(text, label->m_font, add_space);
if (label->m_padding > 0)
space(label->m_padding);
color_close(m_lazy && label->m_margin > 0);
background_close(m_lazy && label->m_margin > 0);
if (!label->m_underline.empty() || (label->m_margin > 0 && m_counters[syntaxtag::u] > 0))
underline_close(m_lazy && label->m_margin > 0);
if (!label->m_overline.empty() || (label->m_margin > 0 && m_counters[syntaxtag::o] > 0))
overline_close(m_lazy && label->m_margin > 0);
if (label->m_margin > 0)
space(label->m_margin);
}
// void node(ramp_t ramp, float perc, bool add_space = false) {
// if (!ramp)
// return;
// node(ramp->get_by_percentage(math_util::cap<float>(0, 100, perc)), add_space);
// }
// void node(animation_t animation, bool add_space = false) {
// if (!animation)
// return;
// node(animation->get(), add_space);
// }
void offset(int pixels = 0) {
if (pixels != 0)
tag_open('O', std::to_string(pixels));
}
void space(int width = DEFAULT_SPACING) {
if (width == DEFAULT_SPACING)
width = m_bar.spacing;
if (width <= 0)
return;
string str(width, ' ');
append(str);
}
void remove_trailing_space(int width = DEFAULT_SPACING) {
if (width == DEFAULT_SPACING)
width = m_bar.spacing;
if (width <= 0)
return;
string::size_type spacing = width;
string str(spacing, ' ');
if (m_output.length() >= spacing && m_output.substr(m_output.length() - spacing) == str)
m_output = m_output.substr(0, m_output.length() - spacing);
}
void invert() {
tag_open('R', "");
}
void font(int index) {
if (index <= 0 && m_counters[syntaxtag::T] > 0)
font_close(true);
if (index <= 0 || index == m_fontindex)
return;
if (m_lazy && m_counters[syntaxtag::T] > 0)
font_close(true);
m_counters[syntaxtag::T]++;
m_fontindex = index;
tag_open('T', std::to_string(index));
}
void font_close(bool force = false) {
if ((!force && m_lazy) || m_counters[syntaxtag::T] <= 0)
return;
m_counters[syntaxtag::T]--;
m_fontindex = 1;
tag_close('T');
}
void background(string color) {
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
color = "#" + color.substr(color.length() - 2);
auto bg = m_bar.background.hex();
color += bg.substr(bg.length() - (bg.length() < 6 ? 3 : 6));
} else if (color.length() >= 7 && color == "#" + string(color.length() - 1, color[1])) {
color = color.substr(0, 4);
}
if (color.empty() && m_counters[syntaxtag::B] > 0)
background_close(true);
if (color.empty() || color == m_colors[syntaxtag::B])
return;
if (m_lazy && m_counters[syntaxtag::B] > 0)
background_close(true);
m_counters[syntaxtag::B]++;
m_colors[syntaxtag::B] = color;
tag_open('B', color);
}
void background_close(bool force = false) {
if ((!force && m_lazy) || m_counters[syntaxtag::B] <= 0)
return;
m_counters[syntaxtag::B]--;
m_colors[syntaxtag::B] = "";
tag_close('B');
}
void color(string color_) {
auto color(color_);
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
color = "#" + color.substr(color.length() - 2);
auto bg = m_bar.foreground.hex();
color += bg.substr(bg.length() - (bg.length() < 6 ? 3 : 6));
} else if (color.length() >= 7 && color == "#" + string(color.length() - 1, color[1])) {
color = color.substr(0, 4);
}
if (color.empty() && m_counters[syntaxtag::F] > 0)
color_close(true);
if (color.empty() || color == m_colors[syntaxtag::F])
return;
if (m_lazy && m_counters[syntaxtag::F] > 0)
color_close(true);
m_counters[syntaxtag::F]++;
m_colors[syntaxtag::F] = color;
tag_open('F', color);
}
void color_alpha(string alpha_) {
auto alpha(alpha_);
string val = m_bar.foreground.hex();
if (alpha.find("#") == std::string::npos) {
alpha = "#" + alpha;
}
if (alpha.size() == 4) {
color(alpha);
return;
}
if (val.size() < 6 && val.size() > 2) {
val.append(val.substr(val.size() - 3));
}
color((alpha.substr(0, 3) + val.substr(val.size() - 6)).substr(0, 9));
}
void color_close(bool force = false) {
if ((!force && m_lazy) || m_counters[syntaxtag::F] <= 0)
return;
m_counters[syntaxtag::F]--;
m_colors[syntaxtag::F] = "";
tag_close('F');
}
void line_color(string color) {
if (color.empty() && m_counters[syntaxtag::U] > 0)
line_color_close(true);
if (color.empty() || color == m_colors[syntaxtag::U])
return;
if (m_lazy && m_counters[syntaxtag::U] > 0)
line_color_close(true);
m_counters[syntaxtag::U]++;
m_colors[syntaxtag::U] = color;
tag_open('U', color);
}
void line_color_close(bool force = false) {
if ((!force && m_lazy) || m_counters[syntaxtag::U] <= 0)
return;
m_counters[syntaxtag::U]--;
m_colors[syntaxtag::U] = "";
tag_close('U');
}
void overline(string color = "") {
if (!color.empty())
line_color(color);
if (m_counters[syntaxtag::o] > 0)
return;
m_counters[syntaxtag::o]++;
append("%{+o}");
}
void overline_close(bool force = false) {
if ((!force && m_lazy) || m_counters[syntaxtag::o] <= 0)
return;
m_counters[syntaxtag::o]--;
append("%{-o}");
}
void underline(string color = "") {
if (!color.empty())
line_color(color);
if (m_counters[syntaxtag::u] > 0)
return;
m_counters[syntaxtag::u]++;
append("%{+u}");
}
void underline_close(bool force = false) {
if ((!force && m_lazy) || m_counters[syntaxtag::u] <= 0)
return;
m_counters[syntaxtag::u]--;
append("%{-u}");
}
void cmd(mousebtn index, string action, bool condition = true) {
int button = static_cast<int>(index);
if (!condition || action.empty())
return;
action = string_util::replace_all(action, ":", "\\:");
action = string_util::replace_all(action, "$", "\\$");
action = string_util::replace_all(action, "}", "\\}");
action = string_util::replace_all(action, "{", "\\{");
action = string_util::replace_all(action, "%", "\x0025");
append("%{A" + std::to_string(button) + ":" + action + ":}");
m_counters[syntaxtag::A]++;
}
void cmd_close(bool force = false) {
if (m_counters[syntaxtag::A] > 0 || force)
append("%{A}");
if (m_counters[syntaxtag::A] > 0)
m_counters[syntaxtag::A]--;
}
protected:
void tag_open(char tag, string value) {
append("%{" + string({tag}) + value + "}");
}
void tag_close(char tag) {
append("%{" + string({tag}) + "-}");
}
private:
const bar_settings m_bar;
string m_output;
bool m_lazy = true;
map<syntaxtag, int> m_counters{
// clang-format off
{syntaxtag::A, 0},
{syntaxtag::B, 0},
{syntaxtag::F, 0},
{syntaxtag::T, 0},
{syntaxtag::U, 0},
{syntaxtag::O, 0},
{syntaxtag::R, 0},
// clang-format on
};
map<syntaxtag, string> m_colors{
// clang-format off
{syntaxtag::B, ""},
{syntaxtag::F, ""},
{syntaxtag::U, ""},
// clang-format on
};
int m_fontindex = 1;
};
LEMONBUDDY_NS_END;

View File

@ -0,0 +1,229 @@
#pragma once
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include "common.hpp"
LEMONBUDDY_NS
namespace command_line {
class option;
using choices = vector<string>;
using options = vector<option>;
using values = map<string, string>;
// class definition : option {{{
class option {
public:
string flag;
string flag_long;
string desc;
string token;
choices values;
/**
* Construct option
*/
explicit option(
string&& flag, string&& flag_long, string&& desc, string&& token = "", choices&& c = {})
: flag(forward<string>(flag))
, flag_long(forward<string>(flag_long))
, desc(forward<string>(desc))
, token(forward<string>(token))
, values(forward<choices>(c)) {}
};
// }}}
// class definition : parser {{{
class parser {
public:
/**
* Construct parser
*/
explicit parser(string&& synopsis, options&& opts)
: m_synopsis(forward<string>(synopsis)), m_opts(forward<options>(opts)) {}
/**
* Process input values
*
* This is done outside the constructor due to boost::di noexcept
*/
void process_input(const vector<string>& values) {
for (size_t i = 0; i < values.size(); i++) {
parse(values[i], values.size() > i + 1 ? values[i + 1] : "");
}
}
/**
* Test if the passed option was provided
*/
bool has(string&& option) const {
return m_optvalues.find(forward<string>(option)) != m_optvalues.end();
}
/**
* Compares the option value with given string
*/
bool compare(string&& opt, string val) const {
return get(forward<string>(opt)) == val;
}
/**
* Gets the value defined for given option
*/
string get(string&& opt) const {
if (has(forward<string>(opt)))
return m_optvalues.find(forward<string>(opt))->second;
return "";
}
/**
* Prints application usage message
*/
void usage() const {
std::cout << m_synopsis << "\n" << std::endl;
// get the length of the longest string in the flag column
// which is used to align the description fields
size_t maxlen{0};
for (auto it = m_opts.begin(); it != m_opts.end(); ++it) {
size_t len{it->flag_long.length() + it->flag.length() + 4};
maxlen = len > maxlen ? len : maxlen;
}
for (auto& opt : m_opts) {
int pad = maxlen - opt.flag_long.length() - opt.token.length();
std::cout << " " << opt.flag << ", " << opt.flag_long;
if (!opt.token.empty()) {
std::cout << "=" << opt.token;
pad--;
}
// output the list with accepted values
if (!opt.values.empty()) {
std::cout << std::setw(pad + opt.desc.length()) << std::setfill(' ') << opt.desc
<< std::endl;
pad = pad + opt.flag_long.length() + opt.token.length() + 7;
std::cout << string(pad, ' ') << opt.token << " is one of: ";
for (auto& v : opt.values) {
std::cout << v << (v != opt.values.back() ? ", " : "");
}
} else {
std::cout << std::setw(pad + opt.desc.length()) << std::setfill(' ') << opt.desc;
}
std::cout << std::endl;
}
}
/**
* Configure injection module
*/
template <class T = parser>
static di::injector<T> configure(string scriptname, options opts) {
// clang-format off
return di::make_injector(
di::bind<>().to("Usage: " + scriptname + " bar_name [OPTION...]"),
di::bind<>().to(opts));
// clang-format on
}
protected:
/**
* Compare option with its short version
*/
auto is_short(string option, string opt_short) const {
return option.compare(0, opt_short.length(), opt_short) == 0;
}
/**
* Compare option with its long version
*/
auto is_long(string option, string opt_long) const {
return option.compare(0, opt_long.length(), opt_long) == 0;
}
/**
* Compare option with both versions
*/
auto is(string option, string opt_short, string opt_long) const {
return is_short(option, opt_short) || is_long(opt_short, opt_long);
}
/**
* Gets value defined for
*/
auto parse_value(string input, string input_next, choices values) const {
string opt = input;
size_t pos;
string value;
if (input_next.empty() && opt.compare(0, 2, "--") != 0)
throw application_error("Missing value for " + opt);
else if (!input_next.empty())
value = input_next;
else if ((pos = opt.compare(0, 1, "=")) == string::npos)
throw application_error("Missing value for " + opt);
else {
value = opt.substr(pos + 1);
opt = opt.substr(0, pos);
}
if (!values.empty() && std::find(values.begin(), values.end(), value) == values.end())
throw application_error("Invalid argument '" + value + "' for " + string{opt});
return value;
}
/**
* Parses and validates passed arguments and flags
*/
void parse(string input, string input_next = "") {
if (m_skipnext) {
m_skipnext = false;
if (!input_next.empty())
return;
}
for (auto&& opt : m_opts) {
if (is(input, opt.flag, opt.flag_long)) {
if (opt.token.empty()) {
m_optvalues.insert(std::make_pair(opt.flag_long.substr(2), ""));
} else {
auto value = parse_value(input, input_next, opt.values);
m_skipnext = (value == input_next);
m_optvalues.insert(std::make_pair(opt.flag_long.substr(2), value));
}
return;
}
}
if (input.compare(0, 1, "-") == 0) {
throw application_error("Unrecognized option " + input);
}
}
private:
string m_synopsis;
options m_opts;
values m_optvalues;
bool m_skipnext = false;
};
// }}}
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,230 @@
#pragma once
#include <boost/lexical_cast.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include "common.hpp"
#include "components/logger.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
using ptree = boost::property_tree::ptree;
DEFINE_ERROR(value_error);
DEFINE_ERROR(key_error);
class config {
public:
/**
* Construct config
*/
explicit config(const logger& logger) : m_logger(logger) {}
/**
* Load configuration and validate bar section
*
* This is done outside the constructor due to boost::di noexcept
*/
void load(string file, string barname) {
m_file = file;
m_current_bar = barname;
if (!file_util::exists(file))
throw application_error("Could not find config file: " + file);
try {
boost::property_tree::read_ini(file, m_ptree);
} catch (const std::exception& e) {
throw application_error(e.what());
}
auto bars = defined_bars();
if (std::find(bars.begin(), bars.end(), m_current_bar) == bars.end())
throw application_error("Undefined bar: " + m_current_bar);
if (has_env("XDG_CONFIG_HOME"))
file = string_util::replace(file, read_env("XDG_CONFIG_HOME"), "$XDG_CONFIG_HOME");
if (has_env("HOME"))
file = string_util::replace(file, read_env("HOME"), "~");
m_logger.trace("config: Loaded %s", file);
m_logger.trace("config: Current bar section: [%s]", bar_section());
}
/**
* Get path of loaded file
*/
string filepath() const {
return m_file;
}
/**
* Get the section name of the bar in use
*/
string bar_section() const {
return "bar/" + m_current_bar;
}
/**
* Get a list of defined bar sections in the current config
*/
vector<string> defined_bars() const {
vector<string> bars;
for (auto&& p : m_ptree) {
if (p.first.compare(0, 4, "bar/") == 0)
bars.emplace_back(p.first.substr(4));
}
return bars;
}
/**
* Build path used to find a parameter in the given section
*/
string build_path(const string& section, const string& key) const {
return section + "." + key;
}
/**
* Get parameter for the current bar by name
*/
template <typename T>
T get(string key) const {
return get<T>(bar_section(), key);
}
/**
* Get value of a variable by section and parameter name
*/
template <typename T>
T get(string section, string key) const {
auto val = m_ptree.get_optional<T>(build_path(section, key));
if (val == boost::none)
throw key_error("Missing property '" + key + "' in section [" + section + "]");
auto str_val = m_ptree.get<string>(build_path(section, key));
return dereference_var<T>(section, key, str_val, val.get());
}
/**
* Get value of a variable by section and parameter name
* with a default value in case the parameter isn't defined
*/
template <typename T>
T get(string section, string key, T default_value) const {
auto val = m_ptree.get_optional<T>(build_path(section, key));
auto str_val = m_ptree.get_optional<string>(build_path(section, key));
return dereference_var<T>(
section, key, str_val.get_value_or(""), val.get_value_or(default_value));
}
/**
* Get list of values for the current bar by name
*/
template <typename T>
T get_list(string key) const {
return get_list<T>(bar_section(), key);
}
/**
* Get list of values by section and parameter name
*/
template <typename T>
vector<T> get_list(string section, string key) const {
vector<T> vec;
optional<T> value;
while ((value = m_ptree.get_optional<T>(
build_path(section, key) + "-" + to_string(vec.size()))) != boost::none) {
auto str_val = m_ptree.get<string>(build_path(section, key) + "-" + to_string(vec.size()));
vec.emplace_back(dereference_var<T>(section, key, str_val, value.get()));
}
if (vec.empty())
throw key_error("Missing property '" + key + "-0' in section [" + section + "]");
return vec;
}
/**
* Get list of values by section and parameter name
* with a default list in case the list isn't defined
*/
template <typename T>
vector<T> get_list(string section, string key, vector<T> default_value) const {
vector<T> vec;
optional<T> value;
while ((value = m_ptree.get_optional<T>(
build_path(section, key) + "-" + to_string(vec.size()))) != boost::none) {
auto str_val = m_ptree.get<string>(build_path(section, key) + "-" + 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;
}
/**
* Configure injection module
*/
template <typename T = const config&>
static di::injector<T> configure() {
return di::make_injector(logger::configure());
}
protected:
/**
* Find value of a config parameter defined as a reference
* variable using ${section.param} or ${env:VAR}
*/
template <typename T>
T dereference_var(string ref_section, string ref_key, string var, const T ref_val) const {
auto n = var.find("${");
auto m = var.find("}");
if (n != 0 || m != var.length() - 1)
return ref_val;
auto path = var.substr(2, m - 2);
if (path.find("env:") == 0) {
if (has_env(path.substr(4).c_str()))
return boost::lexical_cast<T>(read_env(path.substr(4).c_str()));
return ref_val;
}
auto ref_path = build_path(ref_section, ref_key);
if ((n = path.find(".")) == string::npos)
throw value_error("Invalid variable " + ref_path + " => ${" + path + "}");
auto section = string_util::replace(path.substr(0, n), "BAR", bar_section());
auto key = path.substr(n + 1, path.length() - n - 1);
auto val = m_ptree.get_optional<T>(build_path(section, key));
if (val == boost::none)
throw value_error("Variable defined at [" + ref_path +
"] points to a non existing parameter: [" + build_path(section, key) + "]");
auto str_val = m_ptree.get<string>(build_path(section, key));
return dereference_var<T>(section, key, str_val, val.get());
}
private:
const logger& m_logger;
ptree m_ptree;
string m_file;
string m_current_bar;
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,479 @@
#pragma once
#include <thread>
#include "common.hpp"
#include "components/bar.hpp"
#include "components/config.hpp"
#include "components/logger.hpp"
#include "components/x11/connection.hpp"
#include "components/x11/randr.hpp"
#include "components/x11/tray.hpp"
#include "components/x11/types.hpp"
#include "config.hpp"
#include "utils/inotify.hpp"
#include "utils/process.hpp"
#include "utils/socket.hpp"
#include "utils/throttle.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/i3.hpp"
#include "modules/memory.hpp"
#include "modules/menu.hpp"
#include "modules/mpd.hpp"
#include "modules/network.hpp"
#include "modules/script.hpp"
#include "modules/text.hpp"
#include "modules/volume.hpp"
LEMONBUDDY_NS
using namespace modules;
using module_t = unique_ptr<module_interface>;
class controller {
public:
/**
* Construct controller
*/
explicit controller(connection& conn, const logger& logger, const config& config,
unique_ptr<bar> bar, unique_ptr<traymanager> tray, inotify_watch_t& confwatch)
: m_connection(conn)
, m_log(logger)
, m_conf(config)
, m_bar(forward<decltype(bar)>(bar))
, m_traymanager(forward<decltype(tray)>(tray))
, m_confwatch(confwatch) {}
/**
* Stop modules and cleanup X components,
* threads and spawned processes
*/
~controller() noexcept {
std::lock_guard<threading_util::spin_lock> lck(m_lock);
m_log.trace("controller: Stop modules");
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
module->on_update.disconnect(this, &controller::on_module_update);
module->stop();
}
}
m_log.trace("controller: Deconstruct bar instance");
m_bar.reset();
if (m_traymanager) {
m_log.trace("controller: Deactivate tray manager");
m_traymanager->deactivate();
}
m_log.trace("controller: Interrupt X event loop");
m_connection.send_dummy_event(m_connection.root());
if (m_confwatch) {
try {
m_log.trace("controller: Remove config watch");
m_confwatch->remove();
} catch (const system_error& err) {
}
}
m_log.trace("controller: Stop modules");
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
module->on_update.disconnect(this, &controller::on_module_update);
module->stop();
}
}
if (!m_threads.empty()) {
m_log.trace("controller: Join active threads");
for (auto&& thread : m_threads) {
if (thread.joinable())
thread.join();
}
}
m_log.trace("controller: Wait for spawned processes");
while (process_util::notify_childprocess())
;
m_connection.flush();
}
/**
* Setup X environment
*/
auto bootstrap(bool to_stdout = false, bool dump_wmname = false) {
m_stdout = to_stdout;
m_log.trace("controller: Initialize X atom cache");
m_connection.preload_atoms();
m_log.trace("controller: Query X extension data");
m_connection.query_extensions();
// const auto& damage_ext = m_connection.extension<xpp::damage::extension>();
// m_log.trace("controller: Found 'Damage' (first_event: %i, first_error: %i)",
// damage_ext->first_event, damage_ext->first_error);
// const auto& render_ext = m_connection.extension<xpp::render::extension>();
// m_log.trace("controller: Found 'Render' (first_event: %i, first_error: %i)",
// render_ext->first_event, render_ext->first_error);
const auto& randr_ext = m_connection.extension<xpp::randr::extension>();
m_log.trace("controller: Found 'RandR' (first_event: %i, first_error: %i)",
randr_ext->first_event, randr_ext->first_error);
// Listen for events on the root window to be able to
// break the blocking wait call when cleaning up
m_log.trace("controller: Listen for events on the root window");
const uint32_t value_list[]{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
m_connection.change_window_attributes(m_connection.root(), XCB_CW_EVENT_MASK, &value_list);
try {
m_log.trace("controller: Setup bar renderer");
m_bar->bootstrap(m_stdout || dump_wmname);
if (dump_wmname) {
std::cout << m_bar->settings().wmname << std::endl;
return;
}
} catch (const std::exception& err) {
throw application_error("Failed to setup bar renderer: " + string{err.what()});
}
try {
if (m_stdout) {
m_log.trace("controller: Disabling tray (reason: stdout mode)");
m_traymanager.reset();
} else if (m_bar->tray().align == alignment::NONE) {
m_log.trace("controller: Disabling tray (reason: tray-position)");
m_traymanager.reset();
} else {
m_log.trace("controller: Setup tray manager");
m_traymanager->bootstrap(m_bar->tray());
}
} catch (const std::exception& err) {
m_log.err(err.what());
m_log.warn("controller: Disabling tray...");
m_traymanager.reset();
}
m_log.trace("main: Setup bar modules");
bootstrap_modules();
// Allow <throttle_limit> ticks within <throttle_ms> timeframe
const auto throttle_limit = m_conf.get<unsigned int>("settings", "throttle_limit", 3);
const auto throttle_ms = chrono::duration<double, std::milli>(
m_conf.get<unsigned int>("settings", "throttle_ms", 60));
m_throttler = throttle_util::make_throttler(throttle_limit, throttle_ms);
}
/**
* Launch the controller
*/
auto run() {
assert(!m_connection.connection_has_error());
m_log.info("Starting application...");
m_running = true;
install_sigmask();
install_confwatch();
m_threads.emplace_back([this] {
m_connection.flush();
m_log.trace("controller: Start modules");
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
module->on_update.connect(this, &controller::on_module_update);
module->start();
}
}
if (m_stdout) {
m_log.trace("controller: Ignoring tray manager (reason: stdout mode)");
m_log.trace("controller: Ignoring X event loop (reason: stdout mode)");
return;
}
if (m_traymanager) {
try {
m_log.trace("controller: Activate tray manager");
m_traymanager->activate();
} catch (const std::exception& err) {
m_log.err(err.what());
m_log.err("controller: Failed to activate tray manager...");
}
}
m_log.trace("controller: Listen for X events");
while (m_running) {
m_connection.flush();
m_connection.dispatch_event(m_connection.wait_for_event());
}
});
wait();
m_running = false;
return !m_reload;
}
/**
* Block execution until a defined signal is raised
*/
void wait() {
m_log.trace("controller: Wait for signal");
int caught_signal = 0;
sigwait(&m_waitmask, &caught_signal);
m_log.warn("Termination signal received, shutting down...");
m_log.trace("controller: Caught signal %d", caught_signal);
m_reload = (caught_signal == SIGUSR1);
}
/**
* Configure injection module
*/
static di::injector<unique_ptr<controller>> configure(inotify_watch_t& confwatch) {
// clang-format off
return di::make_injector(di::bind<controller>().to<controller>(),
di::bind<>().to(confwatch),
connection::configure(),
logger::configure(), config::configure(),
bar::configure(),
traymanager::configure());
// clang-format on
}
protected:
/**
* Set signal mask for the current and future threads
*/
void install_sigmask() {
if (!m_running)
return;
m_log.trace("controller: Set sigmask for current and future threads");
sigemptyset(&m_waitmask);
sigaddset(&m_waitmask, SIGINT);
sigaddset(&m_waitmask, SIGQUIT);
sigaddset(&m_waitmask, SIGTERM);
sigaddset(&m_waitmask, SIGUSR1);
if (pthread_sigmask(SIG_BLOCK, &m_waitmask, nullptr) == -1)
throw system_error();
}
/**
* Listen for changes to the config file
*/
void install_confwatch() {
if (!m_running)
return;
if (!m_confwatch) {
m_log.trace("controller: Config watch not set, skip...");
return;
}
m_threads.emplace_back([this] {
this_thread::sleep_for(1s);
try {
if (!m_running)
return;
m_log.trace("controller: Attach config watch");
m_confwatch->attach(IN_MODIFY);
m_log.trace("controller: Wait for config file inotify event");
m_confwatch->get_event();
if (!m_running)
return;
m_log.info("Configuration file changed...");
kill(getpid(), SIGUSR1);
} catch (const system_error& err) {
m_log.err(err.what());
m_log.trace("controller: Reset config watch");
m_confwatch.reset();
}
});
}
/**
* Create and initialize bar modules
*/
void bootstrap_modules() {
m_modules.emplace(alignment::LEFT, vector<module_t>{});
m_modules.emplace(alignment::CENTER, vector<module_t>{});
m_modules.emplace(alignment::RIGHT, vector<module_t>{});
for (auto& block : m_modules) {
string bs{m_conf.bar_section()};
string confkey;
switch (block.first) {
case alignment::LEFT:
confkey = "modules-left";
break;
case alignment::CENTER:
confkey = "modules-center";
break;
case alignment::RIGHT:
confkey = "modules-right";
break;
default:
break;
}
for (auto& module_name : string_util::split(m_conf.get<string>(bs, confkey, ""), ' ')) {
auto type = m_conf.get<string>("module/" + module_name, "type");
auto bar = m_bar->settings();
auto& modules = block.second;
if (type == "internal/counter")
modules.emplace_back(new counter_module(bar, m_log, m_conf, module_name));
else if (type == "internal/backlight")
modules.emplace_back(new backlight_module(bar, m_log, m_conf, module_name));
else if (type == "internal/battery")
modules.emplace_back(new battery_module(bar, m_log, m_conf, module_name));
else if (type == "internal/bspwm")
modules.emplace_back(new bspwm_module(bar, m_log, m_conf, module_name));
else if (type == "internal/cpu")
modules.emplace_back(new cpu_module(bar, m_log, m_conf, module_name));
else if (type == "internal/date")
modules.emplace_back(new date_module(bar, m_log, m_conf, module_name));
else if (type == "internal/memory")
modules.emplace_back(new memory_module(bar, m_log, m_conf, module_name));
else if (type == "internal/i3")
modules.emplace_back(new i3_module(bar, m_log, m_conf, module_name));
else if (type == "internal/mpd")
modules.emplace_back(new mpd_module(bar, m_log, m_conf, module_name));
else if (type == "internal/volume")
modules.emplace_back(new volume_module(bar, m_log, m_conf, module_name));
else if (type == "internal/network")
modules.emplace_back(new network_module(bar, m_log, m_conf, module_name));
else if (type == "custom/text")
modules.emplace_back(new text_module(bar, m_log, m_conf, module_name));
else if (type == "custom/script")
modules.emplace_back(new script_module(bar, m_log, m_conf, module_name));
else if (type == "custom/menu")
modules.emplace_back(new menu_module(bar, m_log, m_conf, module_name));
else
throw application_error("Unknown module: " + module_name);
}
}
}
void on_module_update(string module_name) {
std::lock_guard<threading_util::spin_lock> lck(m_lock);
if (!m_running)
return;
if (!m_throttler->passthrough(throttle_util::strategy::try_once_or_leave_yolo{})) {
m_log.trace("Throttled");
return;
}
string contents{""};
string separator{m_bar->settings().separator.empty()};
string padding_left(m_bar->settings().padding_left, ' ');
string padding_right(m_bar->settings().padding_right, ' ');
auto margin_left = m_bar->settings().module_margin_left;
auto margin_right = m_bar->settings().module_margin_right;
for (auto&& block : m_modules) {
string block_contents;
for (auto&& module : block.second) {
auto module_contents = module->contents();
if (module_contents.empty())
continue;
if (!block_contents.empty())
block_contents += separator;
if (!(block.first == alignment::LEFT && module == block.second.front()))
block_contents += string(margin_left, ' ');
block_contents += module->contents();
if (!(block.first == alignment::RIGHT && module == block.second.back()))
block_contents += string(margin_right, ' ');
}
if (block_contents.empty())
continue;
switch (block.first) {
case alignment::LEFT:
contents += "%{l}";
contents += padding_left;
break;
case alignment::CENTER:
contents += "%{c}";
break;
case alignment::RIGHT:
contents += "%{r}";
block_contents += padding_right;
break;
case alignment::NONE:
break;
}
block_contents = string_util::replace_all(block_contents, "B-}%{B#", "B#");
block_contents = string_util::replace_all(block_contents, "F-}%{F#", "F#");
block_contents = string_util::replace_all(block_contents, "T-}%{T", "T");
contents += string_util::replace_all(block_contents, "}%{", " ");
}
if (m_stdout)
std::cout << contents << std::endl;
else
m_bar->parse(contents);
}
private:
connection& m_connection;
registry m_registry{m_connection};
const logger& m_log;
const config& m_conf;
unique_ptr<bar> m_bar;
unique_ptr<traymanager> m_traymanager;
threading_util::spin_lock m_lock;
stateflag m_stdout{false};
stateflag m_running{false};
stateflag m_reload{false};
sigset_t m_waitmask;
inotify_watch_t& m_confwatch;
vector<thread> m_threads;
map<alignment, vector<module_t>> m_modules;
unique_ptr<throttle_util::event_throttler> m_throttler;
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,188 @@
#pragma once
#include <cstdio>
#include <cstring>
#include "common.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
enum class loglevel {
NONE = 0,
ERROR,
WARNING,
INFO,
TRACE,
};
/**
* Convert given loglevel name to its enum type counterpart
*/
auto parse_loglevel_name = [](string name) {
if (string_util::compare(name, "error"))
return loglevel::ERROR;
else if (string_util::compare(name, "warning"))
return loglevel::WARNING;
else if (string_util::compare(name, "info"))
return loglevel::INFO;
else if (string_util::compare(name, "trace"))
return loglevel::TRACE;
else
return loglevel::NONE;
};
class logger {
public:
/**
* Construct logger
*/
explicit logger(loglevel level) : m_level(level) {
if (isatty(STDOUT_FILENO)) {
// clang-format off
m_prefixes[loglevel::TRACE] = "\r\033[0;90m- ";
m_prefixes[loglevel::INFO] = "\r\033[1;32m* \033[0m";
m_prefixes[loglevel::WARNING] = "\r\033[1;33mwarning: \033[0m";
m_prefixes[loglevel::ERROR] = "\r\033[1;31merror: \033[0m";
m_suffixes[loglevel::TRACE] = "\033[0m";
m_suffixes[loglevel::INFO] = "\033[0m";
m_suffixes[loglevel::WARNING] = "\033[0m";
m_suffixes[loglevel::ERROR] = "\033[0m";
// clang-format on
}
}
/**
* Construct logger
*/
explicit logger(string level_name) : logger(parse_loglevel_name(level_name)) {}
/**
* Set output verbosity
*/
void verbosity(loglevel level) {
#ifndef DEBUG
if (level == loglevel::TRACE)
throw application_error("not a debug build: trace disabled...");
#endif
m_level = level;
}
/**
* Set output verbosity by loglevel name
*/
void verbosity(string level) {
verbosity(parse_loglevel_name(level));
}
/**
* Output a trace message
*/
template <typename... Args>
void trace(string message, Args... args) const {
#ifdef DEBUG
output(loglevel::TRACE, message, args...);
#endif
}
/**
* Output an info message
*/
template <typename... Args>
void info(string message, Args... args) const {
output(loglevel::INFO, message, args...);
}
/**
* Output a warning message
*/
template <typename... Args>
void warn(string message, Args... args) const {
output(loglevel::WARNING, message, args...);
}
/**
* Output an error message
*/
template <typename... Args>
void err(string message, Args... args) const {
output(loglevel::ERROR, message, args...);
}
/**
* Configure injection module
*/
template <typename T = const logger&>
static di::injector<T> configure(loglevel level = loglevel::NONE) {
auto instance = factory::generic_singleton<logger>(level);
return di::make_injector(di::bind<>().to(instance));
}
protected:
template <typename T>
decltype(auto) convert(T&& arg) const {
return forward<T>(arg);
};
/**
* Convert string to const char*
*/
const char* convert(string arg) const {
return arg.c_str();
};
/**
* Write the log message to the output channel
* if the defined verbosity level allows it
*/
template <typename... Args>
void output(loglevel level, string format, Args... values) const {
if (level > m_level)
return;
auto prefix = m_prefixes.find(level)->second;
auto suffix = m_suffixes.find(level)->second;
// silence the compiler
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
dprintf(m_fd, (prefix + format + suffix + "\n").c_str(), convert(values)...);
#pragma clang diagnostic pop
}
private:
/**
* Logger verbosity level
*/
loglevel m_level = loglevel::TRACE;
/**
* File descriptor used when writing the log messages
*/
int m_fd = STDOUT_FILENO;
/**
* Loglevel specific prefixes
*/
// clang-format off
map<loglevel, string> m_prefixes {
{loglevel::TRACE, "lemonbuddy|trace "},
{loglevel::INFO, "lemonbuddy|info "},
{loglevel::WARNING, "lemonbuddy|warn "},
{loglevel::ERROR, "lemonbuddy|error "},
};
/**
* Loglevel specific suffixes
*/
map<loglevel, string> m_suffixes {
{loglevel::TRACE, ""},
{loglevel::INFO, ""},
{loglevel::WARNING, ""},
{loglevel::ERROR, ""},
};
// clang-format on
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,256 @@
#pragma once
#include <fastdelegate/fastdelegate.hpp>
#include "common.hpp"
#include "components/logger.hpp"
#include "components/types.hpp"
#include "utils/math.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
DEFINE_ERROR(unrecognized_token);
namespace parser_signals {
delegate::Signal1<alignment> alignment_change;
delegate::Signal1<attribute> attribute_set;
delegate::Signal1<attribute> attribute_unset;
delegate::Signal1<attribute> attribute_toggle;
delegate::Signal2<mousebtn, string> action_block_open;
delegate::Signal1<mousebtn> action_block_close;
delegate::Signal2<gc, color> color_change;
delegate::Signal1<int> font_change;
delegate::Signal1<int> pixel_offset;
delegate::Signal1<uint16_t> ascii_text_write;
delegate::Signal1<uint16_t> unicode_text_write;
};
class parser {
public:
/**
* Construct parser
*/
explicit parser(const bar_settings& bar) : m_bar(bar) {}
/**
* Parse input data
*/
void operator()(string data) { // {{{
size_t pos;
while (data.length()) {
if (data.compare(0, 2, "%{") == 0 && (pos = data.find("}")) != string::npos) {
codeblock(data.substr(2, pos - 2));
data.erase(0, pos + 1);
} else {
if ((pos = data.find("%{")) == string::npos)
pos = data.length();
data.erase(0, text(data.substr(0, pos)));
}
}
} // }}}
/**
* Parse contents in tag blocks, i.e: %{...}
*/
void codeblock(string data) { // {{{
size_t pos;
while (data.length()) {
data = string_util::ltrim(data, ' ');
if (data.empty())
break;
char tag = data[0];
string value;
// Remove the tag
data.erase(0, 1);
if ((pos = data.find_first_of(" }")) != string::npos)
value = data.substr(0, pos);
else
value = data;
switch (tag) {
case 'B':
// Ignore tag if it occurs again later in the same block
if (data.find(" B") == string::npos && !parser_signals::color_change.empty())
parser_signals::color_change.emit(gc::BG, parse_color(value, m_bar.background));
break;
case 'F':
// Ignore tag if it occurs again later in the same block
if (data.find(" F") == string::npos && !parser_signals::color_change.empty())
parser_signals::color_change.emit(gc::FG, parse_color(value, m_bar.foreground));
break;
case 'U':
// Ignore tag if it occurs again later in the same block
if (data.find(" U") == string::npos && !parser_signals::color_change.empty()) {
parser_signals::color_change.emit(gc::UL, parse_color(value, m_bar.linecolor));
parser_signals::color_change.emit(gc::OL, parse_color(value, m_bar.linecolor));
}
break;
case 'R':
if (!parser_signals::color_change.empty()) {
parser_signals::color_change.emit(gc::BG, m_bar.foreground);
parser_signals::color_change.emit(gc::FG, m_bar.background);
}
break;
case 'T':
if (data.find(" T") == string::npos && !parser_signals::font_change.empty())
parser_signals::font_change.emit(parse_fontindex(value));
break;
case 'O':
if (!parser_signals::pixel_offset.empty())
parser_signals::pixel_offset.emit(std::atoi(value.c_str()));
break;
case 'l':
if (!parser_signals::alignment_change.empty())
parser_signals::alignment_change.emit(alignment::LEFT);
break;
case 'c':
if (!parser_signals::alignment_change.empty())
parser_signals::alignment_change.emit(alignment::CENTER);
break;
case 'r':
if (!parser_signals::alignment_change.empty())
parser_signals::alignment_change.emit(alignment::RIGHT);
break;
case '+':
if (!parser_signals::attribute_set.empty())
parser_signals::attribute_set.emit(parse_attr(value[0]));
break;
case '-':
if (!parser_signals::attribute_unset.empty())
parser_signals::attribute_unset.emit(parse_attr(value[0]));
break;
case '!':
if (!parser_signals::attribute_toggle.empty())
parser_signals::attribute_toggle.emit(parse_attr(value[0]));
break;
case 'A':
if (isdigit(data[0])) {
value = parse_action_cmd(data);
if (!parser_signals::action_block_open.empty())
parser_signals::action_block_open.emit(parse_action_btn(data), value);
m_actions.push_back(data[0] - '0');
value += "0::"; // make sure we strip the correct length (btn+wrapping colons)
} else {
if (!parser_signals::action_block_close.empty())
parser_signals::action_block_close.emit(parse_action_btn(data));
m_actions.pop_back();
}
break;
default:
throw unrecognized_token(string{tag});
}
if (!data.empty())
data.erase(0, !value.empty() ? value.length() : 1);
}
} // }}}
/**
* Parse text strings
*/
size_t text(string data) { // {{{
uint8_t* utf = (uint8_t*)data.c_str();
// if (utf[0] < 0x80) {
// // grab all consecutive ascii chars
// size_t next_tag = data.find("%{");
// string sequence{(next_tag != string::npos) ? data.substr(0, next_tag) : data};
// size_t n = 0;
// while (sequence[n] != '\0' && static_cast<uint8_t>(sequence[n]) < 0x80 && ++n <= next_tag)
// ;
// parser_signals::ascii_text_write.emit(data.substr(0, n));
// return data.length();
// }
if (utf[0] < 0x80) {
parser_signals::ascii_text_write.emit(utf[0]);
return 1;
} else if ((utf[0] & 0xe0) == 0xc0) { // 2 byte utf-8 sequence
parser_signals::unicode_text_write.emit((utf[0] & 0x1f) << 6 | (utf[1] & 0x3f));
return 2;
} else if ((utf[0] & 0xf0) == 0xe0) { // 3 byte utf-8 sequence
parser_signals::unicode_text_write.emit(
(utf[0] & 0xf) << 12 | (utf[1] & 0x3f) << 6 | (utf[2] & 0x3f));
return 3;
} else if ((utf[0] & 0xf8) == 0xf0) { // 4 byte utf-8 sequence
parser_signals::unicode_text_write.emit(0xfffd);
return 4;
} else if ((utf[0] & 0xfc) == 0xf8) { // 5 byte utf-8 sequence
parser_signals::unicode_text_write.emit(0xfffd);
return 5;
} else if ((utf[0] & 0xfe) == 0xfc) { // 6 byte utf-8 sequence
parser_signals::unicode_text_write.emit(0xfffd);
return 6;
} else { // invalid utf-8 sequence
parser_signals::ascii_text_write.emit(utf[0]);
return 1;
}
} // }}}
protected:
color parse_color(string s, color fallback = color{0}) { // {{{
if (s.empty() || s == "-")
return fallback;
return color::parse(s, fallback);
} // }}}
int parse_fontindex(string s) { // {{{
if (s.empty() || s == "-")
return -1;
char* p = (char*)s.c_str();
return std::strtoul(p, &p, 10);
} // }}}
attribute parse_attr(const char s) { // {{{
switch (s) {
case 'o':
return attribute::o;
break;
case 'u':
return attribute::u;
break;
}
return attribute::NONE;
} // }}}
mousebtn parse_action_btn(string data) { // {{{
if (isdigit(data[0] - '0'))
return static_cast<mousebtn>(data[0] - '0');
else if (!m_actions.empty())
return static_cast<mousebtn>(m_actions.back());
else
return mousebtn::NONE;
} // }}}
string parse_action_cmd(string data) { // {{{
auto start = string_util::find_nth(data, 0, ":", 1);
auto end = string_util::find_nth(data, 0, ":", 2);
return string_util::trim(data.substr(start, end), ':');
} // }}}
private:
const bar_settings& m_bar;
vector<int> m_actions;
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,101 @@
#pragma once
#include "common.hpp"
#include "components/x11/color.hpp"
#include "components/x11/randr.hpp"
LEMONBUDDY_NS
enum class border { NONE = 0, TOP, BOTTOM, LEFT, RIGHT, ALL };
enum class alignment { NONE = 0, LEFT, CENTER, RIGHT };
enum class syntaxtag { NONE = 0, A, B, F, T, U, O, R, o, u };
enum class attribute { NONE = 0, o = 2, u = 4 };
enum class mousebtn { NONE = 0, LEFT, MIDDLE, RIGHT, SCROLL_UP, SCROLL_DOWN };
enum class gc { NONE = 0, BG, FG, OL, UL, BT, BB, BL, BR };
struct bar_settings {
bar_settings() = default;
int16_t x{0};
int16_t y{0};
uint16_t width{0};
uint16_t height{0};
int16_t offset_y{0};
int16_t offset_x{0};
uint16_t padding_left{0};
uint16_t padding_right{0};
int16_t module_margin_left = 0;
int16_t module_margin_right = 2;
int16_t lineheight{0};
int16_t spacing{1};
string separator;
color background{g_colorwhite};
color foreground{g_colorblack};
color linecolor{g_colorblack};
alignment align{alignment::RIGHT};
bool bottom{false};
bool dock{true};
shared_ptr<monitor> monitor;
string wmname;
int16_t vertical_mid{0};
string geom() {
char buffer[32]{
'\0',
};
snprintf(buffer, sizeof(buffer), "%dx%d+%d+%d", width, height, x, y);
return string{*buffer};
};
};
struct tray_settings {
tray_settings() = default;
tray_settings& operator=(tray_settings& o) {
background = o.background;
align = o.align;
orig_x = o.orig_x;
orig_y = o.orig_y;
width = o.width;
height = o.height;
spacing = o.spacing;
slots = o.slots;
return *this;
}
uint32_t background;
alignment align{alignment::NONE};
int16_t orig_x{0};
int16_t orig_y{0};
uint16_t width{0};
uint16_t height{0};
uint16_t spacing{0};
uint16_t slots{0};
};
struct border_settings {
border_settings() = default;
lemonbuddy::color color{g_colorblack};
uint16_t size{0};
};
struct action_block {
action_block() = default;
mousebtn mousebtn{mousebtn::NONE};
string command;
int16_t start_x{0};
int16_t end_x{0};
alignment align;
bool active{true};
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,52 @@
#pragma once
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
struct cached_atom {
const char* name;
size_t len;
xcb_atom_t* atom;
};
xcb_atom_t _NET_WM_NAME;
xcb_atom_t _NET_WM_WINDOW_TYPE;
xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK;
xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL;
xcb_atom_t _NET_WM_PID;
xcb_atom_t _NET_WM_STATE;
xcb_atom_t _NET_WM_STATE_STICKY;
xcb_atom_t _NET_WM_STATE_SKIP_TASKBAR;
xcb_atom_t _NET_WM_STATE_ABOVE;
xcb_atom_t WM_PROTOCOLS;
xcb_atom_t WM_DELETE_WINDOW;
xcb_atom_t _XEMBED;
xcb_atom_t _XEMBED_INFO;
xcb_atom_t _NET_SYSTEM_TRAY_OPCODE;
xcb_atom_t MANAGER;
xcb_atom_t WM_STATE;
xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION;
xcb_atom_t WM_TAKE_FOCUS;
// clang-format off
cached_atom ATOMS[18] = {
{"_NET_WM_NAME", sizeof("_NET_WM_NAME") - 1, &_NET_WM_NAME},
{"_NET_WM_WINDOW_TYPE", sizeof("_NET_WM_WINDOW_TYPE") - 1, &_NET_WM_WINDOW_TYPE},
{"_NET_WM_WINDOW_TYPE_DOCK", sizeof("_NET_WM_WINDOW_TYPE_DOCK") - 1, &_NET_WM_WINDOW_TYPE_DOCK},
{"_NET_WM_WINDOW_TYPE_NORMAL", sizeof("_NET_WM_WINDOW_TYPE_NORMAL") - 1, &_NET_WM_WINDOW_TYPE_NORMAL},
{"_NET_WM_PID", sizeof("_NET_WM_PID") - 1, &_NET_WM_PID},
{"_NET_WM_STATE", sizeof("_NET_WM_STATE") - 1, &_NET_WM_STATE},
{"_NET_WM_STATE_STICKY", sizeof("_NET_WM_STATE_STICKY") - 1, &_NET_WM_STATE_STICKY},
{"_NET_WM_STATE_SKIP_TASKBAR", sizeof("_NET_WM_STATE_SKIP_TASKBAR") - 1, &_NET_WM_STATE_SKIP_TASKBAR},
{"_NET_WM_STATE_ABOVE", sizeof("_NET_WM_STATE_ABOVE") - 1, &_NET_WM_STATE_ABOVE},
{"WM_PROTOCOLS", sizeof("WM_PROTOCOLS") - 1, &WM_PROTOCOLS},
{"WM_DELETE_WINDOW", sizeof("WM_DELETE_WINDOW") - 1, &WM_DELETE_WINDOW},
{"_XEMBED", sizeof("_XEMBED") - 1, &_XEMBED},
{"_XEMBED_INFO", sizeof("_XEMBED_INFO") - 1, &_XEMBED_INFO},
{"_NET_SYSTEM_TRAY_OPCODE", sizeof("_NET_SYSTEM_TRAY_OPCODE") - 1, &_NET_SYSTEM_TRAY_OPCODE},
{"MANAGER", sizeof("MANAGER") - 1, &MANAGER},
{"WM_STATE", sizeof("WM_STATE") - 1, &WM_STATE},
{"_NET_SYSTEM_TRAY_ORIENTATION", sizeof("_NET_SYSTEM_TRAY_ORIENTATION") - 1, &_NET_SYSTEM_TRAY_ORIENTATION},
{"WM_TAKE_FOCUS", sizeof("WM_TAKE_FOCUS") - 1, &WM_TAKE_FOCUS},
};
// clang-format on

View File

@ -0,0 +1,92 @@
#pragma once
#include <iomanip>
#include "common.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
union rgba {
struct {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
uint32_t v;
};
class color;
static map<string, color> g_colorstore;
class color {
public:
explicit color(string hex) : m_hex(string_util::upper(hex)) {
m_rgba.v = static_cast<uint32_t>(strtoul(&hex[1], nullptr, 16));
// premultiply alpha
m_rgba.r = m_rgba.r * m_rgba.a / 255;
m_rgba.g = m_rgba.g * m_rgba.a / 255;
m_rgba.b = m_rgba.b * m_rgba.a / 255;
}
explicit color(uint32_t v) {
char buffer[7];
snprintf(buffer, sizeof(buffer), "%06x", v);
m_hex = "#" + string{buffer};
m_rgba.v = v;
}
uint32_t value() const {
return m_rgba.v;
}
string rgb() const {
// clang-format off
return string_util::from_stream(stringstream()
<< "#"
<< std::setw(6)
<< std::setfill('0')
<< std::hex
<< std::uppercase
<< (m_rgba.v & 0x00FFFFFF));
// clang-format on
}
string hex() const {
return m_hex;
}
static auto parse(string input, color fallback) {
auto it = g_colorstore.find(input);
if (it != g_colorstore.end()) {
return it->second;
}
string hex{input};
if (hex.substr(0, 1) != "#")
hex = "#" + hex;
if (hex.length() == 4)
hex = {'#', hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]};
if (hex.length() == 7)
hex = "#FF" + hex.substr(1);
if (hex.length() != 9)
return fallback;
color result{hex};
g_colorstore.emplace(input, result);
return result;
}
static auto parse(string input) {
static color none{0};
return parse(input, none);
}
protected:
rgba m_rgba;
string m_hex;
};
static color g_colorblack{"#ff000000"};
static color g_colorwhite{"#ffffffff"};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,197 @@
#pragma once
#include <X11/X.h>
#include <X11/Xlib-xcb.h>
#include <xcb/xcb.h>
#include <iomanip>
#include <xpp/xpp.hpp>
#include "common.hpp"
#include "components/x11/atoms.hpp"
#include "components/x11/types.hpp"
#include "components/x11/xutils.hpp"
#include "utils/memory.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
using xpp_connection = xpp::connection<xpp::randr::extension>;
class connection : public xpp_connection {
public:
explicit connection() {}
explicit connection(xcb_connection_t* conn) : xpp_connection(conn) {}
connection& operator=(const connection&) {
return *this;
}
virtual ~connection() {}
/**
* Preload required xcb atoms
*/
auto preload_atoms() {
for (auto&& a : ATOMS) *a.atom = intern_atom(false, a.len, a.name).atom();
}
/**
* Check if required X extensions are available
*/
auto query_extensions() {
// damage().query_version(XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
// if (!extension<xpp::damage::extension>()->present)
// throw application_error("Missing X extension: Damage");
// render().query_version(XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION);
// if (!extension<xpp::render::extension>()->present)
// throw application_error("Missing X extension: Render");
randr().query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
if (!extension<xpp::randr::extension>()->present)
throw application_error("Missing X extension: RandR");
}
/**
* Create X window id string
*/
auto id(xcb_window_t w) const {
return string_util::from_stream(
std::stringstream() << "0x" << std::hex << std::setw(8) << std::setfill('0') << w);
}
/**
* Get pointer to the default xcb screen
*/
auto screen() {
if (m_screen == nullptr)
m_screen = screen_of_display(default_screen());
return m_screen;
}
/**
* Creates an instance of shared_ptr<xcb_client_message_event_t>
*/
auto make_client_message(xcb_atom_t type, xcb_window_t target) const {
auto client_message = memory_util::make_malloc_ptr<xcb_client_message_event_t>(size_t{32});
client_message->response_type = XCB_CLIENT_MESSAGE;
client_message->format = 32;
client_message->type = type;
client_message->window = target;
client_message->sequence = 0;
client_message->data.data32[0] = 0;
client_message->data.data32[1] = 0;
client_message->data.data32[2] = 0;
client_message->data.data32[3] = 0;
client_message->data.data32[4] = 0;
return client_message;
}
/**
* Send client message event
*/
void send_client_message(shared_ptr<xcb_client_message_event_t> message, xcb_window_t target,
uint32_t event_mask = 0xFFFFFF, bool propagate = false) {
send_event(propagate, target, event_mask, reinterpret_cast<char*>(message.get()));
flush();
}
/**
* Sends a dummy event to the specified window
* Used to interrupt blocking wait call
*
* @XXX: Find the proper way to interrupt the blocking wait
* except the obvious event polling
*/
auto send_dummy_event(
xcb_window_t target, uint32_t event = XCB_EVENT_MASK_STRUCTURE_NOTIFY) const {
if (target == XCB_NONE)
target = root();
auto message = make_client_message(XCB_NONE, target);
send_event(false, target, event, reinterpret_cast<char*>(message.get()));
flush();
}
/**
* Try to get a visual type for the given screen that
* matches the given depth
*/
optional<xcb_visualtype_t*> visual_type(xcb_screen_t* screen, int match_depth = 32) {
xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(screen);
if (depth_iter.data) {
for (; depth_iter.rem; xcb_depth_next(&depth_iter))
if (match_depth == 0 || match_depth == depth_iter.data->depth)
for (auto it = xcb_depth_visuals_iterator(depth_iter.data); it.rem;
xcb_visualtype_next(&it))
return it.data;
if (match_depth > 0)
return visual_type(screen, 0);
}
return {};
}
/**
* Parse connection error
*/
static string error_str(int error_code) {
switch (error_code) {
case XCB_CONN_ERROR:
return "Socket, pipe or stream error";
case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
return "Unsupported extension";
case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
return "Not enough memory";
case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
return "Request length exceeded";
case XCB_CONN_CLOSED_PARSE_ERR:
return "Can't parse display string";
case XCB_CONN_CLOSED_INVALID_SCREEN:
return "Invalid screen";
case XCB_CONN_CLOSED_FDPASSING_FAILED:
return "Failed to pass FD";
default:
return "Unknown error";
}
}
/**
* Attach sink to the registry
*/
template <typename Sink>
void attach_sink(Sink&& sink, registry::priority prio = 0) {
m_registry.attach(prio, forward<Sink>(sink));
}
/**
* Detach sink from the registry
*/
template <typename Sink>
void detach_sink(Sink&& sink, registry::priority prio = 0) {
m_registry.detach(prio, forward<Sink>(sink));
}
/**
* Dispatch event through the registry
*/
void dispatch_event(const shared_ptr<xcb_generic_event_t>& evt) {
m_registry.dispatch(forward<decltype(evt)>(evt));
}
/**
* Configure injection module
*/
template <typename T = connection&>
static di::injector<T> configure() {
return di::make_injector(di::bind<>().to(
factory::generic_singleton<lemonbuddy::connection>(xutils::get_connection())));
}
protected:
registry m_registry{*this};
xcb_screen_t* m_screen = nullptr;
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,61 @@
#pragma once
#include <xcb/xcbext.h>
#include "common.hpp"
#include "components/x11/color.hpp"
#include "components/x11/connection.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
namespace draw_util {
/**
* Fill region of drawable with color defined by gcontext
*/
inline void fill(connection& c, xcb_drawable_t d, xcb_gcontext_t g, int16_t x, int16_t y,
uint16_t w, uint16_t h) {
array<xcb_rectangle_t, 1> rects{{xcb_rectangle_t({x, y, w, h})}};
c.poly_fill_rectangle(d, g, rects.size(), rects.data());
}
/**
* The xcb version of this function does not compose the correct request
*
* Code: http://wmdia.sourceforge.net/
*/
auto xcb_poly_text_16_patched(xcb_connection_t* conn, xcb_drawable_t d, xcb_gcontext_t gc,
int16_t x, int16_t y, uint32_t len, uint16_t* str) {
static const xcb_protocol_request_t xcb_req = {
5, // count
0, // ext
XCB_POLY_TEXT_16, // opcode
1 // isvoid
};
struct iovec xcb_parts[7];
uint8_t xcb_lendelta[2];
xcb_void_cookie_t xcb_ret;
xcb_poly_text_8_request_t xcb_out;
xcb_out.pad0 = 0;
xcb_out.drawable = d;
xcb_out.gc = gc;
xcb_out.x = x;
xcb_out.y = y;
xcb_lendelta[0] = len;
xcb_lendelta[1] = 0;
xcb_parts[2].iov_base = reinterpret_cast<char*>(&xcb_out);
xcb_parts[2].iov_len = sizeof(xcb_out);
xcb_parts[3].iov_base = 0;
xcb_parts[3].iov_len = -xcb_parts[2].iov_len & 3;
xcb_parts[4].iov_base = xcb_lendelta;
xcb_parts[4].iov_len = sizeof(xcb_lendelta);
xcb_parts[5].iov_base = reinterpret_cast<char*>(str);
xcb_parts[5].iov_len = len * sizeof(int16_t);
xcb_parts[6].iov_base = 0;
xcb_parts[6].iov_len = -(xcb_parts[4].iov_len + xcb_parts[5].iov_len) & 3;
xcb_ret.sequence = xcb_send_request(conn, 0, xcb_parts + 2, &xcb_req);
return xcb_ret;
}
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,250 @@
#pragma once
#include <X11/Xft/Xft.h>
#include <X11/Xlib-xcb.h>
#include <xcb/xcbext.h>
#include <map>
#include <string>
#include "common.hpp"
#include "components/logger.hpp"
#include "components/x11/color.hpp"
#include "components/x11/connection.hpp"
#include "components/x11/types.hpp"
#include "components/x11/xlib.hpp"
#include "utils/memory.hpp"
#include "utils/mixins.hpp"
LEMONBUDDY_NS
#define XFT_MAXCHARS (1 << 16)
static array<char, XFT_MAXCHARS> xft_widths;
static array<wchar_t, XFT_MAXCHARS> xft_chars;
struct fonttype {
fonttype() {}
XftFont* xft;
xcb_font_t ptr;
int offset_y = 0;
int ascent = 0;
int descent = 0;
int height = 0;
int width = 0;
uint16_t char_max = 0;
uint16_t char_min = 0;
vector<xcb_charinfo_t> width_lut;
};
struct fonttype_deleter {
void operator()(fonttype* f) {
if (f->xft != nullptr)
XftFontClose(xlib::get_display(), f->xft);
else
xcb_close_font(xutils::get_connection(), f->ptr);
}
};
using font_t = unique_ptr<fonttype, fonttype_deleter>;
class fontmanager {
public:
explicit fontmanager(connection& conn, const logger& logger)
: m_connection(conn), m_logger(logger) {
m_display = xlib::get_display();
m_visual = xlib::get_visual();
m_colormap = XDefaultColormap(m_display, 0);
}
~fontmanager() {
XftColorFree(m_display, m_visual, m_colormap, &m_xftcolor);
XFreeColormap(m_display, m_colormap);
m_fonts.clear();
}
/**
* Configure injection module
*/
template <typename T = unique_ptr<fontmanager>>
static di::injector<T> configure() {
return di::make_injector(connection::configure(), logger::configure());
}
void set_preferred_font(int index) { // {{{
if (index <= 0) {
m_fontindex = -1;
return;
}
for (auto&& font : m_fonts) {
if (font.first == index) {
m_fontindex = index;
break;
}
}
} // }}}
bool load(string name, int fontindex = -1, int offset_y = 0) { // {{{
if (fontindex != -1 && m_fonts.find(fontindex) != m_fonts.end()) {
m_logger.warn("A font with index '%i' has already been loaded, skip...", fontindex);
return false;
} else if (fontindex == -1) {
fontindex = m_fonts.size();
m_logger.trace("fontmanager: Assign font '%s' to index '%d'", name.c_str(), fontindex);
} else {
m_logger.trace("fontmanager: Add font '%s' to index '%i'", name, fontindex);
}
m_fonts.emplace(make_pair(fontindex, font_t{new fonttype(), fonttype_deleter{}}));
m_fonts[fontindex]->offset_y = offset_y;
m_fonts[fontindex]->ptr = 0;
m_fonts[fontindex]->xft = nullptr;
if (open_xcb_font(m_fonts[fontindex], name)) {
m_logger.trace("fontmanager: Successfully loaded X font '%s'", name);
} else if ((m_fonts[fontindex]->xft = XftFontOpenName(m_display, 0, name.c_str())) != nullptr) {
m_fonts[fontindex]->ptr = 0;
m_fonts[fontindex]->ascent = m_fonts[fontindex]->xft->ascent;
m_fonts[fontindex]->descent = m_fonts[fontindex]->xft->descent;
m_fonts[fontindex]->height = m_fonts[fontindex]->ascent + m_fonts[fontindex]->descent;
m_logger.trace("fontmanager: Successfully loaded Freetype font '%s'", name);
} else {
return false;
}
int max_height = 0;
for (auto& iter : m_fonts)
if (iter.second->height > max_height)
max_height = iter.second->height;
for (auto& iter : m_fonts) {
iter.second->height = max_height;
}
return true;
} // }}}
font_t& match_char(uint16_t chr) { // {{{
static font_t notfound;
if (!m_fonts.empty()) {
if (m_fontindex != -1 && size_t(m_fontindex) <= m_fonts.size()) {
auto iter = m_fonts.find(m_fontindex);
if (iter != m_fonts.end() && has_glyph(iter->second, chr))
return iter->second;
}
for (auto& font : m_fonts) {
if (has_glyph(font.second, chr))
return font.second;
}
}
return notfound;
} // }}}
int char_width(font_t& font, uint16_t chr) { // {{{
if (!font)
return 0;
if (font->xft == nullptr) {
if (chr - font->char_min < font->width_lut.size())
return font->width_lut[chr - font->char_min].character_width;
else
return font->width;
}
auto index = chr % XFT_MAXCHARS;
while (xft_chars[index] != 0 && xft_chars[index] != chr) index = (index + 1) % XFT_MAXCHARS;
if (!xft_chars[index]) {
XGlyphInfo gi;
FT_UInt glyph = XftCharIndex(m_display, font->xft, (FcChar32)chr);
XftFontLoadGlyphs(m_display, font->xft, FcFalse, &glyph, 1);
XftGlyphExtents(m_display, font->xft, &glyph, 1, &gi);
XftFontUnloadGlyphs(m_display, font->xft, &glyph, 1);
xft_chars[index] = chr;
xft_widths[index] = gi.xOff;
return gi.xOff;
} else if (xft_chars[index] == chr) {
return xft_widths[index];
}
return 0;
} // }}}
XftColor xftcolor() { // {{{
return m_xftcolor;
} // }}}
void allocate_color(color xcolor, bool initial_alloc = false) { // {{{
if (!initial_alloc)
XftColorFree(m_display, m_visual, m_colormap, &m_xftcolor);
if (!XftColorAllocName(m_display, m_visual, m_colormap, xcolor.rgb().c_str(), &m_xftcolor))
m_logger.err("Failed to allocate color '%s'", xcolor.hex());
} // }}}
void set_gcontext_font(gcontext& gc, xcb_font_t font) { // {{{
const uint32_t values[]{font};
m_connection.change_gc(gc, XCB_GC_FONT, &values);
} // }}}
protected:
bool open_xcb_font(font_t& fontptr, string fontname) { // {{{
try {
font xfont(m_connection, m_connection.generate_id());
m_connection.open_font_checked(xfont, fontname);
m_logger.trace("Found X font '%s'", fontname);
auto query = m_connection.query_font(xfont);
fontptr->descent = query->font_descent;
fontptr->height = query->font_ascent + query->font_descent;
fontptr->width = query->max_bounds.character_width;
fontptr->char_max = query->max_byte1 << 8 | query->max_char_or_byte2;
fontptr->char_min = query->min_byte1 << 8 | query->min_char_or_byte2;
if (query->char_infos_len > 0) {
auto chars = query.char_infos();
for (auto it = chars.begin(); it != chars.end(); it++)
fontptr->width_lut.emplace_back(forward<xcb_charinfo_t>(*it));
}
fontptr->ptr = xfont;
return true;
} catch (const xpp::x::error::name& e) {
m_logger.trace("fontmanager: Could not find X font '%s'", fontname);
} catch (const shared_ptr<xcb_generic_error_t>& e) {
m_logger.trace("fontmanager: Could not find X font '%s'", fontname);
} catch (const std::exception& e) {
m_logger.trace("fontmanager: Could not find X font '%s' (what: %s)", fontname, e.what());
}
return false;
} // }}}
bool has_glyph(font_t& font, uint16_t chr) { // {{{
if (font->xft != nullptr) {
return XftCharExists(m_display, font->xft, (FcChar32)chr) == true;
} else {
if (chr < font->char_min || chr > font->char_max)
return false;
if (font->width_lut[chr - font->char_min].character_width == 0)
return false;
return true;
}
} // }}}
private:
connection& m_connection;
const logger& m_logger;
Display* m_display = nullptr;
Visual* m_visual = nullptr;
Colormap m_colormap;
map<int, font_t> m_fonts;
int m_fontindex = -1;
XftColor m_xftcolor;
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,64 @@
#pragma once
#include "common.hpp"
#include "components/x11/connection.hpp"
#include "utils/memory.hpp"
LEMONBUDDY_NS
struct monitor {
string name;
int w = 0;
int h = 0;
int x = 0;
int y = 0;
};
namespace randr_util {
/**
* Define monitor
*/
inline shared_ptr<monitor> make_monitor(string name, int w, int h, int x, int y) {
auto mon = make_shared<monitor>();
mon->name = name;
mon->x = x;
mon->y = y;
mon->h = h;
mon->w = w;
return mon;
}
/**
* Create a list of all available randr outputs
*/
inline vector<shared_ptr<monitor>> get_monitors(connection& conn, xcb_window_t root) {
vector<shared_ptr<monitor>> monitors;
auto outputs = conn.get_screen_resources(root).outputs();
for (auto it = outputs.begin(); it != outputs.end(); it++) {
auto info = conn.get_output_info(*it);
if (info->connection != XCB_RANDR_CONNECTION_CONNECTED)
continue;
auto crtc = conn.get_crtc_info(info->crtc);
string name{info.name().begin(), info.name().end()};
monitors.emplace_back(make_monitor(name, crtc->width, crtc->height, crtc->x, crtc->y));
}
// use the same sort algo as lemonbar, to match the defaults
sort(monitors.begin(), monitors.end(),
[](shared_ptr<monitor>& m1, shared_ptr<monitor>& m2) -> bool {
if (m1->x < m2->x || m1->y + m1->h <= m2->y)
return 1;
if (m1->x > m2->x || m1->y + m1->h > m2->y)
return -1;
return 0;
});
return monitors;
}
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,681 @@
#pragma once
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include <boost/core/null_deleter.hpp>
#include <fastdelegate/fastdelegate.hpp>
#include "common.hpp"
#include "components/logger.hpp"
#include "components/types.hpp"
#include "components/x11/connection.hpp"
#include "components/x11/xembed.hpp"
#include "utils/memory.hpp"
#include "utils/process.hpp"
#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
#define SYSTEM_TRAY_REQUEST_DOCK 0
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
#define TRAY_WM_NAME "Lemonbuddy tray window"
#define TRAY_WM_CLASS "tray\0Lemonbuddy"
LEMONBUDDY_NS
namespace tray_signals {
delegate::Signal1<uint16_t> report_slotcount;
};
// class definition : trayclient {{{
class trayclient {
public:
explicit trayclient(connection& conn, xcb_window_t win) : m_connection(conn), m_window(win) {
m_xembed = memory_util::make_malloc_ptr<xembed_data>();
m_xembed->version = XEMBED_VERSION;
m_xembed->flags = XEMBED_MAPPED;
}
/**
* Match given window against client window
*/
bool match(const xcb_window_t& win) const {
return win == m_window;
}
/**
* Get client window mapped state
*/
bool mapped() const {
return m_mapped;
}
/**
* Set client window mapped state
*/
void mapped(bool state) {
m_mapped = state;
}
/**
* Get client window
*/
xcb_window_t window() const {
return m_window;
}
/**
* Get xembed data pointer
*/
xembed_data* xembed() const {
return m_xembed.get();
}
protected:
connection& m_connection;
xcb_window_t m_window{0};
shared_ptr<xembed_data> m_xembed;
stateflag m_mapped{false};
};
// }}}
// class definition : traymanager {{{
class traymanager
: public xpp::event::sink<evt::expose, evt::visibility_notify, evt::client_message,
evt::configure_request, evt::selection_clear, evt::selection_notify, evt::property_notify,
evt::reparent_notify, evt::destroy_notify, evt::map_notify, evt::unmap_notify> {
public:
explicit traymanager(connection& conn, const logger& logger)
: m_connection(conn), m_logger(logger) {
m_connection.attach_sink(this, 2);
m_sinkattached = true;
}
~traymanager() {
if (m_activated)
deactivate();
if (m_sinkattached)
m_connection.detach_sink(this, 2);
}
/**
* Initialize data
*/
auto bootstrap(tray_settings settings) {
m_settings = settings;
query_atom();
create_window();
set_wmhints();
}
/**
* Activate systray management
*/
void activate() {
if (m_activated) {
// m_logger.warn("Tray is already activated...");
return;
}
m_logger.info("Activating traymanager");
if (!m_sinkattached) {
m_connection.attach_sink(this, 2);
m_sinkattached = true;
}
acquire_selection();
notify_clients();
m_activated = true;
m_connection.flush();
}
/**
* Deactivate systray management
*/
void deactivate() {
if (!m_activated) {
// m_logger.warn("Tray is already deactivated...");
return;
}
m_logger.info("Deactivating traymanager");
if (m_sinkattached) {
m_connection.detach_sink(this, 2);
m_sinkattached = false;
}
// Dismiss all clients by reparenting them to the root window
m_logger.trace("tray: Unembed clients");
for (auto&& client : m_clients) {
xembed::unembed(m_connection, client->window(), m_connection.root());
}
m_activated = false;
if (m_gotselection && !m_othermanager) {
m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME);
m_gotselection = false;
}
m_connection.unmap_window(m_tray);
m_connection.flush();
}
/**
* Reconfigure container window size and
* reposition embedded clients
*/
void reconfigure() {
uint32_t width = 0;
uint16_t mapped_clients = 0;
for (auto&& client : m_clients) {
if (client->mapped()) {
mapped_clients++;
width += m_settings.spacing + m_settings.width;
}
}
if (!tray_signals::report_slotcount.empty())
tray_signals::report_slotcount.emit(mapped_clients);
if (!width && m_mapped) {
m_connection.unmap_window(m_tray);
return;
} else if (width && !m_mapped) {
m_connection.map_window(m_tray);
m_lastwidth = 0;
return;
} else if (!width) {
return;
}
if ((width += m_settings.spacing) == m_lastwidth)
return;
m_lastwidth = width;
// update window
const uint32_t val[2]{static_cast<uint32_t>(calculate_x()), width};
m_connection.configure_window(m_tray, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH, val);
// reposition clients
uint32_t pos_x = m_settings.spacing;
for (auto&& client : m_clients) {
if (!client->mapped())
continue;
try {
const uint32_t val[1]{pos_x};
m_connection.configure_window_checked(client->window(), XCB_CONFIG_WINDOW_X, val);
pos_x += m_settings.width + m_settings.spacing;
} catch (const xpp::x::error::window& err) {
}
}
}
/**
* Configure injection module
*/
template <class T = unique_ptr<traymanager>>
static di::injector<T> configure() {
return di::make_injector(logger::configure(), connection::configure());
}
protected:
/**
* Calculate the tray window's horizontal position
*/
int16_t calculate_x() const {
auto x = m_settings.orig_x;
if (m_settings.align == alignment::RIGHT)
x -= ((m_settings.width + m_settings.spacing) * m_clients.size() + m_settings.spacing);
return x;
}
/**
* Calculate the tray window's vertical position
*/
int16_t calculate_y() const {
return m_settings.orig_y;
}
/**
* Find tray client by window
*/
shared_ptr<trayclient> find_client(const xcb_window_t& win) {
for (auto&& client : m_clients)
if (client->match(win)) {
return shared_ptr<trayclient>{client.get(), boost::null_deleter()};
}
return {};
}
/**
* Find the systray selection atom
*/
void query_atom() {
m_logger.trace("tray: Find systray selection atom for the default screen");
string name{"_NET_SYSTEM_TRAY_S" + to_string(m_connection.default_screen())};
auto reply = m_connection.intern_atom(false, name.length(), name.c_str());
m_atom = reply.atom();
}
/**
* Create tray window
*/
void create_window() {
auto x = calculate_x();
auto y = calculate_y();
m_tray = m_connection.generate_id();
m_logger.trace("tray: Create tray window %s, (%ix%i+%i+%i)", m_connection.id(m_tray),
m_settings.width, m_settings.height, x, y);
auto scr = m_connection.screen();
const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
const uint32_t values[2]{m_settings.background,
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
m_connection.create_window_checked(scr->root_depth, m_tray, scr->root, x, y,
m_settings.width + m_settings.spacing * 2, m_settings.height + m_settings.spacing * 2, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, values);
}
/**
* Set window WM hints
*/
void set_wmhints() {
m_logger.trace("tray: Set window WM_NAME / WM_CLASS", m_connection.id(m_tray));
xcb_icccm_set_wm_name(m_connection, m_tray, XCB_ATOM_STRING, 8, 22, TRAY_WM_NAME);
xcb_icccm_set_wm_class(m_connection, m_tray, 15, TRAY_WM_CLASS);
m_logger.trace("tray: Set window WM_PROTOCOLS");
vector<xcb_atom_t> wm_flags;
wm_flags.emplace_back(WM_DELETE_WINDOW);
wm_flags.emplace_back(WM_TAKE_FOCUS);
xcb_icccm_set_wm_protocols(
m_connection, m_tray, WM_PROTOCOLS, wm_flags.size(), wm_flags.data());
m_logger.trace("tray: Set window _NET_WM_WINDOW_TYPE");
vector<xcb_atom_t> types;
types.emplace_back(_NET_WM_WINDOW_TYPE_DOCK);
types.emplace_back(_NET_WM_WINDOW_TYPE_NORMAL);
m_connection.change_property(XCB_PROP_MODE_REPLACE, m_tray, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM,
32, types.size(), types.data());
m_logger.trace("tray: Set window _NET_WM_STATE");
vector<xcb_atom_t> states;
states.emplace_back(_NET_WM_STATE_SKIP_TASKBAR);
m_connection.change_property(XCB_PROP_MODE_REPLACE, m_tray, _NET_WM_STATE, XCB_ATOM_ATOM, 32,
states.size(), states.data());
m_logger.trace("tray: Set window _NET_SYSTEM_TRAY_ORIENTATION");
const uint32_t values[1]{_NET_SYSTEM_TRAY_ORIENTATION_HORZ};
m_connection.change_property_checked(XCB_PROP_MODE_REPLACE, m_tray,
_NET_SYSTEM_TRAY_ORIENTATION, _NET_SYSTEM_TRAY_ORIENTATION, 32, 1, values);
m_logger.trace("tray: Set window _NET_WM_PID");
int pid = getpid();
m_connection.change_property(
XCB_PROP_MODE_REPLACE, m_tray, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
}
/**
* Acquire the systray selection
*/
void acquire_selection() {
xcb_window_t owner = m_connection.get_selection_owner_unchecked(m_atom)->owner;
if (owner == m_tray) {
m_logger.info("tray: Already managing the systray selection");
return;
} else if (owner != XCB_NONE) {
m_logger.info("tray: Replace existing selection manager %s", m_connection.id(owner));
// track_selection_owner(owner);
}
m_logger.trace("tray: Change selection owner to %s", m_connection.id(m_tray));
m_connection.set_selection_owner_checked(m_tray, m_atom, XCB_CURRENT_TIME);
if (m_connection.get_selection_owner_unchecked(m_atom)->owner != m_tray)
throw application_error("Failed to get control of the systray selection");
m_gotselection = true;
m_othermanager = XCB_NONE;
}
/**
* Notify pending clients about the new systray MANAGER
*/
void notify_clients() {
m_logger.trace("tray: Broadcast new selection manager to pending clients");
auto message = m_connection.make_client_message(MANAGER, m_connection.root());
message->data.data32[0] = XCB_CURRENT_TIME;
message->data.data32[1] = m_atom;
message->data.data32[2] = m_tray;
m_connection.send_client_message(message, m_connection.root());
}
/**
* Track changes to the given selection owner
* If it gets destroyed or goes away we can reactivate the traymanager
*/
// void track_selection_owner(xcb_window_t owner = XCB_NONE) {
// try {
// if (owner != XCB_NONE) {
// m_othermanager = owner;
// } else {
// m_othermanager = m_connection.get_selection_owner_unchecked(m_atom)->owner;
// }
// m_logger.trace("tray: Listen for events on the new selection window");
// const uint32_t event_mask[1]{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
// m_connection.change_window_attributes_checked(m_othermanager, XCB_CW_EVENT_MASK, event_mask);
// } catch (const xpp::x::error::window& err) {
// m_logger.err("Failed to track selection owner");
// }
// }
/**
* Calculates a client's x position
*/
int calculate_client_xpos(const xcb_window_t& win) {
for (size_t i = 0; i < m_clients.size(); i++)
if (m_clients[i]->match(win))
return m_settings.spacing + m_settings.width * i;
return m_settings.spacing;
}
/**
* Process client docking request
*/
void process_docking_request(xcb_window_t win) {
auto client = find_client(win);
if (client) {
if (client->mapped()) {
m_logger.trace("tray: Client %s is already embedded, skipping...", m_connection.id(win));
} else {
m_logger.trace("tray: Refresh _XEMBED_INFO");
xembed::query(m_connection, win, client->xembed());
if ((client->xembed()->flags & XEMBED_MAPPED) == XEMBED_MAPPED) {
m_logger.trace("tray: XEMBED_MAPPED flag set, map client window...");
m_connection.map_window(client->window());
}
}
return;
}
m_logger.trace("tray: Process docking request from %s", m_connection.id(win));
m_clients.emplace_back(make_shared<trayclient>(m_connection, win));
client = m_clients.back();
try {
m_logger.trace("tray: Get client _XEMBED_INFO");
xembed::query(m_connection, win, client->xembed());
} catch (const application_error& err) {
m_logger.err(err.what());
}
m_logger.trace("tray: Add tray client window to the save set");
m_connection.change_save_set(XCB_SET_MODE_INSERT, client->window());
m_logger.trace("tray: Update tray client event mask");
const uint32_t event_mask[]{XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
m_connection.change_window_attributes(client->window(), XCB_CW_EVENT_MASK, event_mask);
m_logger.trace("tray: Reparent tray client");
m_connection.reparent_window(client->window(), m_tray, m_settings.spacing, m_settings.spacing);
m_logger.trace("tray: Configure tray client size");
const uint32_t values[]{m_settings.width, m_settings.height};
m_connection.configure_window(
client->window(), XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
m_logger.trace("tray: Send embbeded notification to tray client");
xembed::notify_embedded(m_connection, client->window(), m_tray, client->xembed()->version);
m_logger.trace("tray: Map tray client");
m_connection.map_window(client->window());
}
/**
* Event callback : XCB_EXPOSE
*/
void handle(const evt::expose& evt) {
if (!m_activated || m_clients.empty())
return;
m_logger.trace("tray: Received expose event");
reconfigure();
}
/**
* Event callback : XCB_VISIBILITY_NOTIFY
*/
void handle(const evt::visibility_notify& evt) {
if (!m_activated || m_clients.empty())
return;
m_logger.trace("tray: Received visibility_notify");
reconfigure();
}
/**
* Event callback : XCB_CLIENT_MESSAGE
*/
void handle(const evt::client_message& evt) {
if (evt->type == _NET_SYSTEM_TRAY_OPCODE && evt->format == 32) {
m_logger.trace("tray: Received client_message");
switch (evt->data.data32[1]) {
case SYSTEM_TRAY_REQUEST_DOCK:
process_docking_request(evt->data.data32[2]);
return;
case SYSTEM_TRAY_BEGIN_MESSAGE:
// process_messages(...);
return;
case SYSTEM_TRAY_CANCEL_MESSAGE:
// process_messages(...);
return;
}
} else if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW) {
if (evt->window == m_tray) {
m_logger.info("Received WM_DELETE; removing system tray");
m_logger.err("FIXME: disabled...");
std::exit(EXIT_FAILURE);
}
}
}
/**
* Event callback : XCB_CONFIGURE_REQUEST
*
* Called when a tray client thinks he's part of the free world and
* wants to reconfigure its window. This is of course nothing we appreciate
* so we return an answer that'll put him in place.
*/
void handle(const evt::configure_request& evt) {
if (!find_client(evt->window))
return;
m_logger.trace("tray: Received configure_request");
auto resp = reinterpret_cast<xcb_configure_notify_event_t*>(calloc(32, 1));
resp->response_type = XCB_CONFIGURE_NOTIFY;
resp->event = evt->window;
resp->window = evt->window;
resp->override_redirect = false;
resp->above_sibling = XCB_NONE;
resp->x = calculate_client_xpos(evt->window);
resp->y = m_settings.spacing;
resp->width = m_settings.width;
resp->height = m_settings.height;
resp->border_width = 0;
m_connection.send_event(
false, resp->event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast<char*>(resp));
m_connection.flush();
free(resp);
}
/**
* Event callback : XCB_SELECTION_CLEAR
*/
void handle(const evt::selection_clear& evt) {
if (evt->selection != m_atom)
return;
m_logger.trace("tray: Received selection_clear");
if (m_activated && evt->owner == m_tray) {
m_logger.warn("Lost systray selection, deactivating...");
m_othermanager = m_connection.get_selection_owner_unchecked(m_atom)->owner;
// track_selection_owner();
deactivate();
}
}
/**
* Event callback : XCB_SELECTION_NOTIFY
*/
void handle(const evt::selection_notify& evt) {
m_logger.trace("tray: Received selection_notify");
}
/**
* Event callback : XCB_PROPERTY_NOTIFY
*/
void handle(const evt::property_notify& evt) {
if (evt->atom != _XEMBED_INFO)
return;
m_logger.trace("tray: _XEMBED_INFO: %s", m_connection.id(evt->window));
auto client = find_client(evt->window);
if (!client)
return;
auto xd = client->xembed();
auto win = client->window();
if (evt->state == XCB_PROPERTY_NEW_VALUE)
m_logger.trace("tray: _XEMBED_INFO value has changed");
xembed::query(m_connection, win, xd);
m_logger.trace("tray: _XEMBED_INFO[0]=%u _XEMBED_INFO[1]=%u", xd->version, xd->flags);
if (!client->mapped() && ((xd->flags & XEMBED_MAPPED) == XEMBED_MAPPED)) {
m_logger.info("tray: Map client window: %s", m_connection.id(win));
m_connection.map_window(win);
} else if (client->mapped() && ((xd->flags & XEMBED_MAPPED) != XEMBED_MAPPED)) {
m_logger.info("tray: Unmap client window: %s", m_connection.id(win));
m_connection.unmap_window(win);
}
}
/**
* Event callback : XCB_REPARENT_NOTIFY
*/
void handle(const evt::reparent_notify& evt) {
if (evt->parent == m_tray)
return;
auto client = find_client(evt->window);
if (client) {
m_logger.trace("tray: Received reparent_notify");
m_logger.trace("tray: Remove tray client");
m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client));
reconfigure();
}
}
/**
* Event callback : XCB_DESTROY_NOTIFY
*/
void handle(const evt::destroy_notify& evt) {
auto client = find_client(evt->window);
if (!m_activated && evt->window == m_othermanager) {
m_logger.trace("tray: Received destroy_notify");
m_logger.trace("tray: Systray selection is available... activating");
activate();
} else if (client) {
m_logger.trace("tray: Received destroy_notify");
m_logger.trace("tray: Remove tray client");
m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client));
reconfigure();
}
}
/**
* Event callback : XCB_MAP_NOTIFY
*/
void handle(const evt::map_notify& evt) {
if (evt->window == m_tray && !m_mapped) {
m_logger.trace("tray: Received map_notify");
if (m_mapped)
return;
m_logger.trace("tray: Update container mapped flag");
m_mapped = true;
reconfigure();
} else {
auto client = find_client(evt->window);
if (client) {
m_logger.trace("tray: Received map_notify");
m_logger.trace("tray: Set client mapped");
client->mapped(true);
reconfigure();
}
}
}
/**
* Event callback : XCB_UNMAP_NOTIFY
*/
void handle(const evt::unmap_notify& evt) {
if (evt->window == m_tray) {
m_logger.trace("tray: Received unmap_notify");
if (!m_mapped)
return;
m_logger.trace("tray: Update container mapped flag");
m_mapped = false;
reconfigure();
} else {
auto client = find_client(evt->window);
if (client) {
m_logger.trace("tray: Received unmap_notify");
m_logger.trace("tray: Set client unmapped");
client->mapped(true);
reconfigure();
}
}
}
private:
connection& m_connection;
const logger& m_logger;
vector<shared_ptr<trayclient>> m_clients;
tray_settings m_settings;
xcb_atom_t m_atom{0};
xcb_window_t m_tray{0};
xcb_window_t m_othermanager{0};
uint32_t m_lastwidth;
stateflag m_activated{false};
stateflag m_mapped{false};
stateflag m_sinkattached{false};
stateflag m_gotselection{false};
};
// }}}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,95 @@
#pragma once
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_util.h>
#include <xpp/proto/randr.hpp>
#include <xpp/xpp.hpp>
#include "common.hpp"
#include "components/x11/connection.hpp"
LEMONBUDDY_NS
// xpp types {{{
class connection;
using registry = xpp::event::registry<connection&, xpp::randr::extension>;
using atom = xpp::atom<connection&>;
using colormap = xpp::colormap<connection&>;
using cursor = xpp::cursor<connection&>;
using drawable = xpp::drawable<connection&>;
using font = xpp::font<connection&>;
using gcontext = xpp::gcontext<connection&>;
using pixmap = xpp::pixmap<connection&>;
// }}}
namespace evt {
// window focus events {{{
using focus_in = xpp::x::event::focus_in<connection&>;
using focus_out = xpp::x::event::focus_out<connection&>;
// }}}
// cursor events {{{
using enter_notify = xpp::x::event::enter_notify<connection&>;
using leave_notify = xpp::x::event::leave_notify<connection&>;
using motion_notify = xpp::x::event::motion_notify<connection&>;
// }}}
// keyboard events {{{
using button_press = xpp::x::event::button_press<connection&>;
using button_release = xpp::x::event::button_release<connection&>;
using key_press = xpp::x::event::key_press<connection&>;
using key_release = xpp::x::event::key_release<connection&>;
using keymap_notify = xpp::x::event::keymap_notify<connection&>;
// }}}
// render events {{{
using circulate_notify = xpp::x::event::circulate_notify<connection&>;
using circulate_request = xpp::x::event::circulate_request<connection&>;
using colormap_notify = xpp::x::event::colormap_notify<connection&>;
using configure_notify = xpp::x::event::configure_notify<connection&>;
using configure_request = xpp::x::event::configure_request<connection&>;
using create_notify = xpp::x::event::create_notify<connection&>;
using destroy_notify = xpp::x::event::destroy_notify<connection&>;
using expose = xpp::x::event::expose<connection&>;
using graphics_exposure = xpp::x::event::graphics_exposure<connection&>;
using gravity_notify = xpp::x::event::gravity_notify<connection&>;
using map_notify = xpp::x::event::map_notify<connection&>;
using map_request = xpp::x::event::map_request<connection&>;
using mapping_notify = xpp::x::event::mapping_notify<connection&>;
using no_exposure = xpp::x::event::no_exposure<connection&>;
using reparent_notify = xpp::x::event::reparent_notify<connection&>;
using resize_request = xpp::x::event::resize_request<connection&>;
using unmap_notify = xpp::x::event::unmap_notify<connection&>;
using visibility_notify = xpp::x::event::visibility_notify<connection&>;
// }}}
// data events {{{
using client_message = xpp::x::event::client_message<connection&>;
using ge_generic = xpp::x::event::ge_generic<connection&>;
using property_notify = xpp::x::event::property_notify<connection&>;
// }}}
// selection events {{{
using selection_clear = xpp::x::event::selection_clear<connection&>;
using selection_notify = xpp::x::event::selection_notify<connection&>;
using selection_request = xpp::x::event::selection_request<connection&>;
// }}}
// randr events {{{
using randr_notify = xpp::randr::event::notify<connection&>;
using randr_screen_change_notify = xpp::randr::event::screen_change_notify<connection&>;
// }}}
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,165 @@
#pragma once
#include <stdint.h>
#include <xcb/xcb.h>
#include "common.hpp"
#include "components/x11/color.hpp"
#include "components/x11/connection.hpp"
#include "components/x11/xutils.hpp"
LEMONBUDDY_NS
using connection_t = connection;
class window : public xpp::window<connection_t&> {
public:
using xpp::window<connection_t&>::window;
explicit window(connection_t& conn) : xpp::window<connection_t&>(conn, conn.generate_id()) {}
window create_checked(
int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t mask, const xcb_params_cw_t* params) {
auto screen = connection().screen();
auto visual = connection().visual_type(screen, 32).get();
auto depth = (visual->visual_id == screen->root_visual) ? XCB_COPY_FROM_PARENT : 32;
uint32_t value_list[16];
xutils::pack_values(mask, params, value_list);
connection().create_window_checked(depth, operator*(), screen->root, x, y, w, h, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, visual->visual_id, mask, &value_list);
return *this;
}
window create_checked(uint16_t w, uint16_t h, uint32_t mask, const xcb_params_cw_t* params) {
return create_checked(0, 0, w, h, mask, params);
}
};
// struct cw_size {
// cw_size(uint16_t w, uint16_t h) : w(w), h(h){};
// uint16_t w;
// uint16_t h;
// };
// struct cw_pos {
// cw_pos(int16_t x, int16_t y) : x(x), y(y){};
// int16_t x;
// int16_t y;
// };
// struct cw_border {
// cw_border(uint16_t border_width) : border_width(border_width){};
// uint16_t border_width;
// };
// struct cw_class {
// cw_class(uint16_t class_) : class_(class_){};
// uint16_t class_;
// };
// struct cw_parent {
// cw_parent(xcb_window_t parent) : parent(parent){};
// xcb_window_t parent;
// };
// struct cw_depth {
// cw_depth(uint8_t depth) : depth(depth){};
// uint8_t depth;
// };
// struct cw_visual {
// cw_visual(xcb_visualid_t visualid) : visualid(visualid){};
// xcb_visualid_t visualid;
// };
// struct cw_mask {
// cw_mask(uint32_t mask) : mask(mask){};
// const uint32_t mask;
// };
// struct cw_params {
// cw_params(const xcb_params_cw_t* params) : params(params){};
// const xcb_params_cw_t* params;
// };
// struct cw_flush {
// cw_flush(bool checked = true) : checked(checked){};
// bool checked;
// };
// /**
// * Create X window
// *
// * Example usage:
// * @code cpp
// * auto win = winspec()
// * << cw_size(100, 200)
// * << cw_pos(10, -20)
// * << cw_border(9)
// * << cw_class(XCB_WINDOW_CLASS_INPUT_ONLY)
// * << cw_parent(0x000110a);
// * << cw_flush(false);
// * @endcode
// */
// class winspec {
// public:
// explicit winspec(connection& conn) : m_connection(conn) {}
//
// winspec& operator<<(cw_size w) {
// m_width = w.w;
// m_height = w.h;
// return *this;
// }
// winspec& operator<<(cw_pos p) {
// m_x = p.x;
// m_y = p.y;
// return *this;
// }
// winspec& operator<<(cw_border b) {
// m_border = b.border_width;
// return *this;
// }
// winspec& operator<<(cw_class c) {
// m_class = c.class_;
// return *this;
// }
// winspec& operator<<(cw_parent p) {
// m_parent = p.parent;
// return *this;
// }
// winspec& operator<<(cw_depth d) {
// m_depth = d.depth;
// return *this;
// }
// winspec& operator<<(cw_visual v) {
// m_visual = v.visualid;
// return *this;
// }
// winspec& operator<<(cw_mask m) {
// m_mask = m.mask;
// return *this;
// }
// winspec& operator<<(cw_params p) {
// m_params = p.params;
// return *this;
// }
//
// window operator<<(cw_flush f) {
// if (f.checked)
// m_connection.create_window_checked(m_depth, m_window, m_parent, m_x, m_y, m_width, m_height,
// m_border, m_class, m_visual, m_mask, m_params);
// else
// m_connection.create_window(m_depth, m_window, m_parent, m_x, m_y, m_width, m_height, m_border,
// m_class, m_visual, m_mask, m_params);
// return m_window;
// }
//
// protected:
// connection& m_connection;
// window m_window{m_connection};
//
// uint8_t m_depth;
// xcb_window_t m_parent;
// int16_t m_x;
// int16_t m_y;
// uint16_t m_width;
// uint16_t m_height;
// uint16_t m_border;
// uint16_t m_class;
// xcb_visualid_t m_visual;
// uint32_t m_mask;
// const xcb_params_cw_t* m_params;
// };
LEMONBUDDY_NS_END

View File

@ -0,0 +1,124 @@
#pragma once
#include <xcb/xcb.h>
#include "common.hpp"
#include "components/x11/connection.hpp"
LEMONBUDDY_NS
#define XEMBED_VERSION 0
#define XEMBED_MAPPED (1 << 0)
#define XEMBED_EMBEDDED_NOTIFY 0
#define XEMBED_WINDOW_ACTIVATE 1
#define XEMBED_WINDOW_DEACTIVATE 2
#define XEMBED_REQUEST_FOCUS 3
#define XEMBED_FOCUS_IN 3
#define XEMBED_FOCUS_OUT 4
#define XEMBED_FOCUS_NEXT 5
#define XEMBED_FOCUS_PREV 6
#define XEMBED_FOCUS_CURRENT 0
#define XEMBED_FOCUS_FIRST 1
#define XEMBED_FOCUS_LAST 1
struct xembed_data {
unsigned long version;
unsigned long flags;
xcb_timestamp_t time;
xcb_atom_t xembed;
xcb_atom_t xembed_info;
};
namespace xembed {
/**
* Query _XEMBED_INFO for the given window
*/
xembed_data* query(connection& conn, xcb_window_t win, xembed_data* data) {
auto info = conn.get_property(false, win, _XEMBED_INFO, XCB_GET_PROPERTY_TYPE_ANY, 0L, 2);
if (info->value_len == 0)
throw application_error("Invalid _XEMBED_INFO for window " + conn.id(win));
std::vector<uint32_t> xembed_data{info.value<uint32_t>().begin(), info.value<uint32_t>().end()};
data->xembed = _XEMBED;
data->xembed_info = _XEMBED_INFO;
data->time = XCB_CURRENT_TIME;
data->flags = xembed_data[1];
data->version = xembed_data[0];
return data;
}
/**
* Send _XEMBED messages
*/
inline auto send_message(
connection& conn, xcb_window_t target, long message, long d1, long d2, long d3) {
auto msg = conn.make_client_message(_XEMBED, target);
msg->data.data32[0] = XCB_CURRENT_TIME;
msg->data.data32[1] = message;
msg->data.data32[2] = d1;
msg->data.data32[3] = d2;
msg->data.data32[4] = d3;
conn.send_client_message(msg, target);
}
/**
* Send window focus event
*/
inline auto send_focus_event(connection& conn, xcb_window_t target) {
auto msg = conn.make_client_message(WM_PROTOCOLS, target);
msg->data.data32[0] = WM_TAKE_FOCUS;
msg->data.data32[1] = XCB_CURRENT_TIME;
conn.send_client_message(msg, target);
}
/**
* Acknowledge window embedding
*/
inline auto notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, long version) {
send_message(conn, win, XEMBED_EMBEDDED_NOTIFY, 0, embedder, version);
}
/**
* Send window activate notification
*/
inline auto notify_activated(connection& conn, xcb_window_t win) {
send_message(conn, win, XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
}
/**
* Send window deactivate notification
*/
inline auto notify_deactivated(connection& conn, xcb_window_t win) {
send_message(conn, win, XEMBED_WINDOW_DEACTIVATE, 0, 0, 0);
}
/**
* Send window focused notification
*/
inline auto notify_focused(connection& conn, xcb_window_t win, long focus_type) {
send_message(conn, win, XEMBED_FOCUS_IN, focus_type, 0, 0);
}
/**
* Send window unfocused notification
*/
inline auto notify_unfocused(connection& conn, xcb_window_t win) {
send_message(conn, win, XEMBED_FOCUS_OUT, 0, 0, 0);
}
/**
* Unembed given window
*/
inline auto unembed(connection& conn, xcb_window_t win, xcb_window_t root) {
conn.unmap_window(win);
conn.reparent_window(win, root, 0, 0);
}
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,67 @@
#pragma once
#include <X11/Xutil.h>
#include "common.hpp"
#include "components/x11/xutils.hpp"
LEMONBUDDY_NS
namespace xlib {
static Display* g_display = nullptr;
static Visual* g_visual = nullptr;
/**
* Get pointer of Xlib Display
*/
inline Display* get_display() {
if (g_display == nullptr)
g_display = XOpenDisplay(nullptr);
return g_display;
}
/**
* Get pointer of Xlib visual
*/
inline Visual* get_visual() {
if (g_visual == nullptr) {
XVisualInfo xv;
xv.depth = 32;
int result = 0;
auto result_ptr = XGetVisualInfo(get_display(), VisualDepthMask, &xv, &result);
if (result > 0)
g_visual = result_ptr->visual;
else
g_visual = XDefaultVisual(get_display(), 0);
free(result_ptr);
}
return g_visual;
}
/**
* RAII wrapper for Xlib display locking
*/
// class xlib_lock {
// public:
// explicit xlib_lock(Display* display) {
// XLockDisplay(display);
// m_display = display;
// }
//
// ~xlib_lock() {
// XUnlockDisplay(m_display);
// }
//
// protected:
// Display* m_display;
// };
// inline auto make_display_lock() {
// return make_unique<xlib_lock>(get_display());
// }
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,37 @@
#pragma once
#include "common.hpp"
#include "components/x11/types.hpp"
#include "components/x11/xlib.hpp"
LEMONBUDDY_NS
namespace xutils {
static xcb_connection_t* g_connection_ptr = nullptr;
inline xcb_connection_t* get_connection() {
if (g_connection_ptr == nullptr) {
Display* dsp;
if ((dsp = xlib::get_display()) == nullptr)
return nullptr;
XSetEventQueueOwner(dsp, XCBOwnsEventQueue);
g_connection_ptr = XGetXCBConnection(dsp);
}
return g_connection_ptr;
}
inline void pack_values(uint32_t mask, const uint32_t* src, uint32_t* dest) {
for (; mask; mask >>= 1, src++)
if (mask & 1)
*dest++ = *src;
}
inline void pack_values(uint32_t mask, const xcb_params_cw_t* src, uint32_t* dest) {
xutils::pack_values(mask, reinterpret_cast<const uint32_t*>(src), dest);
}
inline void pack_values(uint32_t mask, const xcb_params_gc_t* src, uint32_t* dest) {
xutils::pack_values(mask, reinterpret_cast<const uint32_t*>(src), dest);
}
}
LEMONBUDDY_NS_END

View File

@ -1,12 +1,21 @@
#pragma once
#include <iostream>
#include "version.hpp"
#define APP_NAME "@PROJECT_NAME@"
#define BASE_PATH "@PROJECT_SOURCE_DIR@"
#cmakedefine01 ENABLE_ALSA
#cmakedefine01 ENABLE_MPD
#cmakedefine01 ENABLE_NETWORK
#cmakedefine01 ENABLE_I3
#cmakedefine DISABLE_MODULES
#cmakedefine DISABLE_TRAY
#cmakedefine DISABLE_DRAW
#define BUILDER_SPACE_TOKEN "%__"
#define ALSA_SOUNDCARD "@SETTING_ALSA_SOUNDCARD@"
#define CONNECTION_TEST_IP "@SETTING_CONNECTION_TEST_IP@"
@ -18,3 +27,29 @@
#define BSPWM_STATUS_PREFIX "@SETTING_BSPWM_STATUS_PREFIX@"
#define PATH_CPU_INFO "@SETTING_PATH_CPU_INFO@"
#define PATH_MEMORY_INFO "@SETTING_PATH_MEMORY_INFO@"
auto print_build_info = []() {
// clang-format off
std::cout << APP_NAME << " " << GIT_TAG
<< "\n\n"
<< "Built with: "
<< (ENABLE_ALSA ? "+" : "-") << "alsa "
<< (ENABLE_I3 ? "+" : "-") << "i3 "
<< (ENABLE_MPD ? "+" : "-") << "mpd "
<< (ENABLE_NETWORK ? "+" : "-") << "network "
<< "\n\n"
<< "ALSA_SOUNDCARD " << ALSA_SOUNDCARD << "\n"
<< "BSPWM_SOCKET_PATH " << BSPWM_SOCKET_PATH << "\n"
<< "BSPWM_STATUS_PREFIX " << BSPWM_STATUS_PREFIX << "\n"
<< "BUILDER_SPACE_TOKEN " << BUILDER_SPACE_TOKEN << "\n"
<< "CONNECTION_TEST_IP " << CONNECTION_TEST_IP << "\n"
<< "PATH_ADAPTER_STATUS " << PATH_ADAPTER_STATUS << "\n"
<< "PATH_BACKLIGHT_MAX " << PATH_BACKLIGHT_MAX << "\n"
<< "PATH_BACKLIGHT_VAL " << PATH_BACKLIGHT_VAL << "\n"
<< "PATH_BATTERY_CAPACITY " << PATH_BATTERY_CAPACITY << "\n"
<< "PATH_CPU_INFO " << PATH_CPU_INFO << "\n"
<< "PATH_MEMORY_INFO " << PATH_MEMORY_INFO << "\n";
// clang-format on
};
// vim:ft=cpp

21
include/debug.hpp Normal file
View File

@ -0,0 +1,21 @@
#ifdef DEBUG
#pragma once
#include <iostream>
template <class T>
void benchmark_execution_speed(const T& expr) noexcept {
auto start = std::chrono::high_resolution_clock::now();
expr();
auto finish = std::chrono::high_resolution_clock::now();
std::cout << "execution speed: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(finish - start).count() << "ms"
<< std::endl;
}
template <class T>
void benchmark_memory_usage(const T& object) noexcept {
std::cout << "memory usage: " << sizeof(object) << "b" << std::endl;
}
#endif

View File

@ -1,38 +1,83 @@
#pragma once
#include <string>
#include <vector>
#include <chrono>
#include "common.hpp"
#include "components/config.hpp"
#include "drawtypes/label.hpp"
#include "utils/mixins.hpp"
#include "drawtypes/icon.hpp"
LEMONBUDDY_NS
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;
namespace drawtypes {
class animation;
using animation_t = shared_ptr<animation>;
void tick();
class animation : public non_copyable_mixin<animation> {
public:
explicit animation(int framerate_ms) : m_framerate_ms(framerate_ms) {}
explicit animation(vector<icon_t>&& frames, int framerate_ms)
: m_frames(forward<decltype(frames)>(frames))
, m_framerate_ms(framerate_ms)
, m_framecount(m_frames.size())
, m_lastupdate(chrono::system_clock::now()) {}
public:
Animation(std::vector<std::unique_ptr<Icon>> &&frames, int framerate_ms = 1);
explicit Animation(int framerate_ms)
: framerate_ms(framerate_ms){}
void add(icon_t&& frame) {
m_frames.emplace_back(forward<decltype(frame)>(frame));
m_framecount = m_frames.size();
}
void add(std::unique_ptr<Icon> &&frame);
icon_t get() {
tick();
return m_frames[m_frame];
}
std::unique_ptr<Icon> &get();
int framerate() {
return m_framerate_ms;
}
int get_framerate();
operator bool() {
return !m_frames.empty();
}
operator bool() {
return !this->frames.empty();
}
protected:
vector<icon_t> m_frames;
int m_framerate_ms = 1000;
int m_frame = 0;
int m_framecount = 0;
chrono::system_clock::time_point m_lastupdate;
void tick() {
auto now = chrono::system_clock::now();
auto diff = chrono::duration_cast<chrono::milliseconds>(now - m_lastupdate);
if (diff.count() < m_framerate_ms)
return;
if (++m_frame >= m_framecount)
m_frame = 0;
m_lastupdate = now;
}
};
std::unique_ptr<Animation> get_config_animation(std::string config_path, std::string animation_name = "animation", bool required = true);
inline auto get_config_animation(
const config& conf, string section, string name = "animation", bool required = true) {
vector<icon_t> vec;
vector<string> frames;
name = string_util::ltrim(string_util::rtrim(name, '>'), '<');
if (required)
frames = conf.get_list<string>(section, name);
else
frames = conf.get_list<string>(section, name, {});
for (int i = 0; i < (int)frames.size(); i++)
vec.emplace_back(forward<icon_t>(
get_optional_config_icon(conf, section, name + "-" + to_string(i), frames[i])));
auto framerate = conf.get<int>(section, name + "-framerate_ms", 1000);
return animation_t{new animation(move(vec), framerate)};
}
}
LEMONBUDDY_NS_END

View File

@ -1,42 +0,0 @@
#pragma once
#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 = false;
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, std::string fmt, 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(std::string config_path, std::string bar_name = "bar", bool lazy_builder_closing = true);
}

View File

@ -1,38 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "drawtypes/label.hpp"
namespace drawtypes
{
struct Icon : public Label
{
Icon(std::string icon, int font = 0)
: Label(icon, font){}
Icon(std::string icon, std::string fg, std::string bg = "", std::string ul = "", 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(std::string id, std::unique_ptr<Icon> &&icon);
std::unique_ptr<Icon> &get(std::string id, std::string fallback_id = "");
bool has(std::string id);
operator bool() {
return this->icons.size() > 0;
}
};
std::unique_ptr<Icon> get_config_icon(std::string module_name, std::string icon_name = "icon", bool required = true, std::string def = "");
std::unique_ptr<Icon> get_optional_config_icon(std::string module_name, std::string icon_name = "icon", std::string def = "");
}

View File

@ -0,0 +1,38 @@
#pragma once
#include "common.hpp"
#include "drawtypes/label.hpp"
#include "utils/mixins.hpp"
LEMONBUDDY_NS
namespace drawtypes {
class iconset : public non_copyable_mixin<iconset> {
public:
void add(string id, icon_t&& icon) {
m_icons.emplace(id, forward<decltype(icon)>(icon));
}
bool has(string id) {
return m_icons.find(id) != m_icons.end();
}
icon_t get(string id, string fallback_id = "") {
auto icon = m_icons.find(id);
if (icon == m_icons.end())
return m_icons.find(fallback_id)->second;
return icon->second;
}
operator bool() {
return m_icons.size() > 0;
}
protected:
map<string, icon_t> m_icons;
};
using iconset_t = shared_ptr<iconset>;
}
LEMONBUDDY_NS_END

View File

@ -1,30 +1,104 @@
#pragma once
#include <string>
#include <memory>
#include "common.hpp"
#include "components/config.hpp"
#include "utils/mixins.hpp"
namespace drawtypes
{
struct Label
{
std::string text, fg, bg, ul, ol;
int font = 0, padding = 0, margin = 0;
LEMONBUDDY_NS
Label(std::string text, int font)
: text(text), font(font){}
Label(std::string text, std::string fg = "", std::string bg = "", std::string ul = "", 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){}
namespace drawtypes {
class label;
using icon = label;
using label_t = shared_ptr<label>;
using icon_t = label_t;
class label : public non_copyable_mixin<label> {
public:
string m_text{""};
string m_foreground{""};
string m_background{""};
string m_underline{""};
string m_overline{""};
int m_font{0};
int m_padding{0};
int m_margin{0};
size_t m_maxlen{0};
bool m_ellipsis{true};
explicit label(string text, int font) : m_text(text), m_font(font) {}
explicit label(string text, string foreground = "", string background = "",
string underline = "", string overline = "", int font = 0, int padding = 0, int margin = 0,
size_t maxlen = 0, bool ellipsis = true)
: m_text(text)
, m_foreground(foreground)
, m_background(background)
, m_underline(underline)
, m_overline(overline)
, m_font(font)
, m_padding(padding)
, m_margin(margin)
, m_maxlen(maxlen)
, m_ellipsis(ellipsis) {}
operator bool() {
return !this->text.empty();
return !m_text.empty();
}
std::unique_ptr<Label> clone();
label_t clone() {
return label_t{new label(m_text, m_foreground, m_background, m_underline, m_overline, m_font,
m_padding, m_margin, m_maxlen, m_ellipsis)};
}
void replace_token(std::string token, std::string replacement);
void replace_defined_values(std::unique_ptr<Label> &label);
void replace_token(string token, string replacement) {
m_text = string_util::replace_all(m_text, token, replacement);
}
void replace_defined_values(label_t label) {
if (!label->m_foreground.empty())
m_foreground = label->m_foreground;
if (!label->m_background.empty())
m_background = label->m_background;
if (!label->m_underline.empty())
m_underline = label->m_underline;
if (!label->m_overline.empty())
m_overline = label->m_overline;
}
};
std::unique_ptr<Label> get_config_label(std::string module_name, std::string label_name = "label", bool required = true, std::string def = "");
std::unique_ptr<Label> get_optional_config_label(std::string module_name, std::string label_name = "label", std::string def = "");
inline label_t get_config_label(const config& conf, string section, string name = "label",
bool required = true, string def = "") {
string text;
name = string_util::ltrim(string_util::rtrim(name, '>'), '<');
if (required)
text = conf.get<string>(section, name);
else
text = conf.get<string>(section, name, def);
return label_t{new label(text, conf.get<string>(section, name + "-foreground", ""),
conf.get<string>(section, name + "-background", ""),
conf.get<string>(section, name + "-underline", ""),
conf.get<string>(section, name + "-overline", ""),
conf.get<int>(section, name + "-font", 0), conf.get<int>(section, name + "-padding", 0),
conf.get<int>(section, name + "-margin", 0), conf.get<size_t>(section, name + "-maxlen", 0),
conf.get<bool>(section, name + "-ellipsis", true))};
}
inline label_t get_optional_config_label(
const config& conf, string section, string name = "label", string def = "") {
return get_config_label(conf, section, name, false, def);
}
inline icon_t get_config_icon(const config& conf, string section, string name = "icon",
bool required = true, string def = "") {
return get_config_label(conf, section, name, required, def);
}
inline icon_t get_optional_config_icon(
const config& conf, string section, string name = "icon", string def = "") {
return get_config_icon(conf, section, name, false, def);
}
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,137 @@
#pragma once
#include "common.hpp"
#include "components/builder.hpp"
#include "components/config.hpp"
#include "components/types.hpp"
#include "drawtypes/label.hpp"
#include "utils/mixins.hpp"
LEMONBUDDY_NS
namespace drawtypes {
class progressbar : public non_copyable_mixin<progressbar> {
public:
explicit progressbar(
const bar_settings& bar, int width, string format, bool lazy_builder_closing)
: m_builder(make_unique<builder>(bar, lazy_builder_closing))
, m_format(format)
, m_width(width) {}
explicit progressbar(const bar_settings& bar, int width, bool lazy_builder_closing = true)
: progressbar(bar, width, "<fill><indicator><empty>", lazy_builder_closing) {}
void set_fill(icon_t&& fill) {
m_fill = forward<decltype(fill)>(fill);
}
void set_empty(icon_t&& empty) {
m_empty = forward<decltype(empty)>(empty);
}
void set_indicator(icon_t&& indicator) {
m_indicator = forward<decltype(indicator)>(indicator);
}
void set_gradient(bool mode) {
m_gradient = mode;
}
void set_colors(vector<string>&& colors) {
m_colors = forward<decltype(colors)>(colors);
}
string output(float percentage, bool floor_percentage = false) {
if (m_colors.empty())
m_colors.emplace_back(m_fill->m_foreground);
float add = floor_percentage ? 0 : 0.5;
int fill_width = (int)m_width * percentage / 100 + add;
int empty_width = m_width - fill_width;
int color_step = m_width / m_colors.size() + 0.5f;
auto output = string(m_format);
if (m_indicator && *m_indicator) {
if (empty_width == 1)
empty_width = 0;
else if (fill_width == 0)
empty_width--;
else
fill_width--;
}
if (!m_gradient) {
auto color = m_colors.size() * percentage / 100;
if (color >= m_colors.size())
color = 0;
m_fill->m_foreground = m_colors[color - 1];
while (fill_width--) m_builder->node(m_fill);
} else {
int i = 0;
for (auto color : m_colors) {
i += 1;
int j = 0;
if ((i - 1) * color_step >= fill_width)
break;
m_fill->m_foreground = color;
while (j++ < color_step && (i - 1) * color_step + j <= fill_width)
m_builder->node(m_fill);
}
}
output = string_util::replace_all(output, "%fill%", m_builder->flush());
m_builder->node(m_indicator);
output = string_util::replace_all(output, "%indicator%", m_builder->flush());
while (empty_width--) m_builder->node(m_empty);
output = string_util::replace_all(output, "%empty%", m_builder->flush());
return output;
}
protected:
unique_ptr<builder> m_builder;
vector<string> m_colors;
string m_format;
unsigned int m_width;
bool m_gradient = false;
icon_t m_fill;
icon_t m_empty;
icon_t m_indicator;
};
using progressbar_t = shared_ptr<progressbar>;
inline auto get_config_bar(const bar_settings& bar, const config& conf, string section,
string name = "bar", bool lazy_builder_closing = true) {
progressbar_t p;
name = string_util::ltrim(string_util::rtrim(name, '>'), '<');
auto width = conf.get<int>(section, name + "-width");
auto format = conf.get<string>(section, name + "-format", "%fill%%indicator%%empty%");
if (format.empty())
p.reset(new progressbar(bar, width, lazy_builder_closing));
else
p.reset(new progressbar(bar, width, format, lazy_builder_closing));
p->set_gradient(conf.get<bool>(section, name + "-gradient", true));
p->set_colors(conf.get_list<string>(section, name + "-foreground", {}));
p->set_indicator(get_config_icon(
conf, section, name + "-indicator", format.find("%indicator%") != string::npos, ""));
p->set_fill(
get_config_icon(conf, section, name + "-fill", format.find("%fill%") != string::npos, ""));
p->set_empty(get_config_icon(
conf, section, name + "-empty", format.find("%empty%") != string::npos, ""));
return p;
}
}
LEMONBUDDY_NS_END

View File

@ -1,31 +1,65 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "common.hpp"
#include "components/config.hpp"
#include "drawtypes/label.hpp"
#include "utils/mixins.hpp"
#include "drawtypes/icon.hpp"
LEMONBUDDY_NS
namespace drawtypes
{
class Ramp
{
protected:
std::vector<std::unique_ptr<Icon>> icons;
namespace drawtypes {
class ramp;
using ramp_t = shared_ptr<ramp>;
public:
Ramp(){}
explicit Ramp(std::vector<std::unique_ptr<Icon>> icons);
class ramp : public non_copyable_mixin<ramp> {
public:
explicit ramp() = default;
explicit ramp(vector<icon_t>&& icons) : m_icons(forward<decltype(icons)>(icons)) {}
void add(std::unique_ptr<Icon> &&icon);
std::unique_ptr<Icon> &get(int idx);
std::unique_ptr<Icon> &get_by_percentage(float percentage);
void add(icon_t&& icon) {
m_icons.emplace_back(forward<decltype(icon)>(icon));
}
operator bool() {
return this->icons.size() > 0;
}
icon_t get(int index) {
return m_icons[index];
}
icon_t get_by_percentage(float percentage) {
return m_icons[static_cast<int>(percentage * (m_icons.size() - 1) / 100.0f + 0.5f)];
}
operator bool() {
return m_icons.size() > 0;
}
protected:
vector<icon_t> m_icons;
};
std::unique_ptr<Ramp> get_config_ramp(std::string module_name, std::string ramp_name = "ramp", bool required = true);
inline auto get_config_ramp(
const config& conf, string section, string name = "ramp", bool required = true) {
vector<icon_t> vec;
name = string_util::ltrim(string_util::rtrim(name, '>'), '<');
vector<string> icons;
if (required)
icons = conf.get_list<string>(section, name);
else
icons = conf.get_list<string>(section, name, {});
auto foreground = conf.get<string>(section, name + "-foreground", "");
for (int i = 0; i < (int)icons.size(); i++) {
auto ramp = name + "-" + to_string(i);
auto icon = get_optional_config_icon(conf, section, ramp, icons[i]);
if (icon->m_foreground.empty() && !foreground.empty())
icon->m_foreground = foreground;
vec.emplace_back(std::move(icon));
}
return ramp_t{new ramp(std::move(vec))};
}
}
LEMONBUDDY_NS_END

View File

@ -1,53 +0,0 @@
#pragma once
#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 { 0 };
std::thread t_write;
std::thread t_read;
int fd_stdin = STDIN_FILENO;
int fd_stdout = STDOUT_FILENO;
std::string pipe_filename;
// <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:
explicit EventLoop(std::string input_pipe);
void start();
void stop();
void wait();
void cleanup(int timeout_ms = 5000);
};

View File

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

View File

@ -1,93 +0,0 @@
#pragma once
#include <functional>
#include <string>
#include <stdio.h>
#include <alsa/asoundlib.h>
#include <mutex>
#include "exception.hpp"
#include "utils/concurrency.hpp"
#include "utils/macros.hpp"
#define StrSndErr(s) ToStr(snd_strerror(s))
namespace alsa
{
// Errors {{{
class Exception : public ::Exception
{
public:
explicit Exception(std::string msg) : ::Exception("[Alsa] "+ msg){}
};
class ControlInterfaceError : public Exception
{
public:
ControlInterfaceError(int code, std::string msg)
: Exception(msg +" ["+ std::to_string(code) +"]") {}
};
class MixerError : public Exception {
using Exception::Exception;
};
// }}}
// ControlInterface {{{
class ControlInterface
{
concurrency::SpinLock lock;
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:
explicit ControlInterface(int numid);
~ControlInterface();
ControlInterface(const ControlInterface &) = delete;
ControlInterface &operator=(const ControlInterface &) = delete;
bool wait(int timeout = -1);
void process_events();
bool test_device_plugged();
};
// }}}
// Mixer {{{
class Mixer
{
concurrency::SpinLock lock;
snd_mixer_t *hardware_mixer = nullptr;
snd_mixer_elem_t *mixer_element = nullptr;
public:
explicit Mixer(std::string mixer_control_name);
~Mixer();
Mixer(const Mixer &) = delete;
Mixer &operator=(const Mixer &) = delete;
bool wait(int timeout = -1);
int process_events();
int get_volume();
void set_volume(float percentage);
void set_mute(bool mode);
void toggle_mute();
bool is_muted();
protected:
void error_handler(std::string message);
};
// }}}
}

View File

@ -1,188 +0,0 @@
#pragma once
#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(std::string msg, bool clearable)
: ::Exception(msg + (clearable ? " (clearable)" : " (not clearable)")){}
};
class ClientError : public Exception
{
public:
explicit ClientError(std::string msg, mpd_error code, bool clearable)
: Exception("[mpd::ClientError::"+ std::to_string(code) +"] "+ msg, clearable){}
};
class ServerError : public Exception
{
public:
ServerError(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(){}
explicit 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;
}
};
class Connection;
struct Status
{
struct StatusDeleter
{
void operator()(mpd_status *status) {
mpd_status_free(status);
}
};
explicit 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, std::unique_ptr<Connection>& connection);
void update(int event, Connection *connection);
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;
std::string password;
int port = 6600;
int timeout = 15;
bool mpd_command_list_active = false;
bool mpd_idle = false;
int mpd_fd = -1;
void check_prerequisites();
void check_prerequisites_commands_list();
public:
Connection(std::string host, int port, std::string password)
: host(host), password(password), port(port) {}
void connect();
void disconnect();
bool connected();
// bool retry_connection(int interval = 1);
void idle();
int noidle();
void set_host(std::string host) { this->host = host; }
void set_port(int port) { this->port = port; }
void set_password(std::string password) { this->password = password; }
void set_timeout(int timeout) { this->timeout = timeout; }
std::unique_ptr<Status> get_status(bool update = true);
std::unique_ptr<Status> get_status_safe(bool update = true);
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 set_repeat(bool mode);
void set_random(bool mode);
void set_single(bool mode);
};
struct MpdStatus
{
bool random = false,
repeat = false,
single = false;
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();
}
};
}

View File

@ -1,83 +0,0 @@
#pragma once
#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(std::string ifname);
// Network
class NetworkException : public Exception
{
public:
explicit NetworkException(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();
bool test_connection();
public:
explicit Network(std::string interface);
~Network();
virtual bool connected();
virtual bool test();
std::string get_ip();
};
// WiredNetwork
class WiredNetworkException : public NetworkException {
using NetworkException::NetworkException;
};
class WiredNetwork : public Network
{
int linkspeed = 0;
public:
explicit WiredNetwork(std::string interface);
std::string get_link_speed();
};
// WirelessNetwork
class WirelessNetworkException : public NetworkException {
using NetworkException::NetworkException;
};
class WirelessNetwork : public Network
{
struct iwreq iw;
public:
explicit WirelessNetwork(std::string interface);
std::string get_essid();
float get_signal_dbm();
float get_signal_quality();
};
}

1
include/lemonbuddy Symbolic link
View File

@ -0,0 +1 @@
../include

View File

@ -1,10 +0,0 @@
#pragma once
#include "exception.hpp"
DefineBaseException(ApplicationError);
void register_pid(pid_t pid);
void unregister_pid(pid_t pid);
void register_command_handler(std::string module_name);

View File

@ -1,36 +1,102 @@
#pragma once
#include "config.hpp"
#include "modules/base.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(BacklightModule, InotifyModule)
{
LEMONBUDDY_NS
namespace modules {
class backlight_module : public inotify_module<backlight_module> {
public:
using inotify_module::inotify_module;
void setup() {
// Load configuration values
auto card = m_conf.get<string>(name(), "card");
// Add formats and elements
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR, TAG_RAMP});
if (m_formatter->has(TAG_LABEL))
m_label = get_optional_config_label(m_conf, name(), TAG_LABEL, "%percentage%");
if (m_formatter->has(TAG_BAR))
m_progressbar = get_config_bar(m_bar, m_conf, name(), TAG_BAR);
if (m_formatter->has(TAG_RAMP))
m_ramp = get_config_ramp(m_conf, name(), TAG_RAMP);
if (m_label)
m_tokenized = m_label->clone();
// Build path to the file where the current/maximum brightness value is located
m_path_val = string_util::replace(PATH_BACKLIGHT_VAL, "%card%", card);
m_path_max = string_util::replace(PATH_BACKLIGHT_MAX, "%card%", card);
if (!file_util::exists(m_path_val))
throw module_error("backlight_module: The file '" + m_path_val + "' does not exist");
if (!file_util::exists(m_path_max))
throw module_error("backlight_module: The file '" + m_path_max + "' does not exist");
// Add inotify watch
watch(string_util::replace(PATH_BACKLIGHT_VAL, "%card%", card));
}
bool on_event(inotify_event* event) {
if (event != nullptr)
m_log.trace("%s: %s", name(), event->filename);
auto val = file_util::get_contents(m_path_val);
m_val = std::stoull(val.c_str(), 0, 10);
auto max = file_util::get_contents(m_path_max);
m_max = std::stoull(max.c_str(), 0, 10);
m_percentage = static_cast<int>(float(m_val) / float(m_max) * 100.0f + 0.5f);
if (!m_label)
return true;
m_tokenized->m_text = m_label->m_text;
m_tokenized->replace_token("%percentage%", to_string(m_percentage) + "%");
return true;
}
bool build(builder* builder, string tag) {
if (tag == TAG_BAR)
builder->node(m_progressbar->output(m_percentage));
else if (tag == TAG_RAMP)
builder->node(m_ramp->get_by_percentage(m_percentage));
else if (tag == TAG_LABEL)
builder->node(m_tokenized);
else
return false;
return true;
}
void idle() {
this_thread::sleep_for(75ms);
}
private:
static constexpr auto TAG_LABEL = "<label>";
static constexpr auto TAG_BAR = "<bar>";
static constexpr auto 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;
ramp_t m_ramp;
label_t m_label;
label_t m_tokenized;
progressbar_t m_progressbar;
std::string path_val, path_max;
float val = 0, max = 0;
string m_path_val;
string m_path_max;
float m_val = 0;
float m_max = 0;
concurrency::Atomic<int> percentage;
public:
explicit BacklightModule(std::string name);
bool on_event(InotifyEvent *event);
bool build(Builder *builder, std::string tag);
void idle() const {
std::this_thread::sleep_for(25ms);
}
int m_percentage;
};
}
LEMONBUDDY_NS_END

View File

@ -1,441 +0,0 @@
#pragma once
#include <chrono>
#include <memory>
#include <string>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <map>
#include <thread>
#include <algorithm>
#include "exception.hpp"
#include "services/builder.hpp"
#include "services/inotify.hpp"
#include "services/logger.hpp"
#include "services/event_throttler.hpp"
#include "utils/config.hpp"
#include "utils/string.hpp"
#include "utils/concurrency.hpp"
using namespace std::chrono_literals;
#define Concat(one, two) one ## two
#define _Stringify(expr) #expr
#define Stringify(expr) _Stringify(expr)
#define DefineModule(ModuleName, ModuleType) struct ModuleName : public ModuleType<ModuleName>
#define DefineModule2(ModuleName, ModuleType, P2) struct ModuleName : public ModuleType<ModuleName, P2>
#define DefineModule3(ModuleName, ModuleType, P2, P3) struct ModuleName : public ModuleType<ModuleName, P2, P3>
#define CastModule(ModuleName) static_cast<ModuleName *>(this)
#define ConstCastModule(ModuleName) static_cast<ModuleName const &>(*this)
#define DEFAULT_FORMAT "format"
DefineBaseException(ModuleError);
DefineChildException(UndefinedFormat, ModuleError);
DefineChildException(UndefinedFormatTag, ModuleError);
class Registry;
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, std::string output);
};
std::string module_name;
std::map<std::string, std::shared_ptr<Format>> formats;
public:
explicit ModuleFormatter(std::string module_name)
: module_name(module_name) {}
void add(std::string name, std::string fallback, std::vector<std::string>&& tags, std::vector<std::string>&& whitelist = {});
std::shared_ptr<Format> get(std::string format_name);
bool has(std::string tag, std::string format_name);
bool has(std::string tag);
};
namespace modules
{
void broadcast_module_update(std::shared_ptr<Registry> registry, std::string module_name);
std::string get_tag_name(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 void attach_registry(std::shared_ptr<Registry> registry) = 0;
virtual bool handle_command(std::string cmd) = 0;
virtual bool register_for_events() const = 0;
};
template<class ModuleImpl>
class Module : public ModuleInterface
{
concurrency::Atomic<bool> enabled_flag { false };
concurrency::Value<std::string> cache { "" };
protected:
concurrency::SpinLock output_lock;
concurrency::SpinLock broadcast_lock;
concurrency::SpinLock update_lock;
std::mutex sleep_lock;
std::condition_variable sleep_handler;
std::string name_;
std::shared_ptr<Registry> registry;
std::shared_ptr<Logger> logger;
std::unique_ptr<Builder> builder;
std::unique_ptr<EventThrottler> broadcast_throttler;
std::unique_ptr<ModuleFormatter> formatter;
std::vector<std::thread> threads;
event_throttler::limit_t broadcast_throttler_limit() const {
return event_throttler::limit_t(1);
}
event_throttler::timewindow_t broadcast_throttler_timewindow() const {
return event_throttler::timewindow_t(25);
}
public:
Module(std::string name, bool lazy_builder = true)
: name_("module/"+ name)
, logger(get_logger())
, builder(std::make_unique<Builder>(lazy_builder))
, broadcast_throttler(std::make_unique<EventThrottler>(ConstCastModule(ModuleImpl).broadcast_throttler_limit(), ConstCastModule(ModuleImpl).broadcast_throttler_timewindow()))
, formatter(std::make_unique<ModuleFormatter>(ConstCastModule(ModuleImpl).name())) {}
~Module()
{
if (this->enabled())
this->stop();
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
{
for (auto &&t : this->threads) {
if (t.joinable())
t.join();
else
this->logger->warning("["+ ConstCastModule(ModuleImpl).name() +"] Runner thread not joinable");
}
this->threads.clear();
}
log_trace2(this->logger, name());
}
std::string name() const {
return name_;
}
void stop()
{
std::lock_guard<concurrency::SpinLock> lck(this->broadcast_lock);
log_trace2(this->logger, name());
this->wakeup();
this->enable(false);
}
void refresh() {
this->cache = CastModule(ModuleImpl)->get_output();
}
std::string operator()() {
return this->cache();
}
void attach_registry(std::shared_ptr<Registry> registry) {
this->registry = registry;
}
bool handle_command(std::string cmd) {
return CastModule(ModuleImpl)->handle_command(cmd);
}
bool register_for_events() const {
return false;
}
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);
if (!this->broadcast_throttler->passthrough()) {
log_trace2(this->logger, "Throttled broadcast for: "+ this->name_);
return;
}
broadcast_module_update(this->registry, ConstCastModule(ModuleImpl).name());
}
void sleep(std::chrono::duration<double> sleep_duration)
{
std::unique_lock<std::mutex> lck(this->sleep_lock);
this->sleep_handler.wait_for(lck, sleep_duration);
}
void wakeup()
{
log_trace2(this->logger, "Releasing sleep lock for "+ this->name_);
this->sleep_handler.notify_all();
}
std::string get_format() {
return DEFAULT_FORMAT;
}
std::string get_output()
{
std::lock_guard<concurrency::SpinLock> lck(this->output_lock);
if (!this->enabled()) {
log_trace2(this->logger, ConstCastModule(ModuleImpl).name() +" is disabled");
return "";
} else {
log_trace2(this->logger, ConstCastModule(ModuleImpl).name());
}
auto format_name = CastModule(ModuleImpl)->get_format();
auto format = this->formatter->get(format_name);
int i = 0;
bool tag_built = true;
for (auto tag : string::split(format->value, ' ')) {
bool is_blankspace = tag.empty();
if (tag[0] == '<' && tag[tag.length()-1] == '>') {
if (i > 0)
this->builder->space(format->spacing);
if (!(tag_built = CastModule(ModuleImpl)->build(this->builder.get(), tag)) && i > 0)
this->builder->remove_trailing_space(format->spacing);
if (tag_built)
i++;
} else if (is_blankspace && tag_built) {
this->builder->node(" ");
} else if (!is_blankspace) {
this->builder->node(tag);
}
}
return format->decorate(this->builder.get(), this->builder->flush());
}
};
template<class ModuleImpl>
class StaticModule : public Module<ModuleImpl>
{
using Module<ModuleImpl>::Module;
public:
void start()
{
this->enable(true);
this->threads.emplace_back(std::thread(&StaticModule::broadcast, this));
}
bool build(Builder *builder, std::string tag) {
return true;
}
};
template<class ModuleImpl>
class TimerModule : public Module<ModuleImpl>
{
protected:
std::chrono::duration<double> interval = 1s;
void runner()
{
while (this->enabled()) {
{ std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
if (CastModule(ModuleImpl)->update())
CastModule(ModuleImpl)->broadcast();
}
this->sleep(this->interval);
}
}
public:
template<typename I>
TimerModule(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<class ModuleImpl>
class EventModule : public Module<ModuleImpl>
{
using Module<ModuleImpl>::Module;
protected:
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<class ModuleImpl, const int ThrottleLimit = 2, const int ThrottleWindowMs = 50>
class InotifyModule : public Module<ModuleImpl>
{
protected:
concurrency::SpinLock poll_lock;
std::unique_ptr<EventThrottler> poll_throttler;
std::map<std::string, int> watch_list;
void runner()
{
// warmup
CastModule(ModuleImpl)->on_event(nullptr);
CastModule(ModuleImpl)->broadcast();
while (this->enabled()) {
try {
std::lock_guard<concurrency::SpinLock> lck(this->poll_lock);
if (this->poll_throttler->passthrough())
this->poll_events();
} catch (InotifyException &e) {
get_logger()->fatal(e.what());
}
}
}
void idle() const {
// this->sleep(1s);
}
void poll_events()
{
std::vector<std::unique_ptr<InotifyWatch>> watches;
try {
for (auto &&w : this->watch_list)
watches.emplace_back(std::make_unique<InotifyWatch>(w.first, w.second));
} catch (InotifyException &e) {
watches.clear();
log_error(e.what());
this->sleep(0.1s);
return;
}
while (this->enabled()) {
ConstCastModule(ModuleImpl).idle();
for (auto &&w : watches) {
log_trace2(this->logger, "Polling inotify event for watch at "+ (*w)());
if (w->has_event(500 / watches.size())) {
std::unique_ptr<InotifyEvent> event = w->get_event();
watches.clear();
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
if (!this->poll_throttler->passthrough())
return;
if (CastModule(ModuleImpl)->on_event(event.get()))
CastModule(ModuleImpl)->broadcast();
return;
}
}
}
}
void watch(std::string path, int mask = InotifyEvent::ALL)
{
log_trace2(this->logger, path);
this->watch_list.insert(std::make_pair(path, mask));
}
public:
explicit InotifyModule(std::string name)
: Module<ModuleImpl>(name)
, poll_throttler(std::make_unique<EventThrottler>(event_throttler::limit_t(ThrottleLimit), event_throttler::timewindow_t(ThrottleWindowMs))) {}
InotifyModule(std::string name, std::string path, int mask = InotifyEvent::ALL)
: Module<ModuleImpl>(name)
, poll_throttler(std::make_unique<EventThrottler>(event_throttler::limit_t(ThrottleLimit), event_throttler::timewindow_t(ThrottleWindowMs))) {
this->watch(path, mask);
}
InotifyModule(std::string name, std::vector<std::string> paths, int mask = InotifyEvent::ALL)
: Module<ModuleImpl>(name)
, poll_throttler(std::make_unique<EventThrottler>(event_throttler::limit_t(ThrottleLimit), event_throttler::timewindow_t(ThrottleWindowMs)))
{
for (auto &&path : paths)
this->watch(path, mask);
}
~InotifyModule() {
std::lock_guard<concurrency::SpinLock> lck(this->poll_lock);
}
void start()
{
this->enable(true);
this->threads.emplace_back(std::thread(&InotifyModule::runner, this));
}
};
}

View File

@ -1,62 +1,242 @@
#pragma once
#include <memory>
#include <string>
#include "modules/base.hpp"
#include "config.hpp"
#include "drawtypes/animation.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "modules/meta.hpp"
#include "utils/file.hpp"
#include "utils/inotify.hpp"
#include "utils/string.hpp"
namespace modules
{
DefineModule(BatteryModule, InotifyModule)
{
public:
static const int STATE_UNKNOWN = 1;
static const int STATE_CHARGING = 2;
static const int STATE_DISCHARGING = 3;
static const int STATE_FULL = 4;
LEMONBUDDY_NS
protected:
static constexpr auto FORMAT_CHARGING = "format-charging";
static constexpr auto FORMAT_DISCHARGING = "format-discharging";
static constexpr auto FORMAT_FULL = "format-full";
namespace modules {
class battery_module : public inotify_module<battery_module> {
public:
using inotify_module::inotify_module;
static constexpr auto TAG_ANIMATION_CHARGING = "<animation-charging>";
static constexpr auto TAG_BAR_CAPACITY = "<bar-capacity>";
static constexpr auto TAG_RAMP_CAPACITY = "<ramp-capacity>";
static constexpr auto TAG_LABEL_CHARGING = "<label-charging>";
static constexpr auto TAG_LABEL_DISCHARGING = "<label-discharging>";
static constexpr auto TAG_LABEL_FULL = "<label-full>";
void setup() {
// Load configuration values {{{
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;
m_battery = m_conf.get<string>(name(), "battery", "BAT0");
m_adapter = m_conf.get<string>(name(), "adapter", "ADP1");
m_fullat = m_conf.get<int>(name(), "full_at", 100);
std::string battery, adapter;
std::string path_capacity, path_adapter;
m_path_capacity = string_util::replace(PATH_BATTERY_CAPACITY, "%battery%", m_battery);
m_path_adapter = string_util::replace(PATH_ADAPTER_STATUS, "%adapter%", m_adapter);
concurrency::Atomic<int> state;
concurrency::Atomic<int> percentage;
int full_at;
m_state = STATE_UNKNOWN;
m_percentage = 0;
void subthread_routine();
// }}}
// Validate paths {{{
public:
explicit BatteryModule(std::string name);
if (!file_util::exists(m_path_capacity))
throw module_error("battery_module: The file '" + m_path_capacity + "' does not exist");
if (!file_util::exists(m_path_adapter))
throw module_error("battery_module: The file '" + m_path_adapter + "' does not exist");
void start();
// }}}
// Add formats and elements {{{
bool on_event(InotifyEvent *event);
std::string get_format();
bool build(Builder *builder, std::string tag);
m_formatter->add(FORMAT_CHARGING, TAG_LABEL_CHARGING,
{TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_ANIMATION_CHARGING, TAG_LABEL_CHARGING});
m_formatter->add(FORMAT_DISCHARGING, TAG_LABEL_DISCHARGING,
{TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_LABEL_DISCHARGING});
m_formatter->add(
FORMAT_FULL, TAG_LABEL_FULL, {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_LABEL_FULL});
if (m_formatter->has(TAG_ANIMATION_CHARGING, FORMAT_CHARGING))
m_animation_charging = get_config_animation(m_conf, name(), TAG_ANIMATION_CHARGING);
if (m_formatter->has(TAG_BAR_CAPACITY))
m_bar_capacity = get_config_bar(m_bar, m_conf, name(), TAG_BAR_CAPACITY);
if (m_formatter->has(TAG_RAMP_CAPACITY))
m_ramp_capacity = get_config_ramp(m_conf, name(), TAG_RAMP_CAPACITY);
if (m_formatter->has(TAG_LABEL_CHARGING, FORMAT_CHARGING)) {
m_label_charging =
get_optional_config_label(m_conf, name(), TAG_LABEL_CHARGING, "%percentage%");
m_label_charging_tokenized = m_label_charging->clone();
}
if (m_formatter->has(TAG_LABEL_DISCHARGING, FORMAT_DISCHARGING)) {
m_label_discharging =
get_optional_config_label(m_conf, name(), TAG_LABEL_DISCHARGING, "%percentage%");
m_label_discharging_tokenized = m_label_discharging->clone();
}
if (m_formatter->has(TAG_LABEL_FULL, FORMAT_FULL)) {
m_label_full = get_optional_config_label(m_conf, name(), TAG_LABEL_FULL, "%percentage%");
m_label_full_tokenized = m_label_full->clone();
}
// }}}
// Create inotify watches {{{
watch(m_path_capacity, IN_ACCESS);
watch(m_path_adapter, IN_ACCESS);
// }}}
}
void start() {
m_threads.emplace_back(thread(&battery_module::subthread_routine, this));
inotify_module::start();
}
bool on_event(inotify_event* event) {
if (event != nullptr)
m_log.trace("%s: %s", name(), event->filename);
auto status = file_util::get_contents(m_path_adapter);
if (status.empty()) {
m_log.err("%s: Failed to read '%s'", name(), m_path_adapter);
return false;
}
auto capacity = file_util::get_contents(m_path_capacity);
if (capacity.empty()) {
m_log.err("%s: Failed to read '%s'", name(), m_path_capacity);
return false;
}
int percentage = math_util::cap<float>(std::atof(capacity.c_str()), 0, 100) + 0.5;
int state = STATE_UNKNOWN;
switch (status[0]) {
case '0':
state = STATE_DISCHARGING;
break;
case '1':
state = STATE_CHARGING;
break;
}
if (state == STATE_CHARGING) {
if (percentage >= m_fullat)
percentage = 100;
if (percentage == 100)
state = STATE_FULL;
}
// check for nullptr since we don't want to ignore the update for the warmup run
if (event != nullptr && m_state == state && m_percentage == percentage) {
m_log.trace("%s: Ignore update since values are unchanged", name());
return false;
}
if (m_label_charging_tokenized) {
m_label_charging_tokenized->m_text = m_label_charging->m_text;
m_label_charging_tokenized->replace_token("%percentage%", to_string(percentage) + "%");
}
if (m_label_discharging_tokenized) {
m_label_discharging_tokenized->m_text = m_label_discharging->m_text;
m_label_discharging_tokenized->replace_token("%percentage%", to_string(percentage) + "%");
}
if (m_label_full_tokenized) {
m_label_full_tokenized->m_text = m_label_full->m_text;
m_label_full_tokenized->replace_token("%percentage%", to_string(percentage) + "%");
}
m_state = state;
m_percentage = percentage;
return true;
}
string get_format() {
if (m_state == STATE_FULL)
return FORMAT_FULL;
else if (m_state == STATE_CHARGING)
return FORMAT_CHARGING;
else
return FORMAT_DISCHARGING;
}
bool build(builder* builder, string tag) {
if (tag == TAG_ANIMATION_CHARGING)
builder->node(m_animation_charging->get());
else if (tag == TAG_BAR_CAPACITY) {
builder->node(m_bar_capacity->output(m_percentage));
} else if (tag == TAG_RAMP_CAPACITY)
builder->node(m_ramp_capacity->get_by_percentage(m_percentage));
else if (tag == TAG_LABEL_CHARGING)
builder->node(m_label_charging_tokenized);
else if (tag == TAG_LABEL_DISCHARGING)
builder->node(m_label_discharging_tokenized);
else if (tag == TAG_LABEL_FULL)
builder->node(m_label_full_tokenized);
else
return false;
return true;
}
protected:
void subthread_routine() {
this_thread::yield();
chrono::duration<double> dur = 1s;
if (m_animation_charging)
dur = chrono::duration<double>(float(m_animation_charging->framerate()) / 1000.0f);
int i = 0;
const int poll_seconds = m_conf.get<float>(name(), "poll_interval", 3.0f) / dur.count();
while (enabled()) {
// TODO(jaagr): Keep track of when the values were last read to determine
// if we need to trigger the event manually or not.
if (poll_seconds > 0 && (++i % poll_seconds) == 0) {
// Trigger an inotify event in case the underlying filesystem doesn't
m_log.trace("%s: Poll battery capacity", name());
file_util::get_contents(m_path_capacity);
i = 0;
}
if (m_state == STATE_CHARGING)
broadcast();
sleep(dur);
}
m_log.trace("%s: Reached end of battery subthread", name());
}
private:
static const int STATE_UNKNOWN = 1;
static const int STATE_CHARGING = 2;
static const int STATE_DISCHARGING = 3;
static const int STATE_FULL = 4;
static constexpr auto FORMAT_CHARGING = "format-charging";
static constexpr auto FORMAT_DISCHARGING = "format-discharging";
static constexpr auto FORMAT_FULL = "format-full";
static constexpr auto TAG_ANIMATION_CHARGING = "<animation-charging>";
static constexpr auto TAG_BAR_CAPACITY = "<bar-capacity>";
static constexpr auto TAG_RAMP_CAPACITY = "<ramp-capacity>";
static constexpr auto TAG_LABEL_CHARGING = "<label-charging>";
static constexpr auto TAG_LABEL_DISCHARGING = "<label-discharging>";
static constexpr auto TAG_LABEL_FULL = "<label-full>";
animation_t m_animation_charging;
ramp_t m_ramp_capacity;
progressbar_t m_bar_capacity;
label_t m_label_charging;
label_t m_label_charging_tokenized;
label_t m_label_discharging;
label_t m_label_discharging_tokenized;
label_t m_label_full;
label_t m_label_full_tokenized;
string m_battery;
string m_adapter;
string m_path_capacity;
string m_path_adapter;
int m_state;
int m_percentage;
int m_fullat;
};
}
LEMONBUDDY_NS_END

View File

@ -1,87 +1,353 @@
#pragma once
#include <memory>
#include <string>
#include <unistd.h>
#include "modules/base.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/iconset.hpp"
#include "drawtypes/label.hpp"
#include "modules/meta.hpp"
#include "utils/bspwm.hpp"
namespace modules
{
namespace bspwm
{
typedef struct payload_t {
char data[BUFSIZ];
size_t len = 0;
} payload_t;
LEMONBUDDY_NS
enum Flag
{
WORKSPACE_NONE,
WORKSPACE_ACTIVE,
WORKSPACE_URGENT,
WORKSPACE_EMPTY,
WORKSPACE_OCCUPIED,
// used when the monitor is unfocused
WORKSPACE_DIMMED,
namespace modules {
enum class bspwm_flag {
WORKSPACE_NONE,
WORKSPACE_ACTIVE,
WORKSPACE_URGENT,
WORKSPACE_EMPTY,
WORKSPACE_OCCUPIED,
WORKSPACE_DIMMED, // used when the monitor is out of focus
MODE_NONE,
MODE_LAYOUT_MONOCLE,
MODE_LAYOUT_TILED,
MODE_STATE_FULLSCREEN,
MODE_STATE_FLOATING,
MODE_NODE_LOCKED,
MODE_NODE_STICKY,
MODE_NODE_PRIVATE
};
MODE_NONE,
MODE_LAYOUT_MONOCLE,
MODE_LAYOUT_TILED,
MODE_STATE_FULLSCREEN,
MODE_STATE_FLOATING,
MODE_NODE_LOCKED,
MODE_NODE_STICKY,
MODE_NODE_PRIVATE
};
struct bspwm_workspace {
bspwm_flag flag;
label_t label;
struct Workspace
{
Flag flag;
std::unique_ptr<drawtypes::Label> label;
bspwm_workspace(bspwm_flag flag, label_t&& label)
: flag(flag), label(forward<decltype(label)>(label)) {}
Workspace(Flag flag, std::unique_ptr<drawtypes::Label> label) {
this->flag = flag;
this->label.swap(label);
operator bool() {
return label && *label;
}
};
using bspwm_workspace_t = unique_ptr<bspwm_workspace>;
class bspwm_module : public event_module<bspwm_module> {
public:
using event_module::event_module;
void setup() {
m_subscriber = bspwm_util::make_subscriber();
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE}, {TAG_LABEL_MODE});
if (m_formatter->has(TAG_LABEL_STATE)) {
m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_ACTIVE,
get_optional_config_label(m_conf, name(), "label-active", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_OCCUPIED,
get_optional_config_label(m_conf, name(), "label-occupied", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_URGENT,
get_optional_config_label(m_conf, name(), "label-urgent", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_EMPTY,
get_optional_config_label(m_conf, name(), "label-empty", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_DIMMED,
get_optional_config_label(m_conf, name(), "label-dimmed")));
}
operator bool() { return this->label && *this->label; }
};
}
if (m_formatter->has(TAG_LABEL_MODE)) {
m_modelabels.insert(make_pair(bspwm_flag::MODE_LAYOUT_MONOCLE,
get_optional_config_label(m_conf, name(), "label-monocle")));
m_modelabels.insert(make_pair(bspwm_flag::MODE_LAYOUT_TILED,
get_optional_config_label(m_conf, name(), "label-tiled")));
m_modelabels.insert(make_pair(bspwm_flag::MODE_STATE_FULLSCREEN,
get_optional_config_label(m_conf, name(), "label-fullscreen")));
m_modelabels.insert(make_pair(bspwm_flag::MODE_STATE_FLOATING,
get_optional_config_label(m_conf, name(), "label-floating")));
m_modelabels.insert(make_pair(bspwm_flag::MODE_NODE_LOCKED,
get_optional_config_label(m_conf, name(), "label-locked")));
m_modelabels.insert(make_pair(bspwm_flag::MODE_NODE_STICKY,
get_optional_config_label(m_conf, name(), "label-sticky")));
m_modelabels.insert(make_pair(bspwm_flag::MODE_NODE_PRIVATE,
get_optional_config_label(m_conf, name(), "label-private")));
}
m_monitor = m_bar.monitor->name;
m_log.trace("%s: Primary monitor '%s'", name(), m_monitor);
m_icons = iconset_t{new iconset()};
m_icons->add(
DEFAULT_WS_ICON, icon_t{new icon(m_conf.get<string>(name(), DEFAULT_WS_ICON, ""))});
for (auto workspace : m_conf.get_list<string>(name(), "workspace_icon", {})) {
auto vec = string_util::split(workspace, ';');
if (vec.size() == 2) {
m_icons->add(vec[0], icon_t{new icon{vec[1]}});
}
}
}
void start() {
event_module::start();
}
void stop() {
event_module::stop();
m_subscriber && m_subscriber->disconnect();
}
bool has_event() {
if (m_subscriber->poll(POLLHUP, 0)) {
m_log.warn("%s: Reconnecting to socket...", name());
m_subscriber = bspwm_util::make_subscriber();
}
ssize_t bytes = 0;
m_subscriber->receive(1, bytes, MSG_PEEK);
return bytes > 0;
}
bool update() {
ssize_t bytes = 0;
string data = m_subscriber->receive(BUFSIZ - 1, bytes, 0);
if (bytes == 0)
return false;
data = string_util::strip_trailing_newline(data);
unsigned long pos;
while ((pos = data.find("\n")) != string::npos) data.erase(pos);
if (data.empty())
return false;
const auto prefix = string{BSPWM_STATUS_PREFIX};
// If there were more than 1 row available in the channel
// we'll strip out the old updates
if ((pos = data.find_last_of(prefix)) > 0)
data = data.substr(pos);
if (data.compare(0, prefix.length(), prefix) != 0) {
m_log.err("%s: Unknown status '%s'", name(), data);
return false;
}
unsigned long hash;
if ((hash = string_util::hash(data)) == m_hash)
return false;
m_hash = hash;
// Extract the string for the defined monitor
const auto needle_active = ":M" + m_monitor + ":";
const auto needle_inactive = ":m" + m_monitor + ":";
if ((pos = data.find(prefix)) != std::string::npos)
data = data.replace(pos, prefix.length(), ":");
if ((pos = data.find(needle_active)) != std::string::npos)
data.erase(0, pos + 1);
if ((pos = data.find(needle_inactive)) != std::string::npos)
data.erase(0, pos + 1);
if ((pos = data.find(":m", 1)) != std::string::npos)
data.erase(pos);
if ((pos = data.find(":M", 1)) != std::string::npos)
data.erase(pos);
m_modes.clear();
m_workspaces.clear();
bool monitor_focused = true;
int workspace_n = 0;
for (auto&& tag : string_util::split(data, ':')) {
if (tag.empty())
continue;
auto value = tag.size() > 0 ? tag.substr(1) : "";
auto workspace_flag = bspwm_flag::WORKSPACE_NONE;
auto mode_flag = bspwm_flag::MODE_NONE;
switch (tag[0]) {
case 'm':
monitor_focused = false;
break;
case 'M':
monitor_focused = true;
break;
case 'F':
workspace_flag = bspwm_flag::WORKSPACE_ACTIVE;
break;
case 'O':
workspace_flag = bspwm_flag::WORKSPACE_ACTIVE;
break;
case 'o':
workspace_flag = bspwm_flag::WORKSPACE_OCCUPIED;
break;
case 'U':
workspace_flag = bspwm_flag::WORKSPACE_URGENT;
break;
case 'u':
workspace_flag = bspwm_flag::WORKSPACE_URGENT;
break;
case 'f':
workspace_flag = bspwm_flag::WORKSPACE_EMPTY;
break;
case 'L':
switch (value[0]) {
case 0:
break;
case 'M':
mode_flag = bspwm_flag::MODE_LAYOUT_MONOCLE;
break;
case 'T':
mode_flag = bspwm_flag::MODE_LAYOUT_TILED;
break;
default:
m_log.warn("%s: Undefined L => '%s'", name(), value);
}
break;
case 'T':
switch (value[0]) {
case 0:
break;
case 'T':
break;
case '=':
mode_flag = bspwm_flag::MODE_STATE_FULLSCREEN;
break;
case 'F':
mode_flag = bspwm_flag::MODE_STATE_FLOATING;
break;
default:
m_log.warn("%s: Undefined T => '%s'", name(), value);
}
break;
case 'G':
for (int i = 0; i < (int)value.length(); i++) {
switch (value[i]) {
case 0:
break;
case 'L':
mode_flag = bspwm_flag::MODE_NODE_LOCKED;
break;
case 'S':
mode_flag = bspwm_flag::MODE_NODE_STICKY;
break;
case 'P':
mode_flag = bspwm_flag::MODE_NODE_PRIVATE;
break;
default:
m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1));
}
if (mode_flag != bspwm_flag::MODE_NONE && !m_modelabels.empty())
m_modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
}
continue;
default:
m_log.warn("%s: Undefined tag => '%s'", name(), tag.substr(0, 1));
}
if (workspace_flag != bspwm_flag::WORKSPACE_NONE && m_formatter->has(TAG_LABEL_STATE)) {
auto icon = m_icons->get(value, DEFAULT_WS_ICON);
auto label = m_statelabels.find(workspace_flag)->second->clone();
if (!monitor_focused)
label->replace_defined_values(m_statelabels.find(bspwm_flag::WORKSPACE_DIMMED)->second);
label->replace_token("%name%", value);
label->replace_token("%icon%", icon->m_text);
label->replace_token("%index%", to_string(++workspace_n));
m_workspaces.emplace_back(make_unique<bspwm_workspace>(workspace_flag, std::move(label)));
}
if (mode_flag != bspwm_flag::MODE_NONE && !m_modelabels.empty())
m_modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
}
if (!monitor_focused)
m_modes.clear();
return true;
}
bool build(builder* builder, string tag) {
if (tag != TAG_LABEL_STATE)
return false;
int workspace_n = 0;
for (auto&& ws : m_workspaces) {
if (!ws.get()->label->m_text.empty())
builder->cmd(mousebtn::LEFT, string(EVENT_CLICK) + to_string(++workspace_n));
builder->node(ws.get()->label);
if (ws->flag == bspwm_flag::WORKSPACE_ACTIVE && m_formatter->has(TAG_LABEL_MODE)) {
for (auto&& mode : m_modes) builder->node(mode);
}
if (!ws.get()->label->m_text.empty())
builder->cmd_close(true);
}
return true;
}
bool handle_command(string cmd) {
return false;
// if (cmd.find(EVENT_CLICK) == string::npos || cmd.length() <= strlen(EVENT_CLICK))
// return false;
//
// stringstream payload_s;
//
// payload_s << "desktop -f " << m_monitor << ":^"
// << std::atoi(cmd.substr(strlen(EVENT_CLICK)).c_str());
//
// int payload_fd;
// string socket_path = get_socket_path();
//
// if ((payload_fd = io_util::socket::open(socket_path)) == -1)
// m_log.err("%s: Failed to open socket '%s'", name(), socket_path);
// else if (!send_payload(payload_fd, generate_payload(payload_s.str())))
// m_log.err("%s: Failed to change desktop", name());
//
// close(payload_fd);
//
// return true;
}
private:
static constexpr auto DEFAULT_WS_ICON = "workspace_icon-default";
static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%";
DefineModule(BspwmModule, EventModule)
{
static constexpr auto TAG_LABEL_STATE = "<label-state>";
static constexpr auto TAG_LABEL_MODE = "<label-mode>";
static constexpr auto 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;
bspwm_util::subscriber_t m_subscriber;
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 = -1;
std::string prev_data;
public:
BspwmModule(std::string name, std::string monitor);
~BspwmModule();
void start();
bool has_event();
bool update();
bool build(Builder *builder, std::string tag);
bool handle_command(std::string cmd);
bool register_for_events() const {
return true;
}
map<bspwm_flag, label_t> m_modelabels;
map<bspwm_flag, label_t> m_statelabels;
vector<bspwm_workspace_t> m_workspaces;
vector<label_t> m_modes;
iconset_t m_icons;
string m_monitor;
// int m_socketfd = -1;
unsigned long m_hash;
};
}
LEMONBUDDY_NS_END

View File

@ -1,19 +1,39 @@
#pragma once
#include "modules/base.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(CounterModule, TimerModule)
{
LEMONBUDDY_NS
using namespace drawtypes;
namespace modules {
class counter_module : public timer_module<counter_module> {
public:
using timer_module::timer_module;
void setup() {
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 1));
m_formatter->add(DEFAULT_FORMAT, TAG_COUNTER, {TAG_COUNTER});
}
bool update() {
m_counter++;
return true;
}
bool build(builder* builder, string tag) {
if (tag == TAG_COUNTER) {
builder->node(to_string(m_counter));
return true;
}
return false;
}
private:
static constexpr auto TAG_COUNTER = "<counter>";
concurrency::Atomic<int> counter;
public:
explicit CounterModule(std::string name);
bool update();
bool build(Builder *builder, std::string tag);
int m_counter{0};
};
}
LEMONBUDDY_NS_END

View File

@ -1,18 +1,17 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <istream>
#include "modules/base.hpp"
#include "drawtypes/icon.hpp"
#include "config.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "modules/meta.hpp"
namespace modules
{
struct CpuTime
{
LEMONBUDDY_NS
namespace modules {
struct cpu_time {
unsigned long long user;
unsigned long long nice;
unsigned long long system;
@ -20,32 +19,160 @@ namespace modules
unsigned long long total;
};
DefineModule(CpuModule, TimerModule)
{
using cpu_time_t = unique_ptr<cpu_time>;
class cpu_module : public timer_module<cpu_module> {
public:
using timer_module::timer_module;
void setup() {
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 1));
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL,
{TAG_LABEL, TAG_BAR_LOAD, TAG_RAMP_LOAD, TAG_RAMP_LOAD_PER_CORE});
if (m_formatter->has(TAG_BAR_LOAD))
m_barload = get_config_bar(m_bar, m_conf, name(), TAG_BAR_LOAD);
if (m_formatter->has(TAG_RAMP_LOAD))
m_rampload = get_config_ramp(m_conf, name(), TAG_RAMP_LOAD);
if (m_formatter->has(TAG_RAMP_LOAD_PER_CORE))
m_rampload_core = get_config_ramp(m_conf, name(), TAG_RAMP_LOAD_PER_CORE);
if (m_formatter->has(TAG_LABEL)) {
m_label = get_optional_config_label(m_conf, name(), TAG_LABEL, "%percentage%");
m_tokenized = m_label->clone();
}
// warmup
read_values();
read_values();
}
bool update() {
if (!read_values())
return false;
m_total = 0.0f;
m_load.clear();
auto cores_n = m_cputimes.size();
if (!cores_n)
return false;
for (size_t i = 0; i < cores_n; i++) {
auto load = get_load(i);
m_total += load;
m_load.emplace_back(load);
}
m_total = m_total / static_cast<float>(cores_n);
if (m_tokenized) {
m_tokenized->m_text = m_label->m_text;
m_tokenized->replace_token(
"%percentage%", to_string(static_cast<int>(m_total + 0.5f)) + "%");
}
return true;
}
bool build(builder* builder, string tag) {
if (tag == TAG_LABEL)
builder->node(m_tokenized);
else if (tag == TAG_BAR_LOAD)
builder->node(m_barload->output(m_total));
else if (tag == TAG_RAMP_LOAD)
builder->node(m_rampload->get_by_percentage(m_total));
else if (tag == TAG_RAMP_LOAD_PER_CORE) {
auto i = 0;
for (auto&& load : m_load) {
if (i++ > 0)
builder->space(1);
builder->node(m_rampload_core->get_by_percentage(load));
}
builder->node(builder->flush());
} else
return false;
return true;
}
protected:
bool read_values() {
m_cputimes_prev.swap(m_cputimes);
m_cputimes.clear();
try {
std::ifstream in(PATH_CPU_INFO);
string str;
while (std::getline(in, str) && str.compare(0, 3, "cpu") == 0) {
// skip line with accumulated value
if (str.compare(0, 4, "cpu ") == 0)
continue;
auto values = string_util::split(str, ' ');
m_cputimes.emplace_back(new cpu_time);
m_cputimes.back()->user = std::stoull(values[1].c_str(), 0, 10);
m_cputimes.back()->nice = std::stoull(values[2].c_str(), 0, 10);
m_cputimes.back()->system = std::stoull(values[3].c_str(), 0, 10);
m_cputimes.back()->idle = std::stoull(values[4].c_str(), 0, 10);
m_cputimes.back()->total = m_cputimes.back()->user + m_cputimes.back()->nice +
m_cputimes.back()->system + m_cputimes.back()->idle;
}
} catch (const std::ios_base::failure& e) {
m_log.err("Failed to read CPU values (what: %s)", e.what());
}
return !m_cputimes.empty();
}
float get_load(size_t core) {
if (m_cputimes.size() == 0)
return 0;
else if (m_cputimes_prev.size() == 0)
return 0;
else if (!core)
return 0;
else if (core > m_cputimes.size() - 1)
return 0;
else if (core > m_cputimes_prev.size() - 1)
return 0;
auto& last = m_cputimes[core];
auto& prev = m_cputimes_prev[core];
auto last_idle = last->idle;
auto prev_idle = prev->idle;
auto diff = last->total - prev->total;
if (diff == 0)
return 0;
float percentage = 100.0f * (diff - (last_idle - prev_idle)) / diff;
return math_util::cap<float>(percentage, 0, 100);
}
private:
static constexpr auto TAG_LABEL = "<label>";
static constexpr auto TAG_BAR_LOAD = "<bar-load>";
static constexpr auto TAG_RAMP_LOAD = "<ramp-load>";
static constexpr auto 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;
progressbar_t m_barload;
ramp_t m_rampload;
ramp_t m_rampload_core;
label_t m_label;
label_t m_tokenized;
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;
vector<cpu_time_t> m_cputimes;
vector<cpu_time_t> m_cputimes_prev;
float current_total_load = 0;
std::vector<float> current_load;
bool read_values();
float get_load(int core);
public:
explicit CpuModule(std::string name);
bool update();
bool build(Builder *builder, std::string tag);
float m_total = 0;
vector<float> m_load;
};
}
LEMONBUDDY_NS_END

View File

@ -1,34 +1,65 @@
#pragma once
#include "modules/base.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(DateModule, TimerModule)
{
LEMONBUDDY_NS
namespace modules {
class date_module : public timer_module<date_module> {
public:
using timer_module::timer_module;
void setup() {
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 1));
m_formatter->add(DEFAULT_FORMAT, TAG_DATE, {TAG_DATE});
m_format = m_conf.get<string>(name(), "date");
m_format_detailed = m_conf.get<string>(name(), "date_detailed", "");
}
bool update() {
if (!m_formatter->has(TAG_DATE))
return false;
auto time = std::time(nullptr);
auto date_format = m_detailed ? m_format_detailed : m_format;
char buffer[256] = {'\0'};
std::strftime(buffer, sizeof(buffer), date_format.c_str(), std::localtime(&time));
if (std::strncmp(buffer, m_buffer, sizeof(buffer)) == 0)
return false;
else
std::memmove(m_buffer, buffer, sizeof(buffer));
return true;
}
string get_output() {
if (!m_format_detailed.empty())
m_builder->cmd(mousebtn::LEFT, EVENT_TOGGLE);
m_builder->node(timer_module::get_output());
return m_builder->flush();
}
bool build(builder* builder, string tag) {
if (tag == TAG_DATE)
builder->node(m_buffer);
return tag == TAG_DATE;
}
private:
static constexpr auto TAG_DATE = "<date>";
static constexpr auto EVENT_TOGGLE = "datetoggle";
std::unique_ptr<Builder> builder;
string m_format;
string m_format_detailed;
std::string date;
std::string date_detailed;
char date_str[256] = {};
concurrency::Atomic<bool> detailed { false };
public:
explicit DateModule(std::string name);
bool update();
bool build(Builder *builder, std::string tag);
std::string get_output();
bool handle_command(std::string cmd);
bool register_for_events() const {
return true;
}
char m_buffer[256] = {'\0'};
stateflag m_detailed{false};
};
}
LEMONBUDDY_NS_END

View File

@ -1,80 +1,242 @@
#pragma once
#include <memory>
#include <unistd.h>
#include <i3ipc++/ipc.hpp>
// #include <i3ipc++/ipc-util.hpp>
// #include <i3ipc++/ipc.hpp>
#include "config.hpp"
#include "modules/base.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/iconset.hpp"
#include "drawtypes/label.hpp"
#include "modules/meta.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,
LEMONBUDDY_NS
namespace modules {
enum class i3_flag {
WORKSPACE_NONE,
WORKSPACE_FOCUSED,
WORKSPACE_UNFOCUSED,
WORKSPACE_VISIBLE,
WORKSPACE_URGENT,
WORKSPACE_DIMMED, // used when the monitor is out of focus
};
struct i3_workspace {
explicit i3_workspace(int idx, i3_flag flag, label_t&& label_)
: idx(idx), flag(flag), label(forward<decltype(label_)>(label_)) {}
operator bool() {
return label && label.get();
}
int idx;
i3_flag flag;
label_t label;
};
namespace i3ipc {
static const int ET_WORKSPACE = 1;
static const int ET_OUTPUT = 2;
static const int ET_WINDOW = 4;
class ws {
public:
bool focused;
bool urgent;
bool visible;
string output;
string name;
unsigned int num;
};
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);
class connection {
public:
void subscribe(int) {}
void send_command(string) {}
void prepare_to_event_handling() {}
void handle_event() {}
vector<unique_ptr<ws>> get_workspaces() {
return {};
}
operator bool() { return this->label && *this->label; }
};
}
DefineModule(i3Module, EventModule)
{
using i3_workspace_t = unique_ptr<i3_workspace>;
class i3_module : public event_module<i3_module> {
public:
using event_module::event_module;
~i3_module() {
if (!m_ipc)
return;
// FIXME: Hack to release the recv lock. Will need to patch the ipc lib
m_ipc->send_command("workspace back_and_forth");
m_ipc->send_command("workspace back_and_forth");
}
void setup() {
try {
m_ipc = make_unique<i3ipc::connection>();
} catch (std::runtime_error& e) {
throw module_error(e.what());
}
m_localworkspaces = m_conf.get<bool>(name(), "local_workspaces", m_localworkspaces);
m_workspace_name_strip_nchars =
m_conf.get<size_t>(name(), "workspace_name_strip_nchars", m_workspace_name_strip_nchars);
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE});
if (m_formatter->has(TAG_LABEL_STATE)) {
m_statelabels.insert(make_pair(i3_flag::WORKSPACE_FOCUSED,
get_optional_config_label(m_conf, name(), "label-focused", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(i3_flag::WORKSPACE_UNFOCUSED,
get_optional_config_label(m_conf, name(), "label-unfocused", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(i3_flag::WORKSPACE_VISIBLE,
get_optional_config_label(m_conf, name(), "label-visible", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(i3_flag::WORKSPACE_URGENT,
get_optional_config_label(m_conf, name(), "label-urgent", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair(
i3_flag::WORKSPACE_DIMMED, get_optional_config_label(m_conf, name(), "label-dimmed")));
}
m_icons = iconset_t{new iconset()};
m_icons->add(
DEFAULT_WS_ICON, icon_t{new icon(m_conf.get<string>(name(), DEFAULT_WS_ICON, ""))});
for (auto workspace : m_conf.get_list<string>(name(), "workspace_icon", {})) {
auto vec = string_util::split(workspace, ';');
if (vec.size() == 2)
m_icons->add(vec[0], icon_t{new icon{vec[1]}});
}
// m_ipc->subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_OUTPUT | i3ipc::ET_WINDOW);
// m_ipc->subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_OUTPUT);
// m_ipc->prepare_to_event_handling();
}
bool has_event() {
if (!m_ipc || !enabled())
return false;
m_ipc->handle_event();
return true;
}
bool update() {
if (!enabled())
return false;
i3ipc::connection connection;
try {
// for (auto &&m : connection.get_outputs()) {
// if (m->name == m_monitor) {
// monitor_focused = m->active;
// break;
// }
// }
m_workspaces.clear();
auto workspaces = connection.get_workspaces();
string focused_monitor;
for (auto&& ws : workspaces) {
if (ws->focused) {
focused_monitor = ws->output;
break;
}
}
bool monitor_focused = (focused_monitor == m_monitor);
for (auto&& ws : connection.get_workspaces()) {
if (m_localworkspaces && ws->output != m_monitor)
continue;
auto flag = i3_flag::WORKSPACE_NONE;
if (ws->focused)
flag = i3_flag::WORKSPACE_FOCUSED;
else if (ws->urgent)
flag = i3_flag::WORKSPACE_URGENT;
else if (ws->visible)
flag = i3_flag::WORKSPACE_VISIBLE;
else
flag = i3_flag::WORKSPACE_UNFOCUSED;
// if (!monitor_focused)
// flag = i3_flag::WORKSPACE_DIMMED;
auto workspace_name = ws->name;
if (m_workspace_name_strip_nchars > 0 &&
workspace_name.length() > m_workspace_name_strip_nchars)
workspace_name.erase(0, m_workspace_name_strip_nchars);
auto icon = m_icons->get(workspace_name, DEFAULT_WS_ICON);
auto label = m_statelabels.find(flag)->second->clone();
if (!monitor_focused)
label->replace_defined_values(m_statelabels.find(i3_flag::WORKSPACE_DIMMED)->second);
label->replace_token("%name%", workspace_name);
label->replace_token("%icon%", icon->m_text);
label->replace_token("%index%", to_string(ws->num));
m_workspaces.emplace_back(make_unique<i3_workspace>(ws->num, flag, std::move(label)));
}
} catch (std::runtime_error& e) {
m_log.err("%s: %s", name(), e.what());
}
return true;
}
bool build(builder* builder, string tag) {
if (tag != TAG_LABEL_STATE)
return false;
for (auto&& ws : m_workspaces) {
builder->cmd(mousebtn::LEFT, string{EVENT_CLICK} + to_string(ws.get()->idx));
builder->node(ws.get()->label);
builder->cmd_close(true);
}
return true;
}
// bool handle_command(string cmd) {
// if (cmd.find(EVENT_CLICK) == string::npos || cmd.length() < strlen(EVENT_CLICK))
// return false;
//
// m_ipc->send_command("workspace number " + cmd.substr(strlen(EVENT_CLICK)));
//
// return true;
// }
private:
static constexpr auto DEFAULT_WS_ICON = "workspace_icon-default";
static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%";
static constexpr auto TAG_LABEL_STATE = "<label-state>";
static constexpr auto EVENT_CLICK = "i3";
std::unique_ptr<i3ipc::connection> ipc;
unique_ptr<i3ipc::connection> m_ipc;
// std::map<i3::Flag, std::unique_ptr<drawtypes::Label>> mode_labels;
std::map<i3::Flag, std::unique_ptr<drawtypes::Label>> state_labels;
map<i3_flag, label_t> m_statelabels;
vector<i3_workspace_t> m_workspaces;
std::vector<std::unique_ptr<i3::Workspace>> workspaces;
// std::vector<std::unique_ptr<drawtypes::Label>*> modes;
// map<i3_flag, label_t> mode_labels;
// vector<label_t> modes;
std::unique_ptr<drawtypes::IconMap> icons;
std::string monitor;
iconset_t m_icons;
string m_monitor;
bool local_workspaces = true;
std::size_t workspace_name_strip_nchars = 0;
int ipc_fd = -1;
public:
i3Module(std::string name, std::string monitor);
~i3Module();
void start();
bool has_event();
bool update();
bool build(Builder *builder, std::string tag);
bool handle_command(std::string cmd);
bool register_for_events() const {
return true;
}
bool m_localworkspaces = true;
size_t m_workspace_name_strip_nchars = 0;
};
}
LEMONBUDDY_NS_END

View File

@ -1,32 +1,124 @@
#pragma once
#include <atomic>
#include <istream>
#include "modules/base.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/icon.hpp"
#include "config.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(MemoryModule, TimerModule)
{
LEMONBUDDY_NS
namespace modules {
enum class memtype { NONE = 0, TOTAL, USED, FREE, SHARED, BUFFERS, CACHE, AVAILABLE };
class memory_module : public timer_module<memory_module> {
public:
using timer_module::timer_module;
void setup() {
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 1));
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR_USED, TAG_BAR_FREE});
if (m_formatter->has(TAG_BAR_USED))
m_bars[memtype::USED] = get_config_bar(m_bar, m_conf, name(), TAG_BAR_USED);
if (m_formatter->has(TAG_BAR_FREE))
m_bars[memtype::FREE] = get_config_bar(m_bar, m_conf, name(), TAG_BAR_FREE);
if (m_formatter->has(TAG_LABEL)) {
m_label = get_optional_config_label(m_conf, name(), TAG_LABEL, "%percentage_used%");
m_tokenized = m_label->clone();
}
}
bool update() {
float kb_total;
float kb_avail;
// long kb_free;
try {
std::ifstream in(PATH_MEMORY_INFO);
std::stringstream buffer;
string str, rdbuf;
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;
kb_total = std::atol(rdbuf.c_str());
buffer >> rdbuf; // kb_free = std::atol(rdbuf.c_str());
buffer >> rdbuf;
kb_avail = std::atol(rdbuf.c_str());
} catch (const std::ios_base::failure& e) {
kb_total = 0;
// kb_free = 0;
kb_avail = 0;
m_log.err("Failed to read memory values (what: %s)", e.what());
}
if (kb_total > 0)
m_perc[memtype::FREE] = (kb_avail / kb_total) * 100.0f + 0.5f;
else
m_perc[memtype::FREE] = 0;
m_perc[memtype::USED] = 100 - m_perc[memtype::FREE];
// replace tokens
if (m_tokenized) {
m_tokenized->m_text = m_label->m_text;
auto replace_unit = [](label_t& label, string token, float value, string unit) {
if (label->m_text.find(token) == string::npos)
return;
auto formatted = string_util::from_stream(
stringstream() << std::setprecision(2) << std::fixed << value << " " << unit);
label->replace_token(token, formatted);
};
replace_unit(m_tokenized, "%gb_used%", (kb_total - kb_avail) / 1024 / 1024, "GB");
replace_unit(m_tokenized, "%gb_free%", kb_avail / 1024 / 1024, "GB");
replace_unit(m_tokenized, "%gb_total%", kb_total / 1024 / 1024, "GB");
replace_unit(m_tokenized, "%mb_used%", (kb_total - kb_avail) / 1024, "MB");
replace_unit(m_tokenized, "%mb_free%", kb_avail / 1024, "MB");
replace_unit(m_tokenized, "%mb_total%", kb_total / 1024, "MB");
m_tokenized->replace_token("%percentage_used%", to_string(m_perc[memtype::USED]) + "%");
m_tokenized->replace_token("%percentage_free%", to_string(m_perc[memtype::FREE]) + "%");
}
return true;
}
bool build(builder* builder, string tag) {
if (tag == TAG_BAR_USED) {
builder->node(m_bars[memtype::USED]->output(m_perc[memtype::USED]));
} else if (tag == TAG_BAR_FREE)
builder->node(m_bars[memtype::FREE]->output(m_perc[memtype::FREE]));
else if (tag == TAG_LABEL)
builder->node(m_tokenized);
else
return false;
return true;
}
private:
static constexpr auto TAG_LABEL = "<label>";
static constexpr auto TAG_BAR_USED = "<bar-used>";
static constexpr auto 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:
explicit MemoryModule(std::string name);
bool update();
bool build(Builder *builder, std::string tag);
label_t m_label;
label_t m_tokenized;
map<memtype, progressbar_t> m_bars;
map<memtype, int> m_perc;
};
}
LEMONBUDDY_NS_END

View File

@ -1,48 +1,142 @@
#pragma once
#include <mutex>
#include "drawtypes/label.hpp"
#include "modules/meta.hpp"
#include "modules/base.hpp"
LEMONBUDDY_NS
namespace modules
{
struct MenuTreeItem {
std::string exec;
std::unique_ptr<drawtypes::Label> label;
namespace modules {
struct menu_tree_item {
string exec;
label_t label;
};
struct MenuTree {
std::vector<std::unique_ptr<MenuTreeItem>> items;
struct menu_tree {
vector<unique_ptr<menu_tree_item>> items;
};
DefineModule(MenuModule, StaticModule)
{
class menu_module : public static_module<menu_module> {
public:
using static_module::static_module;
void setup() {
auto default_format_string = string{TAG_LABEL_TOGGLE} + " " + string{TAG_MENU};
m_formatter->add(DEFAULT_FORMAT, default_format_string, {TAG_LABEL_TOGGLE, TAG_MENU});
if (m_formatter->has(TAG_LABEL_TOGGLE)) {
m_labelopen = get_config_label(m_conf, name(), "label-open");
m_labelclose = get_optional_config_label(m_conf, name(), "label-close", "x");
}
if (m_formatter->has(TAG_MENU)) {
int level_n = 0;
while (true) {
auto level_path = "menu-" + to_string(level_n);
if (m_conf.get<string>(name(), level_path + "-0", "") == "")
break;
m_levels.emplace_back(make_unique<menu_tree>());
int item_n = 0;
while (true) {
auto item_path = level_path + "-" + to_string(item_n);
if (m_conf.get<string>(name(), item_path, "") == "")
break;
auto item = make_unique<menu_tree_item>();
item->label = get_config_label(m_conf, name(), item_path);
item->exec = m_conf.get<string>(name(), item_path + "-exec", EVENT_MENU_CLOSE);
m_levels.back()->items.emplace_back(std::move(item));
item_n++;
}
level_n++;
}
}
}
string get_output() {
m_builder->node(module::get_output());
return m_builder->flush();
}
bool build(builder* builder, string tag) {
if (tag == TAG_LABEL_TOGGLE && m_level == -1) {
builder->cmd(mousebtn::LEFT, string(EVENT_MENU_OPEN) + "0");
builder->node(m_labelopen);
builder->cmd_close(true);
} else if (tag == TAG_LABEL_TOGGLE && m_level > -1) {
builder->cmd(mousebtn::LEFT, EVENT_MENU_CLOSE);
builder->node(m_labelclose);
builder->cmd_close(true);
} else if (tag == TAG_MENU && m_level > -1) {
int i = 0;
for (auto&& m : m_levels[m_level]->items) {
if (i++ > 0)
builder->space();
builder->color_alpha("77");
builder->node("/");
builder->color_close(true);
builder->space();
builder->cmd(mousebtn::LEFT, m->exec);
builder->node(m->label);
builder->cmd_close(true);
}
} else {
return false;
}
return true;
}
// bool handle_command(string cmd) {
// if (cmd.compare(0, strlen(EVENT_MENU_OPEN), EVENT_MENU_OPEN) == 0) {
// auto level = cmd.substr(strlen(EVENT_MENU_OPEN));
//
// if (level.empty())
// level = "0";
//
// m_level = std::atoi(level.c_str());
//
// if (m_level >= (int)m_levels.size()) {
// m_log.err("%s: Cannot open unexisting menu level '%d'", name(), level);
// m_level = -1;
// }
//
// } else if (cmd == EVENT_MENU_CLOSE) {
// m_level = -1;
// } else {
// m_level = -1;
// broadcast();
// return false;
// }
//
// broadcast();
//
// return true;
// }
private:
static constexpr auto TAG_LABEL_TOGGLE = "<label-toggle>";
static constexpr auto TAG_MENU = "<menu>";
static constexpr auto EVENT_MENU_OPEN = "menu_open-";
static constexpr auto EVENT_MENU_CLOSE = "menu_close";
std::mutex output_mtx;
std::mutex cmd_mtx;
int m_level = -1;
vector<unique_ptr<menu_tree>> m_levels;
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:
explicit MenuModule(std::string name);
std::string get_output();
bool build(Builder *builder, std::string tag);
bool handle_command(std::string cmd);
bool register_for_events() const {
return true;
}
label_t m_labelopen;
label_t m_labelclose;
};
}
LEMONBUDDY_NS_END

492
include/modules/meta.hpp Normal file
View File

@ -0,0 +1,492 @@
#pragma once
#include <algorithm>
#include <condition_variable>
#include <fastdelegate/fastdelegate.hpp>
#include <mutex>
#include "common.hpp"
#include "components/builder.hpp"
#include "components/config.hpp"
#include "components/logger.hpp"
#include "utils/inotify.hpp"
#include "utils/string.hpp"
#include "utils/threading.hpp"
LEMONBUDDY_NS
#define DEFAULT_FORMAT "format"
#define DEFINE_MODULE(name, type) struct name : public type<name>
#define CONST_CAST_MODULE(name) static_cast<name const&>(*this)
#define CAST_MODULE(name) static_cast<name*>(this)
namespace modules {
using namespace drawtypes;
DEFINE_ERROR(module_error);
DEFINE_CHILD_ERROR(undefined_format, module_error);
DEFINE_CHILD_ERROR(undefined_format_tag, module_error);
// class definition : module_format {{{
struct module_format {
string value;
vector<string> tags;
string fg;
string bg;
string ul;
string ol;
int spacing;
int padding;
int margin;
int offset;
string decorate(builder* builder, string output) {
if (offset != 0)
builder->offset(offset);
if (margin > 0)
builder->space(margin);
if (!bg.empty())
builder->background(bg);
if (!fg.empty())
builder->color(fg);
if (!ul.empty())
builder->underline(ul);
if (!ol.empty())
builder->overline(ol);
if (padding > 0)
builder->space(padding);
builder->append(output);
if (padding > 0)
builder->space(padding);
if (!ol.empty())
builder->overline_close();
if (!ul.empty())
builder->underline_close();
if (!fg.empty())
builder->color_close();
if (!bg.empty())
builder->background_close();
if (margin > 0)
builder->space(margin);
return builder->flush();
}
};
// }}}
// class definition : module_formatter {{{
class module_formatter {
public:
explicit module_formatter(const config& conf, string modname)
: m_conf(conf), m_modname(modname) {}
void add(string name, string fallback, vector<string>&& tags, vector<string>&& whitelist = {}) {
auto format = make_unique<module_format>();
format->value = m_conf.get<string>(m_modname, name, fallback);
format->fg = m_conf.get<string>(m_modname, name + "-foreground", "");
format->bg = m_conf.get<string>(m_modname, name + "-background", "");
format->ul = m_conf.get<string>(m_modname, name + "-underline", "");
format->ol = m_conf.get<string>(m_modname, name + "-overline", "");
format->spacing = m_conf.get<int>(m_modname, name + "-spacing", DEFAULT_SPACING);
format->padding = m_conf.get<int>(m_modname, name + "-padding", 0);
format->margin = m_conf.get<int>(m_modname, name + "-margin", 0);
format->offset = m_conf.get<int>(m_modname, name + "-offset", 0);
format->tags.swap(tags);
for (auto&& tag : string_util::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 undefined_format_tag("[" + m_modname + "] Undefined \"" + name + "\" tag: " + tag);
}
m_formats.insert(make_pair(name, std::move(format)));
}
shared_ptr<module_format> get(string format_name) {
auto format = m_formats.find(format_name);
if (format == m_formats.end())
throw undefined_format("Format \"" + format_name + "\" has not been added");
return format->second;
}
bool has(string tag, string format_name) {
auto format = m_formats.find(format_name);
if (format == m_formats.end())
throw undefined_format(format_name.c_str());
return format->second->value.find(tag) != string::npos;
}
bool has(string tag) {
for (auto&& format : m_formats)
if (format.second->value.find(tag) != string::npos)
return true;
return false;
}
protected:
const config& m_conf;
string m_modname;
map<string, shared_ptr<module_format>> m_formats;
};
// }}}
// class definition : module_interface {{{
struct module_interface {
public:
virtual ~module_interface() {}
virtual string name() const = 0;
virtual void setup() = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void refresh() = 0;
virtual string contents() = 0;
virtual bool handle_command(string cmd) = 0;
virtual bool register_for_events() const = 0;
delegate::Signal1<string> on_update;
};
// }}}
// class definition : module {{{
template <class Impl>
class module : public module_interface {
public:
module(const bar_settings bar, const logger& logger, const config& config, string name)
: m_bar(bar)
, m_log(logger)
, m_conf(config)
, m_name("module/" + name)
, m_builder(make_unique<builder>(bar))
, m_formatter(make_unique<module_formatter>(m_conf, m_name)) {}
~module() {
if (enabled())
stop();
std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
{
for (auto&& thread_ : m_threads) {
if (thread_.joinable())
thread_.join();
}
m_threads.clear();
}
m_log.trace("%s: Done cleaning up", name());
}
string name() const {
return m_name;
}
void setup() {
m_log.trace("%s: Setup", name());
CAST_MODULE(Impl)->setup();
}
void stop() {
// std::lock_guard<concurrency::SpinLock> lck(this->broadcast_lock);
// std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
m_log.trace("%s: Stop", name());
enable(false);
wakeup();
}
void refresh() {
m_cache = CAST_MODULE(Impl)->get_output();
}
string contents() {
return m_cache;
}
bool handle_command(string cmd) {
return CAST_MODULE(Impl)->handle_command(cmd);
}
bool register_for_events() const {
return false;
}
protected:
bool enabled() const {
return m_enabled;
}
void enable(bool state) {
m_enabled = state;
}
void broadcast() {
if (!enabled())
return;
refresh();
if (contents().empty())
return;
else if (on_update.empty())
m_log.warn("%s: No signal handlers connected... ignoring broadcast", name());
else
on_update.emit(name());
}
void idle() {}
void sleep(chrono::duration<double> sleep_duration) {
std::unique_lock<std::mutex> lck(m_sleeplock);
m_sleephandler.wait_for(lck, sleep_duration);
}
void wakeup() {
std::unique_lock<std::mutex> lck(m_sleeplock);
m_log.trace("%s: Release sleep lock", name());
m_sleephandler.notify_all();
}
string get_format() const {
return DEFAULT_FORMAT;
}
string get_output() {
// std::lock_guard<concurrency::SpinLock> lck(this->output_lock);
if (!enabled()) {
m_log.trace("%s: Module is disabled", name());
return "";
}
auto format_name = CAST_MODULE(Impl)->get_format();
auto format = m_formatter->get(format_name);
int i = 0;
bool tag_built = true;
for (auto tag : string_util::split(format->value, ' ')) {
bool is_blankspace = tag.empty();
if (tag[0] == '<' && tag[tag.length() - 1] == '>') {
if (i > 0)
m_builder->space(format->spacing);
if (!(tag_built = CAST_MODULE(Impl)->build(m_builder.get(), tag)) && i > 0)
m_builder->remove_trailing_space(format->spacing);
if (tag_built)
i++;
} else if (is_blankspace && tag_built) {
m_builder->node(" ");
} else if (!is_blankspace) {
m_builder->node(tag);
}
}
return format->decorate(m_builder.get(), m_builder->flush());
}
protected:
// concurrency::SpinLock output_lock;
// concurrency::SpinLock broadcast_lock;
threading_util::spin_lock update_lock;
const bar_settings m_bar;
const logger& m_log;
const config& m_conf;
std::mutex m_sleeplock;
std::condition_variable m_sleephandler;
string m_name;
unique_ptr<builder> m_builder;
unique_ptr<module_formatter> m_formatter;
vector<thread> m_threads;
private:
stateflag m_enabled{false};
string m_cache;
};
// }}}
// class definition : static_module {{{
template <class Impl>
class static_module : public module<Impl> {
public:
using module<Impl>::module;
void start() {
CAST_MODULE(Impl)->setup();
CAST_MODULE(Impl)->enable(true);
CAST_MODULE(Impl)->m_threads.emplace_back(thread(&static_module::broadcast, this));
}
bool build(builder*, string) {
return true;
}
};
// }}}
// class definition : timer_module {{{
using interval_t = chrono::duration<double>;
template <class Impl>
class timer_module : public module<Impl> {
public:
using module<Impl>::module;
void start() {
CAST_MODULE(Impl)->enable(true);
CAST_MODULE(Impl)->m_threads.emplace_back(thread(&timer_module::runner, this));
}
protected:
interval_t m_interval = 1s;
void runner() {
CAST_MODULE(Impl)->setup();
while (CONST_CAST_MODULE(Impl).enabled()) {
{
std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
if (CAST_MODULE(Impl)->update())
CAST_MODULE(Impl)->broadcast();
}
CAST_MODULE(Impl)->sleep(m_interval);
}
}
};
// }}}
// class definition : event_module {{{
template <class Impl>
class event_module : public module<Impl> {
public:
using module<Impl>::module;
void start() {
CAST_MODULE(Impl)->enable(true);
CAST_MODULE(Impl)->m_threads.emplace_back(thread(&event_module::runner, this));
}
protected:
void runner() {
CAST_MODULE(Impl)->setup();
// warmup
CAST_MODULE(Impl)->update();
CAST_MODULE(Impl)->broadcast();
while (CONST_CAST_MODULE(Impl).enabled()) {
std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
if (!CAST_MODULE(Impl)->has_event())
continue;
if (!CAST_MODULE(Impl)->update())
continue;
CAST_MODULE(Impl)->broadcast();
CAST_MODULE(Impl)->idle();
}
}
};
// }}}
// class definition : inotify_module {{{
template <class Impl>
class inotify_module : public module<Impl> {
public:
using module<Impl>::module;
void start() {
CAST_MODULE(Impl)->enable(true);
CAST_MODULE(Impl)->m_threads.emplace_back(thread(&inotify_module::runner, this));
}
protected:
void runner() {
CAST_MODULE(Impl)->setup();
CAST_MODULE(Impl)->on_event(nullptr); // warmup
CAST_MODULE(Impl)->broadcast();
while (CAST_MODULE(Impl)->enabled()) {
std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
try {
CAST_MODULE(Impl)->poll_events();
} catch (const system_error& e) {
this->m_log.err("%s: Error while polling for events (what: %s)", this->name(), e.what());
return;
}
}
}
void watch(string path, int mask = IN_ALL_EVENTS) {
this->m_log.trace("%s: Attach inotify at %s", this->name(), path);
m_watchlist.insert(make_pair(path, mask));
}
void idle() {
CAST_MODULE(Impl)->sleep(200ms);
}
void poll_events() {
vector<unique_ptr<inotify_watch>> watches;
try {
for (auto&& w : m_watchlist) {
watches.emplace_back(inotify_util::make_watch(w.first));
watches.back()->attach(w.second);
}
} catch (const system_error& e) {
watches.clear();
this->m_log.err(
"%s: Error while creating inotify watch (what: %s)", this->name(), e.what());
CAST_MODULE(Impl)->sleep(0.1s);
return;
}
while (CONST_CAST_MODULE(Impl).enabled()) {
for (auto&& w : watches) {
this->m_log.trace("%s: Poll inotify watch %s", this->name(), w->path());
if (w->poll(1000 / watches.size())) {
auto event = w->get_event();
w->remove();
if (CAST_MODULE(Impl)->on_event(event.get()))
CAST_MODULE(Impl)->broadcast();
return;
}
}
CAST_MODULE(Impl)->idle();
}
}
private:
map<string, int> m_watchlist;
};
// }}}
}
LEMONBUDDY_NS_END

View File

@ -1,21 +1,321 @@
#pragma once
#include "modules/base.hpp"
#include "interfaces/mpd.hpp"
#include "drawtypes/bar.hpp"
#include "drawtypes/icon.hpp"
#include "adapters/mpd.hpp"
#include "drawtypes/iconset.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "modules/meta.hpp"
#include "utils/threading.hpp"
namespace modules
{
DefineModule(MpdModule, EventModule)
{
std::string mpd_host = "127.0.0.1";
std::string mpd_pass = "";
int mpd_port = 6600;
LEMONBUDDY_NS
static const int PROGRESSBAR_THREAD_SYNC_COUNT = 10;
const std::chrono::duration<double> PROGRESSBAR_THREAD_INTERVAL = 1s;
using namespace mpd;
namespace modules {
class mpd_module : public event_module<mpd_module> {
public:
using event_module::event_module;
~mpd_module() {
std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
if (m_mpd && m_mpd->connected()) {
try {
m_mpd->disconnect();
} catch (const mpd_exception& e) {
m_log.trace("%s: %s", name(), e.what());
}
}
}
void idle() {
sleep(80ms);
}
void setup() {
m_host = m_conf.get<string>(name(), "host", m_host);
m_port = m_conf.get<unsigned int>(name(), "port", m_port);
m_pass = m_conf.get<string>(name(), "password", m_pass);
m_synctime = m_conf.get<float>(name(), "interval", m_synctime);
// Add formats and elements {{{
m_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, TAG_ICON_SEEKB, TAG_ICON_SEEKF});
m_formatter->add(FORMAT_OFFLINE, "", {TAG_LABEL_OFFLINE});
m_icons = iconset_t{new iconset()};
if (m_formatter->has(TAG_ICON_PLAY) || m_formatter->has(TAG_TOGGLE))
m_icons->add("play", get_config_icon(m_conf, name(), TAG_ICON_PLAY));
if (m_formatter->has(TAG_ICON_PAUSE) || m_formatter->has(TAG_TOGGLE))
m_icons->add("pause", get_config_icon(m_conf, name(), TAG_ICON_PAUSE));
if (m_formatter->has(TAG_ICON_STOP))
m_icons->add("stop", get_config_icon(m_conf, name(), TAG_ICON_STOP));
if (m_formatter->has(TAG_ICON_PREV))
m_icons->add("prev", get_config_icon(m_conf, name(), TAG_ICON_PREV));
if (m_formatter->has(TAG_ICON_NEXT))
m_icons->add("next", get_config_icon(m_conf, name(), TAG_ICON_NEXT));
if (m_formatter->has(TAG_ICON_SEEKB))
m_icons->add("seekb", get_config_icon(m_conf, name(), TAG_ICON_SEEKB));
if (m_formatter->has(TAG_ICON_SEEKF))
m_icons->add("seekf", get_config_icon(m_conf, name(), TAG_ICON_SEEKF));
if (m_formatter->has(TAG_ICON_RANDOM))
m_icons->add("random", get_config_icon(m_conf, name(), TAG_ICON_RANDOM));
if (m_formatter->has(TAG_ICON_REPEAT))
m_icons->add("repeat", get_config_icon(m_conf, name(), TAG_ICON_REPEAT));
if (m_formatter->has(TAG_ICON_REPEAT_ONE))
m_icons->add("repeat_one", get_config_icon(m_conf, name(), TAG_ICON_REPEAT_ONE));
if (m_formatter->has(TAG_LABEL_SONG)) {
m_label_song =
get_optional_config_label(m_conf, name(), TAG_LABEL_SONG, "%artist% - %title%");
m_label_song_tokenized = m_label_song->clone();
}
if (m_formatter->has(TAG_LABEL_TIME)) {
m_label_time =
get_optional_config_label(m_conf, name(), TAG_LABEL_TIME, "%elapsed% / %total%");
m_label_time_tokenized = m_label_time->clone();
}
if (m_formatter->has(TAG_ICON_RANDOM) || m_formatter->has(TAG_ICON_REPEAT) ||
m_formatter->has(TAG_ICON_REPEAT_ONE)) {
m_toggle_on_color = m_conf.get<string>(name(), "toggle_on-foreground", "");
m_toggle_off_color = m_conf.get<string>(name(), "toggle_off-foreground", "");
}
if (m_formatter->has(TAG_LABEL_OFFLINE, FORMAT_OFFLINE))
m_label_offline = get_config_label(m_conf, name(), TAG_LABEL_OFFLINE);
if (m_formatter->has(TAG_BAR_PROGRESS)) {
m_bar_progress = get_config_bar(m_bar, m_conf, name(), TAG_BAR_PROGRESS);
}
// }}}
m_lastsync = chrono::system_clock::now();
try {
m_mpd = make_unique<mpdconnection>(m_log, m_host, m_port, m_pass);
m_mpd->connect();
m_status = m_mpd->get_status();
} catch (const mpd_exception& e) {
m_log.err("%s: %s", name(), e.what());
}
}
bool has_event() {
if (!m_mpd->connected()) {
m_connection_state_broadcasted = false;
try {
m_mpd->connect();
} catch (const mpd_exception& e) {
m_log.trace("%s: %s", name(), e.what());
}
if (!m_mpd->connected()) {
sleep(2s);
return false;
}
}
if (!m_status)
m_status = m_mpd->get_status_safe();
try {
m_mpd->idle();
int idle_flags;
if ((idle_flags = m_mpd->noidle()) != 0) {
m_status->update(idle_flags, m_mpd.get());
return true;
} else if (m_status->match_state(mpdstate::PLAYING)) {
m_status->update_timer();
}
} catch (const mpd_exception& e) {
m_log.err(e.what());
m_mpd->disconnect();
return true;
}
if ((m_label_time || m_bar_progress) && m_status->match_state(mpdstate::PLAYING)) {
auto now = chrono::system_clock::now();
auto diff = now - m_lastsync;
if (chrono::duration_cast<chrono::milliseconds>(diff).count() > m_synctime * 1000) {
m_lastsync = now;
return true;
}
}
return !m_connection_state_broadcasted;
}
bool update() {
if (!m_mpd->connected())
return true;
if (!m_status && !(m_status = m_mpd->get_status_safe()))
return true;
m_connection_state_broadcasted = true;
string artist;
string album;
string title;
string elapsed_str;
string total_str;
try {
elapsed_str = m_status->get_formatted_elapsed();
total_str = m_status->get_formatted_total();
auto song = m_mpd->get_song();
if (song && song.get()) {
artist = song->get_artist();
album = song->get_album();
title = song->get_title();
}
} catch (const mpd_exception& e) {
m_log.err(e.what());
m_mpd->disconnect();
return true;
}
if (m_label_song_tokenized) {
m_label_song_tokenized->m_text = m_label_song->m_text;
m_label_song_tokenized->replace_token(
"%artist%", !artist.empty() ? artist : "untitled artist");
m_label_song_tokenized->replace_token("%album%", !album.empty() ? album : "untitled album");
m_label_song_tokenized->replace_token("%title%", !title.empty() ? title : "untitled track");
}
if (m_label_time_tokenized) {
m_label_time_tokenized->m_text = m_label_time->m_text;
m_label_time_tokenized->replace_token("%elapsed%", elapsed_str);
m_label_time_tokenized->replace_token("%total%", total_str);
}
if (m_icons->has("random"))
m_icons->get("random")->m_foreground =
m_status->random() ? m_toggle_on_color : m_toggle_off_color;
if (m_icons->has("repeat"))
m_icons->get("repeat")->m_foreground =
m_status->repeat() ? m_toggle_on_color : m_toggle_off_color;
if (m_icons->has("repeat_one"))
m_icons->get("repeat_one")->m_foreground =
m_status->single() ? m_toggle_on_color : m_toggle_off_color;
return true;
}
string get_format() {
return m_mpd->connected() ? FORMAT_ONLINE : FORMAT_OFFLINE;
}
bool build(builder* builder, string tag) {
bool is_playing = false;
bool is_paused = false;
bool is_stopped = true;
int elapsed_percentage = 0;
if (m_status) {
elapsed_percentage = m_status->get_elapsed_percentage();
if (m_status->match_state(mpdstate::PLAYING))
is_playing = true;
if (m_status->match_state(mpdstate::PAUSED))
is_paused = true;
if (!(m_status->match_state(mpdstate::STOPPED)))
is_stopped = false;
}
auto icon_cmd = [&builder](string cmd, icon_t icon) {
builder->cmd(mousebtn::LEFT, cmd);
builder->node(icon);
builder->cmd_close();
};
if (tag == TAG_LABEL_SONG && !is_stopped)
builder->node(m_label_song_tokenized);
else if (tag == TAG_LABEL_TIME && !is_stopped)
builder->node(m_label_time_tokenized);
else if (tag == TAG_BAR_PROGRESS && !is_stopped)
builder->node(m_bar_progress->output(elapsed_percentage));
else if (tag == TAG_LABEL_OFFLINE)
builder->node(m_label_offline);
else if (tag == TAG_ICON_RANDOM)
icon_cmd(EVENT_RANDOM, m_icons->get("random"));
else if (tag == TAG_ICON_REPEAT)
icon_cmd(EVENT_REPEAT, m_icons->get("repeat"));
else if (tag == TAG_ICON_REPEAT_ONE)
icon_cmd(EVENT_REPEAT_ONE, m_icons->get("repeat_one"));
else if (tag == TAG_ICON_PREV)
icon_cmd(EVENT_PREV, m_icons->get("prev"));
else if (tag == TAG_ICON_STOP && (is_playing || is_paused))
icon_cmd(EVENT_STOP, m_icons->get("stop"));
else if ((tag == TAG_ICON_PAUSE || tag == TAG_TOGGLE) && is_playing)
icon_cmd(EVENT_PAUSE, m_icons->get("pause"));
else if ((tag == TAG_ICON_PLAY || tag == TAG_TOGGLE) && !is_playing)
icon_cmd(EVENT_PLAY, m_icons->get("play"));
else if (tag == TAG_ICON_NEXT)
icon_cmd(EVENT_NEXT, m_icons->get("next"));
else if (tag == TAG_ICON_SEEKB)
icon_cmd(string(EVENT_SEEK).append("-5"), m_icons->get("seekb"));
else if (tag == TAG_ICON_SEEKF)
icon_cmd(string(EVENT_SEEK).append("+5"), m_icons->get("seekf"));
else
return false;
return true;
}
// bool handle_command(string cmd) {
// if (cmd.compare(0, 3, "mpd") != 0)
// return false;
//
// try {
// auto mpd = make_unique<Connection>(m_log, m_host, m_port, m_pass);
// auto status = mpd->get_status();
//
// if (cmd == EVENT_PLAY)
// mpd->play();
// else if (cmd == EVENT_PAUSE)
// mpd->pause(!(status->match_state(mpdstate::PAUSED)));
// 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)
// mpd->set_single(!status->single);
// else if (cmd == EVENT_REPEAT)
// mpd->set_repeat(!status->repeat);
// else if (cmd == EVENT_RANDOM)
// mpd->set_random(!status->random);
// else if (cmd.compare(0, strlen(EVENT_SEEK), EVENT_SEEK) == 0) {
// auto s = cmd.substr(strlen(EVENT_SEEK));
// int percentage = 0;
// if (s.empty())
// return false;
// if (s[0] == '+') {
// percentage = status->get_elapsed_percentage() + std::atoi(s.substr(1).c_str());
// } else if (s[0] == '-') {
// percentage = status->get_elapsed_percentage() - std::atoi(s.substr(1).c_str());
// } else {
// percentage = std::atoi(s.c_str());
// }
// mpd->seek(status->get_songid(), status->get_seek_position(percentage));
// } else
// return false;
// } catch (const mpd_exception& e) {
// m_log.err(e.what());
// }
//
// return true;
// }
private:
// static const int PROGRESSBAR_THREAD_SYNC_COUNT = 10;
// const chrono::duration<double> PROGRESSBAR_THREAD_INTERVAL = 1s;
static constexpr auto FORMAT_ONLINE = "format-online";
static constexpr auto TAG_BAR_PROGRESS = "<bar-progress>";
@ -46,43 +346,35 @@ namespace modules
static constexpr auto EVENT_RANDOM = "mpdrandom";
static constexpr auto 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;
progressbar_t m_bar_progress;
iconset_t m_icons;
label_t m_label_song;
label_t m_label_song_tokenized;
label_t m_label_time;
label_t m_label_time_tokenized;
label_t m_label_offline;
std::unique_ptr<mpd::Status> status;
unique_ptr<mpdconnection> m_mpd;
unique_ptr<mpdstatus> m_status;
std::string toggle_on_color;
std::string toggle_off_color;
string m_host = "127.0.0.1";
string m_pass = "";
unsigned int m_port = 6600;
std::unique_ptr<mpd::Connection> mpd;
std::chrono::system_clock::time_point synced_at;
float sync_interval = 1.0f;
string m_toggle_on_color;
string m_toggle_off_color;
std::string progress_fill, progress_empty, progress_indicator;
chrono::system_clock::time_point m_lastsync;
float m_synctime = 1.0f;
string m_progress_fill;
string m_progress_empty;
string m_progress_indicator;
// This flag is used to let thru a broadcast once every time
// the connection state changes
concurrency::Atomic<bool> connection_state_broadcasted { true };
public:
explicit MpdModule(std::string name);
~MpdModule();
void start();
bool has_event();
bool update();
std::string get_format();
bool build(Builder *builder, std::string tag);
bool handle_command(std::string cmd);
bool register_for_events() const {
return true;
}
stateflag m_connection_state_broadcasted{true};
};
}
LEMONBUDDY_NS_END

View File

@ -1,22 +1,204 @@
#pragma once
#include <atomic>
#include <chrono>
#include <memory>
#include <string>
#include "modules/base.hpp"
#include "interfaces/net.hpp"
#include "services/logger.hpp"
#include "drawtypes/icon.hpp"
#include "drawtypes/label.hpp"
#include "adapters/net.hpp"
#include "drawtypes/animation.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/ramp.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(NetworkModule, TimerModule)
{
#define DEFAULT_FORMAT_CONNECTED TAG_LABEL_CONNECTED
#define DEFAULT_FORMAT_DISCONNECTED TAG_LABEL_DISCONNECTED
#define DEFAULT_FORMAT_PACKETLOSS TAG_LABEL_CONNECTED
#define DEFAULT_LABEL_CONNECTED "%ifname% %local_ip%"
#define DEFAULT_LABEL_DISCONNECTED ""
#define DEFAULT_LABEL_PACKETLOSS ""
LEMONBUDDY_NS
namespace modules {
class network_module : public timer_module<network_module> {
public:
using timer_module::timer_module;
void setup() {
// Load configuration values
m_interface = m_conf.get<string>(name(), "interface");
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 1));
m_ping_nth_update = m_conf.get<int>(name(), "ping_interval", 0);
// Add formats
m_formatter->add(
FORMAT_CONNECTED, DEFAULT_FORMAT_CONNECTED, {TAG_RAMP_SIGNAL, TAG_LABEL_CONNECTED});
m_formatter->add(FORMAT_DISCONNECTED, DEFAULT_FORMAT_DISCONNECTED, {TAG_LABEL_DISCONNECTED});
// Create elements for format-connected
if (m_formatter->has(TAG_RAMP_SIGNAL, FORMAT_CONNECTED))
m_ramp_signal = get_config_ramp(m_conf, name(), TAG_RAMP_SIGNAL);
if (m_formatter->has(TAG_LABEL_CONNECTED, FORMAT_CONNECTED)) {
m_label_connected =
get_optional_config_label(m_conf, name(), TAG_LABEL_CONNECTED, DEFAULT_LABEL_CONNECTED);
m_label_connected_tokenized = m_label_connected->clone();
}
// Create elements for format-disconnected
if (m_formatter->has(TAG_LABEL_DISCONNECTED, FORMAT_DISCONNECTED)) {
m_label_disconnected = get_optional_config_label(
m_conf, name(), TAG_LABEL_DISCONNECTED, DEFAULT_LABEL_DISCONNECTED);
m_label_disconnected->replace_token("%ifname%", m_interface);
}
// Create elements for format-packetloss if we are told to test connectivity
if (m_ping_nth_update > 0) {
m_formatter->add(FORMAT_PACKETLOSS, DEFAULT_FORMAT_PACKETLOSS,
{TAG_ANIMATION_PACKETLOSS, TAG_LABEL_PACKETLOSS, TAG_LABEL_CONNECTED});
if (m_formatter->has(TAG_LABEL_PACKETLOSS, FORMAT_PACKETLOSS)) {
m_label_packetloss = get_optional_config_label(
m_conf, name(), TAG_LABEL_PACKETLOSS, DEFAULT_LABEL_PACKETLOSS);
m_label_packetloss_tokenized = m_label_packetloss->clone();
}
if (m_formatter->has(TAG_ANIMATION_PACKETLOSS, FORMAT_PACKETLOSS))
m_animation_packetloss = get_config_animation(m_conf, name(), TAG_ANIMATION_PACKETLOSS);
}
// Get an intstance of the network interface
try {
if (net::is_wireless_interface(m_interface)) {
m_wireless_network = make_unique<net::wireless_network>(m_interface);
} else {
m_wired_network = make_unique<net::wired_network>(m_interface);
}
} catch (net::network_error& e) {
m_log.err("%s: %s", name(), e.what());
std::exit(EXIT_FAILURE);
}
}
void start() {
timer_module::start();
// We only need to start the subthread if the packetloss animation is used
if (m_animation_packetloss)
m_threads.emplace_back(thread(&network_module::subthread_routine, this));
}
bool update() {
string ip, essid, linkspeed;
int signal_quality = 0;
net::network* network = nullptr;
// Process data for wireless network interfaces
if (m_wireless_network) {
network = m_wireless_network.get();
try {
essid = m_wireless_network->essid();
signal_quality = m_wireless_network->signal_quality();
} catch (net::wireless_network_error& e) {
m_log.trace("%s: %s", name(), e.what());
}
m_signal_quality = signal_quality;
// Process data for wired network interfaces
} else if (m_wired_network) {
network = m_wired_network.get();
linkspeed = m_wired_network->link_speed();
}
if (network != nullptr) {
try {
ip = network->ip();
} catch (net::network_error& e) {
m_log.trace("%s: %s", name(), e.what());
}
m_connected = network->connected();
// Ignore the first run
if (m_counter == -1) {
m_counter = 0;
} else if (m_ping_nth_update > 0 && m_connected && (++m_counter % m_ping_nth_update) == 0) {
m_conseq_packetloss = !network->test();
m_counter = 0;
}
}
// Update label contents
if (m_label_connected || m_label_packetloss) {
auto replace_tokens = [&](label_t label) {
label->replace_token("%ifname%", m_interface);
label->replace_token("%local_ip%", !ip.empty() ? ip : "x.x.x.x");
if (m_wired_network) {
label->replace_token("%linkspeed%", linkspeed);
} else if (m_wireless_network) {
label->replace_token("%essid%", !essid.empty() ? essid : "UNKNOWN");
label->replace_token("%signal%", to_string(signal_quality) + "%");
}
label->replace_token("%upspeed%", network->upspeed());
label->replace_token("%downspeed%", network->downspeed());
};
if (m_label_connected) {
m_label_connected_tokenized->m_text = m_label_connected->m_text;
replace_tokens(m_label_connected_tokenized);
}
if (m_label_packetloss) {
m_label_packetloss_tokenized->m_text = m_label_packetloss->m_text;
replace_tokens(m_label_packetloss_tokenized);
}
}
return true;
}
string get_format() {
if (!m_connected)
return FORMAT_DISCONNECTED;
else if (m_conseq_packetloss && m_ping_nth_update > 0)
return FORMAT_PACKETLOSS;
else
return FORMAT_CONNECTED;
}
bool build(builder* builder, string tag) {
if (tag == TAG_LABEL_CONNECTED)
builder->node(m_label_connected_tokenized);
else if (tag == TAG_LABEL_DISCONNECTED)
builder->node(m_label_disconnected);
else if (tag == TAG_LABEL_PACKETLOSS)
builder->node(m_label_packetloss_tokenized);
else if (tag == TAG_ANIMATION_PACKETLOSS)
builder->node(m_animation_packetloss->get());
else if (tag == TAG_RAMP_SIGNAL)
builder->node(m_ramp_signal->get_by_percentage(m_signal_quality));
else
return false;
return true;
}
protected:
void subthread_routine() {
this_thread::yield();
const auto dur =
chrono::duration<double>(float(m_animation_packetloss->framerate()) / 1000.0f);
while (enabled()) {
if (m_connected && m_conseq_packetloss)
broadcast();
sleep(dur);
}
m_log.trace("%s: Reached end of network subthread", name());
}
private:
static constexpr auto FORMAT_CONNECTED = "format-connected";
static constexpr auto FORMAT_PACKETLOSS = "format-packetloss";
static constexpr auto FORMAT_DISCONNECTED = "format-disconnected";
@ -27,39 +209,26 @@ namespace modules
static constexpr auto TAG_LABEL_PACKETLOSS = "<label-packetloss>";
static constexpr auto TAG_ANIMATION_PACKETLOSS = "<animation-packetloss>";
std::unique_ptr<net::WiredNetwork> wired_network;
std::unique_ptr<net::WirelessNetwork> wireless_network;
unique_ptr<net::wired_network> m_wired_network;
unique_ptr<net::wireless_network> m_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;
ramp_t m_ramp_signal;
animation_t m_animation_packetloss;
label_t m_label_connected;
label_t m_label_connected_tokenized;
label_t m_label_disconnected;
label_t m_label_packetloss;
label_t m_label_packetloss_tokenized;
std::shared_ptr<Logger> logger;
string m_interface;
std::string interface;
concurrency::Atomic<bool> connected;
concurrency::Atomic<bool> conseq_packetloss;
concurrency::Atomic<int> signal_quality;
int ping_nth_update;
int counter = -1; // Set to -1 to ignore the first run
void subthread_routine();
public:
explicit NetworkModule(std::string name);
void start();
bool update();
std::string get_format();
bool build(Builder *builder, std::string tag);
stateflag m_connected{false};
stateflag m_conseq_packetloss{false};
int m_signal_quality;
int m_ping_nth_update;
int m_counter = -1; // -1 to ignore the first run
};
}
LEMONBUDDY_NS_END

View File

@ -1,37 +1,184 @@
#pragma once
#include "modules/base.hpp"
#include "services/command.hpp"
#include "modules/meta.hpp"
#include "utils/command.hpp"
namespace modules
{
DefineModule(ScriptModule, TimerModule)
{
LEMONBUDDY_NS
#define SHELL_CMD "/usr/bin/env\nsh\n-c\n"
namespace modules {
class script_module : public timer_module<script_module> {
public:
using timer_module::timer_module;
void setup() {
// Load configuration values
m_exec = m_conf.get<string>(name(), "exec");
m_tail = m_conf.get<bool>(name(), "tail", m_tail);
m_maxlen = m_conf.get<size_t>(name(), "maxlen", 0);
m_ellipsis = m_conf.get<bool>(name(), "ellipsis", m_ellipsis);
if (m_tail)
m_interval = 0s;
else
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 1));
m_actions[mousebtn::LEFT] = m_conf.get<string>(name(), "click-left", "");
m_actions[mousebtn::MIDDLE] = m_conf.get<string>(name(), "click-middle", "");
m_actions[mousebtn::RIGHT] = m_conf.get<string>(name(), "click-right", "");
m_actions[mousebtn::SCROLL_UP] = m_conf.get<string>(name(), "scroll-up", "");
m_actions[mousebtn::SCROLL_DOWN] = m_conf.get<string>(name(), "scroll-down", "");
// Add formats and elements
m_formatter->add(DEFAULT_FORMAT, TAG_OUTPUT, {TAG_OUTPUT});
}
void start() {
timer_module::start();
if (m_tail) {
dispatch_tailscript_runner();
}
}
bool update() {
m_output.clear();
if (m_tail) {
m_output = tail_command();
} else {
m_output = read_output();
}
if (m_maxlen > 0 && m_output.length() > m_maxlen) {
m_output.erase(m_maxlen);
if (m_ellipsis)
m_output += "...";
}
return enabled() && !m_output.empty();
}
string get_output() {
if (m_output.empty())
return "";
auto counter_str = to_string(m_counter);
if (!m_actions[mousebtn::LEFT].empty())
m_builder->cmd(mousebtn::LEFT,
string_util::replace_all(m_actions[mousebtn::LEFT], "%counter%", counter_str));
if (!m_actions[mousebtn::MIDDLE].empty())
m_builder->cmd(mousebtn::MIDDLE,
string_util::replace_all(m_actions[mousebtn::MIDDLE], "%counter%", counter_str));
if (!m_actions[mousebtn::RIGHT].empty())
m_builder->cmd(mousebtn::RIGHT,
string_util::replace_all(m_actions[mousebtn::RIGHT], "%counter%", counter_str));
if (!m_actions[mousebtn::SCROLL_UP].empty())
m_builder->cmd(mousebtn::SCROLL_UP,
string_util::replace_all(m_actions[mousebtn::SCROLL_UP], "%counter%", counter_str));
if (!m_actions[mousebtn::SCROLL_DOWN].empty())
m_builder->cmd(mousebtn::SCROLL_DOWN,
string_util::replace_all(m_actions[mousebtn::SCROLL_DOWN], "%counter%", counter_str));
m_builder->node(module::get_output());
return m_builder->flush();
}
bool build(builder* builder, string tag) {
if (tag == TAG_OUTPUT)
builder->node(string_util::replace_all(m_output, "\n", ""));
else
return false;
return true;
}
protected:
/**
* Read line from tailscript
*/
string tail_command() {
int bytes_read = 0;
if (!m_command)
return "";
if (io_util::poll_read(m_command->get_stdout(PIPE_READ), 100))
return io_util::readline(m_command->get_stdout(PIPE_READ), bytes_read);
return "";
}
/**
* Execute command and read its output
*/
string read_output() {
string output;
try {
m_log.trace("%s: Executing command '%s'", name(), m_exec);
auto cmd = command_util::make_command(
SHELL_CMD + string_util::replace_all(m_exec, "%counter%", to_string(++m_counter)));
cmd->exec(false);
while (true) {
int bytes_read = 0;
string contents = io_util::readline(cmd->get_stdout(PIPE_READ), bytes_read);
if (bytes_read <= 0)
break;
output += contents;
output += "\n";
}
cmd->wait();
} catch (const system_error& err) {
m_log.err(err.what());
return "";
}
return output;
}
/**
* Run tail script in separate thread
*/
void dispatch_tailscript_runner() {
m_threads.emplace_back([this] {
try {
while (enabled() && (!m_command || !m_command->is_running())) {
m_log.trace("%s: Executing command '%s'", name(), m_exec);
m_command = command_util::make_command(
SHELL_CMD + string_util::replace_all(m_exec, "%counter%", to_string(++m_counter)));
m_command->exec(true);
}
} catch (const system_error& err) {
m_log.err("Failed to create command (what: %s)", err.what());
}
});
}
private:
static constexpr auto TAG_OUTPUT = "<output>";
std::unique_ptr<Builder> builder;
std::unique_ptr<Command> command;
unique_ptr<command_util::command> m_command;
std::string exec;
bool tail = false;
map<mousebtn, string> m_actions;
std::string click_left;
std::string click_middle;
std::string click_right;
std::string scroll_up;
std::string scroll_down;
string m_exec;
bool m_tail = false;
string m_output;
int m_counter{0};
std::string output;
std::atomic<int> counter;
protected:
public:
explicit ScriptModule(std::string name);
void start();
bool update();
bool build(Builder *builder, std::string tag);
std::string get_output();
size_t m_maxlen = 0;
bool m_ellipsis = true;
};
}
LEMONBUDDY_NS_END

View File

@ -1,17 +1,50 @@
#pragma once
#include "modules/base.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(TextModule, StaticModule)
{
LEMONBUDDY_NS
namespace modules {
class text_module : public static_module<text_module> {
public:
using static_module::static_module;
void setup() {
m_formatter->add(FORMAT, "", {});
if (m_formatter->get(FORMAT)->value.empty())
throw undefined_format(FORMAT);
}
string get_format() {
return FORMAT;
}
string get_output() {
auto click_left = m_conf.get<string>(name(), "click-left", "");
auto click_middle = m_conf.get<string>(name(), "click-middle", "");
auto click_right = m_conf.get<string>(name(), "click-right", "");
auto scroll_up = m_conf.get<string>(name(), "scroll-up", "");
auto scroll_down = m_conf.get<string>(name(), "scroll-down", "");
if (!click_left.empty())
m_builder->cmd(mousebtn::LEFT, click_left);
if (!click_middle.empty())
m_builder->cmd(mousebtn::MIDDLE, click_middle);
if (!click_right.empty())
m_builder->cmd(mousebtn::RIGHT, click_right);
if (!scroll_up.empty())
m_builder->cmd(mousebtn::SCROLL_UP, scroll_up);
if (!scroll_down.empty())
m_builder->cmd(mousebtn::SCROLL_DOWN, scroll_down);
m_builder->node(module::get_output());
return m_builder->flush();
}
private:
static constexpr auto FORMAT = "content";
public:
explicit TextModule(std::string name);
std::string get_format();
std::string get_output();
};
}
LEMONBUDDY_NS_END

View File

@ -1,20 +1,268 @@
#pragma once
#include "modules/base.hpp"
#include "interfaces/alsa.hpp"
#include "drawtypes/icon.hpp"
#include "adapters/alsa.hpp"
#include "config.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "drawtypes/bar.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(VolumeModule, EventModule)
{
LEMONBUDDY_NS
namespace modules {
class volume_module : public event_module<volume_module> {
public:
using event_module::event_module;
void setup() {
// Load configuration values {{{
auto master_mixer = m_conf.get<string>(name(), "master_mixer", "Master");
auto speaker_mixer = m_conf.get<string>(name(), "speaker_mixer", "");
auto headphone_mixer = m_conf.get<string>(name(), "headphone_mixer", "");
m_headphone_ctrl_numid = m_conf.get<int>(name(), "headphone_control_numid", -1);
if (!headphone_mixer.empty() && m_headphone_ctrl_numid == -1)
throw module_error(
"volume_module: Missing required property value for \"headphone_control_numid\"...");
else if (headphone_mixer.empty() && m_headphone_ctrl_numid != -1)
throw module_error(
"volume_module: Missing required property value for \"headphone_mixer\"...");
if (string_util::lower(speaker_mixer) == "master")
throw module_error(
"volume_module: The \"Master\" mixer is already processed internally. Specify another "
"mixer or comment out the \"speaker_mixer\" parameter...");
if (string_util::lower(headphone_mixer) == "master")
throw module_error(
"volume_module: The \"Master\" mixer is already processed internally. Specify another "
"mixer or comment out the \"headphone_mixer\" parameter...");
// }}}
// Setup mixers {{{
auto create_mixer = [](const logger& log, string mixer_name) {
unique_ptr<alsa_mixer> mixer;
try {
mixer = make_unique<alsa_mixer>(mixer_name);
} catch (const alsa_mixer_error& e) {
log.err("volume_module: Failed to open '%s' mixer => %s", mixer_name, e.what());
mixer.reset();
}
return mixer;
};
m_master_mixer = create_mixer(m_log, master_mixer);
if (!speaker_mixer.empty())
m_speaker_mixer = create_mixer(m_log, speaker_mixer);
if (!headphone_mixer.empty())
m_headphone_mixer = create_mixer(m_log, headphone_mixer);
if (!m_master_mixer && !m_speaker_mixer && !m_headphone_mixer) {
stop();
return;
}
if (m_headphone_mixer && m_headphone_ctrl_numid > -1) {
try {
m_headphone_ctrl = make_unique<alsa_ctl_interface>(m_headphone_ctrl_numid);
} catch (const alsa_ctl_interface_error& e) {
m_log.err("%s: Failed to open headphone control interface => %s", name(), e.what());
m_headphone_ctrl.reset();
}
}
// }}}
// Add formats and elements {{{
m_formatter->add(
FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
m_formatter->add(
FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});
if (m_formatter->has(TAG_BAR_VOLUME)) {
m_bar_volume = get_config_bar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
}
if (m_formatter->has(TAG_RAMP_VOLUME)) {
m_ramp_volume = get_config_ramp(m_conf, name(), TAG_RAMP_VOLUME);
m_ramp_headphones = get_config_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false);
}
if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
m_label_volume =
get_optional_config_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%");
m_label_volume_tokenized = m_label_volume->clone();
}
if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
m_label_muted = get_optional_config_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%");
m_label_muted_tokenized = m_label_muted->clone();
}
// }}}
}
void stop() {
std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
m_master_mixer.reset();
m_speaker_mixer.reset();
m_headphone_mixer.reset();
m_headphone_ctrl.reset();
event_module::stop();
}
bool has_event() {
if (m_has_changed)
return true;
try {
bool has_event = false;
if (m_master_mixer)
has_event |= m_master_mixer->wait(25);
if (m_speaker_mixer)
has_event |= m_speaker_mixer->wait(25);
if (m_headphone_mixer)
has_event |= m_headphone_mixer->wait(25);
if (m_headphone_ctrl)
has_event |= m_headphone_ctrl->wait(25);
return has_event;
} catch (const alsa_exception& e) {
m_log.err("%s: %s", name(), e.what());
return false;
}
}
bool update() {
m_has_changed = false;
// Consume any other pending events
if (m_master_mixer)
m_master_mixer->process_events();
if (m_speaker_mixer)
m_speaker_mixer->process_events();
if (m_headphone_mixer)
m_headphone_mixer->process_events();
if (m_headphone_ctrl)
m_headphone_ctrl->wait(0);
int volume = 100;
bool muted = false;
if (m_master_mixer) {
volume *= m_master_mixer->get_volume() / 100.0f;
muted |= m_master_mixer->is_muted();
}
if (m_headphone_mixer && m_headphone_ctrl && m_headphone_ctrl->test_device_plugged()) {
m_headphones = true;
volume *= m_headphone_mixer->get_volume() / 100.0f;
muted |= m_headphone_mixer->is_muted();
} else if (m_speaker_mixer) {
m_headphones = false;
volume *= m_speaker_mixer->get_volume() / 100.0f;
muted |= m_speaker_mixer->is_muted();
}
m_volume = volume;
m_muted = muted;
if (m_label_volume_tokenized) {
m_label_volume_tokenized->m_text = m_label_volume->m_text;
m_label_volume_tokenized->replace_token("%percentage%", to_string(m_volume) + "%");
}
if (m_label_muted_tokenized) {
m_label_muted_tokenized->m_text = m_label_muted->m_text;
m_label_muted_tokenized->replace_token("%percentage%", to_string(m_volume) + "%");
}
return true;
}
string get_format() {
return m_muted == true ? FORMAT_MUTED : FORMAT_VOLUME;
}
string get_output() {
m_builder->cmd(mousebtn::LEFT, EVENT_TOGGLE_MUTE);
if (!m_muted && m_volume < 100)
m_builder->cmd(mousebtn::SCROLL_UP, EVENT_VOLUME_UP);
if (!m_muted && m_volume > 0)
m_builder->cmd(mousebtn::SCROLL_DOWN, EVENT_VOLUME_DOWN);
m_builder->node(module::get_output());
return m_builder->flush();
}
bool build(builder* builder, string tag) {
if (tag == TAG_BAR_VOLUME)
builder->node(m_bar_volume->output(m_volume));
else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones))
builder->node(m_ramp_volume->get_by_percentage(m_volume));
else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones)
builder->node(m_ramp_headphones->get_by_percentage(m_volume));
else if (tag == TAG_LABEL_VOLUME)
builder->node(m_label_volume_tokenized);
else if (tag == TAG_LABEL_MUTED)
builder->node(m_label_muted_tokenized);
else
return false;
return true;
}
// bool handle_command(string cmd) {
// if (cmd.length() < strlen(EVENT_PREFIX))
// return false;
// if (std::strncmp(cmd.c_str(), EVENT_PREFIX, 3) != 0)
// return false;
//
// // std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
//
// alsa_mixer* master_mixer = nullptr;
// alsa_mixer* other_mixer = nullptr;
//
// if (m_master_mixer)
// master_mixer = m_master_mixer.get();
//
// if (master_mixer == nullptr)
// return false;
//
// if (m_headphone_mixer && m_headphone_ctrl && m_headphone_ctrl->test_device_plugged())
// other_mixer = m_headphone_mixer.get();
// else if (m_speaker_mixer)
// other_mixer = m_speaker_mixer.get();
//
// if (std::strncmp(cmd.c_str(), EVENT_TOGGLE_MUTE, strlen(EVENT_TOGGLE_MUTE)) == 0) {
// // Toggle mute state
// master_mixer->set_mute(m_muted);
// if (other_mixer != nullptr)
// other_mixer->set_mute(m_muted);
// } else if (std::strncmp(cmd.c_str(), EVENT_VOLUME_UP, strlen(EVENT_VOLUME_UP)) == 0) {
// // Increase volume
// master_mixer->set_volume(math_util::cap<float>(master_mixer->get_volume() + 5, 0, 100));
// if (other_mixer != nullptr)
// other_mixer->set_volume(math_util::cap<float>(other_mixer->get_volume() + 5, 0, 100));
// } else if (std::strncmp(cmd.c_str(), EVENT_VOLUME_DOWN, strlen(EVENT_VOLUME_DOWN)) == 0) {
// // Decrease volume
// master_mixer->set_volume(math_util::cap<float>(master_mixer->get_volume() - 5, 0, 100));
// if (other_mixer != nullptr)
// other_mixer->set_volume(math_util::cap<float>(other_mixer->get_volume() - 5, 0, 100));
// } else {
// return false;
// }
//
// m_has_changed = true;
//
// return true;
// }
private:
static constexpr auto FORMAT_VOLUME = "format-volume";
static constexpr auto FORMAT_MUTED = "format-muted";
static constexpr auto TAG_RAMP_VOLUME = "<ramp-volume>";
static constexpr auto TAG_RAMP_HEADPHONES = "<ramp-headphones>";
static constexpr auto TAG_BAR_VOLUME = "<bar-volume>";
static constexpr auto TAG_LABEL_VOLUME = "<label-volume>";
static constexpr auto TAG_LABEL_MUTED = "<label-muted>";
@ -24,38 +272,24 @@ namespace modules
static constexpr auto EVENT_VOLUME_DOWN = "voldown";
static constexpr auto EVENT_TOGGLE_MUTE = "volmute";
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;
progressbar_t m_bar_volume;
ramp_t m_ramp_volume;
ramp_t m_ramp_headphones;
label_t m_label_volume;
label_t m_label_volume_tokenized;
label_t m_label_muted;
label_t m_label_muted_tokenized;
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;
concurrency::Atomic<int> volume;
concurrency::Atomic<bool> muted;
concurrency::Atomic<bool> has_changed;
public:
explicit VolumeModule(std::string name);
~VolumeModule();
bool has_event();
bool update();
std::string get_format();
std::string get_output();
bool build(Builder *builder, std::string tag);
bool handle_command(std::string cmd);
bool register_for_events() const {
return true;
}
unique_ptr<alsa_mixer> m_master_mixer;
unique_ptr<alsa_mixer> m_speaker_mixer;
unique_ptr<alsa_mixer> m_headphone_mixer;
unique_ptr<alsa_ctl_interface> m_headphone_ctrl;
int m_headphone_ctrl_numid = -1;
int m_volume = 0;
bool m_muted = false;
bool m_has_changed = false;
bool m_headphones = false;
};
}
LEMONBUDDY_NS_END

View File

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

View File

@ -1,104 +0,0 @@
#pragma once
#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;
struct Options;
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, std::string value);
void tag_close(char tag);
void align_left();
void align_center();
void align_right();
std::shared_ptr<Options> opts;
std::shared_ptr<Options>& get_opts();
public:
explicit Builder(bool lazy_closing = true)
: lazy_closing(lazy_closing) {}
void set_lazy_closing(bool mode) { this->lazy_closing = mode; }
std::string flush();
void append(std::string node);
void append_module_output(Alignment alignment, std::string module_output, bool margin_left = true, bool margin_right = true);
void node(std::string str, bool add_space = false);
void node(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(std::string color);
void background_close(bool force = false);
void color(std::string color);
void color_alpha(std::string alpha);
void color_close(bool force = false);
void line_color(std::string color);
void line_color_close(bool force = false);
void overline(std::string color = "");
void overline_close(bool force = false);
void underline(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);
};

View File

@ -1,40 +0,0 @@
#pragma once
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include "exception.hpp"
#include "utils/proc.hpp"
DefineBaseException(CommandException);
class Command
{
protected:
std::string cmd;
int fd_stdout[2];
int fd_stdin[2];
pid_t fork_pid;
int fork_status;
public:
Command(std::string cmd, int stdout[2] = nullptr, int stdin[2] = nullptr);
~Command();
int exec(bool wait_for_completion = true);
int wait();
void tail(std::function<void(std::string)> callback);
int writeline(std::string data);
int get_stdout(int);
// int get_stdin(int);
pid_t get_pid();
bool is_running();
int get_exit_status();
};

View File

@ -1,31 +0,0 @@
#pragma once
#include <chrono>
#include <deque>
#include <memory>
#include <string>
namespace event_throttler
{
typedef std::chrono::duration<double, std::milli> timewindow_t;
typedef std::chrono::high_resolution_clock timepoint_clock_t;
typedef event_throttler::timepoint_clock_t::time_point timepoint_t;
typedef std::deque<event_throttler::timepoint_t> queue_t;
typedef std::size_t limit_t;
}
class EventThrottler
{
event_throttler::queue_t event_queue;
event_throttler::limit_t passthrough_limit;
event_throttler::timewindow_t timewindow;
protected:
void expire();
public:
explicit EventThrottler(int limit, event_throttler::timewindow_t timewindow)
: passthrough_limit(limit), timewindow(timewindow) {}
bool passthrough();
};

View File

@ -1,60 +0,0 @@
#pragma once
#include <memory>
#include <sys/inotify.h>
#include "exception.hpp"
class InotifyException : public Exception
{
public:
explicit InotifyException(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:
explicit InotifyWatch(std::string path, int mask = InotifyEvent::ALL);
~InotifyWatch();
std::string operator()() {
return this->path;
}
bool has_event(int timeout_ms = 1000);
std::unique_ptr<InotifyEvent> get_event();
};

View File

@ -1,80 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include <mutex>
#include <unistd.h>
#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)
#ifdef DEBUG
#define log_trace(s) get_logger()->trace(__FILE__, __FUNCTION__, __LINE__, s)
#define log_trace2(logger, s) logger->trace(__FILE__, __FUNCTION__, __LINE__, s)
#else
#define log_trace(s) if (0) {}
#define log_trace2(logger, s) if (0) {}
#endif
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,
#ifdef DEBUG
LEVEL_ALL = LEVEL_ERROR | LEVEL_WARNING | LEVEL_INFO | LEVEL_DEBUG | LEVEL_TRACE
#else
LEVEL_ALL = LEVEL_ERROR | LEVEL_WARNING | LEVEL_INFO | LEVEL_DEBUG
#endif
};
class Logger
{
std::mutex mtx;
int level = LogLevel::LEVEL_ERROR | LogLevel::LEVEL_WARNING | LogLevel::LEVEL_INFO;
int fd = LOGGER_FD;
void output(std::string tag, std::string msg);
public:
Logger();
void set_level(int level);
void add_level(int level);
void fatal(std::string msg);
void error(std::string msg);
void warning(std::string msg);
void info(std::string msg);
void debug(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, std::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();

View File

@ -1,42 +0,0 @@
#if 0
#ifndef _SERVICES_STORE_HPP_
#define _SERVICES_STORE_HPP_
=======
#pragma once
>>>>>>> task(core): Cleanup
#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(std::string s);
Store(int size);
~Store() {}
};
<<<<<<< 78384e08923e669c65c68a8cdf81dba37a633d6c
#endif
#endif

95
include/utils/bspwm.hpp Normal file
View File

@ -0,0 +1,95 @@
#pragma once
#include <xcb/xcb.h>
#include "common.hpp"
#include "config.hpp"
#include "utils/socket.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
namespace bspwm_util {
struct payload;
using subscriber_t = unique_ptr<socket_util::unix_connection>;
using payload_t = unique_ptr<payload>;
/**
* bspwm payload
*/
struct payload {
char data[BUFSIZ]{'\0'};
size_t len = 0;
};
/**
* Get path to the bspwm socket by the following order
*
* 1. Value of environment variable BSPWM_SOCKET
* 2. Value built from the bspwm socket path template
* 3. Value of the macro BSPWM_SOCKET_PATH
*/
string get_socket_path() {
string env_path{read_env("BSPWM_SOCKET")};
if (!env_path.empty())
return env_path;
struct sockaddr_un sa;
char* tpl_path = nullptr;
char* host = nullptr;
int dsp = 0;
int scr = 0;
if (xcb_parse_display(nullptr, &host, &dsp, &scr) != 0)
std::snprintf(tpl_path, sizeof(sa.sun_path), "/tmp/bspwm%s_%i_%i-socket", host, dsp, scr);
if (tpl_path != nullptr)
return tpl_path;
return BSPWM_SOCKET_PATH;
}
/**
* Generate a payload object with properly formatted data
* ready to be sent to the bspwm ipc controller
*/
unique_ptr<payload> make_payload(string cmd) {
auto pl = make_unique<payload>();
auto size = sizeof(pl->data);
int offset = 0;
int chars = 0;
for (auto&& word : string_util::split(cmd, ' ')) {
chars = snprintf(pl->data + offset, size - offset, "%s%c", word.c_str(), 0);
pl->len += chars;
offset += chars;
}
return pl;
}
/**
* Create a connection and subscribe to events
* on the bspwm socket
*
* Example usage:
* @code cpp
* auto ipc = bspwm_util::make_subscriber();
*
* while (!ipc->poll(POLLHUP, 0)) {
* ssize_t bytes_received = 0;
* auto data = ipc->receive(BUFSIZ-1, bytes_received, 0);
* std::cout << data << std::endl;
* }
* @endcode
*/
subscriber_t make_subscriber() {
auto conn = socket_util::make_unix_connection(BSPWM_SOCKET_PATH);
auto payload = make_payload("subscribe report");
if (conn->send(payload->data, payload->len, 0) == 0)
throw system_error("Failed to initialize subscriber");
return conn;
}
}
LEMONBUDDY_NS_END

View File

@ -1,38 +0,0 @@
#pragma once
#include <vector>
#include "exception.hpp"
namespace cli
{
DefineBaseException(CommandLineError);
struct Option
{
std::string flag_short;
std::string flag_long;
std::string placeholder;
std::string help;
std::vector<std::string> accept;
Option(std::string flag_short, std::string flag_long, 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(std::string opt_short, std::string opt_long, std::string help);
void add_option(std::string opt_short, std::string opt_long, std::string placeholder, std::string help, std::vector<std::string> accept = {});
bool is_option(char *opt, std::string opt_short, std::string opt_long);
bool is_option_short(char *opt, std::string opt_short);
bool is_option_long(char *opt, std::string opt_long);
std::string parse_option_value(int &i, int argc, char **argv, std::vector<std::string> accept = {});
bool has_option(std::string opt);
std::string get_option_value(std::string opt);
bool match_option_value(std::string opt, std::string val);
void parse(int i, int argc, char **argv);
void usage(std::string usage, bool exit_success = true);
}

213
include/utils/command.hpp Normal file
View File

@ -0,0 +1,213 @@
#pragma once
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include "common.hpp"
#include "components/logger.hpp"
#include "utils/io.hpp"
#include "utils/process.hpp"
#include "utils/threading.hpp"
LEMONBUDDY_NS
namespace command_util {
/**
* Example usage:
* @code cpp
* auto cmd = make_unique<Command>("cat /etc/rc.local");
* cmd->exec();
* cmd->tail(callback); //---> the contents of /etc/rc.local is sent to callback()
*
* auto cmd = 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");
* cout << cmd->readline(); //---> data from parent process: Test
*
* auto cmd = make_unique<Command>("/bin/sh\n-c\nfor i in 1 2 3; do echo $i; done");
* cout << cmd->readline(); //---> 1
* cout << cmd->readline() << cmd->readline(); //---> 23
* @encode
*/
class command {
public:
explicit command(const logger& logger, string cmd, int out[2] = nullptr, int in[2] = nullptr)
: m_log(logger), m_cmd(cmd) {
if (in != nullptr) {
m_stdin[PIPE_READ] = in[PIPE_READ];
m_stdin[PIPE_WRITE] = in[PIPE_WRITE];
} else if (pipe(m_stdin) != 0) {
throw system_error("Failed to allocate pipe");
}
if (out != nullptr) {
m_stdout[PIPE_READ] = out[PIPE_READ];
m_stdout[PIPE_WRITE] = out[PIPE_WRITE];
} else if (pipe(m_stdout) != 0) {
close(m_stdin[PIPE_READ]);
close(m_stdin[PIPE_WRITE]);
throw system_error("Failed to allocate pipe");
}
}
~command() {
if (is_running()) {
m_log.warn("command: Sending SIGKILL to forked process (%d)", m_forkpid);
kill(m_forkpid, SIGKILL);
}
if (m_stdin[PIPE_READ] > 0 && (close(m_stdin[PIPE_READ]) == -1))
m_log.err("command: Failed to close fd: %s (%d)", strerror(errno), errno);
if (m_stdin[PIPE_WRITE] > 0 && (close(m_stdin[PIPE_WRITE]) == -1))
m_log.err("command: Failed to close fd: %s (%d)", strerror(errno), errno);
if (m_stdout[PIPE_READ] > 0 && (close(m_stdout[PIPE_READ]) == -1))
m_log.err("command: Failed to close fd: %s (%d)", strerror(errno), errno);
if (m_stdout[PIPE_WRITE] > 0 && (close(m_stdout[PIPE_WRITE]) == -1))
m_log.err("command: Failed to close fd: %s (%d)", strerror(errno), errno);
}
/**
* Execute the command
*/
int exec(bool wait_for_completion = true) {
if ((m_forkpid = fork()) == -1)
throw system_error("Failed to fork process");
if (process_util::in_forked_process(m_forkpid)) {
if (dup2(m_stdin[PIPE_READ], STDIN_FILENO) == -1)
throw system_error("Failed to redirect stdin in child process");
if (dup2(m_stdout[PIPE_WRITE], STDOUT_FILENO) == -1)
throw system_error("Failed to redirect stdout in child process");
if (dup2(m_stdout[PIPE_WRITE], STDERR_FILENO) == -1)
throw system_error("Failed to redirect stderr in child process");
// Close file descriptors that won't be used by forked process
if ((m_stdin[PIPE_READ] = close(m_stdin[PIPE_READ])) == -1)
throw system_error("Failed to close fd");
if ((m_stdin[PIPE_WRITE] = close(m_stdin[PIPE_WRITE])) == -1)
throw system_error("Failed to close fd");
if ((m_stdout[PIPE_READ] = close(m_stdout[PIPE_READ])) == -1)
throw system_error("Failed to close fd");
if ((m_stdout[PIPE_WRITE] = close(m_stdout[PIPE_WRITE])) == -1)
throw system_error("Failed to close fd");
// Replace the forked process with the given command
process_util::exec(m_cmd);
std::exit(0);
} else {
// Close file descriptors that won't be used by parent process
if ((m_stdin[PIPE_READ] = close(m_stdin[PIPE_READ])) == -1)
throw system_error("Failed to close fd");
if ((m_stdout[PIPE_WRITE] = close(m_stdout[PIPE_WRITE])) == -1)
throw system_error("Failed to close fd");
if (wait_for_completion)
return wait();
}
return EXIT_SUCCESS;
}
/**
* Wait for the child processs to finish
*/
int wait() {
do {
pid_t pid;
if ((pid = process_util::wait_for_completion(
m_forkpid, &m_forkstatus, WCONTINUED | WUNTRACED)) == -1) {
throw system_error(
"Process did not finish successfully (" + to_string(m_forkstatus) + ")");
}
if (WIFEXITED(m_forkstatus))
m_log.trace("command: Exited with status %d", WEXITSTATUS(m_forkstatus));
else if (WIFSIGNALED(m_forkstatus))
m_log.trace("command: Got killed by signal %d (%s)", WTERMSIG(m_forkstatus),
strerror(WTERMSIG(m_forkstatus)));
else if (WIFSTOPPED(m_forkstatus))
m_log.trace("command: Stopped by signal %d (%s)", WSTOPSIG(m_forkstatus),
strerror(WSTOPSIG(m_forkstatus)));
else if (WIFCONTINUED(m_forkstatus) == true)
m_log.trace("command: Continued");
} while (!WIFEXITED(m_forkstatus) && !WIFSIGNALED(m_forkstatus));
return m_forkstatus;
}
/**
* Tail command output
*/
void tail(function<void(string)> callback) {
io_util::tail(m_stdout[PIPE_READ], callback);
}
/**
* Write line to command input channel
*/
int writeline(string data) {
std::lock_guard<threading_util::spin_lock> lck(m_pipelock);
return io_util::writeline(m_stdin[PIPE_WRITE], data);
}
/**
* Get command output channel
*/
int get_stdout(int c) {
return m_stdout[c];
}
/**
* Get command input channel
*/
int get_stdin(int c) {
return m_stdin[c];
}
/**
* Get command pid
*/
pid_t get_pid() {
return m_forkpid;
}
/**
* Check if command is running
*/
bool is_running() {
return process_util::wait_for_completion_nohang(m_forkpid, &m_forkstatus) > -1;
}
/**
* Get command exit status
*/
int get_exit_status() {
return m_forkstatus;
}
protected:
const logger& m_log;
string m_cmd;
int m_stdout[2];
int m_stdin[2];
pid_t m_forkpid;
int m_forkstatus;
threading_util::spin_lock m_pipelock;
};
template <typename... Args>
auto make_command(Args&&... args) {
return make_unique<command>(
logger::configure().create<const logger&>(), forward<Args>(args)...);
}
}
LEMONBUDDY_NS_END

View File

@ -1,124 +0,0 @@
#pragma once
#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
{
std::atomic<T> value;
public:
Atomic() = default;
explicit Atomic(T init) {
this->operator=(init);
}
void operator=(T value)
{
this->value = value;
}
T operator()()
{
return this->value;
}
operator bool()
{
return this->value;
}
bool operator==(T const& b)
{
return this->value == b;
}
};
template<typename T>
class Value
{
concurrency::SpinLock lock;
T value;
public:
Value() = default;
explicit 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;
}
bool operator==(T const& b)
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value == b;
}
};
}

View File

@ -1,184 +0,0 @@
#pragma once
#include <mutex>
#include <boost/lexical_cast.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "exception.hpp"
#include "utils/string.hpp"
namespace config
{
DefineBaseException(ConfigException);
DefineChildException(UnexistingFileError, ConfigException);
DefineChildException(ParseError, ConfigException);
DefineChildException(MissingValueException, ConfigException);
DefineChildException(MissingListValueException, ConfigException);
DefineChildException(InvalidVariableException, ConfigException);
DefineChildException(InvalidReferenceException, ConfigException);
static std::recursive_mutex mtx;
/**
* Gets the path used to access the current bar section
*/
std::string get_bar_path();
/**
* Sets the path used to access the current bar section
*/
void set_bar_path(std::string path);
/**
* Loads a configuration file
*/
void load(std::string path);
/**
* Loads a configuration file
*/
void load(const char *dir, std::string path);
/**
* Reloads the configuration values
* TODO: Implement properly
*/
// void reload();
/**
* Gets the boost property tree handler
*/
boost::property_tree::ptree get_tree();
/**
* Builds the path used to find a parameter in the given section
*/
std::string build_path(std::string section, std::string key);
/**
* Gets the location of the configuration file
*/
std::string get_file_path();
/**
* Finds the value of a config parameter defined
* as a reference variable using ${section.param} or ${env:VAR}
*/
template<typename T>
T dereference_var(std::string ref_section, std::string ref_key, std::string var, const T ref_val)
{
std::lock_guard<std::recursive_mutex> lck(config::mtx);
auto n = var.find("${");
auto m = var.find("}");
if (n != 0 || m != var.length()-1)
return ref_val;
auto path = var.substr(2, m-2);
if (path.find("env:") == 0) {
auto *envvar_value = std::getenv(path.substr(4).c_str());
if (envvar_value != nullptr)
return boost::lexical_cast<T>(envvar_value);
return ref_val;
}
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());
}
/**
* Gets the value of a variable by section and parameter name
*/
template<typename T>
T get(std::string section, std::string key)
{
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());
}
/**
* Gets the value of a variable by section and parameter name
* with a default value in case the parameter isn't defined
*/
template<typename T>
T get(std::string section, 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));
}
/**
* Gets a list of values by section and parameter name
*/
template<typename T>
std::vector<T> get_list(std::string section, std::string key)
{
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;
}
/**
* Gets a list of values by section and parameter name
* with a default list in case the list isn't defined
*/
template<typename T>
std::vector<T> get_list(std::string section, 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;
}
}

108
include/utils/file.hpp Normal file
View File

@ -0,0 +1,108 @@
#pragma once
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fstream>
#include "common.hpp"
#include "utils/scope.hpp"
LEMONBUDDY_NS
namespace file_util {
/**
* RAII file wrapper
*/
class file_ptr {
public:
/**
* Constructor: open file handler
*/
explicit file_ptr(const string& path, const string& mode = "a+")
: m_path(string(path)), m_mode(string(mode)) {
m_ptr = fopen(m_path.c_str(), m_mode.c_str());
}
/**
* Destructor: close file handler
*/
~file_ptr() {
if (m_ptr != nullptr)
fclose(m_ptr);
}
/**
* Logical operator testing if the file handler was created
*/
operator bool() {
return m_ptr != nullptr;
}
/**
* Call operator returning a pointer to the file handler
*/
FILE* operator()() {
return m_ptr;
}
protected:
FILE* m_ptr = nullptr;
string m_path;
string m_mode;
};
/**
* Checks if the given file exist
*/
inline auto exists(string filename) {
struct stat buffer;
return stat(filename.c_str(), &buffer) == 0;
}
/**
* Gets the contents of the given file
*/
inline auto get_contents(string filename) {
try {
std::ifstream ifs(filename);
string contents((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
return contents;
} catch (std::ios_base::failure& e) {
return string{""};
}
}
/**
* Puts the given file descriptor into blocking mode
*/
inline auto set_block(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1)
throw system_error("Failed to unset O_NONBLOCK");
}
/**
* Puts the given file descriptor into non-blocking mode
*/
inline auto set_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1)
throw system_error("Failed to set O_NONBLOCK");
}
/**
* Checks if the given file is a named pipe
*/
inline auto is_fifo(string filename) {
auto fileptr = make_unique<file_ptr>(filename);
int fd = fileno((*fileptr)());
struct stat statbuf;
fstat(fd, &statbuf);
return S_ISFIFO(statbuf.st_mode);
}
}
LEMONBUDDY_NS_END

126
include/utils/inotify.hpp Normal file
View File

@ -0,0 +1,126 @@
#pragma once
#include <sys/inotify.h>
#include <sys/poll.h>
#include <cstdio>
#include "common.hpp"
#include "utils/memory.hpp"
LEMONBUDDY_NS
using inotify_event_t = struct inotify_event;
struct inotify_event {
string filename;
bool is_dir;
int wd = 0;
int cookie = 0;
int mask = 0;
};
class inotify_watch {
public:
/**
* Constructor
*/
explicit inotify_watch(string path) : m_path(path) {}
/**
* Destructor
*/
~inotify_watch() noexcept {
if (m_wd != -1)
inotify_rm_watch(m_fd, m_wd);
if (m_fd != -1)
close(m_fd);
}
/**
* Attach inotify watch
*/
void attach(int mask = IN_MODIFY) {
if (m_fd == -1 && (m_fd = inotify_init()) == -1)
throw system_error("Failed to allocate inotify fd");
if ((m_wd = inotify_add_watch(m_fd, m_path.c_str(), mask)) == -1)
throw system_error("Failed to attach inotify watch");
}
/**
* Remove inotify watch
*/
void remove() {
if (inotify_rm_watch(m_fd, m_wd) == -1)
throw system_error("Failed to remove inotify watch");
m_wd = -1;
}
/**
* Poll the inotify fd for events
*
* @brief A wait_ms of -1 blocks until an event is fired
*/
bool poll(int wait_ms = 1000) {
if (m_fd == -1)
return false;
struct pollfd fds[1];
fds[0].fd = m_fd;
fds[0].events = POLLIN;
::poll(fds, 1, wait_ms);
return fds[0].revents & POLLIN;
}
/**
* Get the latest inotify event
*/
unique_ptr<inotify_event> get_event() {
auto event = make_unique<inotify_event>();
if (m_fd == -1 || m_wd == -1)
return event;
char buffer[1024];
size_t bytes = read(m_fd, buffer, 1024);
size_t len = 0;
while (len < bytes) {
auto* e = reinterpret_cast<inotify_event_t*>(&buffer[len]);
event->filename = e->len ? e->name : m_path;
event->wd = e->wd;
event->cookie = e->cookie;
event->is_dir = e->mask & IN_ISDIR;
event->mask |= e->mask;
len += sizeof(inotify_event_t) + e->len;
}
return event;
}
/**
* Get watch file path
*/
const string path() const {
return m_path;
}
protected:
string m_path;
int m_fd = -1;
int m_wd = -1;
};
using inotify_watch_t = unique_ptr<inotify_watch>;
namespace inotify_util {
inline auto make_watch(string path) {
di::injector<inotify_watch_t> injector = di::make_injector(di::bind<>().to(path));
return injector.create<inotify_watch_t>();
};
}
LEMONBUDDY_NS_END

View File

@ -1,77 +1,120 @@
#pragma once
#include <string>
#include <functional>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <unistd.h>
namespace io
{
namespace socket
{
int open(std::string path);
int send(int fd, std::string data, int flags = 0);
int recv(int fd, char *buffer, int recv_bytes, int flags = 0);
#include "common.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
namespace io_util {
inline string read(int read_fd, int bytes_to_read, int& bytes_read_loc, int& status_loc) {
char buffer[BUFSIZ - 1];
if (bytes_to_read == -1)
bytes_to_read = sizeof(buffer);
status_loc = 0;
if ((bytes_read_loc = ::read(read_fd, &buffer, bytes_to_read)) == -1) {
throw system_error("Error trying to read from fd");
} else if (bytes_read_loc == 0) {
status_loc = -1;
} else {
buffer[bytes_read_loc] = '\0';
}
return {buffer};
}
namespace file
{
class FilePtr
{
FILE *fptr = nullptr;
public:
std::string path;
std::string mode;
FilePtr(std::string path, std::string mode = "a+")
: path(std::string(path)), 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(std::string fname);
std::string get_contents(std::string fname);
bool is_fifo(std::string fname);
std::size_t write(FilePtr *fptr, std::string data);
std::size_t write(std::string fpath, std::string data);
inline string read(int read_fd, int bytes_to_read = -1) {
int bytes_read = 0;
int status = 0;
return read(read_fd, bytes_to_read, bytes_read, status);
}
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 &bytes_read);
std::string readline(int read_fd);
inline string readline(int read_fd, int& bytes_read) {
std::stringstream buffer;
char char_;
int write(int write_fd, std::string data);
int writeline(int write_fd, std::string data);
bytes_read = 0;
void tail(int read_fd, std::function<void(std::string)> callback);
void tail(int read_fd, int writeback_fd);
while ((bytes_read += ::read(read_fd, &char_, 1)) > 0) {
buffer << char_;
if (char_ == '\n' || char_ == '\x00')
break;
}
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);
if (bytes_read == 0) {
// Reached EOF
} else if (bytes_read == -1) {
// Read failed
} else {
return string_util::strip_trailing_newline(buffer.str());
}
// int get_flags(int fd);
// int set_blocking(int fd);
// int set_non_blocking(int fd);
return "";
}
inline string readline(int read_fd) {
int bytes_read;
return readline(read_fd, bytes_read);
}
inline size_t write(int write_fd, string data) {
return ::write(write_fd, data.c_str(), strlen(data.c_str()));
}
inline size_t writeline(int write_fd, string data) {
if (data.length() == 0)
return -1;
if (data.substr(data.length() - 1, 1) != "\n")
return io_util::write(write_fd, data + "\n");
else
return io_util::write(write_fd, data);
}
inline void tail(int read_fd, function<void(string)> callback) {
int bytes_read;
while (true) {
auto line = io_util::readline(read_fd, bytes_read);
if (bytes_read <= 0)
break;
callback(line);
}
}
inline void tail(int read_fd, int writeback_fd) {
tail(read_fd, [&writeback_fd](string data) { io_util::writeline(writeback_fd, data); });
}
inline bool poll(int fd, short int events, int timeout_ms = 1) {
struct pollfd fds[0];
fds[0].fd = fd;
fds[0].events = events;
::poll(fds, 1, timeout_ms);
return fds[0].revents & events;
}
inline bool poll_read(int fd, int timeout_ms = 1) {
return poll(fd, POLLIN, timeout_ms);
}
inline bool poll_write(int fd, int timeout_ms = 1) {
return poll(fd, POLLOUT, timeout_ms);
}
inline void interrupt_read(int write_fd) {
char end[1] = {'\n'};
::write(write_fd, end, 1);
}
}
LEMONBUDDY_NS_END

View File

@ -1,13 +0,0 @@
#pragma once
#define writeln(s) std::cout << s << std::endl
#define writeln_ss(s) { std::stringstream ss; ss << s; writeln(ss.str()); std::stringstream s; }
#define ToStr(s) std::string(s)
#define IntToStr(s) std::to_string(s)
#define StrErrno() ToStr(std::strerror(errno))
#define StrErrnoCustom(s) ToStr(std::strerror(s))
#define StrSignal(sig) IntToStr(sig)
#define StrSignalC(sig) strsignal(sig)

View File

@ -1,10 +1,16 @@
#pragma once
#include <string>
#include <algorithm>
namespace math
#include "common.hpp"
LEMONBUDDY_NS
namespace math_util
{
/**
* Limit value T by min and max bounds
*/
template<typename T>
T cap(T value, T min_value, T max_value)
{
@ -13,3 +19,5 @@ namespace math
return value;
}
}
LEMONBUDDY_NS_END

View File

@ -1,23 +1,25 @@
#pragma once
#include <memory>
#include "common.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;
LEMONBUDDY_NS
#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)
namespace memory_util {
/**
* Create a shared pointer using malloc/free
*/
template <typename T>
inline auto make_malloc_ptr(size_t size = sizeof(T)) {
return shared_ptr<T>(static_cast<T*>(malloc(size)), free);
}
namespace memory
{
template<typename T>
std::shared_ptr<T> make_malloc_ptr() {
return std::shared_ptr<T>(static_cast<T*>(malloc(sizeof(T))), free);
/**
* Get the number of elements in T
*/
template <typename T>
inline auto countof(T& p) {
return sizeof(p) / sizeof(p[0]);
}
}
LEMONBUDDY_NS_END

35
include/utils/mixins.hpp Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include "common.hpp"
LEMONBUDDY_NS
/**
* Base class for non copyable objects
*/
template <class T>
class non_copyable_mixin {
protected:
non_copyable_mixin() {}
~non_copyable_mixin() {}
private:
non_copyable_mixin(const non_copyable_mixin&);
non_copyable_mixin& operator=(const non_copyable_mixin&);
};
/**
* Base class for non movable objects
*/
template <class T>
class non_movable_mixin {
protected:
non_movable_mixin() {}
~non_movable_mixin() {}
private:
non_movable_mixin(non_movable_mixin&&);
non_movable_mixin& operator=(non_movable_mixin&&);
};
LEMONBUDDY_NS_END

View File

@ -1,38 +0,0 @@
#pragma once
#include <string>
#include <errno.h>
#include <cstring>
#include <signal.h>
#include "exception.hpp"
#define PIPE_READ 0
#define PIPE_WRITE 1
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(std::string cmd);
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(pid_t pid);
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();
}

108
include/utils/process.hpp Normal file
View File

@ -0,0 +1,108 @@
#pragma once
#include <sys/wait.h>
#include "common.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
namespace process_util {
/**
* Check if currently in main process
*/
inline auto in_parent_process(pid_t pid) {
return pid != -1 && pid != 0;
}
/**
* Check if currently in subprocess
*/
inline auto in_forked_process(pid_t pid) {
return pid == 0;
}
/**
* Replace process with command
*/
inline auto exec(string cmd) {
vector<char*> c_args;
vector<string> args;
if (string_util::contains(cmd, "\n"))
string_util::split_into(cmd, '\n', args);
else
string_util::split_into(cmd, ' ', args);
for (auto&& argument : args) c_args.emplace_back(const_cast<char*>(argument.c_str()));
c_args.emplace_back(nullptr);
execvp(c_args[0], c_args.data());
throw system_error("Failed to execute command");
}
/**
* Wait for child process
*/
inline auto wait_for_completion(pid_t process_id, int* status_addr, int waitflags = 0) {
int saved_errno = errno;
auto retval = waitpid(process_id, status_addr, waitflags);
errno = saved_errno;
return retval;
}
/**
* Wait for child process
*/
inline auto wait_for_completion(int* status_addr, int waitflags = 0) {
return wait_for_completion(-1, status_addr, waitflags);
}
/**
* Wait for child process
*/
inline auto wait_for_completion(pid_t process_id) {
int status = 0;
return wait_for_completion(process_id, &status);
}
/**
* Non-blocking wait
*
* @see wait_for_completion
*/
inline auto wait_for_completion_nohang(pid_t process_id, int* status) {
return wait_for_completion(process_id, status, WNOHANG);
}
/**
* Non-blocking wait
*
* @see wait_for_completion
*/
inline auto wait_for_completion_nohang(int* status) {
return wait_for_completion_nohang(-1, status);
}
/**
* Non-blocking wait
*
* @see wait_for_completion
*/
inline auto wait_for_completion_nohang() {
int status = 0;
return wait_for_completion_nohang(-1, &status);
}
/**
* Non-blocking wait call which returns pid of any child process
*
* @see wait_for_completion
*/
inline auto notify_childprocess() {
return wait_for_completion_nohang() > 0;
}
}
LEMONBUDDY_NS_END

41
include/utils/scope.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "common.hpp"
#include "components/logger.hpp"
LEMONBUDDY_NS
namespace scope_util {
template <typename... Args>
class on_exit {
public:
on_exit(function<void(Args...)>&& fn, Args... args) : m_callback(bind(fn, args...)) {}
virtual ~on_exit() {
m_callback();
}
protected:
function<void()> m_callback;
};
/**
* Creates a wrapper that will trigger given callback when
* leaving the object's scope (i.e, when it gets destroyed)
*
* Example usage:
* @code cpp
* {
* auto handler = scope_util::make_exit_handler([]{ ... })
* ...
* }
* @endcode
*/
template <typename Fn = function<void()>, typename... Args>
decltype(auto) make_exit_handler = [](Fn&& fn, Args&&... args) -> unique_ptr<on_exit<Args...>> {
return make_unique<on_exit<Args...>>(forward<Fn>(fn), forward<Args>(args)...);
};
}
LEMONBUDDY_NS_END

119
include/utils/socket.hpp Normal file
View File

@ -0,0 +1,119 @@
#pragma once
#include <fcntl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "common.hpp"
#include "utils/file.hpp"
#include "utils/mixins.hpp"
LEMONBUDDY_NS
using std::snprintf;
using std::strlen;
namespace socket_util {
class unix_connection {
public:
/**
* Constructor: establishing socket connection
*/
explicit unix_connection(string&& path) : m_socketpath(path) {
struct sockaddr_un socket_addr;
socket_addr.sun_family = AF_UNIX;
if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
throw system_error("Failed to open unix connection");
snprintf(socket_addr.sun_path, sizeof(socket_addr.sun_path), "%s", m_socketpath.c_str());
auto len = sizeof(socket_addr);
if (connect(m_fd, reinterpret_cast<struct sockaddr*>(&socket_addr), len) == -1)
throw system_error("Failed to connect to socket");
}
/**
* Destructor: closes file descriptor
*/
~unix_connection() noexcept {
if (m_fd != -1)
close(m_fd);
}
/**
* Close reading end of connection
*/
int disconnect() {
return shutdown(m_fd, SHUT_RD);
}
/**
* Transmit fixed size data
*/
auto send(const void* data, size_t len, int flags = 0) {
auto bytes_sent = 0;
if ((bytes_sent = ::send(m_fd, data, len, flags)) == -1)
throw system_error("Failed to transmit data");
return bytes_sent;
}
/**
* Transmit string data
*/
auto send(string data, int flags = 0) {
return send(data.c_str(), data.length(), flags);
}
/**
* Receive data
*/
auto receive(ssize_t receive_bytes, ssize_t& bytes_received_addr, int flags = 0) {
char buffer[receive_bytes + 1];
bytes_received_addr = ::recv(m_fd, buffer, receive_bytes, flags);
if (bytes_received_addr == -1)
throw system_error("Failed to receive data");
buffer[bytes_received_addr] = 0;
return string{buffer};
}
/**
* Poll file descriptor for to check for available data
*/
auto poll(short int events = POLLIN, int timeout_ms = -1) {
struct pollfd fds[1];
fds[0].fd = m_fd;
fds[0].events = events;
if (::poll(fds, 1, timeout_ms) == -1)
throw system_error("Failed to poll file descriptor");
return fds[0].revents & events;
}
protected:
int m_fd = -1;
string m_socketpath;
};
/**
* Creates a wrapper for a unix socket connection
*
* Example usage:
* @code cpp
* auto conn = socket_util::make_unix_connection("/tmp/socket");
* conn->send(...);
* conn->receive(...);
* @endcode
*/
auto make_unix_connection = [](string&& path) -> unique_ptr<unix_connection> {
return make_unique<unix_connection>(forward<string>(path));
};
}
LEMONBUDDY_NS_END

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