Merge branch '2.0-beta'
This commit is contained in:
commit
ed77e2eec6
13
.clang-format
Normal file
13
.clang-format
Normal 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
27
.exrc
@ -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
3
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
build
|
||||
build*/
|
||||
tags
|
||||
*.bak
|
||||
*.pyc
|
||||
*.tmp
|
||||
include/config.hpp
|
||||
.tags
|
||||
|
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -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
|
||||
|
96
.travis.yml
96
.travis.yml
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
191
CMakeLists.txt
191
CMakeLists.txt
@ -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
250
README.md
@ -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
6
TODO
Normal 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
|
22
cmake/clang-cpp-tools.cmake
Normal file
22
cmake/clang-cpp-tools.cmake
Normal 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()
|
54
cmake/modules/FindCppUnit.cmake
Normal file
54
cmake/modules/FindCppUnit.cmake
Normal 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
152
cmake/utils.cmake
Normal 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()
|
||||
|
||||
# }}}
|
@ -1 +0,0 @@
|
||||
Subproject commit 8ed285ec2289761e6585090724f73093d62f290f
|
13
examples/CMakeLists.txt
Normal file
13
examples/CMakeLists.txt
Normal 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()
|
303
examples/config
303
examples/config
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
246
include/adapters/alsa.hpp
Normal 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
491
include/adapters/mpd.hpp
Normal 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
433
include/adapters/net.hpp
Normal 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
|
103
include/bar.hpp
103
include/bar.hpp
@ -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
132
include/common.hpp
Normal 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
1000
include/components/bar.hpp
Normal file
File diff suppressed because it is too large
Load Diff
463
include/components/builder.hpp
Normal file
463
include/components/builder.hpp
Normal 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;
|
231
include/components/command_line.hpp
Normal file
231
include/components/command_line.hpp
Normal 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
|
231
include/components/config.hpp
Normal file
231
include/components/config.hpp
Normal 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
|
550
include/components/controller.hpp
Normal file
550
include/components/controller.hpp
Normal 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
|
197
include/components/logger.hpp
Normal file
197
include/components/logger.hpp
Normal 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
|
256
include/components/parser.hpp
Normal file
256
include/components/parser.hpp
Normal 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
|
104
include/components/types.hpp
Normal file
104
include/components/types.hpp
Normal 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
|
60
include/components/x11/atoms.hpp
Normal file
60
include/components/x11/atoms.hpp
Normal 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
|
91
include/components/x11/color.hpp
Normal file
91
include/components/x11/color.hpp
Normal 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
|
197
include/components/x11/connection.hpp
Normal file
197
include/components/x11/connection.hpp
Normal 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
|
61
include/components/x11/draw.hpp
Normal file
61
include/components/x11/draw.hpp
Normal 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
|
250
include/components/x11/fontmanager.hpp
Normal file
250
include/components/x11/fontmanager.hpp
Normal 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
|
66
include/components/x11/randr.hpp
Normal file
66
include/components/x11/randr.hpp
Normal 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
|
681
include/components/x11/tray.hpp
Normal file
681
include/components/x11/tray.hpp
Normal 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
|
95
include/components/x11/types.hpp
Normal file
95
include/components/x11/types.hpp
Normal 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
|
167
include/components/x11/window.hpp
Normal file
167
include/components/x11/window.hpp
Normal 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
|
124
include/components/x11/xembed.hpp
Normal file
124
include/components/x11/xembed.hpp
Normal 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
|
67
include/components/x11/xlib.hpp
Normal file
67
include/components/x11/xlib.hpp
Normal 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
|
37
include/components/x11/xutils.hpp
Normal file
37
include/components/x11/xutils.hpp
Normal 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
|
@ -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
21
include/debug.hpp
Normal 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
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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 = "");
|
||||
}
|
38
include/drawtypes/iconset.hpp
Normal file
38
include/drawtypes/iconset.hpp
Normal 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
|
@ -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
|
||||
|
136
include/drawtypes/progressbar.hpp
Normal file
136
include/drawtypes/progressbar.hpp
Normal 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
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
@ -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)
|
@ -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);
|
||||
};
|
||||
|
||||
// }}}
|
||||
}
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
@ -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
1
include/lemonbuddy
Symbolic link
@ -0,0 +1 @@
|
||||
../include
|
@ -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);
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
513
include/modules/meta.hpp
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
36
include/modules/unsupported.hpp
Normal file
36
include/modules/unsupported.hpp
Normal 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
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
@ -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);
|
||||
};
|
@ -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();
|
||||
};
|
@ -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();
|
||||
};
|
@ -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();
|
||||
};
|
@ -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();
|
@ -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
162
include/utils/bspwm.hpp
Normal 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
|
@ -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
213
include/utils/command.hpp
Normal 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
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
@ -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
108
include/utils/file.hpp
Normal 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
64
include/utils/i3.hpp
Normal 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
126
include/utils/inotify.hpp
Normal 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
|
@ -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
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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
35
include/utils/mixins.hpp
Normal 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
Loading…
Reference in New Issue
Block a user