Merge branch '2.0-beta'

This commit is contained in:
Michael Carlberg 2016-10-12 05:26:41 +02:00
commit ed77e2eec6
172 changed files with 19355 additions and 10018 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

3
.gitignore vendored
View File

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

4
.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

View File

@ -1,36 +1,98 @@
sudo: required
dist: trusty
language: cpp
compiler:
- clang
- gcc
cache: ccache
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.8
packages:
- gcc-5
- g++-5
- clang-3.8
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"
cache:
directories:
- ${TRAVIS_BUILD_DIR}/deps/cmake
- ${TRAVIS_BUILD_DIR}/deps/llvm-3.8.1/install
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
- export PYTHONPATH="/usr/lib/python2.7/dist-packages:$PYTHONPATH"
- 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
# Install packages : core
#--------------------------------------------------------------------------x
- sudo apt-get install -y cmake cmake-data libcppunit-dev libboost-dev libfreetype6-dev
# Install packages : xcb
#--------------------------------------------------------------------------x
- sudo apt-get install -y libxcb1-dev libxcb-util0-dev libxcb-randr0-dev libxcb-ewmh-dev libxcb-icccm4-dev xcb-proto python-xcbgen
# Install packages : optional
#--------------------------------------------------------------------------x
- sudo apt-get install -y libiw-dev libasound2-dev libmpdclient-dev libjsoncpp-dev
install:
# Install dependencies in ${TRAVIS_BUILD_DIR}/deps
#--------------------------------------------------------------------------x
- DEPS_DIR="${TRAVIS_BUILD_DIR}/deps"
- LLVM_ROOT="${DEPS_DIR}/llvm-${LLVM_VERSION}"
# Update python path to make sure we find the xcbgen module
#--------------------------------------------------------------------------x
- export PYTHONPATH="/usr/lib/python2.7/dist-packages:${PYTHONPATH}"
# Install a later version of cmake
#--------------------------------------------------------------------------x
- |
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}"
# Install LLVM libc++
#--------------------------------------------------------------------------x
- |
if [[ "${CXX}" == "clang++" ]] && [[ -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="${CXXFLAGS} -I ${LLVM_ROOT}/install/include/c++/v1"
export LDFLAGS="${LDFLAGS} -L ${LLVM_ROOT}/install/lib -lc++ -lc++abi"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${LLVM_ROOT}/install/lib"
fi
# Set compiler's
#--------------------------------------------------------------------------x
- 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
before_script:
- if [ "$CXX" = "clang++" ]; then export CPPFLAGS="-I $HOME/clang-$LLVM_VERSION/include/c++/v1" CXXFLAGS="-Qunused-arguments"; 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}" -DENABLE_CCACHE=OFF ..
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

@ -2,35 +2,45 @@
# Build configuration
#
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(lemonbuddy C CXX)
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")
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 ESC)
include(cmake/utils.cmake)
include(cmake/clang-cpp-tools.cmake)
#
# Project settings
#
# Figure out default option values {{{
# Make the default options based on available libs
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()
find_program(I3_BINARY i3)
if(I3_BINARY)
set(I3_FOUND ON)
endif()
# }}}
# Project settings {{{
option(ENABLE_CCACHE "Enable ccache support" ${CCACHE_FOUND})
option(ENABLE_ALSA "Enable alsa support" ${ALSA_FOUND})
option(ENABLE_I3 "Enable i3 support" ${I3_FOUND})
@ -61,123 +71,116 @@ set(SETTING_PATH_MEMORY_INFO "/proc/meminfo"
CACHE STRING "Path to file containing memory info")
if(ENABLE_CCACHE)
find_program(CCACHE_BINARY ccache REQUIRED)
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)
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()
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})
link_libraries(${X11_LIBRARIES})
link_libraries(${FREETYPE_LIBRARIES})
link_libraries(${FONTCONFIG_LIBRARIES})
link_libraries()
include_directories(
${BOOST_INCLUDE_DIR}
${FONTCONFIG_INCLUDE_DIRS}
${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)
link_libraries(${XPP_LIBRARIES})
if(ENABLE_ALSA)
find_package(ALSA REQUIRED)
set(PROJECT_INCL_DIRS ${PROJECT_INCL_DIRS} ${ALSA_INCLUDE_DIR})
set(PROJECT_LINK_LIBS ${PROJECT_LINK_LIBS} ${ALSA_LIBRARY})
link_libraries(${ALSA_LIBRARY})
endif()
if(ENABLE_MPD)
find_package(LibMPDClient REQUIRED)
set(PROJECT_INCL_DIRS ${PROJECT_INCL_DIRS} ${LIBMPDCLIENT_INCLUDE_DIR})
set(PROJECT_LINK_LIBS ${PROJECT_LINK_LIBS} ${LIBMPDCLIENT_LIBRARY})
link_libraries(${LIBMPDCLIENT_LIBRARIES})
endif()
if(ENABLE_NETWORK)
find_package(Libiw REQUIRED)
set(PROJECT_INCL_DIRS ${PROJECT_INCL_DIRS} ${LIBIW_INCLUDE_DIR})
set(PROJECT_LINK_LIBS ${PROJECT_LINK_LIBS} ${LIBIW_LIBRARY})
link_libraries(${LIBIW_LIBRARY})
endif()
if(ENABLE_I3)
add_subdirectory(${PROJECT_SOURCE_DIR}/lib/i3ipcpp EXCLUDE_FROM_ALL)
include_directories(${I3IPCPP_INCLUDE_DIRS})
link_libraries(${I3IPCPP_LIBRARIES})
endif()
#
# Load the xpp library
#
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})
# }}}
# 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)
# }}}

250
README.md
View File

@ -98,25 +98,25 @@ 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:
- wireless_tools (required for `internal/network` support)
- alsa-lib (required for `internal/volume` support)
- libmpdclient (required for `internal/mpd` support)
- jsoncpp, libsigc++ (required for `internal/i3` support)
- jsoncpp (required for `internal/i3` 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
$ pacman -S cmake boost libxcb xcb-proto wireless_tools alsa-lib libmpdclient jsoncpp
$ xbps-install cmake boost-devel libxcb-devel alsa-lib-devel i3-devel libmpdclient-devel jsoncpp-devel freetype-devel wireless_tools-devel
$ apt-get install cmake libxcb1-dev xcb-proto python-xcbgen libboost-dev libiw-dev libasound2-dev libmpdclient-dev libjsoncpp-dev libfreetype6-dev
~~~
@ -125,10 +125,10 @@ $ 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.6 --recursive https://github.com/jaagr/lemonbuddy
$ git clone --branch 2.0.0 --recursive https://github.com/jaagr/lemonbuddy
$ mkdir lemonbuddy/build
$ cd lemonbuddy/build
$ cmake ..
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ sudo make install
~~~
@ -150,43 +150,26 @@ The following code will get you started:
# Launch the bar
# (where "example" is the name of the bar as defined by [bar/NAME] in the config)
$ lemonbuddy_wrapper example
$ lemonbuddy example
~~~
> **NOTE:** If you are running i3 or bspwm and you don't see the workspace icons
> it probably depends on the font. Install `font-awesome` and relaunch the bar.
> ...or replace the icons in the config.
**It is recommended** to always use `lemonbuddy_wrapper` when launching the bars.
`lemonbuddy_wrapper` is just a simple shell script that takes care
of redirecting the in-/output streams between `lemonbuddy` and `lemonbar`.
If you handle the in-/output stream redirection's manually, the internal command
handlers (e.g. mpd or volume controls) might stop working. It won't change the
output of the bar but you will miss out on the internal API calls, which is one
of the main advantages of using the application.
The `lemonbuddy_wrapper` will be deprecated once `lemonbar` is integrated
into the project.
> **NOTE:** In case the bar output looks odd, it's probably because you're
> missing he fonts defined in the config. Update the config or install the
> missing fonts.
## Launching the bar in your wm's bootstrap routine
When using the wrapper to start the bar in in your wm's autostart routine, make sure to include
a kill directive before launching the bar. This is done to make sure that any previously spawned
processes gets terminated before before we launch the new ones.
Create an executable file containing the startup logic, for example `$HOME/.config/lemonbuddy/launch.sh`:
~~~ sh
#!/usr/bin/env sh
# Terminate already running bar instances
lemonbuddy_terminate noconfirm
killall -q lemonbuddy
# Launch bar1 and bar2
lemonbuddy_wrapper bar1 &
lemonbuddy_wrapper bar2 &
lemonbuddy bar1 &
lemonbuddy bar2 &
echo "Bars launched..."
~~~
@ -203,7 +186,7 @@ If you are using **bspwm**, add the following line to `bspwmrc`:
If you are using **i3**, add the following line to your configuration:
~~~ sh
exec_always $HOME/.config/lemonbuddy/launch.sh
exec_always --no-startup-id $HOME/.config/lemonbuddy/launch.sh
~~~
@ -219,10 +202,10 @@ the resulting output might not be award-winning.
### Fonts
When working with unicode symbols, remember that fonts render the symbols differently. Changing font
can drastically improve the quality of your bar. One must-have font
is [Unifont](http://unifoundry.com/unifont.html), which has great unicode coverage.
can drastically improve the quality of your bar. [Unifont](http://unifoundry.com/unifont.html) has great unicode coverage, which makes
it really useful.
Also try different icon fonts, such as [Font Awesome](http://fontawesome.io/icons/) and [Material Icons](https://design.google.com/icons/).
Also try different icon fonts, such as [Font Awesome](http://fontawesome.io/icons), [Material Icons](https://design.google.com/icons) and my personal favorite: [Siji](https://github.com/stark/siji).
*TODO: Describe usage in configuration...*
@ -323,11 +306,11 @@ The configuration syntax is based on the `ini` file format.
### Application settings
~~~ ini
[settings]
; Limit the amount of events sent to lemonbar within a set timeframe:
; Limit the amount of update events 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
~~~
@ -335,6 +318,7 @@ The configuration syntax is based on the `ini` file format.
~~~ ini
[bar/example]
; Use the following command to list available outputs:
; If unspecified, the application will pick the first one it finds.
; $ xrandr -q | grep " connected" | cut -d ' ' -f1
monitor = HDMI1
@ -343,14 +327,16 @@ The configuration syntax is based on the `ini` file format.
height = 30
; Offset value defined in pixels
offset_x = 0
offset_y = 0
offset-x = 0
offset-y = 0
; Put the bar at the bottom of the screen
bottom = true
; Weather to force docking mode or not
dock = false
; If you are using i3wm it's recommended to use the default value
; Default: false
dock = true
; This value is used as a multiplier when adding spaces between elements
spacing = 3
@ -359,17 +345,30 @@ The configuration syntax is based on the `ini` file format.
lineheight = 14
; Colors
background = #222222
background = #ee222222
foreground = #eefafafa
linecolor = ${bar/example.background}
; Amount of spaces to add at the start/end of the whole bar
padding_left = 5
padding_right = 2
; 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
; Number of spaces to add at the beginning/end of the bar
padding-left = 5
padding-right = 2
; Amount of spaces to add before/after each module
module_margin_left = 3
module_margin_right = 3
module-margin-left = 3
module-margin-right = 3
; Fonts are defined using: <FontName>;<Offset>
font-0 = NotoSans-Regular:size=8;0
@ -380,13 +379,9 @@ 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
wm-name = mybar
; Locale used to localize module output (for example date)
;locale = sv_SE.UTF-8
@ -395,6 +390,26 @@ 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
; Restack the bar window and put it above the
; selected window manager's root
;
; Fixes the issue where the bar is being drawn
; on top of fullscreen window's
;
; Currently supported WM's:
; bspwm
; i3
; Default: none
wm-restack = bspwm
~~~
### Modules
@ -441,7 +456,7 @@ The configuration syntax is based on the `ini` file format.
type = internal/battery
; This is useful in case the battery never reports 100% charge
full_at = 99
full-at = 99
; Use the following command to list batteries and adapters:
; $ ls -1 /sys/class/power_supply/
@ -450,7 +465,7 @@ The configuration syntax is based on the `ini` file format.
; Seconds between reading battery capacity.
; If set to 0, polling will be disabled.
;poll_interval = 3
;poll-interval = 3
~~~
##### Extra formatting (example)
@ -499,7 +514,8 @@ The configuration syntax is based on the `ini` file format.
animation-charging-2 = 
animation-charging-3 = 
animation-charging-4 = 
animation-charging-framerate_ms = 750
; Framerate in milliseconds
animation-charging-framerate = 750
~~~
@ -514,13 +530,13 @@ To specify a custom path to the bspwm socket, you can set the environment variab
##### Extra formatting (example)
~~~ ini
; workspace_icon-[0-9]+ = label;icon
workspace_icon-0 = code;♚
workspace_icon-1 = office;♛
workspace_icon-2 = graphics;♜
workspace_icon-3 = mail;♝
workspace_icon-4 = web;♞
workspace_icon-default = ♟
; ws-icon-[0-9]+ = label;icon
ws-icon-0 = code;♚
ws-icon-1 = office;♛
ws-icon-2 = graphics;♜
ws-icon-3 = mail;♝
ws-icon-4 = web;♞
ws-icon-default = ♟
; Available tags:
; <label-state> (default) - gets replaced with <label-(active|urgent|occupied|empty)>
@ -598,21 +614,21 @@ To specify a custom path to the bspwm socket, you can set the environment variab
; <label> (default)
; <bar-load>
; <ramp-load>
; <ramp-load_per_core>
format = <label> <ramp-load_per_core>
; <ramp-coreload>
format = <label> <ramp-coreload>
; Available tokens:
; %percentage% (default) - total cpu load
label = CPU %percentage%
ramp-load_per_core-0 = ▁
ramp-load_per_core-1 = ▂
ramp-load_per_core-2 = ▃
ramp-load_per_core-3 = ▄
ramp-load_per_core-4 = ▅
ramp-load_per_core-5 = ▆
ramp-load_per_core-6 = ▇
ramp-load_per_core-7 = █
ramp-coreload-0 = ▁
ramp-coreload-1 = ▂
ramp-coreload-2 = ▃
ramp-coreload-3 = ▄
ramp-coreload-4 = ▅
ramp-coreload-5 = ▆
ramp-coreload-6 = ▇
ramp-coreload-7 = █
~~~
@ -628,8 +644,8 @@ To specify a custom path to the bspwm socket, you can set the environment variab
; NOTE: if you want to use lemonbar tags here you need to use %%{...}
date = %Y-%m-%d% %H:%M
; if date_detailed is defined, clicking the area will toggle between formats
date_detailed = %%{F#888}%A, %d %B %Y %%{F#fff}%H:%M%%{F#666}:%%{F#fba922}%S%%{F-}
; if `date-alt` is defined, clicking the area will toggle between formats
date-alt = %%{F#888}%A, %d %B %Y %%{F#fff}%H:%M%%{F#666}:%%{F#fba922}%S%%{F-}
~~~
##### Extra formatting (example)
@ -655,17 +671,34 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
~~~ ini
[module/i3]
type = internal/i3
; Only show workspaces defined on the same output as the bar
;
; Useful if you want to show monitor specific workspaces
; in different bars
;
; Default: false
pin-workspaces = true
; Limit the amount of chars to output for each workspace name
; Default: 0
wsname-maxlen = 2
; Sort the workspaces by index instead of the default
; sorting that groups the workspaces by output
; Default: false
index-sort = true
~~~
##### Extra formatting (example)
~~~ ini
; workspace_icon-[0-9]+ = label;icon
workspace_icon-0 = 1;♚
workspace_icon-1 = 2;♛
workspace_icon-2 = 3;♜
workspace_icon-3 = 4;♝
workspace_icon-4 = 5;♞
workspace_icon-default = ♟
ws-icon-0 = 1;♚
ws-icon-1 = 2;♛
ws-icon-2 = 3;♜
ws-icon-3 = 4;♝
ws-icon-4 = 5;♞
ws-icon-default = ♟
; Available tags:
; <label-state> (default) - gets replaced with <label-(focused|unfocused|visible|urgent)>
@ -675,6 +708,7 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
; %name%
; %icon%
; %index%
; %output%
; Default: %icon% %name%
label-focused = %icon%
label-focused-foreground = #ffffff
@ -815,8 +849,8 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
;icon-repeatone = 🔂
; Used to display the state of random/repeat/repeatone
toggle_on-foreground = #ff
toggle_off-foreground = #55
toggle-on-foreground = #ff
toggle-off-foreground = #55
bar-progress-width = 45
bar-progress-indicator = |
@ -853,7 +887,11 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
; Recommended minimum value: round(10 / interval)
; - which would test the connection approx. every 10th sec.
; Default: 0
;ping_interval = 3
;ping-interval = 3
; Minimum output width of upload/download rate
; Default: 3
;udspeed-minwidth = 0
~~~
##### Extra formatting (example)
@ -878,9 +916,11 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
; %local_ip% [wireless+wired]
; %essid% [wireless]
; %signal% [wireless]
; %upspeed% [wireless+wired]
; %downspeed% [wireless+wired]
; %linkspeed% [wired]
; Default: %ifname% %local_ip%
label-connected = %essid%
label-connected = %essid% %downspeed%
label-connected-foreground = #eefafafa
; Available tokens:
@ -910,7 +950,8 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
animation-packetloss-0-foreground = #ffa64c
animation-packetloss-1 = 📶
animation-packetloss-1-foreground = #000000
animation-packetloss-framerate_ms = 500
; Framerate in milliseconds
animation-packetloss-framerate = 500
~~~
@ -921,17 +962,17 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
~~~ ini
[module/volume]
type = internal/volume
;master_mixer = Master
;master-mixer = Master
; Use the following command to list available mixer controls:
; $ amixer scontrols | sed -nr "s/.*'([[:alnum:]]+)'.*/\1/p"
speaker_mixer = Speaker
headphone_mixer = Headphone
speaker-mixer = Speaker
headphone-mixer = Headphone
; NOTE: This is required if headphone_mixer is defined
; Use the following command to list available device controls
; $ amixer controls | sed -r "/CARD/\!d; s/.*=([0-9]+).*name='([^']+)'.*/printf '%3.0f: %s\n' '\1' '\2'/e" | sort
headphone_control_numid = 9
headphone-id = 9
~~~
##### Extra formatting (example)
@ -960,6 +1001,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 = 
~~~
@ -972,15 +1019,15 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
; the additional "exec" property
;
; Available exec commands:
; menu_open-LEVEL
; menu_close
; menu-open-LEVEL
; menu-close
; Other commands will be executed using "/usr/bin/env sh -c $COMMAND"
menu-0-0 = Browsers
menu-0-0-exec = menu_open-1
menu-0-0-exec = menu-open-1
menu-0-0-foreground = #fba922
menu-0-2 = Multimedia
menu-0-2-exec = menu_open-3
menu-0-2-exec = menu-open-3
menu-0-2-foreground = #fba922
menu-1-0 = Firefox
@ -1003,10 +1050,14 @@ See [the bspwm module](#module-internalbspwm) for details on `label-dimmed`.
; Available tags:
; <label-toggle> (default) - gets replaced with <label-(open|close)>
; <menu> (default)
;f-ormat = <label-toggle> <menu>
;format = <label-toggle> <menu>
label-open = Apps
label-close = x
; Optional item separator
; Default: none
label-separator = |
~~~
@ -1028,6 +1079,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)
@ -1063,6 +1122,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: rewrite README for 2.0
task: rewrite i3 module
task: improve test coverage
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()
# }}}

1166
config

File diff suppressed because it is too large Load Diff

@ -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

@ -7,94 +7,257 @@
;
;=====================================================
[settings]
; 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
[bar/example]
;monitor = eDP1
bottom = true
dock = false
[bar/top]
monitor = eDP-1
dock = true
width = 100%
height = 25
height = 27
offset-x = 0
offset-y = 0
;offset_x = 0
;offset_y = 0
background = #ee222222
foreground = #ccfafafa
linecolor = #666
background = #00ffffff
foreground = #fff
;linecolor = #ff0000
border-bottom = 2
border-bottom-color = #333
spacing = 1
lineheight = 1
padding-left = 0
padding-right = 2
module-margin-left = 1
module-margin-right = 2
;separator = |
font-0 = tamzen:size=9;1
font-1 = siji:pixelsize=10;0
font-2 = unifont:size=6;-1
;locale = en_US.UTF-8
modules-left = mpd
modules-center = date
modules-right = volume memory cpu
padding_left = 1
padding_right = 1
module_margin_left = 0
module_margin_right = 0
tray-position = right
font-0 = Sans:size=8;0
font-1 = FontAwesome:size=10:weight=heavy;0
modules-left = label
modules-right = volume cpu ram clock
[module/label]
type = custom/text
content = Lemonbuddy example
content-background = #af2031
content-underline = #cf4253
content-overline = #cf4253
content-padding = 2
[module/cpu]
type = internal/cpu
label = CPU: %percentage%
format-background = #c42
format-underline = #f75
format-overline = #f75
format-padding = 2
[module/ram]
[module/memory]
type = internal/memory
label = RAM: %percentage_used%
format-background = #42c
format-underline = #75f
format-overline = #75f
format-padding = 2
interval = 2
[module/clock]
format = <label> <bar-used>
label = RAM
bar-used-width = 20
bar-used-foreground-0 = #55aa55
bar-used-foreground-1 = #55aa55
bar-used-foreground-2 = #f5a70a
bar-used-foreground-3 = #ff5555
bar-used-gradient = false
bar-used-indicator = │
bar-used-indicator-font = 2
bar-used-indicator-foreground = #ff
bar-used-fill = ━
bar-used-fill-font = 2
bar-used-empty = ━
bar-used-empty-font = 2
bar-used-empty-foreground = #444444
[module/wifi]
type = internal/network
interface = net1
interval = 3.0
;udspeed-minwidth = 3
format-connected = <ramp-signal> <label-connected>
;label-connected = %essid% %{F#66}%local_ip%
;label-connected = %{F#666}%{F#cc} %upspeed% %{F#666}%{F#cc} %downspeed%
label-connected = %{O-6 F#666}%{O2 F#cc}%downspeed%
label-disconnected = %{F#666}%{F#cc} not connected
label-disconnected-foreground = #66
ramp-signal-0 = 
ramp-signal-1 = 
ramp-signal-2 = 
ramp-signal-3 = 
ramp-signal-4 = 
ramp-signal-foreground = #666
[module/wired]
type = internal/network
interface = net0
interval = 3.0
label-connected = %{T3}%local_ip%%{T-}
label-disconnected = %{T3}Not connected%{T-}
label-disconnected-foreground = #66
[module/date]
type = internal/date
date = %Y-%m-%d %H:%M
format-background = #493
format-underline = #7a6
format-overline = #7a6
format-padding = 2
date = %%{F#666}%{F#cc} %Y-%m-%d %%{F#e60053}%%{F#cc} %H:%M
date-alt = %%{F#666}%{F#cc} %Y-%m-%d %%{F#e60053}%%{F#cc} %H:%M:%S
interval = 5
[module/backlight]
type = internal/backlight
card = intel_backlight
format = <ramp> <label>
ramp-0 = 
ramp-1 = 
ramp-2 = 
ramp-foreground = #666
[module/volume]
type = internal/volume
;speaker_mixer = Speaker
;headphone_mixer = Headphone
;headphone_control_numid = 9
speaker-mixer = Speaker
headphone-mixer = Headphone
headphone-id = 9
format-volume-background = #933484
format-volume-underline = #9d6294
format-volume-overline = #9d6294
format-volume-padding = 2
format-muted-background = #933484
format-muted-underline = #9d6294
format-muted-overline = #9d6294
format-muted-padding = 2
; format-volume = <ramp-volume> <label-volume>
format-volume = <label-volume> <bar-volume>
label-volume = Volume: %percentage%
label-muted = Sound is muted
label-volume = VOL
label-volume-foreground = ${BAR.foreground}
label-muted = %{F#66} sound muted
;ramp-volume-0 = 
;ramp-volume-1 = 
;ramp-volume-2 = 
;ramp-volume-3 = 
;ramp-volume-foreground = #666
;ramp-headphones-0 = 
;ramp-headphones-1 = 
bar-volume-width = 20
bar-volume-foreground-0 = #55aa55
bar-volume-foreground-1 = #55aa55
bar-volume-foreground-2 = #55aa55
bar-volume-foreground-3 = #55aa55
bar-volume-foreground-4 = #55aa55
bar-volume-foreground-5 = #f5a70a
bar-volume-foreground-6 = #ff5555
bar-volume-gradient = false
bar-volume-indicator = │
bar-volume-indicator-font = 2
bar-volume-indicator-foreground = #ff
bar-volume-fill = ━
bar-volume-fill-font = 2
bar-volume-empty = ━
bar-volume-empty-font = 2
bar-volume-empty-foreground = #444444
[module/battery]
type = internal/battery
full-at = 98
format-charging = <animation-charging> <label-charging>
format-discharging = <ramp-capacity> <label-discharging>
format-full = %{F#666}%{F#cc} <label-full>
ramp-capacity-0 = 
ramp-capacity-1 = 
ramp-capacity-2 = 
ramp-capacity-foreground = #666
animation-charging-0 = 
animation-charging-1 = 
animation-charging-2 = 
animation-charging-foreground = #666
animation-charging-framerate = 750
[module/mpd]
type = internal/mpd
format-online = <label-time> <bar-progress> <label-song> <icon-prev> <icon-seekb> <icon-stop> <toggle> <icon-seekf> <icon-next> <icon-repeat> <icon-random>
format-online-background = #ee333333
format-online-underline = #cc333333
format-online-padding = 3
format-offline = <label-offline>
format-offline-foreground = #66
label-offline = mpd is off
label-song-maxlen = 45
label-song-ellipsis = true
icon-prev = 
icon-seekb = 
icon-stop = 
icon-play = 
icon-pause = 
icon-next = 
icon-seekf = 
; icon-prev = ⏮
; icon-seekb = ⏪
; icon-stop = ⏹
; icon-play = ⏵
; icon-pause = ⏸
; icon-seekf = ⏩
; icon-next = ⏭
icon-random = 
icon-repeat = 
toggle-on-foreground = #e60053
toggle-off-foreground = #66
bar-progress-width = 15
bar-progress-indicator = 
bar-progress-indicator-foreground = #bb
bar-progress-fill = ─
bar-progress-fill-foreground = #bb
bar-progress-fill-font = 3
bar-progress-empty = ─
bar-progress-empty-foreground = #44
bar-progress-empty-font = 3
label-time-foreground = #77
[module/powermenu]
type = custom/menu
label-open = 
label-close = 
label-separator = /
menu-0-0 = Terminate WM
menu-0-0-foreground = #e60053
menu-0-0-exec = bspc quit -1
menu-0-1 = Reboot
menu-0-1-foreground = #e60053
menu-0-1-exec = menu_open-1
menu-0-2 = Power off
menu-0-2-foreground = #e60053
menu-0-2-exec = menu_open-2
menu-1-0 = Cancel
menu-1-0-foreground = #e60053
menu-1-0-exec = menu_open-0
menu-1-1 = Reboot
menu-1-1-foreground = #e60053
menu-1-1-exec = sudo reboot
menu-2-0 = Power off
menu-2-0-foreground = #e60053
menu-2-0-exec = sudo poweroff
menu-2-1 = Cancel
menu-2-1-foreground = #e60053
menu-2-1-exec = menu_open-0
[module/counter]
type = internal/counter
format = <counter>
interval = 0.1
; vim:ft=dosini

View File

@ -7,104 +7,359 @@
;
;=====================================================
[settings]
; 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
[bar/example]
;monitor = eDP1
bottom = true
dock = false
[bar/top]
monitor = eDP-1
dock = true
width = 100%
height = 25
height = 27
offset-x = 0
offset-y = 0
;offset_x = 0
;offset_y = 0
background = #ee222222
foreground = #ccfafafa
linecolor = #666
background = #00ffffff
foreground = #fff
;linecolor = #ff0000
border-bottom = 2
border-bottom-color = #333
spacing = 1
lineheight = 1
padding-left = 0
padding-right = 2
module-margin-left = 1
module-margin-right = 2
;separator = |
font-0 = tamzen:size=9;1
font-1 = siji:pixelsize=10;0
font-2 = unifont:size=6;-1
;locale = en_US.UTF-8
padding_left = 1
padding_right = 1
module_margin_left = 0
module_margin_right = 0
font-0 = Sans:size=8;0
font-1 = FontAwesome:size=10:weight=heavy;0
modules-left = label
modules-left = mpd
modules-center = bspwm
modules-right = volume cpu ram clock
modules-right = volume memory cpu date
[module/label]
type = custom/text
content = Lemonbuddy example
content-background = #af2031
content-underline = #cf4253
content-overline = #cf4253
content-padding = 2
tray-position = right
[module/cpu]
type = internal/cpu
label = CPU: %percentage%
format-background = #c42
format-underline = #f75
format-overline = #f75
format-padding = 2
wm-restack = bspwm
[module/ram]
type = internal/memory
label = RAM: %percentage_used%
format-background = #42c
format-underline = #75f
format-overline = #75f
format-padding = 2
[module/clock]
type = internal/date
date = %Y-%m-%d %H:%M
format-background = #493
format-underline = #7a6
format-overline = #7a6
format-padding = 2
[bar/external_bottom]
monitor = HDMI-1
dock = false
bottom = true
width = 100%
height = ${bar/top.height}
[module/volume]
type = internal/volume
;speaker_mixer = Speaker
;headphone_mixer = Headphone
;headphone_control_numid = 9
background = ${bar/top.background}
foreground = ${bar/top.foreground}
linecolor = ${bar/top.linecolor}
format-volume-background = #933484
format-volume-underline = #9d6294
format-volume-overline = #9d6294
format-volume-padding = 2
format-muted-background = #933484
format-muted-underline = #9d6294
format-muted-overline = #9d6294
format-muted-padding = 2
border-top = 2
border-top-color = #333
spacing = ${bar/top.spacing}
lineheight = ${bar/top.lineheight}
padding-right = ${bar/top.padding_right}
module-margin-left = 0
module-margin-right = ${bar/top.module_margin_right}
font-0 = ${bar/top.font-0}
font-1 = ${bar/top.font-1}
font-2 = ${bar/top.font-2}
modules-left = bspwm
modules-right = date powermenu
wm-restack = ${bar/top.wm-restack}
label-volume = Volume: %percentage%
label-muted = Sound is muted
[module/bspwm]
type = internal/bspwm
label-active = 
label-active-padding = 1
label-occupied = 
label-occupied-padding = 1
label-empty = 
label-empty-padding = 1
ws-icon-default = x
label-active = o %index%
label-active-background = #ee333333
label-active-underline= #cc333333
label-active-padding = 2
label-occupied = %{O-1}⏺%{O-1} %index%
label-occupied-padding = 2
label-urgent = ! %index%
label-urgent-background = #bd2c40
label-urgent-padding = 2
label-empty = x %index%
label-empty-foreground = #444
label-empty-padding = 2
[module/cpu]
type = internal/cpu
interval = 2
;format = <label> <bar-load> <ramp-coreload>
format = <label> <bar-load>
label = CPU
; ramp-coreload-0 = ▁
; ramp-coreload-0-font = 2
; ramp-coreload-0-foreground = #55aa55
; ramp-coreload-1 = ▂
; ramp-coreload-1-font = 2
; ramp-coreload-1-foreground = #55aa55
; ramp-coreload-2 = ▃
; ramp-coreload-2-font = 2
; ramp-coreload-2-foreground = #55aa55
; ramp-coreload-3 = ▄
; ramp-coreload-3-font = 2
; ramp-coreload-3-foreground = #55aa55
; ramp-coreload-4 = ▅
; ramp-coreload-4-font = 2
; ramp-coreload-4-foreground = #f5a70a
; ramp-coreload-5 = ▆
; ramp-coreload-5-font = 2
; ramp-coreload-5-foreground = #f5a70a
; ramp-coreload-6 = ▇
; ramp-coreload-6-font = 2
; ramp-coreload-6-foreground = #ff5555
; ramp-coreload-7 = █
; ramp-coreload-7-font = 2
; ramp-coreload-7-foreground = #ff5555
bar-load-width = 20
bar-load-foreground-0 = #55aa55
bar-load-foreground-1 = #55aa55
bar-load-foreground-2 = #f5a70a
bar-load-foreground-3 = #ff5555
bar-load-gradient = false
bar-load-indicator = │
bar-load-indicator-font = 2
bar-load-indicator-foreground = #ff
bar-load-fill = ━
bar-load-fill-font = 2
bar-load-empty = ━
bar-load-empty-font = 2
bar-load-empty-foreground = #444444
[module/memory]
type = internal/memory
interval = 2
format = <label> <bar-used>
label = RAM
bar-used-width = 20
bar-used-foreground-0 = #55aa55
bar-used-foreground-1 = #55aa55
bar-used-foreground-2 = #f5a70a
bar-used-foreground-3 = #ff5555
bar-used-gradient = false
bar-used-indicator = │
bar-used-indicator-font = 2
bar-used-indicator-foreground = #ff
bar-used-fill = ━
bar-used-fill-font = 2
bar-used-empty = ━
bar-used-empty-font = 2
bar-used-empty-foreground = #444444
[module/wifi]
type = internal/network
interface = net1
interval = 3.0
;udspeed-minwidth = 3
format-connected = <ramp-signal> <label-connected>
;label-connected = %essid% %{F#66}%local_ip%
;label-connected = %{F#666}%{F#cc} %upspeed% %{F#666}%{F#cc} %downspeed%
label-connected = %{O-6 F#666}%{O2 F#cc}%downspeed%
label-disconnected = %{F#666}%{F#cc} not connected
label-disconnected-foreground = #66
ramp-signal-0 = 
ramp-signal-1 = 
ramp-signal-2 = 
ramp-signal-3 = 
ramp-signal-4 = 
ramp-signal-foreground = #666
[module/wired]
type = internal/network
interface = net0
interval = 3.0
label-connected = %{T3}%local_ip%%{T-}
label-disconnected = %{T3}Not connected%{T-}
label-disconnected-foreground = #66
[module/date]
type = internal/date
date = %%{F#666}%{F#cc} %Y-%m-%d %%{F#e60053}%%{F#cc} %H:%M
date-alt = %%{F#666}%{F#cc} %Y-%m-%d %%{F#e60053}%%{F#cc} %H:%M:%S
interval = 5
[module/backlight]
type = internal/backlight
card = intel_backlight
format = <ramp> <label>
ramp-0 = 
ramp-1 = 
ramp-2 = 
ramp-foreground = #666
[module/volume]
type = internal/volume
speaker-mixer = Speaker
headphone-mixer = Headphone
headphone-id = 9
; format-volume = <ramp-volume> <label-volume>
format-volume = <label-volume> <bar-volume>
label-volume = VOL
label-volume-foreground = ${BAR.foreground}
label-muted = %{F#66} sound muted
;ramp-volume-0 = 
;ramp-volume-1 = 
;ramp-volume-2 = 
;ramp-volume-3 = 
;ramp-volume-foreground = #666
;ramp-headphones-0 = 
;ramp-headphones-1 = 
bar-volume-width = 20
bar-volume-foreground-0 = #55aa55
bar-volume-foreground-1 = #55aa55
bar-volume-foreground-2 = #55aa55
bar-volume-foreground-3 = #55aa55
bar-volume-foreground-4 = #55aa55
bar-volume-foreground-5 = #f5a70a
bar-volume-foreground-6 = #ff5555
bar-volume-gradient = false
bar-volume-indicator = │
bar-volume-indicator-font = 2
bar-volume-indicator-foreground = #ff
bar-volume-fill = ━
bar-volume-fill-font = 2
bar-volume-empty = ━
bar-volume-empty-font = 2
bar-volume-empty-foreground = #444444
[module/battery]
type = internal/battery
full-at = 98
format-charging = <animation-charging> <label-charging>
format-discharging = <ramp-capacity> <label-discharging>
format-full = %{F#666}%{F#cc} <label-full>
ramp-capacity-0 = 
ramp-capacity-1 = 
ramp-capacity-2 = 
ramp-capacity-foreground = #666
animation-charging-0 = 
animation-charging-1 = 
animation-charging-2 = 
animation-charging-foreground = #666
animation-charging-framerate = 750
[module/mpd]
type = internal/mpd
format-online = <label-time> <bar-progress> <label-song> <icon-prev> <icon-seekb> <icon-stop> <toggle> <icon-seekf> <icon-next> <icon-repeat> <icon-random>
format-online-background = #ee333333
format-online-underline = #cc333333
format-online-padding = 3
format-offline = <label-offline>
format-offline-foreground = #66
label-offline = mpd is off
label-song-maxlen = 45
label-song-ellipsis = true
icon-prev = 
icon-seekb = 
icon-stop = 
icon-play = 
icon-pause = 
icon-next = 
icon-seekf = 
; icon-prev = ⏮
; icon-seekb = ⏪
; icon-stop = ⏹
; icon-play = ⏵
; icon-pause = ⏸
; icon-seekf = ⏩
; icon-next = ⏭
icon-random = 
icon-repeat = 
toggle-on-foreground = #e60053
toggle-off-foreground = #66
bar-progress-width = 15
bar-progress-indicator = 
bar-progress-indicator-foreground = #bb
bar-progress-fill = ─
bar-progress-fill-foreground = #bb
bar-progress-fill-font = 3
bar-progress-empty = ─
bar-progress-empty-foreground = #44
bar-progress-empty-font = 3
label-time-foreground = #77
[module/powermenu]
type = custom/menu
label-open = 
label-close = 
label-separator = /
menu-0-0 = Terminate WM
menu-0-0-foreground = #e60053
menu-0-0-exec = bspc quit -1
menu-0-1 = Reboot
menu-0-1-foreground = #e60053
menu-0-1-exec = menu_open-1
menu-0-2 = Power off
menu-0-2-foreground = #e60053
menu-0-2-exec = menu_open-2
menu-1-0 = Cancel
menu-1-0-foreground = #e60053
menu-1-0-exec = menu_open-0
menu-1-1 = Reboot
menu-1-1-foreground = #e60053
menu-1-1-exec = sudo reboot
menu-2-0 = Power off
menu-2-0-foreground = #e60053
menu-2-0-exec = sudo poweroff
menu-2-1 = Cancel
menu-2-1-foreground = #e60053
menu-2-1-exec = menu_open-0
[module/counter]
type = internal/counter
format = <counter>
interval = 0.1
; vim:ft=dosini

View File

@ -7,104 +7,372 @@
;
;=====================================================
[settings]
; 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
[bar/example]
;monitor = eDP1
bottom = true
[bar/top]
monitor = eDP-1
dock = false
width = 100%
height = 25
height = 27
offset-x = 0
offset-y = 0
;offset_x = 0
;offset_y = 0
background = #ee222222
foreground = #ccfafafa
linecolor = #666
background = #00ffffff
foreground = #fff
;linecolor = #ff0000
border-bottom = 2
border-bottom-color = #333
spacing = 1
lineheight = 1
;separator = |
padding-left = 0
padding-right = 2
;locale = en_US.UTF-8
module-margin-left = 1
module-margin-right = 2
padding_left = 1
padding_right = 1
module_margin_left = 0
module_margin_right = 0
font-0 = tamzen:size=9;1
font-1 = siji:pixelsize=10;0
font-2 = unifont:size=6;-1
font-0 = Sans:size=8;0
font-1 = FontAwesome:size=10:weight=heavy;0
modules-left = label
modules-left = mpd
modules-center = i3
modules-right = volume cpu ram clock
modules-right = volume memory cpu date
[module/label]
type = custom/text
content = Lemonbuddy example
content-background = #af2031
content-underline = #cf4253
content-overline = #cf4253
content-padding = 2
tray-position = right
[module/cpu]
type = internal/cpu
label = CPU: %percentage%
format-background = #c42
format-underline = #f75
format-overline = #f75
format-padding = 2
wm-restack = i3
[module/ram]
type = internal/memory
label = RAM: %percentage_used%
format-background = #42c
format-underline = #75f
format-overline = #75f
format-padding = 2
[module/clock]
type = internal/date
date = %Y-%m-%d %H:%M
format-background = #493
format-underline = #7a6
format-overline = #7a6
format-padding = 2
[bar/external_bottom]
monitor = HDMI-1
dock = false
bottom = true
width = 100%
height = ${bar/top.height}
[module/volume]
type = internal/volume
;speaker_mixer = Speaker
;headphone_mixer = Headphone
;headphone_control_numid = 9
background = ${bar/top.background}
foreground = ${bar/top.foreground}
linecolor = ${bar/top.linecolor}
format-volume-background = #933484
format-volume-underline = #9d6294
format-volume-overline = #9d6294
format-volume-padding = 2
format-muted-background = #933484
format-muted-underline = #9d6294
format-muted-overline = #9d6294
format-muted-padding = 2
border-top = 2
border-top-color = #333
spacing = ${bar/top.spacing}
lineheight = ${bar/top.lineheight}
padding-right = ${bar/top.padding_right}
module-margin-left = 0
module-margin-right = ${bar/top.module_margin_right}
font-0 = ${bar/top.font-0}
font-1 = ${bar/top.font-1}
font-2 = ${bar/top.font-2}
modules-left = i3
modules-right = date powermenu
wm-restack = ${bar/top.wm-restack}
label-volume = Volume: %percentage%
label-muted = Sound is muted
[module/i3]
type = internal/i3
label-focused = 
label-focused-padding = 1
label-unfocused = 
label-unfocused-padding = 1
label-visible = 
label-visible-padding = 1
format = <label-state>
;pin-workspaces = true
;wsname-maxlen = 1
;index-sort = true
;ws-icon-0 = term;
;ws-icon-1 = web;
;ws-icon-2 = code;
;ws-icon-3 = music;
;ws-icon-4 = irssi;
;ws-icon-default = 
ws-icon-default = x
label-focused = o %index%
label-focused-background = #ee333333
label-focused-underline= #cc333333
label-focused-padding = 2
label-unfocused = %{O-1}⏺%{O-1} %index%
label-unfocused-padding = 2
label-urgent = ! %index%
label-urgent-background = #bd2c40
label-urgent-padding = 2
label-visible = x %index%
label-visible-foreground = #444
label-visible-padding = 2
[module/cpu]
type = internal/cpu
interval = 2
format = <label> <bar-load>
label = CPU
; ramp-coreload-0 = ▁
; ramp-coreload-0-font = 2
; ramp-coreload-0-foreground = #55aa55
; ramp-coreload-1 = ▂
; ramp-coreload-1-font = 2
; ramp-coreload-1-foreground = #55aa55
; ramp-coreload-2 = ▃
; ramp-coreload-2-font = 2
; ramp-coreload-2-foreground = #55aa55
; ramp-coreload-3 = ▄
; ramp-coreload-3-font = 2
; ramp-coreload-3-foreground = #55aa55
; ramp-coreload-4 = ▅
; ramp-coreload-4-font = 2
; ramp-coreload-4-foreground = #f5a70a
; ramp-coreload-5 = ▆
; ramp-coreload-5-font = 2
; ramp-coreload-5-foreground = #f5a70a
; ramp-coreload-6 = ▇
; ramp-coreload-6-font = 2
; ramp-coreload-6-foreground = #ff5555
; ramp-coreload-7 = █
; ramp-coreload-7-font = 2
; ramp-coreload-7-foreground = #ff5555
bar-load-width = 20
bar-load-foreground-0 = #55aa55
bar-load-foreground-1 = #55aa55
bar-load-foreground-2 = #f5a70a
bar-load-foreground-3 = #ff5555
bar-load-gradient = false
bar-load-indicator = │
bar-load-indicator-font = 2
bar-load-indicator-foreground = #ff
bar-load-fill = ━
bar-load-fill-font = 2
bar-load-empty = ━
bar-load-empty-font = 2
bar-load-empty-foreground = #444444
[module/memory]
type = internal/memory
interval = 2
format = <label> <bar-used>
label = RAM
bar-used-width = 20
bar-used-foreground-0 = #55aa55
bar-used-foreground-1 = #55aa55
bar-used-foreground-2 = #f5a70a
bar-used-foreground-3 = #ff5555
bar-used-gradient = false
bar-used-indicator = │
bar-used-indicator-font = 2
bar-used-indicator-foreground = #ff
bar-used-fill = ━
bar-used-fill-font = 2
bar-used-empty = ━
bar-used-empty-font = 2
bar-used-empty-foreground = #444444
[module/wifi]
type = internal/network
interface = net1
interval = 3.0
;udspeed-minwidth = 3
format-connected = <ramp-signal> <label-connected>
;label-connected = %essid% %{F#66}%local_ip%
;label-connected = %{F#666}%{F#cc} %upspeed% %{F#666}%{F#cc} %downspeed%
label-connected = %{O-6 F#666}%{O2 F#cc}%downspeed%
label-disconnected = %{F#666}%{F#cc} not connected
label-disconnected-foreground = #66
ramp-signal-0 = 
ramp-signal-1 = 
ramp-signal-2 = 
ramp-signal-3 = 
ramp-signal-4 = 
ramp-signal-foreground = #666
[module/wired]
type = internal/network
interface = net0
interval = 3.0
label-connected = %{T3}%local_ip%%{T-}
label-disconnected = %{T3}Not connected%{T-}
label-disconnected-foreground = #66
[module/date]
type = internal/date
date = %%{F#666}%{F#cc} %Y-%m-%d %%{F#e60053}%%{F#cc} %H:%M
date-alt = %%{F#666}%{F#cc} %Y-%m-%d %%{F#e60053}%%{F#cc} %H:%M:%S
interval = 5
[module/backlight]
type = internal/backlight
card = intel_backlight
format = <ramp> <label>
ramp-0 = 
ramp-1 = 
ramp-2 = 
ramp-foreground = #666
[module/volume]
type = internal/volume
speaker-mixer = Speaker
headphone-mixer = Headphone
headphone-id = 9
; format-volume = <ramp-volume> <label-volume>
format-volume = <label-volume> <bar-volume>
label-volume = VOL
label-volume-foreground = ${BAR.foreground}
label-muted = %{F#66} sound muted
;ramp-volume-0 = 
;ramp-volume-1 = 
;ramp-volume-2 = 
;ramp-volume-3 = 
;ramp-volume-foreground = #666
;ramp-headphones-0 = 
;ramp-headphones-1 = 
bar-volume-width = 20
bar-volume-foreground-0 = #55aa55
bar-volume-foreground-1 = #55aa55
bar-volume-foreground-2 = #55aa55
bar-volume-foreground-3 = #55aa55
bar-volume-foreground-4 = #55aa55
bar-volume-foreground-5 = #f5a70a
bar-volume-foreground-6 = #ff5555
bar-volume-gradient = false
bar-volume-indicator = │
bar-volume-indicator-font = 2
bar-volume-indicator-foreground = #ff
bar-volume-fill = ━
bar-volume-fill-font = 2
bar-volume-empty = ━
bar-volume-empty-font = 2
bar-volume-empty-foreground = #444444
[module/battery]
type = internal/battery
full-at = 98
format-charging = <animation-charging> <label-charging>
format-discharging = <ramp-capacity> <label-discharging>
format-full = %{F#666}%{F#cc} <label-full>
ramp-capacity-0 = 
ramp-capacity-1 = 
ramp-capacity-2 = 
ramp-capacity-foreground = #666
animation-charging-0 = 
animation-charging-1 = 
animation-charging-2 = 
animation-charging-foreground = #666
animation-charging-framerate = 750
[module/mpd]
type = internal/mpd
format-online = <label-time> <bar-progress> <label-song> <icon-prev> <icon-seekb> <icon-stop> <toggle> <icon-seekf> <icon-next> <icon-repeat> <icon-random>
format-online-background = #ee333333
format-online-underline = #cc333333
format-online-padding = 3
format-offline = <label-offline>
format-offline-foreground = #66
label-offline = mpd is off
label-song-maxlen = 45
label-song-ellipsis = true
icon-prev = 
icon-seekb = 
icon-stop = 
icon-play = 
icon-pause = 
icon-next = 
icon-seekf = 
; icon-prev = ⏮
; icon-seekb = ⏪
; icon-stop = ⏹
; icon-play = ⏵
; icon-pause = ⏸
; icon-seekf = ⏩
; icon-next = ⏭
icon-random = 
icon-repeat = 
toggle-on-foreground = #e60053
toggle-off-foreground = #66
bar-progress-width = 15
bar-progress-indicator = 
bar-progress-indicator-foreground = #bb
bar-progress-fill = ─
bar-progress-fill-foreground = #bb
bar-progress-fill-font = 3
bar-progress-empty = ─
bar-progress-empty-foreground = #44
bar-progress-empty-font = 3
label-time-foreground = #77
[module/powermenu]
type = custom/menu
label-open = 
label-close = 
label-separator = /
menu-0-0 = Terminate WM
menu-0-0-foreground = #e60053
menu-0-0-exec = i3-msg -t command exit
menu-0-1 = Reboot
menu-0-1-foreground = #e60053
menu-0-1-exec = menu_open-1
menu-0-2 = Power off
menu-0-2-foreground = #e60053
menu-0-2-exec = menu_open-2
menu-1-0 = Cancel
menu-1-0-foreground = #e60053
menu-1-0-exec = menu_open-0
menu-1-1 = Reboot
menu-1-1-foreground = #e60053
menu-1-1-exec = sudo reboot
menu-2-0 = Power off
menu-2-0-foreground = #e60053
menu-2-0-exec = sudo poweroff
menu-2-1 = Cancel
menu-2-1-foreground = #e60053
menu-2-1-exec = menu_open-0
[module/counter]
type = internal/counter
format = <counter>
interval = 0.1
; 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

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

@ -0,0 +1,246 @@
#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> guard(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> guard(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() {
assert(m_elem);
assert(m_value);
std::lock_guard<threading_util::spin_lock> guard(m_lock);
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> guard(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::unique_lock<threading_util::spin_lock> guard(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);
guard.unlock();
return process_events() > 0;
}
int process_events() {
std::lock_guard<threading_util::spin_lock> guard(m_lock);
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> guard(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> guard(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> guard(m_lock);
snd_mixer_selem_set_playback_switch_all(m_mixerelement, mode);
}
void toggle_mute() {
std::lock_guard<threading_util::spin_lock> guard(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> guard(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;
}
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(int minwidth = 3) {
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(minwidth) << std::setfill(' ')
<< std::setprecision(0) << std::fixed << speed
<< " " << suffixes[suffix_n] << "/s");
}
string upspeed(int minwidth = 3) {
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(minwidth) << 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,103 +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.imbue(std::locale::classic());
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();

132
include/common.hpp Normal file
View File

@ -0,0 +1,132 @@
#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>
#include "config.hpp"
#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>;
//==================================================
// Instance factory
//==================================================
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;
}
}
struct null_deleter {
template <typename T>
void operator()(T*) const {}
};
//==================================================
// 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

1000
include/components/bar.hpp Normal file

File diff suppressed because it is too large Load Diff

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,231 @@
#pragma once
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include "common.hpp"
LEMONBUDDY_NS
namespace command_line {
DEFINE_ERROR(argument_error);
DEFINE_ERROR(value_error);
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, const options& opts)
: m_synopsis(forward<string>(synopsis)), m_opts(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, const 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(option, 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 value_error("Missing value for " + opt);
else if ((pos = opt.find("=")) == string::npos && opt.compare(0, 2, "--") == 0)
throw value_error("Missing value for " + opt);
else if (pos == string::npos && !input_next.empty())
value = input_next;
else {
value = opt.substr(pos + 1);
opt = opt.substr(0, pos);
}
if (!values.empty() && std::find(values.begin(), values.end(), value) == values.end())
throw value_error("Invalid value '" + value + "' for argument " + 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 argument_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,231 @@
#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"
#define GET_CONFIG_VALUE(section, var, name) var = m_conf.get<decltype(var)>(section, name, var)
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 parameter [" + section + "." + key + "]");
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 parameter [" + section + "." + key + "-0]");
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 reference defined at [" + ref_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("Unexisting reference defined at [" + ref_path + "]");
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,550 @@
#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/command.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/memory.hpp"
#include "modules/menu.hpp"
#include "modules/script.hpp"
#include "modules/text.hpp"
#include "modules/unsupported.hpp"
#if ENABLE_I3
#include "modules/i3.hpp"
#endif
#if ENABLE_MPD
#include "modules/mpd.hpp"
#endif
#if ENABLE_NETWORK
#include "modules/network.hpp"
#endif
#if ENABLE_ALSA
#include "modules/volume.hpp"
#endif
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 {
if (!m_mutex.try_lock_for(3s)) {
m_log.warn("Failed to acquire lock for 3s... Forcing shutdown using SIGKILL");
raise(SIGKILL);
}
std::lock_guard<std::timed_mutex> guard(m_mutex, std::adopt_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->on_stop.disconnect(this, &controller::on_module_stop);
module->stop();
}
}
m_log.trace("controller: Deconstruct bar instance");
bar_signals::action_click.disconnect(this, &controller::on_module_click);
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->on_stop.disconnect(this, &controller::on_module_stop);
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[1]{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;
} else if (!to_stdout) {
bar_signals::action_click.connect(this, &controller::on_module_click);
}
} 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->on_stop.connect(this, &controller::on_module_stop);
try {
module->start();
} catch (const application_error& err) {
m_log.err("Failed to start '%s' (reason: %s)", module->name(), err.what());
}
}
}
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) {
if (!m_mutex.try_lock_for(50ms))
return;
std::lock_guard<std::timed_mutex> guard(m_mutex, std::adopt_lock);
if (!m_running)
return;
if (!m_throttler->passthrough(m_throttle_strategy)) {
m_log.trace("controller: Update event throttled");
return;
}
string contents{""};
string separator{m_bar->settings().separator};
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() && !separator.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);
}
void on_module_stop(string module_name) {
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
if (module->running())
return;
}
}
m_log.warn("No running modules, raising SIGTERM");
kill(getpid(), SIGTERM);
}
void on_module_click(string input) {
if (!m_mutex.try_lock())
return;
std::lock_guard<std::timed_mutex> guard(m_mutex, std::adopt_lock);
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
if (!module->receive_events())
continue;
if (module->handle_event(input))
return;
}
}
m_log.trace("controller: Unrecognized input '%s'", input);
m_log.trace("controller: Forwarding input to shell");
auto command = command_util::make_command("/usr/bin/env\nsh\n-c\n"+ input);
try {
command->exec(false);
command->tail([this](std::string output){
m_log.trace("> %s", output);
});
command->wait();
} catch (const application_error& err) {
m_log.err(err.what());
}
}
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;
std::timed_mutex m_mutex;
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;
throttle_util::strategy::try_once_or_leave_yolo m_throttle_strategy;
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,197 @@
#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
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
#elif defined(__GCC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-security"
#endif
dprintf(m_fd, (prefix + format + suffix + "\n").c_str(), convert(values)...);
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GCC__)
#pragma GCC diagnostic pop
#endif
}
private:
/**
* Logger verbosity level
*/
loglevel m_level = loglevel::TRACE;
/**
* File descriptor used when writing the log messages
*/
int m_fd = STDERR_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]))
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,104 @@
#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{false};
monitor_t 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 button{mousebtn::NONE};
string command;
int16_t start_x{0};
int16_t end_x{0};
alignment align;
bool active{true};
#if DEBUG and DRAW_CLICKABLE_AREA_HINTS
xcb_window_t clickable_area;
#endif
};
LEMONBUDDY_NS_END

View File

@ -0,0 +1,60 @@
#pragma once
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
struct cached_atom {
const char* name;
size_t len;
xcb_atom_t* atom;
};
static xcb_atom_t _NET_WM_NAME;
static xcb_atom_t _NET_WM_DESKTOP;
static xcb_atom_t _NET_WM_WINDOW_TYPE;
static xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK;
static xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL;
static xcb_atom_t _NET_WM_PID;
static xcb_atom_t _NET_WM_STATE;
static xcb_atom_t _NET_WM_STATE_STICKY;
static xcb_atom_t _NET_WM_STATE_SKIP_TASKBAR;
static xcb_atom_t _NET_WM_STATE_ABOVE;
static xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
static xcb_atom_t _NET_WM_STRUT;
static xcb_atom_t _NET_WM_STRUT_PARTIAL;
static xcb_atom_t WM_PROTOCOLS;
static xcb_atom_t WM_DELETE_WINDOW;
static xcb_atom_t _XEMBED;
static xcb_atom_t _XEMBED_INFO;
static xcb_atom_t _NET_SYSTEM_TRAY_OPCODE;
static xcb_atom_t MANAGER;
static xcb_atom_t WM_STATE;
static xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION;
static xcb_atom_t WM_TAKE_FOCUS;
// clang-format off
static cached_atom ATOMS[22] = {
{"_NET_WM_NAME", sizeof("_NET_WM_NAME") - 1, &_NET_WM_NAME},
{"_NET_WM_DESKTOP", sizeof("_NET_WM_DESKTOP") - 1, &_NET_WM_DESKTOP},
{"_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},
{"_NET_WM_STATE_MAXIMIZED_VERT", sizeof("_NET_WM_STATE_MAXIMIZED_VERT") - 1, &_NET_WM_STATE_MAXIMIZED_VERT},
{"_NET_WM_STRUT", sizeof("_NET_WM_STRUT") - 1, &_NET_WM_STRUT},
{"_NET_WM_STRUT_PARTIAL", sizeof("_NET_WM_STRUT_PARTIAL") - 1, &_NET_WM_STRUT_PARTIAL},
{"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,91 @@
#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;
};
static map<string, class 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 (static_cast<size_t>(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[1]{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,66 @@
#pragma once
#include "common.hpp"
#include "components/x11/connection.hpp"
#include "utils/memory.hpp"
LEMONBUDDY_NS
struct randr_output {
string name;
int w = 0;
int h = 0;
int x = 0;
int y = 0;
};
using monitor_t = shared_ptr<randr_output>;
namespace randr_util {
/**
* Define monitor
*/
inline monitor_t make_monitor(string name, int w, int h, int x, int y) {
monitor_t mon{new monitor_t::element_type{}};
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<monitor_t> get_monitors(connection& conn, xcb_window_t root) {
vector<monitor_t> 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(),
[](monitor_t& m1, monitor_t& 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 <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(), 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_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
const uint32_t values[3]{m_settings.background, true,
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,167 @@
#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,29 @@
#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
#ifdef DEBUG
#cmakedefine01 DRAW_CLICKABLE_AREA_HINTS
#cmakedefine DRAW_CLICKABLE_AREA_HINTS_OFFSET_Y @DRAW_CLICKABLE_AREA_HINTS_OFFSET_Y@
#ifndef DRAW_CLICKABLE_AREA_HINTS_OFFSET_Y
#define DRAW_CLICKABLE_AREA_HINTS_OFFSET_Y 0
#endif
#endif
#define BUILDER_SPACE_TOKEN "%__"
#define ALSA_SOUNDCARD "@SETTING_ALSA_SOUNDCARD@"
#define CONNECTION_TEST_IP "@SETTING_CONNECTION_TEST_IP@"
@ -18,3 +35,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;
void tick();
namespace drawtypes {
class animation;
using animation_t = shared_ptr<animation>;
class animation : public non_copyable_mixin<animation> {
public:
Animation(std::vector<std::unique_ptr<Icon>> &&frames, int framerate_ms = 1);
explicit Animation(int framerate_ms)
: framerate_ms(framerate_ms){}
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()) {}
void add(std::unique_ptr<Icon> &&frame);
void add(icon_t&& frame) {
m_frames.emplace_back(forward<decltype(frame)>(frame));
m_framecount = m_frames.size();
}
std::unique_ptr<Icon> &get();
icon_t get() {
tick();
return m_frames[m_frame];
}
int get_framerate();
int framerate() {
return m_framerate_ms;
}
operator bool() {
return !this->frames.empty();
return !m_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", 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,32 +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;
size_t maxlen = 0;
bool ellipsis = true;
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, size_t maxlen = 0, bool ellipsis = true)
: text(text), fg(fg), bg(bg), ul(ul), ol(ol), font(font), padding(padding), margin(margin), maxlen(maxlen), ellipsis(ellipsis){}
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,136 @@
#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) {
if (m_colors.empty())
m_colors.emplace_back(m_fill->m_foreground);
int fill_width = m_width * percentage / 100.0f + 0.5f;
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 idx = static_cast<int>((m_colors.size() - 1) * percentage / 100.0f + 0.5f);
m_fill->m_foreground = m_colors[idx];
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>;
class ramp : public non_copyable_mixin<ramp> {
public:
Ramp(){}
explicit Ramp(std::vector<std::unique_ptr<Icon>> icons);
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));
}
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 this->icons.size() > 0;
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,25 +1,212 @@
#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)
{
LEMONBUDDY_NS
namespace modules {
class battery_module : public inotify_module<battery_module> {
public:
using inotify_module::inotify_module;
void setup() {
// Load configuration values {{{
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);
m_path_capacity = string_util::replace(PATH_BATTERY_CAPACITY, "%battery%", m_battery);
m_path_adapter = string_util::replace(PATH_ADAPTER_STATUS, "%adapter%", m_adapter);
m_state = STATE_UNKNOWN;
m_percentage = 0;
// }}}
// Validate paths {{{
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");
// }}}
// Add formats and elements {{{
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;
protected:
static constexpr auto FORMAT_CHARGING = "format-charging";
static constexpr auto FORMAT_DISCHARGING = "format-discharging";
static constexpr auto FORMAT_FULL = "format-full";
@ -31,32 +218,25 @@ namespace modules
static constexpr auto TAG_LABEL_DISCHARGING = "<label-discharging>";
static constexpr auto TAG_LABEL_FULL = "<label-full>";
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;
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;
std::string battery, adapter;
std::string path_capacity, path_adapter;
string m_battery;
string m_adapter;
string m_path_capacity;
string m_path_adapter;
concurrency::Atomic<int> state;
concurrency::Atomic<int> percentage;
int full_at;
void subthread_routine();
public:
explicit BatteryModule(std::string name);
void start();
bool on_event(InotifyEvent *event);
std::string get_format();
bool build(Builder *builder, std::string tag);
int m_state;
int m_percentage;
int m_fullat;
};
}
LEMONBUDDY_NS_END

View File

@ -1,32 +1,20 @@
#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
{
namespace modules {
enum class bspwm_flag {
WORKSPACE_NONE,
WORKSPACE_ACTIVE,
WORKSPACE_URGENT,
WORKSPACE_EMPTY,
WORKSPACE_OCCUPIED,
// used when the monitor is unfocused
WORKSPACE_DIMMED,
WORKSPACE_DIMMED, // used when the monitor is out of focus
MODE_NONE,
MODE_LAYOUT_MONOCLE,
MODE_LAYOUT_TILED,
@ -37,51 +25,329 @@ namespace modules
MODE_NODE_PRIVATE
};
struct Workspace
{
Flag flag;
std::unique_ptr<drawtypes::Label> label;
struct bspwm_workspace {
bspwm_flag flag;
label_t label;
Workspace(Flag flag, std::unique_ptr<drawtypes::Label> label) {
this->flag = flag;
this->label.swap(label);
bspwm_workspace(bspwm_flag flag, label_t&& label)
: flag(flag), label(forward<decltype(label)>(label)) {}
operator bool() {
return label && *label;
}
operator bool() { return this->label && *this->label; }
};
using bspwm_workspace_t = unique_ptr<bspwm_workspace>;
class bspwm_module : public event_module<bspwm_module> {
public:
using event_module::event_module;
~bspwm_module() {
if (m_subscriber)
m_subscriber->disconnect();
}
DefineModule(BspwmModule, EventModule)
{
static constexpr auto TAG_LABEL_STATE = "<label-state>";
static constexpr auto TAG_LABEL_MODE = "<label-mode>";
void setup() {
m_monitor = m_bar.monitor->name;
m_log.trace("%s: Primary monitor '%s'", name(), m_monitor);
static constexpr auto EVENT_CLICK = "bwm";
// Add formats and create components {{{
std::map<bspwm::Flag, std::unique_ptr<drawtypes::Label>> mode_labels;
std::map<bspwm::Flag, std::unique_ptr<drawtypes::Label>> state_labels;
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE}, {TAG_LABEL_MODE});
std::vector<std::unique_ptr<bspwm::Workspace>> workspaces;
std::vector<std::unique_ptr<drawtypes::Label>*> modes;
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")));
}
std::unique_ptr<drawtypes::IconMap> icons;
std::string monitor;
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")));
}
int socket_fd = -1;
std::string prev_data;
m_icons = iconset_t{new iconset()};
m_icons->add(
DEFAULT_WS_ICON, icon_t{new icon(m_conf.get<string>(name(), DEFAULT_WS_ICON, ""))});
public:
BspwmModule(std::string name, std::string monitor);
~BspwmModule();
for (auto workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
auto vec = string_util::split(workspace, ';');
if (vec.size() == 2) {
m_icons->add(vec[0], icon_t{new icon{vec[1]}});
}
}
void start();
bool has_event();
bool update();
bool build(Builder *builder, std::string tag);
// }}}
// Create ipc subscriber {{{
m_subscriber = bspwm_util::make_subscriber();
// }}}
}
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();
bool handle_command(std::string cmd);
bool register_for_events() const {
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_event(string cmd) {
if (cmd.find(EVENT_CLICK) == string::npos || cmd.length() <= strlen(EVENT_CLICK))
return false;
try {
auto ipc = bspwm_util::make_connection();
auto payload = bspwm_util::make_payload("desktop -f "+ m_monitor +":^"+ cmd.substr(strlen(EVENT_CLICK)));
m_log.info("%s: Sending desktop focus command to ipc handler", name());
ipc->send(payload->data, payload->len, 0);
ipc->disconnect();
} catch (const system_error& err) {
m_log.err("%s: %s", name(), err.what());
}
return true;
}
bool receive_events() const {
return true;
}
private:
static constexpr auto DEFAULT_WS_ICON = "ws-icon-default";
static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%";
static constexpr auto TAG_LABEL_STATE = "<label-state>";
static constexpr auto TAG_LABEL_MODE = "<label-mode>";
static constexpr auto EVENT_CLICK = "bwm";
bspwm_util::connection_t m_subscriber;
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;
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>";
static constexpr auto TAG_RAMP_LOAD_PER_CORE = "<ramp-coreload>";
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,77 @@
#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_formatalt = m_conf.get<string>(name(), "date-alt", "");
}
bool update() {
if (!m_formatter->has(TAG_DATE))
return false;
auto time = std::time(nullptr);
auto date_format = m_toggled ? m_formatalt : 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_formatalt.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;
}
bool handle_event(string cmd) {
if (cmd == EVENT_TOGGLE) {
m_toggled = !m_toggled;
wakeup();
}
return cmd == EVENT_TOGGLE;
}
bool receive_events() const {
return true;
}
private:
static constexpr auto TAG_DATE = "<date>";
static constexpr auto EVENT_TOGGLE = "datetoggle";
std::unique_ptr<Builder> builder;
string m_format;
string m_formatalt;
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_toggled{false};
};
}
LEMONBUDDY_NS_END

View File

@ -1,80 +1,248 @@
#pragma once
#include <memory>
#include <unistd.h>
#include <i3ipc++/ipc.hpp>
#include "config.hpp"
#include "modules/base.hpp"
#include "drawtypes/icon.hpp"
#include "components/config.hpp"
#include "drawtypes/iconset.hpp"
#include "drawtypes/label.hpp"
#include "modules/meta.hpp"
#include "utils/i3.hpp"
#include "utils/io.hpp"
namespace modules
{
namespace i3
{
enum Flag
{
LEMONBUDDY_NS
namespace modules {
// meta types {{{
enum class i3_flag {
WORKSPACE_NONE,
WORKSPACE_FOCUSED,
WORKSPACE_UNFOCUSED,
WORKSPACE_VISIBLE,
WORKSPACE_URGENT,
// used when the monitor is unfocused
WORKSPACE_DIMMED,
};
struct Workspace
{
int idx;
Flag flag;
std::unique_ptr<drawtypes::Label> label;
struct i3_workspace {
int index;
i3_flag flag;
label_t label;
Workspace(int idx, Flag flag, std::unique_ptr<drawtypes::Label> label) {
this->idx = idx;
this->flag = flag;
this->label.swap(label);
i3_workspace(int index_, i3_flag flag_, label_t&& label_)
: index(index_), flag(flag_), label(forward<decltype(label_)>(label_)) {}
operator bool() {
return label && *label;
}
operator bool() { return this->label && *this->label; }
};
}
DefineModule(i3Module, EventModule)
{
static constexpr auto TAG_LABEL_STATE = "<label-state>";
using i3_workspace_t = unique_ptr<i3_workspace>;
static constexpr auto EVENT_CLICK = "i3";
std::unique_ptr<i3ipc::connection> ipc;
// std::map<i3::Flag, std::unique_ptr<drawtypes::Label>> mode_labels;
std::map<i3::Flag, std::unique_ptr<drawtypes::Label>> state_labels;
std::vector<std::unique_ptr<i3::Workspace>> workspaces;
// std::vector<std::unique_ptr<drawtypes::Label>*> modes;
std::unique_ptr<drawtypes::IconMap> icons;
std::string monitor;
bool local_workspaces = true;
std::size_t workspace_name_strip_nchars = 0;
int ipc_fd = -1;
// }}}
class i3_module : public event_module<i3_module> {
public:
i3Module(std::string name, std::string monitor);
~i3Module();
using event_module::event_module;
void start();
~i3_module() {
// Shutdown ipc connection {{{
bool has_event();
bool update();
bool build(Builder *builder, std::string tag);
try {
shutdown(m_ipc.get_event_socket_fd(), SHUT_RD);
shutdown(m_ipc.get_main_socket_fd(), SHUT_RD);
} catch (const std::exception& err) {
}
bool handle_command(std::string cmd);
bool register_for_events() const {
// }}}
}
void setup() {
// Load configuration values {{{
GET_CONFIG_VALUE(name(), m_indexsort, "index-sort");
GET_CONFIG_VALUE(name(), m_pinworkspaces, "pin-workspaces");
GET_CONFIG_VALUE(name(), m_wsname_maxlen, "wsname-maxlen");
// }}}
// Add formats and create components {{{
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_icons = iconset_t{new iconset()};
m_icons->add(
DEFAULT_WS_ICON, icon_t{new icon(m_conf.get<string>(name(), DEFAULT_WS_ICON, ""))});
// }}}
// Add formats and create components {{{
for (auto workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
auto vec = string_util::split(workspace, ';');
if (vec.size() == 2)
m_icons->add(vec[0], icon_t{new icon{vec[1]}});
}
// }}}
// Subscribe to ipc events {{{
try {
m_ipc.subscribe(i3ipc::ET_WORKSPACE);
m_ipc.prepare_to_event_handling();
} catch (std::runtime_error& e) {
throw module_error(e.what());
}
// }}}
}
bool has_event() {
// Wait for ipc events {{{
try {
m_ipc.handle_event();
return true;
} catch (const std::exception& err) {
if (enabled())
m_log.err("%s: Error while handling ipc event (%s)", name(), err.what());
return false;
}
// }}}
}
bool update() {
// Refresh workspace data {{{
m_workspaces.clear();
i3_util::connection_t ipc;
try {
auto workspaces = ipc.get_workspaces();
vector<shared_ptr<i3ipc::workspace_t>> sorted = workspaces;
string focused_output;
bool output_unfocused = false;
for (auto&& workspace : workspaces)
if (workspace->focused) {
focused_output = workspace->output;
break;
}
if (focused_output != m_bar.monitor->name)
output_unfocused = true;
if (m_indexsort) {
using ws_t = shared_ptr<i3ipc::workspace_t>;
// clang-format off
sort(sorted.begin(), sorted.end(), [](ws_t ws1, ws_t ws2){
return ws1->num < ws2->num;
});
// clang-format on
}
for (auto&& workspace : sorted) {
if (m_pinworkspaces && workspace->output != m_bar.monitor->name)
continue;
auto flag = i3_flag::WORKSPACE_NONE;
if (workspace->focused)
flag = i3_flag::WORKSPACE_FOCUSED;
else if (workspace->urgent)
flag = i3_flag::WORKSPACE_URGENT;
else if (!workspace->visible || (workspace->visible && workspace->output != focused_output))
flag = i3_flag::WORKSPACE_UNFOCUSED;
else
flag = i3_flag::WORKSPACE_VISIBLE;
string wsname{workspace->name};
if (m_wsname_maxlen > 0 && wsname.length() > m_wsname_maxlen)
wsname.erase(m_wsname_maxlen);
auto icon = m_icons->get(workspace->name, DEFAULT_WS_ICON);
auto label = m_statelabels.find(flag)->second->clone();
label->replace_token("%output%", workspace->output);
label->replace_token("%name%", wsname);
label->replace_token("%icon%", icon->m_text);
label->replace_token("%index%", to_string(workspace->num));
m_workspaces.emplace_back(make_unique<i3_workspace>(workspace->num, flag, std::move(label)));
}
return true;
} catch (const std::exception& err) {
m_log.err("%s: %s", name(), err.what());
return false;
}
// }}}
}
bool build(builder* builder, string tag) {
// Output workspace info {{{
if (tag != TAG_LABEL_STATE)
return false;
for (auto&& ws : m_workspaces) {
builder->cmd(mousebtn::LEFT, string{EVENT_CLICK} + to_string(ws.get()->index));
builder->node(ws.get()->label);
builder->cmd_close(true);
}
return true;
// }}}
}
bool handle_event(string cmd) {
// Send ipc commands {{{
if (cmd.find(EVENT_CLICK) == string::npos)
return false;
if (cmd.length() < strlen(EVENT_CLICK))
return false;
try {
i3_util::connection_t ipc;
m_log.info("%s: Sending workspace focus command to ipc handler", name());
ipc.send_command("workspace number "+ cmd.substr(strlen(EVENT_CLICK)));
} catch (const std::exception& err) {
m_log.err("%s: %s", name(), err.what());
}
return true;
// }}}
}
bool receive_events() const {
return true;
}
private:
static constexpr auto DEFAULT_WS_ICON = "ws-icon-default";
static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%";
static constexpr auto TAG_LABEL_STATE = "<label-state>";
static constexpr auto EVENT_CLICK = "i3-wsfocus-";
map<i3_flag, label_t> m_statelabels;
vector<i3_workspace_t> m_workspaces;
iconset_t m_icons;
bool m_indexsort = false;
bool m_pinworkspaces = false;
size_t m_wsname_maxlen = 0;
i3_util::connection_t m_ipc;
};
}
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,146 @@
#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() {
string default_format{TAG_LABEL_TOGGLE + string{" "} + TAG_MENU};
m_formatter->add(DEFAULT_FORMAT, default_format, {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");
}
m_labelseparator = get_optional_config_label(m_conf, name(), "label-separator", "");
if (!m_formatter->has(TAG_MENU))
return;
while (true) {
string level_param{"menu-" + to_string(m_levels.size())};
if (m_conf.get<string>(name(), level_param + "-0", "").empty())
break;
m_log.trace("%s: Creating menu level %i", name(), m_levels.size());
m_levels.emplace_back(make_unique<menu_tree>());
while (true) {
string item_param{level_param + "-" + to_string(m_levels.back()->items.size())};
if (m_conf.get<string>(name(), item_param, "").empty())
break;
m_log.trace("%s: Creating menu level item %i", name(), m_levels.back()->items.size());
auto item = make_unique<menu_tree_item>();
item->label = get_config_label(m_conf, name(), item_param);
item->exec = m_conf.get<string>(name(), item_param + "-exec", EVENT_MENU_CLOSE);
m_levels.back()->items.emplace_back(std::move(item));
}
}
}
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) {
for (auto&& item : m_levels[m_level]->items) {
if (item != m_levels[m_level]->items.front())
builder->space();
if (*m_labelseparator)
builder->node(m_labelseparator, true);
builder->cmd(mousebtn::LEFT, item->exec);
builder->node(item->label);
builder->cmd_close(true);
}
} else {
return false;
}
return true;
}
bool handle_event(string cmd) {
if (cmd.compare(0, 4, "menu") != 0)
return false;
// broadcast update when leaving leaving the function
auto exit_handler = scope_util::make_exit_handler<>([this]() {
if (!m_threads.empty()) {
m_log.trace("%s: Cleaning up previous broadcast threads", name());
for (auto&& thread : m_threads)
if (thread.joinable())
thread.join();
m_threads.clear();
}
m_log.trace("%s: Dispatching broadcast thread", name());
m_threads.emplace_back(thread(&menu_module::broadcast, this));
});
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());
m_log.info("%s: Opening menu level '%i'", name(), static_cast<int>(m_level));
if (static_cast<size_t>(m_level) >= m_levels.size()) {
m_log.warn("%s: Cannot open unexisting menu level '%i'", name(), level);
m_level = -1;
}
} else if (cmd == EVENT_MENU_CLOSE) {
m_log.info("%s: Closing menu tree", name());
m_level = -1;
}
return true;
}
bool receive_events() const {
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";
static constexpr auto EVENT_MENU_OPEN = "menu-open-";
static constexpr auto EVENT_MENU_CLOSE = "menu-close";
std::mutex output_mtx;
std::mutex cmd_mtx;
label_t m_labelopen;
label_t m_labelclose;
label_t m_labelseparator;
int current_level = -1;
std::vector<std::unique_ptr<MenuTree>> levels;
vector<unique_ptr<menu_tree>> m_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;
}
std::atomic<int> m_level{-1};
};
}
LEMONBUDDY_NS_END

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

@ -0,0 +1,513 @@
#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 bool running() 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_event(string cmd) = 0;
virtual bool receive_events() const = 0;
delegate::Signal1<string> on_update;
delegate::Signal1<string> on_stop;
};
// }}}
// 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;
}
bool running() const {
return enabled();
}
void setup() {
m_log.trace("%s: Setup", name());
CAST_MODULE(Impl)->setup();
}
void stop() {
if (!enabled())
return;
m_log.trace("%s: Stop", name());
enable(false);
wakeup();
if (!on_stop.empty())
on_stop.emit(name());
}
void refresh() {
m_cache = CAST_MODULE(Impl)->get_output();
}
string contents() {
return m_cache;
}
bool handle_event(string cmd) {
return CAST_MODULE(Impl)->handle_event(cmd);
}
bool receive_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() {
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)->broadcast();
}
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() {
try {
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);
}
} catch (const application_error& err) {
this->m_log.err("%s: %s", this->name(), err.what());
this->m_log.warn("Stopping '%s'...", this->name());
CAST_MODULE(Impl)->stop();
}
}
};
// }}}
// 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() {
try {
CAST_MODULE(Impl)->setup();
// warmup
CAST_MODULE(Impl)->update();
CAST_MODULE(Impl)->broadcast();
while (CONST_CAST_MODULE(Impl).enabled()) {
std::unique_lock<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();
lck.unlock();
CAST_MODULE(Impl)->idle();
}
} catch (const application_error& err) {
this->m_log.err("%s: %s", this->name(), err.what());
this->m_log.warn("Stopping '%s'...", this->name());
CAST_MODULE(Impl)->stop();
}
}
};
// }}}
// 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() {
try {
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);
CAST_MODULE(Impl)->poll_events();
}
} catch (const application_error& err) {
this->m_log.err("%s: %s", this->name(), err.what());
this->m_log.warn("Stopping '%s'...", this->name());
CAST_MODULE(Impl)->stop();
}
}
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,327 @@
#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_event(string cmd) {
if (cmd.compare(0, 3, "mpd") != 0)
return false;
try {
auto mpd = make_unique<mpdconnection>(m_log, m_host, m_port, m_pass);
mpd->connect();
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("%s: %s", name(), e.what());
}
return true;
}
bool receive_events() const {
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 +352,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,209 @@
#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);
m_udspeed_minwidth = m_conf.get<int>(name(), "udspeed-minwidth", m_udspeed_minwidth);
// 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) + "%");
}
auto upspeed = network->upspeed(m_udspeed_minwidth);
auto downspeed = network->downspeed(m_udspeed_minwidth);
label->replace_token("%upspeed%", upspeed);
label->replace_token("%downspeed%", 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 +214,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;
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};
string m_interface;
int m_signal_quality;
int m_ping_nth_update;
int m_counter = -1; // -1 to ignore the first run
int m_udspeed_minwidth = 3;
};
}
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)
{
static constexpr auto TAG_OUTPUT = "<output>";
LEMONBUDDY_NS
std::unique_ptr<Builder> builder;
std::unique_ptr<Command> command;
#define SHELL_CMD "/usr/bin/env\nsh\n-c\n"
std::string exec;
bool tail = false;
namespace modules {
class script_module : public timer_module<script_module> {
public:
using timer_module::timer_module;
std::string click_left;
std::string click_middle;
std::string click_right;
std::string scroll_up;
std::string scroll_down;
void setup() {
// Load configuration values
std::string output;
std::atomic<int> counter;
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;
public:
explicit ScriptModule(std::string name);
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 "";
}
void start();
bool update();
bool build(Builder *builder, std::string tag);
std::string get_output();
/**
* 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>";
unique_ptr<command_util::command> m_command;
map<mousebtn, string> m_actions;
string m_exec;
bool m_tail = false;
string m_output;
int m_counter{0};
size_t m_maxlen = 0;
bool m_ellipsis = true;
};
}
LEMONBUDDY_NS_END

View File

@ -1,17 +1,51 @@
#pragma once
#include "modules/base.hpp"
#include "modules/meta.hpp"
namespace modules
{
DefineModule(TextModule, StaticModule)
{
static constexpr auto FORMAT = "content";
LEMONBUDDY_NS
namespace modules {
class text_module : public static_module<text_module> {
public:
explicit TextModule(std::string name);
using static_module::static_module;
std::string get_format();
std::string get_output();
void setup() {
m_formatter->add("content", "", {});
if (m_formatter->get("content")->value.empty())
throw module_error(name() + ".content is empty or undefined");
m_formatter->get("content")->value =
string_util::replace_all(m_formatter->get("content")->value, " ", BUILDER_SPACE_TOKEN);
}
string get_format() {
return "content";
}
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();
}
};
}
LEMONBUDDY_NS_END

View File

@ -0,0 +1,36 @@
#pragma once
#include "modules/meta.hpp"
#define DEFINE_UNSUPPORTED_MODULE(MODULE_NAME, MODULE_TYPE) \
class MODULE_NAME : public module<MODULE_NAME> { \
public: \
using module<MODULE_NAME>::module; \
MODULE_NAME(const bar_settings b, const logger& l, const config& c, string n) \
: module<MODULE_NAME>::module(b, l, c, n) { \
throw application_error("No built-in support for '" + string{MODULE_TYPE} + "'"); \
} \
void start() {} \
bool build(builder*, string) { \
return true; \
} \
}
LEMONBUDDY_NS
namespace modules {
#if not ENABLE_I3
DEFINE_UNSUPPORTED_MODULE(i3_module, "internal/i3");
#endif
#if not ENABLE_MPD
DEFINE_UNSUPPORTED_MODULE(mpd_module, "internal/mpd");
#endif
#if not ENABLE_NETWORK
DEFINE_UNSUPPORTED_MODULE(network_module, "internal/network");
#endif
#if not ENABLE_ALSA
DEFINE_UNSUPPORTED_MODULE(volume_module, "internal/volume");
#endif
}
LEMONBUDDY_NS_END

View File

@ -1,20 +1,265 @@
#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-id", -1);
if (!headphone_mixer.empty() && m_headphone_ctrl_numid == -1)
throw module_error(
"volume_module: Missing required property value for \"headphone-id\"...");
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() {
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() {
// 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);
else
m_log.trace("%s: Not adding scroll up handler (muted or volume = 100)", name());
if (!m_muted && m_volume > 0)
m_builder->cmd(mousebtn::SCROLL_DOWN, EVENT_VOLUME_DOWN);
else
m_log.trace("%s: Not adding scroll down handler (muted or volume = 0)", name());
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_event(string cmd) {
if (cmd.compare(0, 3, EVENT_PREFIX) != 0)
return false;
if (!m_master_mixer)
return false;
alsa_mixer* master_mixer = m_master_mixer.get();
alsa_mixer* other_mixer = nullptr;
if (m_headphone_mixer && m_headphones)
other_mixer = m_headphone_mixer.get();
else if (m_speaker_mixer)
other_mixer = m_speaker_mixer.get();
else
return false;
if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) {
master_mixer->set_mute(m_muted);
if (other_mixer != nullptr)
other_mixer->set_mute(m_muted);
} else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) {
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 (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) {
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;
}
return true;
}
bool receive_events() const {
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 +269,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;
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;
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;
}
stateflag m_muted{false};
stateflag 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

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

@ -0,0 +1,162 @@
#pragma once
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "common.hpp"
#include "components/x11/connection.hpp"
#include "components/x11/randr.hpp"
#include "components/x11/window.hpp"
#include "config.hpp"
#include "utils/socket.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
namespace bspwm_util {
struct payload;
using connection_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 all bspwm root windows
*/
auto root_windows(connection& conn) {
vector<xcb_window_t> roots;
auto children = conn.query_tree(conn.screen()->root).children();
for (auto it = children.begin(); it != children.end(); it++) {
auto cookie = xcb_icccm_get_wm_class(conn, *it);
xcb_icccm_get_wm_class_reply_t reply;
if (xcb_icccm_get_wm_class_reply(conn, cookie, &reply, nullptr) == 0)
continue;
if (!string_util::compare("Bspwm", reply.class_name) ||
!string_util::compare("root", reply.instance_name))
continue;
roots.emplace_back(*it);
}
return roots;
}
/**
* Restack given window above the bspwm root window
* for the given monitor.
*
* Fixes the issue with always-on-top window's
*/
bool restack_above_root(connection& conn, const monitor_t& mon, const xcb_window_t win) {
for (auto&& root : root_windows(conn)) {
auto geom = conn.get_geometry(root);
if (mon->x != geom->x || mon->y != geom->y)
continue;
if (mon->w != geom->width || mon->h != geom->height)
continue;
const uint32_t value_mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
const uint32_t value_list[2]{root, XCB_STACK_MODE_ABOVE};
conn.configure_window_checked(win, value_mask, value_list);
conn.flush();
return true;
}
return false;
}
/**
* 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;
if ((env_path = read_env("BSPWM_SOCKET")).empty() == false)
return env_path;
struct sockaddr_un sa;
char* host = nullptr;
int dsp = 0;
int scr = 0;
if (xcb_parse_display(nullptr, &host, &dsp, &scr) == 0)
return BSPWM_SOCKET_PATH;
snprintf(sa.sun_path, sizeof(sa.sun_path), "/tmp/bspwm%s_%i_%i-socket", host, dsp, scr);
return sa.sun_path;
}
/**
* Generate a payload object with properly formatted data
* ready to be sent to the bspwm ipc controller
*/
payload_t make_payload(string cmd) {
payload_t payload{new payload_t::element_type{}};
auto size = sizeof(payload->data);
int offset = 0;
int chars = 0;
for (auto&& word : string_util::split(cmd, ' ')) {
chars = snprintf(payload->data + offset, size - offset, "%s%c", word.c_str(), 0);
payload->len += chars;
offset += chars;
}
return payload;
}
/**
* Create an ipc socket connection
*
* Example usage:
* @code cpp
* auto ipc = bspwm_util::make_connection();
* ipc->send(bspwm_util::make_payload("desktop -f eDP-1:^1"));
* @endcode
*/
connection_t make_connection() {
return socket_util::make_unix_connection(get_socket_path());
}
/**
* 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
*/
connection_t make_subscriber() {
auto conn = make_connection();
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

64
include/utils/i3.hpp Normal file
View File

@ -0,0 +1,64 @@
#pragma once
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "common.hpp"
#include "components/x11/connection.hpp"
#include "components/x11/randr.hpp"
#include "config.hpp"
#include "utils/socket.hpp"
#include "utils/string.hpp"
#include <i3ipc++/ipc.hpp>
LEMONBUDDY_NS
namespace i3_util {
using connection_t = i3ipc::connection;
/**
* Get all i3 root windows
*/
auto root_windows(connection& conn, string output_name = "") {
vector<xcb_window_t> roots;
auto children = conn.query_tree(conn.screen()->root).children();
for (auto it = children.begin(); it != children.end(); it++) {
auto cookie = xcb_icccm_get_wm_name(conn, *it);
xcb_icccm_get_text_property_reply_t reply;
if (xcb_icccm_get_wm_name_reply(conn, cookie, &reply, nullptr) == 0)
continue;
if (("[i3 con] output " + output_name).compare(0, 16 + output_name.length(), reply.name) != 0)
continue;
roots.emplace_back(*it);
}
return roots;
}
/**
* Restack given window above the i3 root window
* defined for the given monitor
*
* Fixes the issue with always-on-top window's
*/
bool restack_above_root(connection& conn, const monitor_t& mon, const xcb_window_t win) {
for (auto&& root : root_windows(conn, mon->name)) {
const uint32_t value_mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
const uint32_t value_list[2]{root, XCB_STACK_MODE_ABOVE};
conn.configure_window_checked(win, value_mask, value_list);
conn.flush();
return true;
}
return false;
}
}
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';
}
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());
return {buffer};
}
~FilePtr()
{
if (this->fptr != nullptr)
fclose(this->fptr);
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);
}
operator bool() {
return this->fptr != nullptr;
inline string readline(int read_fd, int& bytes_read) {
std::stringstream buffer;
char char_;
bytes_read = 0;
while ((bytes_read += ::read(read_fd, &char_, 1)) > 0) {
buffer << char_;
if (char_ == '\n' || char_ == '\x00')
break;
}
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);
if (bytes_read == 0) {
// Reached EOF
} else if (bytes_read == -1) {
// Read failed
} else {
return string_util::strip_trailing_newline(buffer.str());
}
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);
return "";
}
int write(int write_fd, std::string data);
int writeline(int write_fd, std::string data);
inline string readline(int read_fd) {
int bytes_read;
return readline(read_fd, bytes_read);
}
void tail(int read_fd, std::function<void(std::string)> callback);
void tail(int read_fd, int writeback_fd);
inline size_t write(int write_fd, string data) {
return ::write(write_fd, data.c_str(), strlen(data.c_str()));
}
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);
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);
}
// int get_flags(int fd);
// int set_blocking(int fd);
// int set_non_blocking(int fd);
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[1];
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

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