From e7cb345cecbe0b9380e0fb6ad35a65ed25dbf0d8 Mon Sep 17 00:00:00 2001 From: Michael Carlberg Date: Wed, 2 Nov 2016 20:22:45 +0100 Subject: [PATCH] wip: Separate source from definitions --- .exrc | 21 - CMakeLists.txt | 194 +-- cmake/templates/uninstall.cmake.in | 26 + examples/config.bspwm | 4 +- include/adapters/alsa.hpp | 211 +--- include/adapters/mpd.hpp | 472 ++------ include/adapters/net.hpp | 353 +----- include/components/bar.hpp | 1069 +---------------- include/components/builder.hpp | 426 +------ include/components/command_line.hpp | 178 +-- include/components/config.hpp | 75 +- include/components/controller.hpp | 535 +-------- include/components/eventloop.hpp | 243 +--- include/components/logger.hpp | 72 +- include/components/parser.hpp | 242 +--- include/components/signals.hpp | 39 +- include/components/types.hpp | 10 +- include/components/x11/atoms.hpp | 68 -- include/components/x11/color.hpp | 114 -- include/components/x11/connection.hpp | 200 --- include/components/x11/fontmanager.hpp | 264 ---- include/components/x11/tray.hpp | 886 -------------- include/components/x11/xlib.hpp | 60 - include/components/x11/xresources.hpp | 68 -- include/config.hpp.cmake | 9 +- include/drawtypes/animation.hpp | 61 +- include/drawtypes/iconset.hpp | 22 +- include/drawtypes/label.hpp | 123 +- include/drawtypes/progressbar.hpp | 127 +- include/drawtypes/ramp.hpp | 47 +- include/modules/backlight.hpp | 66 +- include/modules/battery.hpp | 209 +--- include/modules/bspwm.hpp | 299 +---- include/modules/counter.hpp | 20 +- include/modules/cpu.hpp | 123 +- include/modules/date.hpp | 60 +- include/modules/i3.hpp | 203 +--- include/modules/memory.hpp | 90 +- include/modules/menu.hpp | 110 +- include/modules/meta.hpp | 2 +- include/modules/mpd.hpp | 328 +---- include/modules/network.hpp | 160 +-- include/modules/script.hpp | 129 +- include/modules/text.hpp | 39 +- include/modules/volume.hpp | 220 +--- include/modules/xbacklight.hpp | 116 +- include/utils/bspwm.hpp | 144 +-- include/utils/color.hpp | 139 +-- include/utils/command.hpp | 196 +-- include/utils/file.hpp | 87 +- include/utils/i3.hpp | 56 +- include/utils/inotify.hpp | 126 +- include/utils/io.hpp | 118 +- include/utils/process.hpp | 113 +- include/utils/socket.hpp | 89 +- include/utils/string.hpp | 188 +-- include/utils/throttle.hpp | 27 +- include/version.hpp | 2 +- include/x11/atoms.hpp | 38 + include/x11/color.hpp | 33 + include/x11/connection.hpp | 100 ++ include/x11/draw.hpp | 16 + include/x11/fontmanager.hpp | 92 ++ include/x11/randr.hpp | 41 + include/x11/tray.hpp | 151 +++ include/{components => }/x11/types.hpp | 36 +- include/{components => }/x11/window.hpp | 22 +- include/x11/xembed.hpp | 46 + include/x11/xlib.hpp | 40 + include/x11/xresources.hpp | 36 + include/x11/xutils.hpp | 19 + src/CMakeLists.txt | 12 +- src/adapters/alsa.cpp | 213 ++++ src/adapters/mpd.cpp | 414 +++++++ src/adapters/net.cpp | 349 ++++++ src/components/bar.cpp | 1047 ++++++++++++++++ src/components/builder.cpp | 415 +++++++ src/components/command_line.cpp | 161 +++ src/components/config.cpp | 71 ++ src/components/controller.cpp | 524 ++++++++ src/components/eventloop.cpp | 240 ++++ src/components/logger.cpp | 58 + src/components/parser.cpp | 251 ++++ src/components/signals.cpp | 32 + src/components/types.cpp | 1 + src/drawtypes/animation.cpp | 62 + src/drawtypes/iconset.cpp | 26 + src/drawtypes/label.cpp | 108 ++ src/drawtypes/progressbar.cpp | 128 ++ src/drawtypes/ramp.cpp | 51 + src/lemonbuddy.cpp | 1 - src/main.cpp | 7 +- src/modules/backlight.cpp | 70 ++ src/modules/battery.cpp | 207 ++++ src/modules/bspwm.cpp | 302 +++++ src/modules/counter.cpp | 25 + src/modules/cpu.cpp | 128 ++ src/modules/date.cpp | 63 + src/modules/i3.cpp | 205 ++++ src/modules/memory.cpp | 95 ++ src/modules/menu.cpp | 115 ++ src/modules/mpd.cpp | 328 +++++ src/modules/network.cpp | 163 +++ src/modules/script.cpp | 130 ++ src/modules/text.cpp | 44 + src/modules/volume.cpp | 220 ++++ src/modules/xbacklight.cpp | 115 ++ src/utils/bspwm.cpp | 141 +++ src/utils/command.cpp | 185 +++ src/utils/file.cpp | 87 ++ src/utils/i3.cpp | 58 + src/utils/inotify.cpp | 95 ++ src/utils/io.cpp | 118 ++ src/utils/process.cpp | 114 ++ src/utils/socket.cpp | 97 ++ src/utils/string.cpp | 180 +++ src/utils/throttle.cpp | 33 + src/x11/atoms.cpp | 62 + src/x11/color.cpp | 76 ++ src/x11/connection.cpp | 146 +++ .../x11/draw.hpp => src/x11/draw.cpp | 15 +- src/x11/fontmanager.cpp | 193 +++ .../x11/randr.hpp => src/x11/randr.cpp | 38 +- src/x11/tray.cpp | 893 ++++++++++++++ src/x11/window.cpp | 23 + .../x11/xembed.hpp => src/x11/xembed.cpp | 48 +- src/x11/xlib.cpp | 31 + src/x11/xresources.cpp | 46 + .../x11/xutils.hpp => src/x11/xutils.cpp | 21 +- tests/CMakeLists.txt | 6 +- tests/unit_tests/utils/color.cpp | 32 +- .../unit_tests/{components => }/x11/color.cpp | 2 +- .../{components => }/x11/connection.cpp | 2 +- .../{components => }/x11/window.cpp | 2 +- 134 files changed, 10292 insertions(+), 9181 deletions(-) create mode 100644 cmake/templates/uninstall.cmake.in delete mode 100644 include/components/x11/atoms.hpp delete mode 100644 include/components/x11/color.hpp delete mode 100644 include/components/x11/connection.hpp delete mode 100644 include/components/x11/fontmanager.hpp delete mode 100644 include/components/x11/tray.hpp delete mode 100644 include/components/x11/xlib.hpp delete mode 100644 include/components/x11/xresources.hpp create mode 100644 include/x11/atoms.hpp create mode 100644 include/x11/color.hpp create mode 100644 include/x11/connection.hpp create mode 100644 include/x11/draw.hpp create mode 100644 include/x11/fontmanager.hpp create mode 100644 include/x11/randr.hpp create mode 100644 include/x11/tray.hpp rename include/{components => }/x11/types.hpp (86%) rename include/{components => }/x11/window.hpp (83%) create mode 100644 include/x11/xembed.hpp create mode 100644 include/x11/xlib.hpp create mode 100644 include/x11/xresources.hpp create mode 100644 include/x11/xutils.hpp create mode 100644 src/adapters/alsa.cpp create mode 100644 src/adapters/mpd.cpp create mode 100644 src/adapters/net.cpp create mode 100644 src/components/bar.cpp create mode 100644 src/components/builder.cpp create mode 100644 src/components/command_line.cpp create mode 100644 src/components/config.cpp create mode 100644 src/components/controller.cpp create mode 100644 src/components/eventloop.cpp create mode 100644 src/components/logger.cpp create mode 100644 src/components/parser.cpp create mode 100644 src/components/signals.cpp create mode 100644 src/components/types.cpp create mode 100644 src/drawtypes/animation.cpp create mode 100644 src/drawtypes/iconset.cpp create mode 100644 src/drawtypes/label.cpp create mode 100644 src/drawtypes/progressbar.cpp create mode 100644 src/drawtypes/ramp.cpp delete mode 100644 src/lemonbuddy.cpp create mode 100644 src/modules/backlight.cpp create mode 100644 src/modules/battery.cpp create mode 100644 src/modules/bspwm.cpp create mode 100644 src/modules/counter.cpp create mode 100644 src/modules/cpu.cpp create mode 100644 src/modules/date.cpp create mode 100644 src/modules/i3.cpp create mode 100644 src/modules/memory.cpp create mode 100644 src/modules/menu.cpp create mode 100644 src/modules/mpd.cpp create mode 100644 src/modules/network.cpp create mode 100644 src/modules/script.cpp create mode 100644 src/modules/text.cpp create mode 100644 src/modules/volume.cpp create mode 100644 src/modules/xbacklight.cpp create mode 100644 src/utils/bspwm.cpp create mode 100644 src/utils/command.cpp create mode 100644 src/utils/file.cpp create mode 100644 src/utils/i3.cpp create mode 100644 src/utils/inotify.cpp create mode 100644 src/utils/io.cpp create mode 100644 src/utils/process.cpp create mode 100644 src/utils/socket.cpp create mode 100644 src/utils/string.cpp create mode 100644 src/utils/throttle.cpp create mode 100644 src/x11/atoms.cpp create mode 100644 src/x11/color.cpp create mode 100644 src/x11/connection.cpp rename include/components/x11/draw.hpp => src/x11/draw.cpp (79%) create mode 100644 src/x11/fontmanager.cpp rename include/components/x11/randr.hpp => src/x11/randr.cpp (74%) create mode 100644 src/x11/tray.cpp create mode 100644 src/x11/window.cpp rename include/components/x11/xembed.hpp => src/x11/xembed.cpp (63%) create mode 100644 src/x11/xlib.cpp create mode 100644 src/x11/xresources.cpp rename include/components/x11/xutils.hpp => src/x11/xutils.cpp (52%) rename tests/unit_tests/{components => }/x11/color.cpp (97%) rename tests/unit_tests/{components => }/x11/connection.cpp (84%) rename tests/unit_tests/{components => }/x11/window.cpp (93%) diff --git a/.exrc b/.exrc index 203a52a6..a6d9e6f4 100644 --- a/.exrc +++ b/.exrc @@ -1,24 +1,3 @@ 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='/home/jaagr/.local/src/c++/lemonbuddy/.tags' set tags+=/home/jaagr/.local/src/c++/lemonbuddy/.tags diff --git a/CMakeLists.txt b/CMakeLists.txt index fdd91e5b..ea71de81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,113 +1,20 @@ # # Build configuration # -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + project(lemonbuddy C CXX) -option(CXXLIB_CLANG "Link against libc++" OFF) -option(CXXLIB_GCC "Link against stdlibc++" OFF) +set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + ${PROJECT_SOURCE_DIR}/cmake + ${PROJECT_SOURCE_DIR}/cmake/modules) -# Include local cmake modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake/modules) - -# Export compile commands used for custom targets -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# Load cmake utility functions -include(cmake/utils.cmake) - -# Set default build type if not specified -if(NOT CMAKE_BUILD_TYPE) - message_colored(STATUS "No build type specified; using Release" 33) - set(CMAKE_BUILD_TYPE "Release") -endif() - -# Generic compiler flags -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") - -# Debug specific compiler flags -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g2") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pedantic") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pedantic-errors") - -# Release specific compiler flags -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") - -# Compiler specific flags -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL Clang) - message_colored(STATUS "Using supported compiler ${CMAKE_CXX_COMPILER_ID}" 32) -elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL GNU) - message_colored(STATUS "Using supported compiler ${CMAKE_CXX_COMPILER_ID}" 32) -else() - message_colored(WARNING "Using unsupported compiler ${CMAKE_CXX_COMPILER_ID} !" 31) -endif() - -# Set compiler and linker flags for preferred C++ library -if(CXXLIB_CLANG) - message_colored(STATUS "Linking against libc++" 32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lc++ -lc++abi") -elseif(CXXLIB_GCC) - message_colored(STATUS "Linking against libstdc++" 32) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lstdc++") -else() - message_colored(STATUS "No preferred c++lib specified... linking against system default" 33) -endif() - -# Project settings {{{ - -# Set default option values based on current environment -find_package(ALSA QUIET) -find_package(Libiw QUIET) -find_package(LibMPDClient QUIET) -find_program(I3_BINARY i3) -if(I3_BINARY) - set(I3_FOUND ON) -endif() - -option(BUILD_TESTS "Build testsuite" OFF) -option(ENABLE_CCACHE "Enable ccache support" OFF) -option(ENABLE_ALSA "Enable alsa support" ${ALSA_FOUND}) -option(ENABLE_I3 "Enable i3 support" ${I3_FOUND}) -option(ENABLE_MPD "Enable mpd support" ${LIBMPDCLIENT_FOUND}) -option(ENABLE_NETWORK "Enable network support" ${LIBIW_FOUND}) - -if(ENABLE_ALSA) - set(SETTING_ALSA_SOUNDCARD "default" - CACHE STRING "Name of the ALSA soundcard driver") -endif() -set(SETTING_CONNECTION_TEST_IP "8.8.8.8" - CACHE STRING "Address to ping when testing network connection") -set(SETTING_PATH_BACKLIGHT_VAL "/sys/class/backlight/%card%/brightness" - CACHE STRING "Path to file containing the current backlight value") -set(SETTING_PATH_BACKLIGHT_MAX "/sys/class/backlight/%card%/max_brightness" - CACHE STRING "Path to file containing the maximum backlight value") -set(SETTING_PATH_BATTERY_CAPACITY "/sys/class/power_supply/%battery%/capacity" - CACHE STRING "Path to file containing the current battery capacity") -set(SETTING_PATH_ADAPTER_STATUS "/sys/class/power_supply/%adapter%/online" - CACHE STRING "Path to file containing the current adapter status") -set(SETTING_BSPWM_SOCKET_PATH "/tmp/bspwm_0_0-socket" - CACHE STRING "Path to bspwm socket") -set(SETTING_BSPWM_STATUS_PREFIX "W" - CACHE STRING "Prefix prepended to the bspwm status line") -set(SETTING_PATH_CPU_INFO "/proc/stat" - CACHE STRING "Path to file containing cpu info") -set(SETTING_PATH_MEMORY_INFO "/proc/meminfo" - CACHE STRING "Path to file containing memory info") - -if(ENABLE_CCACHE) - require_binary(ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${BINPATH_ccache}) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${BINPATH_ccache}) -endif() - -# }}} -# Add subdirectories {{{ +include(utils) +include(build/core) +include(build/options) +include(build/targets) +include(build/summary) add_subdirectory(${PROJECT_SOURCE_DIR}/man) add_subdirectory(${PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/bin) @@ -118,82 +25,3 @@ if(BUILD_TESTS) else() add_subdirectory(${PROJECT_SOURCE_DIR}/tests ${PROJECT_BINARY_DIR}/tests EXCLUDE_FROM_ALL) endif() - -# }}} -# Build summary {{{ - -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() -if(CXXLIB_CLANG) - message(STATUS " Linking C++ library: libc++") -elseif(CXXLIB_GCC) - message(STATUS " Linking C++ library: libstdc++") -else() - message(STATUS " Linking C++ library: system default") -endif() -message(STATUS "---------------------------") -message(STATUS " Build testsuite ${BUILD_TESTS}") -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 "---------------------------") - -# }}} - -# Custom target: uninstall {{{ - -configure_file( - ${PROJECT_SOURCE_DIR}/cmake/uninstall.cmake.in - ${PROJECT_BINARY_DIR}/cmake/uninstall.cmake - IMMEDIATE @ONLY) - -add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} - -P ${PROJECT_BINARY_DIR}/cmake/uninstall.cmake) - -# }}} -# Custom target: clang-format {{{ - -find_program(CLANG_FORMAT "clang-format") - -if(CLANG_FORMAT) - file(GLOB_RECURSE HEADERS ${PROJECT_SOURCE_DIR}/include/*.hpp) - file(GLOB_RECURSE SOURCES ${PROJECT_SOURCE_DIR}/src/*.cpp) - - add_custom_target(clang-format COMMAND ${CLANG_FORMAT} - -i -style=file ${HEADERS} ${SOURCES}) -endif() - -find_program(CLANG_TIDY "clang-tidy") - -# }}} -# Custom target: clang-tidy {{{ - -if(CLANG_TIDY) - add_custom_target(clang-tidy COMMAND ${CLANG_TIDY} - -p ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/src/main.cpp) -endif() - -# }}} diff --git a/cmake/templates/uninstall.cmake.in b/cmake/templates/uninstall.cmake.in new file mode 100644 index 00000000..5e30cb44 --- /dev/null +++ b/cmake/templates/uninstall.cmake.in @@ -0,0 +1,26 @@ +set(INSTALL_MANIFEST "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +if (NOT EXISTS ${INSTALL_MANIFEST}) + message(FATAL_ERROR + "Cannot find install manifest: + \"${INSTALL_MANIFEST}\"") +endif(NOT EXISTS ${INSTALL_MANIFEST}) + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +list(REVERSE files) + +foreach (file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if (EXISTS "$ENV{DESTDIR}${file}") + execute_process( + COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval) + if(NOT ${rm_retval} EQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif (NOT ${rm_retval} EQUAL 0) + else (EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif (EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/examples/config.bspwm b/examples/config.bspwm index 0ef0601d..8d6cb0a0 100644 --- a/examples/config.bspwm +++ b/examples/config.bspwm @@ -33,8 +33,8 @@ font-0 = tamzen:size=9;1 font-1 = siji:pixelsize=10;0 font-2 = unifont:size=6;-1 -modules-left = bspwm -modules-center = mpd +modules-left = mpd +modules-center = bspwm modules-right = volume memory cpu date tray-position = right diff --git a/include/adapters/alsa.hpp b/include/adapters/alsa.hpp index 095d40fc..7feb2709 100644 --- a/include/adapters/alsa.hpp +++ b/include/adapters/alsa.hpp @@ -28,86 +28,13 @@ void throw_exception(string&& message, int error_code) { class alsa_ctl_interface { public: - explicit alsa_ctl_interface(int numid) : m_numid(numid) { - int err = 0; + explicit alsa_ctl_interface(int numid); + ~alsa_ctl_interface(); - 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, m_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( - "Could not open control '" + string{ALSA_SOUNDCARD} + "'", err); - - if ((err = snd_ctl_elem_info(m_ctl, m_info)) < 0) - throw_exception("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("Failed to open hctl", err); - if ((err = snd_hctl_load(m_hctl)) < 0) - throw_exception("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))); - } - - ~alsa_ctl_interface() { - std::lock_guard guard(m_lock); - snd_ctl_close(m_ctl); - snd_hctl_close(m_hctl); - } - - int get_numid() { - return m_numid; - } - - bool wait(int timeout = -1) { - assert(m_ctl); - - std::lock_guard guard(m_lock); - - int err = 0; - - if ((err = snd_ctl_wait(m_ctl, timeout)) < 0) - throw_exception("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 guard(m_lock); - - int err = 0; - if ((err = snd_hctl_elem_read(m_elem, m_value)) < 0) - throw_exception("Could not read control value", err); - return snd_ctl_elem_value_get_boolean(m_value, 0); - } - - void process_events() { - wait(0); - } + int get_numid(); + bool wait(int timeout = -1); + bool test_device_plugged(); + void process_events(); private: int m_numid = 0; @@ -128,125 +55,19 @@ class alsa_ctl_interface { class alsa_mixer { public: - explicit alsa_mixer(string mixer_control_name) : m_name(mixer_control_name) { - snd_mixer_selem_id_t* mixer_id; + explicit alsa_mixer(string mixer_control_name); + ~alsa_mixer(); - snd_mixer_selem_id_alloca(&mixer_id); + string get_name(); - int err = 0; + bool wait(int timeout = -1); + int process_events(); - if ((err = snd_mixer_open(&m_hardwaremixer, 1)) < 0) - throw_exception("Failed to open hardware mixer", err); - if ((err = snd_mixer_attach(m_hardwaremixer, ALSA_SOUNDCARD)) < 0) - throw_exception("Failed to attach hardware mixer control", err); - if ((err = snd_mixer_selem_register(m_hardwaremixer, nullptr, nullptr)) < 0) - throw_exception("Failed to register simple mixer element", err); - if ((err = snd_mixer_load(m_hardwaremixer)) < 0) - throw_exception("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 guard(m_lock); - snd_mixer_elem_remove(m_mixerelement); - snd_mixer_detach(m_hardwaremixer, ALSA_SOUNDCARD); - snd_mixer_close(m_hardwaremixer); - } - - string get_name() { - return m_name; - } - - bool wait(int timeout = -1) { - assert(m_hardwaremixer); - - std::unique_lock guard(m_lock); - - int err = 0; - - if ((err = snd_mixer_wait(m_hardwaremixer, timeout)) < 0) - throw_exception("Failed to wait for events", err); - - guard.unlock(); - - return process_events() > 0; - } - - int process_events() { - std::lock_guard guard(m_lock); - - int num_events = snd_mixer_handle_events(m_hardwaremixer); - - if (num_events < 0) - throw_exception("Failed to process pending events", num_events); - - return num_events; - } - - int get_volume() { - std::lock_guard 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, static_cast(i))) { - snd_mixer_selem_get_playback_volume( - m_mixerelement, static_cast(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 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 guard(m_lock); - snd_mixer_selem_set_playback_switch_all(m_mixerelement, mode); - } - - void toggle_mute() { - std::lock_guard guard(m_lock); - int state; - snd_mixer_selem_get_playback_switch(m_mixerelement, SND_MIXER_SCHN_MONO, &state); - snd_mixer_selem_set_playback_switch_all(m_mixerelement, !state); - } - - bool is_muted() { - std::lock_guard 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, static_cast(i))) { - int state_ = 0; - snd_mixer_selem_get_playback_switch( - m_mixerelement, static_cast(i), &state_); - state = state || state_; - } - } - return !state; - } + int get_volume(); + void set_volume(float percentage); + void set_mute(bool mode); + void toggle_mute(); + bool is_muted(); private: string m_name; diff --git a/include/adapters/mpd.hpp b/include/adapters/mpd.hpp index 315cbfad..e3dcb79b 100644 --- a/include/adapters/mpd.hpp +++ b/include/adapters/mpd.hpp @@ -8,7 +8,6 @@ #include "common.hpp" #include "components/logger.hpp" -#include "utils/math.hpp" LEMONBUDDY_NS @@ -17,58 +16,12 @@ namespace mpd { DEFINE_CHILD_ERROR(client_error, mpd_exception); DEFINE_CHILD_ERROR(server_error, mpd_exception); - enum class connection_state { - NONE = 0, CONNECTED, DISCONNECTED - }; + void check_connection(mpd_connection* conn); + void check_errors(mpd_connection* conn); - // type details {{{ + // types 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; - using mpd_status_t = unique_ptr; - using mpd_song_t = unique_ptr; - } - - 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 connection_state { NONE = 0, CONNECTED, DISCONNECTED }; enum class mpdstate { UNKNOWN = 1 << 0, @@ -77,52 +30,44 @@ namespace mpd { PAUSED = 1 << 4, }; + namespace details { + struct mpd_connection_deleter { + void operator()(mpd_connection* conn); + }; + + struct mpd_status_deleter { + void operator()(mpd_status* status); + }; + + struct mpd_song_deleter { + void operator()(mpd_song* song); + }; + } + + using mpd_connection_t = unique_ptr; + using mpd_status_t = unique_ptr; + using mpd_song_t = unique_ptr; + // }}} - // class: mpdsong {{{ + // class : mpdsong {{{ class mpdsong { public: - explicit mpdsong(details::mpd_song_t&& song) : m_song(forward(song)) {} + explicit mpdsong(mpd_song_t&& song) : m_song(forward(song)) {} - operator bool() { - return m_song.get() != nullptr; - } + operator bool(); - 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()); - } + string get_artist(); + string get_album(); + string get_title(); + unsigned get_duration(); private: - details::mpd_song_t m_song; + mpd_song_t m_song; }; // }}} - // class: mpdconnection {{{ + // class : mpdconnection {{{ class mpdstatus; class mpdconnection { @@ -131,230 +76,40 @@ namespace mpd { 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()); + void connect(); + void disconnect(); + bool connected(); + bool retry_connection(int interval = 1); - if (!m_password.empty()) { - noidle(); - assert(!m_listactive); - mpd_run_password(m_connection.get(), m_password.c_str()); - check_errors(m_connection.get()); - } + int get_fd(); + void idle(); + int noidle(); - m_fd = mpd_connection_get_fd(m_connection.get()); - check_errors(m_connection.get()); - } catch (const client_error& e) { - disconnect(); - throw e; - } - } + unique_ptr get_status(); + unique_ptr get_status_safe(); + unique_ptr get_song(); - void disconnect() { - m_connection.reset(); - m_idle = false; - m_listactive = false; - } + void play(); + void pause(bool state); + void toggle(); + void stop(); + void prev(); + void next(); + void seek(int songid, int pos); - bool connected() { - if (!m_connection) - return false; - return m_connection.get() != nullptr; - } + void set_repeat(bool mode); + void set_random(bool mode); + void set_single(bool mode); - 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(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 get_status() { - check_prerequisites(); - auto status = make_unique(this); - check_errors(m_connection.get()); - // if (update) - // status->update(-1, this); - return status; - } - - unique_ptr get_status_safe() { - try { - return get_status(); - } catch (const mpd_exception& e) { - return {}; - } - } - - unique_ptr 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(std::move(song)); - } - return unique_ptr{}; - } - - 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(); - } + operator mpd_connection_t::element_type*(); protected: - void check_prerequisites() { - check_connection(m_connection.get()); - noidle(); - } - - void check_prerequisites_commands_list() { - noidle(); - assert(!m_listactive); - check_prerequisites(); - } + void check_prerequisites(); + void check_prerequisites_commands_list(); private: const logger& m_log; - details::mpd_connection_t m_connection; + mpd_connection_t m_connection; bool m_listactive = false; bool m_idle = false; @@ -367,115 +122,32 @@ namespace mpd { }; // }}} - // class: mpdstatus {{{ + // class : mpdstatus {{{ class mpdstatus { public: - explicit mpdstatus(mpdconnection* conn, bool autoupdate = true) { - fetch_data(conn); - if (autoupdate) - update(-1, conn); - } + explicit mpdstatus(mpdconnection* conn, bool autoupdate = true); - 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 fetch_data(mpdconnection* conn); + void update(int event, mpdconnection* connection); + void update_timer(); - void update(int event, mpdconnection* connection) { - if (connection == nullptr || (event & (MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS)) == false) - return; + bool random() const; + bool repeat() const; + bool single() const; - fetch_data(connection); + bool match_state(mpdstate state) const; - 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(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(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(0, 100, percentage); - return float(m_total_time) * percentage / 100.0f + 0.5f; - } + int get_songid() const; + unsigned get_total_time() const; + unsigned get_elapsed_time() const; + unsigned get_elapsed_percentage(); + string get_formatted_elapsed(); + string get_formatted_total(); + int get_seek_position(int percentage); private: - details::mpd_status_t m_status; + mpd_status_t m_status; unique_ptr m_song; mpdstate m_state = mpdstate::UNKNOWN; chrono::system_clock::time_point m_updated_at; diff --git a/include/adapters/net.hpp b/include/adapters/net.hpp index a7738ec9..31273ea7 100644 --- a/include/adapters/net.hpp +++ b/include/adapters/net.hpp @@ -1,29 +1,8 @@ #pragma once -#include -#include - #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #ifdef inline #undef inline @@ -31,15 +10,14 @@ #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); + bool is_wireless_interface(string ifname); + // types {{{ struct quality_range { @@ -68,137 +46,24 @@ namespace net { }; // }}} - // class: network {{{ + // class : network {{{ class network { public: - /** - * Construct network interface - */ - 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_socketfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - throw network_error("Failed to open socket"); - } + explicit network(string interface); + virtual ~network(); - /** - * Destruct network interface - */ - virtual ~network() { - if (m_socketfd != -1) - close(m_socketfd); - } - - /** - * Query device driver for information - */ - virtual bool query() { - struct ifaddrs* ifaddr; - - if (getifaddrs(&ifaddr) == -1) - return false; - - for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { - if (m_interface.compare(0, m_interface.length(), ifa->ifa_name) != 0) - continue; - - 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_status.ip = string{ip_buffer}; - break; - - case AF_PACKET: - if (ifa->ifa_data == nullptr) - continue; - struct rtnl_link_stats* link_state = - reinterpret_cast(ifa->ifa_data); - m_status.previous = m_status.current; - m_status.current.transmitted = link_state->tx_bytes; - m_status.current.received = link_state->rx_bytes; - m_status.current.time = chrono::system_clock::now(); - break; - } - } - - freeifaddrs(ifaddr); - - return true; - } - - /** - * Check current connection state - */ + virtual bool query(); virtual bool connected() const = 0; + virtual bool ping() const; - /** - * Run ping command to test internet connectivity - */ - virtual bool ping() const { - try { - auto exec = "ping -c 2 -W 2 -I " + m_interface + " " + string(CONNECTION_TEST_IP); - auto ping = command_util::make_command(exec); - return ping && ping->exec(true) == EXIT_SUCCESS; - } catch (const std::exception& err) { - return false; - } - } - - /** - * Get interface ip address - */ - string ip() const { - return m_status.ip; - } - - /** - * Get download speed rate - */ - string downspeed(int minwidth = 3) const { - float bytes_diff = m_status.current.received - m_status.previous.received; - return format_speedrate(bytes_diff, minwidth); - } - - /** - * Get upload speed rate - */ - string upspeed(int minwidth = 3) const { - float bytes_diff = m_status.current.transmitted - m_status.previous.transmitted; - return format_speedrate(bytes_diff, minwidth); - } + string ip() const; + string downspeed(int minwidth = 3) const; + string upspeed(int minwidth = 3) const; protected: - /** - * Test if the network interface is in a valid state - */ - bool test_interface() const { - auto operstate = file_util::get_contents("/sys/class/net/" + m_interface + "/operstate"); - return operstate.compare(0, 2, "up") == 0; - } - - /** - * Format up- and download speed - */ - string format_speedrate(float bytes_diff, int minwidth) const { - const auto duration = m_status.current.time - m_status.previous.time; - float time_diff = chrono::duration_cast(duration).count(); - float speedrate = bytes_diff / (time_diff ? time_diff : 1); - - vector suffixes{"GB", "MB"}; - string suffix{"KB"}; - - while ((speedrate /= 1000) > 999) { - suffix = suffixes.back(); - suffixes.pop_back(); - } - - return string_util::from_stream(stringstream() << std::setw(minwidth) << std::setfill(' ') - << std::setprecision(0) << std::fixed - << speedrate << " " << suffix << "/s"); - } + bool test_interface() const; + string format_speedrate(float bytes_diff, int minwidth) const; int m_socketfd = 0; link_status m_status; @@ -206,200 +71,37 @@ namespace net { }; // }}} - // class: wired_network {{{ + // class : wired_network {{{ class wired_network : public network { public: explicit wired_network(string interface) : network(interface) {} - /** - * Query device driver for information - */ - bool query() override { - if (!network::query()) - return false; - - struct ethtool_cmd ethernet_data; - ethernet_data.cmd = ETHTOOL_GSET; - - struct ifreq request; - strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1); - request.ifr_data = reinterpret_cast(ðernet_data); - - if (ioctl(m_socketfd, SIOCETHTOOL, &request) == -1) - return false; - - m_linkspeed = ethernet_data.speed; - - return true; - } - - /** - * Check current connection state - */ - bool connected() const override { - if (!network::test_interface()) - return false; - - struct ifreq request; - struct ethtool_value ethernet_data; - - strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1); - ethernet_data.cmd = ETHTOOL_GLINK; - request.ifr_data = reinterpret_cast(ðernet_data); - - if (ioctl(m_socketfd, SIOCETHTOOL, &request) != -1) - return ethernet_data.data != 0; - - return false; - } - - /** - * - * about the current connection - */ - string linkspeed() const { - return (m_linkspeed == 0 ? "???" : to_string(m_linkspeed)) + " Mbit/s"; - } + bool query() override; + bool connected() const override; + string linkspeed() const; private: int m_linkspeed = 0; }; // }}} - // class: wireless_network {{{ + // class : wireless_network {{{ class wireless_network : public network { public: wireless_network(string interface) : network(interface) {} - /** - * Query the wireless device for information - * about the current connection - */ - bool query() override { - if (!network::query()) - return false; + bool query() override; + bool connected() const override; - auto socket_fd = iw_sockets_open(); - - if (socket_fd == -1) - return false; - - struct iwreq req; - - if (iw_get_ext(socket_fd, m_interface.c_str(), SIOCGIWMODE, &req) == -1) - return false; - - // Ignore interfaces in ad-hoc mode - if (req.u.mode == IW_MODE_ADHOC) - return false; - - query_essid(socket_fd); - query_quality(socket_fd); - - iw_sockets_close(socket_fd); - - return true; - } - - /** - * Check current connection state - */ - bool connected() const override { - if (!network::test_interface()) - return false; - - struct ifreq request; - strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1); - - if ((ioctl(m_socketfd, SIOCGIFFLAGS, &request)) == -1) - return false; - if (m_essid.empty()) - return false; - - return true; - } - - /** - * ESSID reported by last query - */ - string essid() const { - return m_essid; - } - - /** - * Signal strength percentage reported by last query - */ - int signal() const { - return m_signalstrength.percentage(); - } - - /** - * Link quality percentage reported by last query - */ - int quality() const { - return m_linkquality.percentage(); - } + string essid() const; + int signal() const; + int quality() const; protected: - /** - * Query for ESSID - */ - void query_essid(const int& socket_fd) { - char essid[IW_ESSID_MAX_SIZE + 1]; - - struct iwreq req; - req.u.essid.pointer = &essid; - req.u.essid.length = sizeof(essid); - req.u.essid.flags = 0; - - if (iw_get_ext(socket_fd, m_interface.c_str(), SIOCGIWESSID, &req) != -1) { - m_essid = string{essid}; - } else { - m_essid.clear(); - } - } - - /** - * Query for device driver quality values - */ - void query_quality(const int& socket_fd) { - iwrange range; - iwstats stats; - - // Fill range - if (iw_get_range_info(socket_fd, m_interface.c_str(), &range) == -1) - return; - // Fill stats - if (iw_get_stats(socket_fd, m_interface.c_str(), &stats, &range, 1) == -1) - return; - - // Check if the driver supplies the quality value - if (stats.qual.updated & IW_QUAL_QUAL_INVALID) - return; - // Check if the driver supplies the quality level value - if (stats.qual.updated & IW_QUAL_LEVEL_INVALID) - return; - - // Check if the link quality has been uodated - if (stats.qual.updated & IW_QUAL_QUAL_UPDATED) { - m_linkquality.val = stats.qual.qual; - m_linkquality.max = range.max_qual.qual; - } - - // Check if the signal strength has been uodated - if (stats.qual.updated & IW_QUAL_LEVEL_UPDATED) { - m_signalstrength.val = stats.qual.level; - m_signalstrength.max = range.max_qual.level; - - // Check if the values are defined in dBm - if (stats.qual.level > range.max_qual.level) { - m_signalstrength.val -= 0x100; - m_signalstrength.max -= 0x100; - } - } - } + void query_essid(const int& socket_fd); + void query_quality(const int& socket_fd); private: shared_ptr m_info; @@ -410,13 +112,6 @@ namespace net { // }}} - /** - * Test if interface with given name is a wireless device - */ - inline bool is_wireless_interface(string ifname) { - return file_util::exists("/sys/class/net/" + ifname + "/wireless"); - } - using wireless_t = unique_ptr; using wired_t = unique_ptr; } diff --git a/include/components/bar.hpp b/include/components/bar.hpp index 8d9a9686..21a9cb94 100644 --- a/include/components/bar.hpp +++ b/include/components/bar.hpp @@ -1,39 +1,24 @@ #pragma once -#include -#include - #include "common.hpp" #include "components/config.hpp" #include "components/logger.hpp" #include "components/parser.hpp" #include "components/signals.hpp" #include "components/types.hpp" -#include "components/x11/connection.hpp" -#include "components/x11/draw.hpp" -#include "components/x11/fontmanager.hpp" -#include "components/x11/randr.hpp" -#include "components/x11/tray.hpp" -#include "components/x11/types.hpp" -#include "components/x11/window.hpp" -#include "components/x11/xlib.hpp" -#include "components/x11/xutils.hpp" -#include "utils/bspwm.hpp" -#include "utils/math.hpp" -#include "utils/string.hpp" #include "utils/threading.hpp" #include "utils/throttle.hpp" -#if ENABLE_I3 -#include "utils/i3.hpp" -#endif +#include "x11/connection.hpp" +#include "x11/draw.hpp" +#include "x11/fontmanager.hpp" +#include "x11/tray.hpp" +#include "x11/types.hpp" +#include "x11/window.hpp" LEMONBUDDY_NS class bar : public xpp::event::sink { public: - /** - * Construct bar - */ explicit bar(connection& conn, const config& config, const logger& logger, unique_ptr fontmanager) : m_connection(conn) @@ -41,1028 +26,41 @@ class bar : public xpp::event::sink(fontmanager)) {} - /** - * Cleanup signal handlers and destroy the bar window - */ - ~bar() { - std::lock_guard lck(m_lock); + ~bar(); - // Disconnect signal handlers {{{ - g_signals::parser::alignment_change = nullptr; - g_signals::parser::attribute_set = nullptr; - g_signals::parser::attribute_unset = nullptr; - g_signals::parser::attribute_toggle = nullptr; - g_signals::parser::action_block_open = nullptr; - g_signals::parser::action_block_close = nullptr; - g_signals::parser::color_change = nullptr; - g_signals::parser::font_change = nullptr; - g_signals::parser::pixel_offset = nullptr; - g_signals::parser::ascii_text_write = nullptr; - g_signals::parser::unicode_text_write = nullptr; - g_signals::tray::report_slotcount = nullptr; - // }}} + void bootstrap(bool nodraw = false); - if (m_sinkattached) - m_connection.detach_sink(this, 1); - m_window.destroy(); - } + const bar_settings settings() const; + const tray_settings tray() const; - /** - * Create required components - * - * This is done outside the constructor due to boost::di noexcept - */ - void bootstrap(bool nodraw = false) { //{{{ - // limit the amount of allowed input events to 1 per 60ms - m_throttler = throttle_util::make_throttler(1, 60ms); + void parse(string data, bool force = false); + void flush(); - m_screen = m_connection.screen(); - m_visual = m_connection.visual_type(m_screen, 32).get(); - auto monitors = randr_util::get_monitors(m_connection, m_connection.screen()->root); - auto bs = m_conf.bar_section(); - - // Look for the defined monitor {{{ - - if (monitors.empty()) - throw application_error("No monitors found"); - - auto monitor_name = m_conf.get(bs, "monitor", ""); - if (monitor_name.empty()) - monitor_name = monitors[0]->name; - - for (auto&& monitor : monitors) { - if (monitor_name.compare(monitor->name) == 0) { - m_bar.monitor = std::move(monitor); - break; - } - } - - if (m_bar.monitor) - m_log.trace("bar: Found matching monitor %s (%ix%i+%i+%i)", m_bar.monitor->name, - m_bar.monitor->w, m_bar.monitor->h, m_bar.monitor->x, m_bar.monitor->y); - else - throw application_error("Could not find monitor: " + monitor_name); - - // }}} - // Set bar colors {{{ - - m_bar.background = - color::parse(m_conf.get(bs, "background", m_bar.background.hex_to_rgba())); - m_bar.foreground = - color::parse(m_conf.get(bs, "foreground", m_bar.foreground.hex_to_rgba())); - m_bar.linecolor = - color::parse(m_conf.get(bs, "linecolor", m_bar.linecolor.hex_to_rgba())); - - // }}} - // Set border values {{{ - - auto bsize = m_conf.get(bs, "border-size", 0); - auto bcolor = m_conf.get(bs, "border-color", ""); - - m_borders.emplace(border::TOP, border_settings{}); - m_borders[border::TOP].size = m_conf.get(bs, "border-top", bsize); - m_borders[border::TOP].color = color::parse(m_conf.get(bs, "border-top-color", bcolor)); - - m_borders.emplace(border::BOTTOM, border_settings{}); - m_borders[border::BOTTOM].size = m_conf.get(bs, "border-bottom", bsize); - m_borders[border::BOTTOM].color = - color::parse(m_conf.get(bs, "border-bottom-color", bcolor)); - - m_borders.emplace(border::LEFT, border_settings{}); - m_borders[border::LEFT].size = m_conf.get(bs, "border-left", bsize); - m_borders[border::LEFT].color = - color::parse(m_conf.get(bs, "border-left-color", bcolor)); - - m_borders.emplace(border::RIGHT, border_settings{}); - m_borders[border::RIGHT].size = m_conf.get(bs, "border-right", bsize); - m_borders[border::RIGHT].color = - color::parse(m_conf.get(bs, "border-right-color", bcolor)); - - // }}} - // Set size and position {{{ - - GET_CONFIG_VALUE(bs, m_bar.dock, "dock"); - GET_CONFIG_VALUE(bs, m_bar.bottom, "bottom"); - GET_CONFIG_VALUE(bs, m_bar.spacing, "spacing"); - GET_CONFIG_VALUE(bs, m_bar.lineheight, "lineheight"); - GET_CONFIG_VALUE(bs, m_bar.padding_left, "padding-left"); - GET_CONFIG_VALUE(bs, m_bar.padding_right, "padding-right"); - GET_CONFIG_VALUE(bs, m_bar.module_margin_left, "module-margin-left"); - GET_CONFIG_VALUE(bs, m_bar.module_margin_right, "module-margin-right"); - - auto w = m_conf.get(bs, "width", "100%"); - auto h = m_conf.get(bs, "height", "24"); - - auto offsetx = m_conf.get(bs, "offset-x", ""); - auto offsety = m_conf.get(bs, "offset-y", ""); - - // look for user-defined width - if ((m_bar.width = std::atoi(w.c_str())) && w.find("%") != string::npos) { - m_bar.width = math_util::percentage_to_value(m_bar.width, m_bar.monitor->w); - } - - // look for user-defined height - if ((m_bar.height = std::atoi(h.c_str())) && h.find("%") != string::npos) { - m_bar.height = math_util::percentage_to_value(m_bar.height, m_bar.monitor->h); - } - - // look for user-defined offset-x - if ((m_bar.offset_x = std::atoi(offsetx.c_str())) != 0 && offsetx.find("%") != string::npos) { - m_bar.offset_x = math_util::percentage_to_value(m_bar.offset_x, m_bar.monitor->w); - } - - // look for user-defined offset-y - if ((m_bar.offset_y = std::atoi(offsety.c_str())) != 0 && offsety.find("%") != string::npos) { - m_bar.offset_y = math_util::percentage_to_value(m_bar.offset_y, m_bar.monitor->h); - } - - // apply offsets - m_bar.x = m_bar.offset_x + m_bar.monitor->x; - m_bar.y = m_bar.offset_y + m_bar.monitor->y; - - // apply borders - m_bar.height += m_borders[border::TOP].size; - m_bar.height += m_borders[border::BOTTOM].size; - - if (m_bar.bottom) - m_bar.y = m_bar.monitor->y + m_bar.monitor->h - m_bar.height - m_bar.offset_y; - - if (m_bar.width <= 0 || m_bar.width > m_bar.monitor->w) - throw application_error("Resulting bar width is out of bounds"); - if (m_bar.height <= 0 || m_bar.height > m_bar.monitor->h) - throw application_error("Resulting bar height is out of bounds"); - - m_bar.width = math_util::cap(m_bar.width, 0, m_bar.monitor->w); - m_bar.height = math_util::cap(m_bar.height, 0, m_bar.monitor->h); - - m_bar.vertical_mid = - (m_bar.height + m_borders[border::TOP].size - m_borders[border::BOTTOM].size) / 2; - - m_log.trace("bar: Resulting bar geom %ix%i+%i+%i", m_bar.width, m_bar.height, m_bar.x, m_bar.y); - - // }}} - // Set the WM_NAME value {{{ - - m_bar.wmname = "lemonbuddy-" + bs.substr(4) + "_" + m_bar.monitor->name; - m_bar.wmname = m_conf.get(bs, "wm-name", m_bar.wmname); - m_bar.wmname = string_util::replace(m_bar.wmname, " ", "-"); - - // }}} - // Set misc parameters {{{ - - m_bar.separator = string_util::trim(m_conf.get(bs, "separator", ""), '"'); - m_bar.locale = m_conf.get(bs, "locale", ""); - - // }}} - // Checking nodraw {{{ - - if (nodraw) { - m_log.trace("bar: Abort bootstrap routine (reason: nodraw)"); - return; - } - - // }}} - // Setup graphic components and create the window {{{ - - m_log.trace("bar: Create colormap"); - { - m_connection.create_colormap_checked( - XCB_COLORMAP_ALLOC_NONE, m_colormap, m_screen->root, m_visual->visual_id); - } - - m_log.trace("bar: Create window %s", m_connection.id(m_window)); - { - uint32_t mask = 0; - xcb_params_cw_t params; - // clang-format off - XCB_AUX_ADD_PARAM(&mask, ¶ms, back_pixel, 0); - XCB_AUX_ADD_PARAM(&mask, ¶ms, border_pixel, 0); - XCB_AUX_ADD_PARAM(&mask, ¶ms, colormap, m_colormap); - XCB_AUX_ADD_PARAM(&mask, ¶ms, override_redirect, m_bar.dock); - XCB_AUX_ADD_PARAM(&mask, ¶ms, event_mask, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS); - // clang-format on - m_window.create_checked(m_bar.x, m_bar.y, m_bar.width, m_bar.height, mask, ¶ms); - } - - m_log.trace("bar: Set WM_NAME"); - { - xcb_icccm_set_wm_name( - m_connection, m_window, XCB_ATOM_STRING, 8, m_bar.wmname.length(), m_bar.wmname.c_str()); - xcb_icccm_set_wm_class(m_connection, m_window, 21, "lemonbuddy\0Lemonbuddy"); - } - - m_log.trace("bar: Set _NET_WM_WINDOW_TYPE"); - { - const uint32_t win_types[1] = {_NET_WM_WINDOW_TYPE_DOCK}; - m_connection.change_property_checked( - XCB_PROP_MODE_REPLACE, m_window, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, 1, win_types); - } - - m_log.trace("bar: Set _NET_WM_STATE"); - { - const uint32_t win_states[2] = {_NET_WM_STATE_STICKY, _NET_WM_STATE_ABOVE}; - m_connection.change_property_checked( - XCB_PROP_MODE_REPLACE, m_window, _NET_WM_STATE, XCB_ATOM_ATOM, 32, 2, win_states); - } - - m_log.trace("bar: Set _NET_WM_STRUT_PARTIAL"); - { - uint32_t none{0}; - uint32_t value_list[12]{none}; - - if (m_bar.bottom) { - value_list[3] = m_bar.height; - value_list[10] = m_bar.x; - value_list[11] = m_bar.x + m_bar.width; - } else { - value_list[2] = m_bar.height; - value_list[8] = m_bar.x; - value_list[9] = m_bar.x + m_bar.width; - } - - m_connection.change_property_checked(XCB_PROP_MODE_REPLACE, m_window, _NET_WM_STRUT_PARTIAL, - XCB_ATOM_CARDINAL, 32, 12, value_list); - } - - m_log.trace("bar: Set _NET_WM_DESKTOP"); - { - const uint32_t value_list[1]{-1u}; - m_connection.change_property_checked( - XCB_PROP_MODE_REPLACE, m_window, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, value_list); - } - - m_log.trace("bar: Set _NET_WM_PID"); - { - const uint32_t value_list[1]{uint32_t(getpid())}; - m_connection.change_property_checked( - XCB_PROP_MODE_REPLACE, m_window, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, value_list); - } - - m_log.trace("bar: Create pixmap"); - { - m_connection.create_pixmap_checked( - m_visual->visual_id == m_screen->root_visual ? XCB_COPY_FROM_PARENT : 32, m_pixmap, - m_window, m_bar.width, m_bar.height); - } - - m_log.trace("bar: Map window"); - { - m_connection.flush(); - m_connection.map_window_checked(m_window); - } - - // }}} - // Restack window and put it above defined WM's root {{{ - - try { - auto wm_restack = m_conf.get(bs, "wm-restack"); - auto restacked = false; - - if (wm_restack == "bspwm") { - restacked = bspwm_util::restack_above_root(m_connection, m_bar.monitor, m_window); - - } else if (wm_restack == "i3" && m_bar.dock && ENABLE_I3) { -#if ENABLE_I3 - restacked = i3_util::restack_above_root(m_connection, m_bar.monitor, m_window); -#endif - - } else if (wm_restack == "i3" && !m_bar.dock) { - m_log.warn("Ignoring restack of i3 window (not needed when dock = false)"); - wm_restack.clear(); - - } else { - m_log.warn("Ignoring unsupported wm-restack option '%s'", wm_restack); - wm_restack.clear(); - } - - if (restacked) { - m_log.info("Successfully restacked bar window"); - } else if (!wm_restack.empty()) { - m_log.err("Failed to restack bar window"); - } - } catch (const key_error& err) { - } - - // }}} - // Create graphic contexts {{{ - - m_log.trace("bar: Create graphic contexts"); - { - // clang-format off - vector colors { - m_bar.background.value(), - m_bar.foreground.value(), - m_bar.linecolor.value(), - m_bar.linecolor.value(), - m_borders[border::TOP].color.value(), - m_borders[border::BOTTOM].color.value(), - m_borders[border::LEFT].color.value(), - m_borders[border::RIGHT].color.value(), - }; - // clang-format on - - for (int i = 1; i <= 8; i++) { - uint32_t mask = 0; - uint32_t value_list[32]; - xcb_params_gc_t params; - XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, colors[i - 1]); - XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0); - xutils::pack_values(mask, ¶ms, value_list); - m_gcontexts.emplace(gc(i), gcontext{m_connection, m_connection.generate_id()}); - m_connection.create_gc_checked(m_gcontexts.at(gc(i)), m_pixmap, mask, value_list); - } - } - - // }}} - // Load fonts {{{ - - auto fonts_loaded = false; - auto fontindex = 0; - auto fonts = m_conf.get_list(bs, "font"); - - for (auto f : fonts) { - fontindex++; - vector fd = string_util::split(f, ';'); - string pattern{fd[0]}; - int offset{0}; - - if (fd.size() > 1) - offset = std::stoi(fd[1], 0, 10); - - if (m_fontmanager->load(pattern, fontindex, offset)) - fonts_loaded = true; - else - m_log.warn("Unable to load font '%s'", fd[0]); - } - - if (!fonts_loaded) { - m_log.warn("Loading fallback font"); - - if (!m_fontmanager->load("fixed")) - throw application_error("Unable to load fonts"); - } - - m_fontmanager->allocate_color(m_bar.foreground); - - // }}} - // Set tray settings {{{ - - try { - auto tray_position = m_conf.get(bs, "tray-position"); - - if (tray_position == "left") - m_tray.align = alignment::LEFT; - else if (tray_position == "right") - m_tray.align = alignment::RIGHT; - else if (tray_position == "center") - m_tray.align = alignment::CENTER; - else - m_tray.align = alignment::NONE; - } catch (const key_error& err) { - m_tray.align = alignment::NONE; - } - - if (m_tray.align != alignment::NONE) { - m_tray.height = m_bar.height; - m_tray.height -= m_borders.at(border::BOTTOM).size; - m_tray.height -= m_borders.at(border::TOP).size; - m_tray.height_fill = m_tray.height; - - if (m_tray.height % 2 != 0) { - m_tray.height--; - } - - auto maxsize = m_conf.get(bs, "tray-maxsize", 16); - if (m_tray.height > maxsize) { - m_tray.spacing += (m_tray.height - maxsize) / 2; - m_tray.height = maxsize; - } - - m_tray.width = m_tray.height; - m_tray.orig_y = m_bar.y + m_borders.at(border::TOP).size; - - // Apply user-defined scaling - auto scale = m_conf.get(bs, "tray-scale", 1.0); - m_tray.width *= scale; - m_tray.height_fill *= scale; - - if (m_tray.align == alignment::RIGHT) { - m_tray.orig_x = m_bar.x + m_bar.width - m_borders.at(border::RIGHT).size; - } else if (m_tray.align == alignment::LEFT) { - m_tray.orig_x = m_bar.x + m_borders.at(border::LEFT).size; - } else if (m_tray.align == alignment::CENTER) { - m_tray.orig_x = center_x() - (m_tray.width / 2); - } - - // Set user-defined background color - try { - auto tray_background = m_conf.get(bs, "tray-background"); - m_tray.background = color::parse(tray_background).value(); - } catch (const key_error&) { - m_tray.background = m_bar.background.value(); - } - - // Add user-defined padding - m_tray.spacing += m_conf.get(bs, "tray-padding", 0); - - // Add user-defiend offset - auto offset_x_def = m_conf.get(bs, "tray-offset-x", ""); - auto offset_y_def = m_conf.get(bs, "tray-offset-y", ""); - - auto offset_x = std::atoi(offset_x_def.c_str()); - auto offset_y = std::atoi(offset_y_def.c_str()); - - if (offset_x != 0 && offset_x_def.find("%") != string::npos) { - offset_x = math_util::percentage_to_value(offset_x, m_bar.monitor->w); - offset_x -= m_tray.width / 2; - } - - if (offset_y != 0 && offset_y_def.find("%") != string::npos) { - offset_y = math_util::percentage_to_value(offset_y, m_bar.monitor->h); - offset_y -= m_tray.width / 2; - } - - m_tray.orig_x += offset_x; - m_tray.orig_y += offset_y; - - // Add tray update callback unless explicitly disabled - if (!m_conf.get(bs, "tray-detached", false)) { - g_signals::tray::report_slotcount = bind(&bar::on_tray_report, this, placeholders::_1); - } - - // Put the tray next to the bar in the window stack - m_tray.sibling = m_window; - } - - // }}} - // Connect signal handlers {{{ - - // clang-format off - g_signals::parser::alignment_change = bind(&bar::on_alignment_change, this, placeholders::_1); - g_signals::parser::attribute_set = bind(&bar::on_attribute_set, this, placeholders::_1); - g_signals::parser::attribute_unset = bind(&bar::on_attribute_unset, this, placeholders::_1); - g_signals::parser::attribute_toggle = bind(&bar::on_attribute_toggle, this, placeholders::_1); - g_signals::parser::action_block_open = bind(&bar::on_action_block_open, this, placeholders::_1, placeholders::_2); - g_signals::parser::action_block_close = bind(&bar::on_action_block_close, this, placeholders::_1); - g_signals::parser::color_change = bind(&bar::on_color_change, this, placeholders::_1, placeholders::_2); - g_signals::parser::font_change = bind(&bar::on_font_change, this, placeholders::_1); - g_signals::parser::pixel_offset = bind(&bar::on_pixel_offset, this, placeholders::_1); - g_signals::parser::ascii_text_write = bind(&bar::draw_character, this, placeholders::_1); - g_signals::parser::unicode_text_write = bind(&bar::draw_character, this, placeholders::_1); - // clang-format on - - // }}} - - m_connection.attach_sink(this, 1); - m_sinkattached = true; - - m_connection.flush(); - } //}}} - - /** - * Parse input string and redraw the bar window - * - * @param data Input string - * @param force Unless true, do not parse unchanged data - */ - void parse(string data, bool force = false) { //{{{ - std::lock_guard lck(m_lock); - { - if (data == m_prevdata && !force) - return; - - m_prevdata = data; - - // TODO: move to fontmanager - m_xftdraw = XftDrawCreate(xlib::get_display(), m_pixmap, xlib::get_visual(), m_colormap); - - m_bar.align = alignment::LEFT; - m_xpos = m_borders[border::LEFT].size; - m_attributes = 0; - -#if DEBUG and DRAW_CLICKABLE_AREA_HINTS - for (auto&& action : m_actions) { - m_connection.destroy_window(action.clickable_area); - } -#endif - - m_actions.clear(); - - draw_background(); - - if (m_tray.align == alignment::LEFT && m_tray.slots) - m_xpos += ((m_tray.width + m_tray.spacing) * m_tray.slots) + m_tray.spacing; - - try { - parser parser(m_bar); - parser(data); - } catch (const unrecognized_token& err) { - m_log.err("Unrecognized syntax token '%s'", err.what()); - } - - if (m_tray.align == alignment::RIGHT && m_tray.slots) - draw_shift(m_xpos, ((m_tray.width + m_tray.spacing) * m_tray.slots) + m_tray.spacing); - - draw_border(border::ALL); - - flush(); - - XftDrawDestroy(m_xftdraw); - } - } //}}} - - /** - * Copy the contents of the pixmap's onto the bar window - */ - void flush() { //{{{ - m_connection.copy_area( - m_pixmap, m_window, m_gcontexts.at(gc::FG), 0, 0, 0, 0, m_bar.width, m_bar.height); - m_connection.copy_area( - m_pixmap, m_window, m_gcontexts.at(gc::BT), 0, 0, 0, 0, m_bar.width, m_bar.height); - m_connection.copy_area( - m_pixmap, m_window, m_gcontexts.at(gc::BB), 0, 0, 0, 0, m_bar.width, m_bar.height); - m_connection.copy_area( - m_pixmap, m_window, m_gcontexts.at(gc::BL), 0, 0, 0, 0, m_bar.width, m_bar.height); - m_connection.copy_area( - m_pixmap, m_window, m_gcontexts.at(gc::BR), 0, 0, 0, 0, m_bar.width, m_bar.height); - m_connection.flush(); - -#if DEBUG and DRAW_CLICKABLE_AREA_HINTS - map hint_num{{ - {alignment::LEFT, 0}, {alignment::CENTER, 0}, {alignment::RIGHT, 0}, - }}; -#endif - - for (auto&& action : m_actions) { - if (action.active) { - m_log.warn("Action block not closed"); - m_log.warn("action.command = %s", action.command); - } else { - m_log.trace_x("bar: Action details (button = %i, start_x = %i, end_x = %i, command = '%s')", - static_cast(action.button), action.start_x, action.end_x, action.command); -#if DEBUG and DRAW_CLICKABLE_AREA_HINTS - m_log.info("Drawing clickable area hints"); - - hint_num[action.align]++; - - auto x = action.start_x; - auto y = m_bar.y + hint_num[action.align]++ * DRAW_CLICKABLE_AREA_HINTS_OFFSET_Y; - auto w = action.end_x - action.start_x - 2; - auto h = m_bar.height - 2; - - const uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT; - const uint32_t border_color = hint_num[action.align] % 2 ? 0xff0000 : 0x00ff00; - const uint32_t values[2]{border_color, true}; - - auto scr = m_connection.screen(); - - action.clickable_area = m_connection.generate_id(); - m_connection.create_window_checked(scr->root_depth, action.clickable_area, scr->root, x, y, - w, h, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, values); - m_connection.map_window_checked(action.clickable_area); -#endif - } - } - } //}}} - - /** - * Get the bar settings container - */ - const bar_settings settings() const { // {{{ - return m_bar; - } // }}} - - /** - * Get the tray settings container - */ - const tray_settings tray() const { // {{{ - return m_tray; - } // }}} - - /** - * Mouse button event handler - */ - void handle(const evt::button_press& evt) { // {{{ - if (!m_throttler->passthrough(throttle_util::strategy::try_once_or_leave_yolo{})) { - return; - } - - std::lock_guard lck(m_lock); - { - m_log.trace_x("bar: Received button press event: %i at pos(%i, %i)", - static_cast(evt->detail), evt->event_x, evt->event_y); - - mousebtn button = static_cast(evt->detail); - - for (auto&& action : m_actions) { - if (action.active) { - m_log.trace_x("bar: Ignoring action: unclosed)"); - continue; - } else if (action.button != button) { - m_log.trace_x("bar: Ignoring action: button mismatch"); - continue; - } else if (action.start_x > evt->event_x) { - m_log.trace_x( - "bar: Ignoring action: start_x(%i) > event_x(%i)", action.start_x, evt->event_x); - continue; - } else if (action.end_x < evt->event_x) { - m_log.trace_x( - "bar: Ignoring action: end_x(%i) < event_x(%i)", action.end_x, evt->event_x); - continue; - } - - m_log.trace("Found matching input area"); - m_log.trace_x("action.command = %s", action.command); - m_log.trace_x("action.button = %i", static_cast(action.button)); - m_log.trace_x("action.start_x = %i", action.start_x); - m_log.trace_x("action.end_x = %i", action.end_x); - - if (g_signals::bar::action_click) - g_signals::bar::action_click(action.command); - else - m_log.warn("No signal handler's connected to 'action_click'"); - - return; - } - - m_log.warn("No matching input area found"); - } - } // }}} - - /** - * Event handler for XCB_EXPOSE events - */ - void handle(const evt::expose& evt) { // {{{ - if (evt->window != m_window) - return; - m_log.trace("bar: Received expose event"); - flush(); - } // }}} - - /** - * Event handler for XCB_PROPERTY_NOTIFY events - * - * Used to emit events whenever the bar window's - * visibility gets changes. This allows us to toggle the - * state of the tray container even though the tray - * window restacking failed. - * - * This is used as a fallback for tedious WM's, like i3. - * - * Some might call it a dirty hack, others a crappy - * solution... I choose to call it a masterpiece! Plus - * it's not really any overhead worth talking about. - */ - void handle(const evt::property_notify& evt) { // {{{ - if (evt->window == m_window && evt->atom == WM_STATE) { - if (!g_signals::bar::visibility_change) - return; - - try { - auto attr = m_connection.get_window_attributes(m_window); - if (attr->map_state == XCB_MAP_STATE_VIEWABLE) - g_signals::bar::visibility_change(true); - else if (attr->map_state == XCB_MAP_STATE_UNVIEWABLE) - g_signals::bar::visibility_change(false); - else if (attr->map_state == XCB_MAP_STATE_UNMAPPED) - g_signals::bar::visibility_change(false); - else - g_signals::bar::visibility_change(true); - } catch (const std::exception& err) { - m_log.warn("Failed to emit bar window's visibility change event"); - } - } - } // }}} + void handle(const evt::button_press& evt); + void handle(const evt::expose& evt); + void handle(const evt::property_notify& evt); protected: - /** - * Get the horizontal center pos - */ - int center_x() { - int x = m_bar.x; - x += m_bar.width; - x -= m_borders[border::RIGHT].size; - x += m_borders[border::LEFT].size; - x /= 2; - return x; - } + int center_x(); + int width_inner(); - /** - * Get the inner width of the bar - */ - int width_inner() { - auto w = m_bar.width; - w -= m_borders[border::RIGHT].size; - w -= m_borders[border::LEFT].size; - return w; - } + void on_alignment_change(alignment align); + void on_attribute_set(attribute attr); + void on_attribute_unset(attribute attr); + void on_attribute_toggle(attribute attr); + void on_action_block_open(mousebtn btn, string cmd); + void on_action_block_close(mousebtn btn); + void on_color_change(gc gc_, color color_); + void on_font_change(int index); + void on_pixel_offset(int px); + void on_tray_report(uint16_t slots); - /** - * Handle alignment update - */ - void on_alignment_change(alignment align) { //{{{ - if (align == m_bar.align) - return; - m_log.trace_x("bar: alignment_change(%i)", static_cast(align)); - m_bar.align = align; - - if (align == alignment::LEFT) { - m_xpos = m_borders[border::LEFT].size; - } else if (align == alignment::RIGHT) { - m_xpos = m_borders[border::RIGHT].size; - } else { - m_xpos = 0; - } - } //}}} - - /** - * Handle attribute on state - */ - void on_attribute_set(attribute attr) { //{{{ - int val{static_cast(attr)}; - if ((m_attributes & val) != 0) - return; - m_log.trace_x("bar: attribute_set(%i)", val); - m_attributes |= val; - } //}}} - - /** - * Handle attribute off state - */ - void on_attribute_unset(attribute attr) { //{{{ - int val{static_cast(attr)}; - if ((m_attributes & val) == 0) - return; - m_log.trace_x("bar: attribute_unset(%i)", val); - m_attributes ^= val; - } //}}} - - /** - * Handle attribute toggle state - */ - void on_attribute_toggle(attribute attr) { //{{{ - int val{static_cast(attr)}; - m_log.trace_x("bar: attribute_toggle(%i)", val); - m_attributes ^= val; - } //}}} - - /** - * Handle action block start - */ - void on_action_block_open(mousebtn btn, string cmd) { //{{{ - if (btn == mousebtn::NONE) - btn = mousebtn::LEFT; - m_log.trace_x("bar: action_block_open(%i, %s)", static_cast(btn), cmd); - action_block action; - action.active = true; - action.align = m_bar.align; - action.button = btn; - action.start_x = m_xpos; - action.command = string_util::replace_all(cmd, ":", "\\:"); - m_actions.emplace_back(action); - } //}}} - - /** - * Handle action block end - */ - void on_action_block_close(mousebtn btn) { //{{{ - m_log.trace_x("bar: action_block_close(%i)", static_cast(btn)); - - for (auto i = m_actions.size(); i > 0; i--) { - auto& action = m_actions[i - 1]; - - if (!action.active || action.button != btn) - continue; - - action.active = false; - - if (action.align == alignment::LEFT) { - action.end_x = m_xpos; - } else if (action.align == alignment::CENTER) { - int base_x = m_bar.width; - base_x -= m_borders[border::RIGHT].size; - base_x /= 2; - base_x += m_borders[border::LEFT].size; - - int clickable_width = m_xpos - action.start_x; - action.start_x = base_x - clickable_width / 2 + action.start_x / 2; - action.end_x = action.start_x + clickable_width; - } else if (action.align == alignment::RIGHT) { - int base_x = m_bar.width - m_borders[border::RIGHT].size; - action.start_x = base_x - m_xpos + action.start_x; - action.end_x = base_x; - } - - return; - } - } //}}} - - /** - * Handle color change - */ - void on_color_change(gc gc_, color color_) { //{{{ - m_log.trace_x("bar: color_change(%i, %s -> %s)", static_cast(gc_), color_.hex_to_rgba(), - color_.hex_to_rgb()); - - if (gc_ == gc::FG) { - m_fontmanager->allocate_color(color_); - m_xfont_color = color_.value(); - } else { - const uint32_t value_list[32]{color_.value()}; - m_connection.change_gc(m_gcontexts.at(gc_), XCB_GC_FOREGROUND, value_list); - } - } //}}} - - /** - * Handle font change - */ - void on_font_change(int index) { //{{{ - m_log.trace_x("bar: font_change(%i)", index); - m_fontmanager->set_preferred_font(index); - } //}}} - - /** - * Handle pixel offsetting - */ - void on_pixel_offset(int px) { //{{{ - m_log.trace_x("bar: pixel_offset(%i)", px); - draw_shift(m_xpos, px); - m_xpos += px; - } //}}} - - /** - * Proess systray report - */ - void on_tray_report(uint16_t slots) { // {{{ - if (m_tray.slots == slots) - return; - - m_log.trace("bar: tray_report(%lu)", slots); - m_tray.slots = slots; - - if (!m_prevdata.empty()) - parse(m_prevdata, true); - } // }}} - - /** - * Draw background onto the pixmap - */ - void draw_background() { //{{{ - draw_util::fill( - m_connection, m_pixmap, m_gcontexts.at(gc::BG), 0, 0, m_bar.width, m_bar.height); - } //}}} - - /** - * Draw borders onto the pixmap - */ - void draw_border(border border_) { //{{{ - switch (border_) { - case border::NONE: - break; - - case border::TOP: - if (m_borders[border::TOP].size > 0) { - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BT), - m_borders[border::LEFT].size, 0, - m_bar.width - m_borders[border::LEFT].size - m_borders[border::RIGHT].size, - m_borders[border::TOP].size); - } - break; - - case border::BOTTOM: - if (m_borders[border::BOTTOM].size > 0) { - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BB), - m_borders[border::LEFT].size, m_bar.height - m_borders[border::BOTTOM].size, - m_bar.width - m_borders[border::LEFT].size - m_borders[border::RIGHT].size, - m_borders[border::BOTTOM].size); - } - break; - - case border::LEFT: - if (m_borders[border::LEFT].size > 0) { - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BL), 0, 0, - m_borders[border::LEFT].size, m_bar.height); - } - break; - - case border::RIGHT: - if (m_borders[border::RIGHT].size > 0) { - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BR), - m_bar.width - m_borders[border::RIGHT].size, 0, m_borders[border::RIGHT].size, - m_bar.height); - } - break; - - case border::ALL: - draw_border(border::TOP); - draw_border(border::BOTTOM); - draw_border(border::LEFT); - draw_border(border::RIGHT); - break; - } - } //}}} - - /** - * Draw over- and underline onto the pixmap - */ - void draw_lines(int x, int w) { //{{{ - if (!m_bar.lineheight) - return; - - if (m_attributes & static_cast(attribute::o)) - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::OL), x, - m_borders[border::TOP].size, w, m_bar.lineheight); - - if (m_attributes & static_cast(attribute::u)) - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::UL), x, - m_bar.height - m_borders[border::BOTTOM].size - m_bar.lineheight, w, m_bar.lineheight); - } //}}} - - /** - * Shift the contents of the pixmap horizontally - */ - int draw_shift(int x, int chr_width) { //{{{ - int delta = chr_width; - - if (m_bar.align == alignment::CENTER) { - int base_x = m_bar.width; - base_x -= m_borders[border::RIGHT].size; - base_x /= 2; - base_x += m_borders[border::LEFT].size; - m_connection.copy_area(m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), base_x - x / 2, 0, - base_x - (x + chr_width) / 2, 0, x, m_bar.height); - x = base_x - (x + chr_width) / 2 + x; - delta /= 2; - } else if (m_bar.align == alignment::RIGHT) { - m_connection.copy_area(m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), m_bar.width - x, 0, - m_bar.width - x - chr_width, 0, x, m_bar.height); - x = m_bar.width - chr_width - m_borders[border::RIGHT].size; - } - - draw_util::fill( - m_connection, m_pixmap, m_gcontexts.at(gc::BG), x, 0, m_bar.width - x, m_bar.height); - - // Translate pos of clickable areas - if (m_bar.align != alignment::LEFT) - for (auto&& action : m_actions) { - if (action.active || action.align != m_bar.align) - continue; - action.start_x -= delta; - action.end_x -= delta; - } - - return x; - } //}}} - - /** - * Draw text contents - */ - void draw_character(uint16_t character) { // {{{ - // TODO: cache - auto& font = m_fontmanager->match_char(character); - - if (!font) { - m_log.warn("No suitable font found for character at index %i", character); - return; - } - - if (font->ptr && font->ptr != m_gcfont) { - m_gcfont = font->ptr; - m_fontmanager->set_gcontext_font(m_gcontexts.at(gc::FG), m_gcfont); - } - - if (font->ptr && m_xfont_color != 0) { - m_log.trace_x("bar: Set gcontext color for xcb font"); - const uint32_t values[1]{m_xfont_color}; - m_connection.change_gc(m_gcontexts.at(gc::FG), XCB_GC_FOREGROUND, values); - m_xfont_color = 0; - } - - // TODO: cache - auto chr_width = m_fontmanager->char_width(font, character); - - // Avoid odd glyph width's for center-aligned text - // since it breaks the positioning of clickable area's - if (m_bar.align == alignment::CENTER && chr_width % 2) - chr_width++; - - auto x = draw_shift(m_xpos, chr_width); - auto y = m_bar.vertical_mid + font->height / 2 - font->descent + font->offset_y; - - // m_log.trace("Draw char(%c, width: %i) at pos(%i,%i)", character, chr_width, x, y); - - if (font->xft != nullptr) { - auto color = m_fontmanager->xftcolor(); - XftDrawString16(m_xftdraw, &color, font->xft, x, y, &character, 1); - } else { - character = (character >> 8) | (character << 8); - draw_util::xcb_poly_text_16_patched( - m_connection, m_pixmap, m_gcontexts.at(gc::FG), x, y, 1, &character); - } - - draw_lines(x, chr_width); - m_xpos += chr_width; - } // }}} + void draw_background(); + void draw_border(border border_); + void draw_lines(int x, int w); + int draw_shift(int x, int chr_width); + void draw_character(uint16_t character); + void draw_textstring(const char* text, size_t len); private: connection& m_connection; @@ -1092,7 +90,6 @@ class bar : public xpp::event::sink 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 flush(); - string output = m_output.data(); + void append(string text); - // reset values - m_output.clear(); - for (auto& counter : m_counters) counter.second = 0; - for (auto& value : m_colors) value.second = ""; - m_fontindex = 1; + void node(string str, bool add_space = false); + void node(string str, int font_index, bool add_space = false); + // void node(progressbar_t bar, float perc, bool add_space = false); + void node(label_t label, bool add_space = false); + // void node(ramp_t ramp, float perc, bool add_space = false); + // void node(animation_t animation, bool add_space = false); - return string_util::replace_all(output, string{BUILDER_SPACE_TOKEN}, " "); - } + void offset(int pixels = 0); + void space(int width = DEFAULT_SPACING); + void remove_trailing_space(int width = DEFAULT_SPACING); - 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 invert(); - void node(string str, bool add_space = false) { - string::size_type n, m; - string s(str); + void font(int index); + void font_close(bool force = false); - while (true) { - if (s.empty()) { - break; + void background(string color); + void background_close(bool force = false); - } else if ((n = s.find("%{F-}")) == 0) { - color_close(!m_lazy); - s.erase(0, 5); + void color(string color_); + void color_alpha(string alpha_); + void color_close(bool force = false); - } 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); + void line_color(string color); + void line_color_close(bool force = false); - } else if ((n = s.find("%{B-}")) == 0) { - background_close(!m_lazy); - s.erase(0, 5); + void overline(string color = ""); + void overline_close(bool force = false); - } else if ((n = s.find("%{B#")) == 0 && (m = s.find("}")) != string::npos) { - background(s.substr(n + 3, m - 3)); - s.erase(n, m + 1); + void underline(string color = ""); + void underline_close(bool force = false); - } 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(0, 100, perc)), add_space); - // } - - void node(label_t label, bool add_space = false) { - if (!label || !*label) - return; - - auto text = label->get(); - - 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(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_to_rgba(); - 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_to_rgba(); - 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_to_rgba(); - 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(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]--; - } + void cmd(mousebtn index, string action, bool condition = true); + void cmd_close(bool force = false); protected: - void tag_open(char tag, string value) { - append("%{" + string({tag}) + value + "}"); - } - - void tag_close(char tag) { - append("%{" + string({tag}) + "-}"); - } + void tag_open(char tag, string value); + void tag_close(char tag); private: const bar_settings m_bar; diff --git a/include/components/command_line.hpp b/include/components/command_line.hpp index 10d99ed0..ea7a937e 100644 --- a/include/components/command_line.hpp +++ b/include/components/command_line.hpp @@ -1,10 +1,5 @@ #pragma once -#include -#include -#include -#include - #include "common.hpp" LEMONBUDDY_NS @@ -28,9 +23,6 @@ namespace command_line { string token; const choices values; - /** - * Construct option - */ explicit option( string flag, string flag_long, string desc, string token = "", const choices c = {}) : flag(flag), flag_long(flag_long), desc(desc), token(token), values(c) {} @@ -41,166 +33,24 @@ namespace command_line { class parser { public: - /** - * Construct parser - */ explicit parser(const string& synopsis, const options& opts) : m_synopsis(synopsis), m_opts(opts) {} - /** - * Process input values - * - * This is done outside the constructor due to boost::di noexcept - */ - void process_input(const vector& values) { - for (size_t i = 0; i < values.size(); i++) { - parse(values[i], values.size() > i + 1 ? values[i + 1] : ""); - } - } + void usage() const; - /** - * Test if the passed option was provided - */ - bool has(const string& option) const { - return m_optvalues.find(option) != m_optvalues.end(); - } + void process_input(const vector& values); - /** - * Compares the option value with given string - */ - bool compare(string opt, string val) const { - return get(opt) == val; - } - - /** - * Gets the value defined for given option - */ - string get(string opt) const { - if (has(forward(opt))) - return m_optvalues.find(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) { - size_t 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; - } - } + bool has(const string& option) const; + string get(string opt) const; + bool compare(string opt, string val) const; 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; - } + auto is_short(string option, string opt_short) const; + auto is_long(string option, string opt_long) const; + auto is(string option, string opt_short, string opt_long) const; - /** - * 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); - } - } + auto parse_value(string input, string input_next, choices values) const; + void parse(string input, string input_next = ""); private: string m_synopsis; @@ -212,12 +62,16 @@ namespace command_line { // }}} } +using cliparser = command_line::parser; +using clioption = command_line::option; +using clioptions = command_line::options; + namespace { /** * Configure injection module */ - template - di::injector configure_cli_parser(string scriptname, const command_line::options& opts) { + template + di::injector configure_cliparser(string scriptname, const clioptions& opts) { // clang-format off return di::make_injector( di::bind<>().to("Usage: " + scriptname + " bar_name [OPTION...]"), diff --git a/include/components/config.hpp b/include/components/config.hpp index 2a73a07c..91e33f8c 100644 --- a/include/components/config.hpp +++ b/include/components/config.hpp @@ -6,9 +6,8 @@ #include "common.hpp" #include "components/logger.hpp" -#include "components/x11/xresources.hpp" -#include "utils/file.hpp" #include "utils/string.hpp" +#include "x11/xresources.hpp" LEMONBUDDY_NS @@ -22,76 +21,14 @@ DEFINE_ERROR(key_error); class config { public: - /** - * Construct config - */ explicit config(const logger& logger, const xresource_manager& xrm) : m_logger(logger), m_xrm(xrm) {} - /** - * 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 defined_bars() const { - vector 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; - } + void load(string file, string barname); + string filepath() const; + string bar_section() const; + vector defined_bars() const; + string build_path(const string& section, const string& key) const; /** * Get parameter for the current bar by name diff --git a/include/components/controller.hpp b/include/components/controller.hpp index 18d844e9..e684a7d1 100644 --- a/include/components/controller.hpp +++ b/include/components/controller.hpp @@ -1,60 +1,25 @@ #pragma once -#include - #include "common.hpp" #include "components/bar.hpp" #include "components/config.hpp" #include "components/eventloop.hpp" #include "components/logger.hpp" #include "components/signals.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/string.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" -#include "modules/xbacklight.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 +#include "x11/connection.hpp" +#include "x11/tray.hpp" +#include "x11/types.hpp" LEMONBUDDY_NS -using namespace modules; - class controller { public: - /** - * Construct controller - */ explicit controller(connection& conn, const logger& logger, const config& config, unique_ptr eventloop, unique_ptr bar, unique_ptr tray, - inotify_watch_t& confwatch) + inotify_util::watch_t& confwatch) : m_connection(conn) , m_log(logger) , m_conf(config) @@ -63,484 +28,27 @@ class controller { , m_traymanager(forward(tray)) , m_confwatch(confwatch) {} - /** - * Stop modules and cleanup X components, - * threads and spawned processes - */ - ~controller() noexcept { - g_signals::bar::action_click = nullptr; + ~controller(); - if (m_command) { - m_log.info("Terminating running shell command"); - m_command->terminate(); - } - - if (m_eventloop) { - m_log.info("Deconstructing eventloop"); - m_eventloop->set_update_cb(nullptr); - m_eventloop->set_input_db(nullptr); - m_eventloop.reset(); - } - - if (m_bar) { - m_log.info("Deconstructing bar"); - m_bar.reset(); - } - - if (m_traymanager) { - m_traymanager.reset(); - } - - m_log.info("Interrupting X event loop"); - m_connection.send_dummy_event(m_connection.root()); - - if (!m_threads.empty()) { - m_log.info("Joining active threads"); - for (auto&& thread : m_threads) { - if (thread.joinable()) - thread.join(); - } - } - - m_log.info("Waiting for spawned processes"); - while (process_util::notify_childprocess()) - ; - - m_connection.flush(); - } - - /** - * Setup X environment - */ - auto bootstrap(bool writeback = false, bool dump_wmname = false) { - m_writeback = writeback; - - m_log.trace("controller: Initialize X atom cache"); - m_connection.preload_atoms(); - - m_log.trace("controller: Query X extension data"); - m_connection.query_extensions(); - - // Disabled X extensions {{{ - // const auto& damage_ext = m_connection.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(); - // 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(); - 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"); - try { - const uint32_t value_list[1]{XCB_EVENT_MASK_STRUCTURE_NOTIFY}; - m_connection.change_window_attributes_checked( - m_connection.root(), XCB_CW_EVENT_MASK, value_list); - } catch (const std::exception& err) { - throw application_error("Failed to change root window event mask: " + string{err.what()}); - } - - g_signals::bar::action_click = bind(&controller::on_mouse_event, this, placeholders::_1); - - m_log.trace("controller: Attach eventloop callbacks"); - m_eventloop->set_update_cb(bind(&controller::on_update, this)); - m_eventloop->set_input_db(bind(&controller::on_unrecognized_action, this, placeholders::_1)); - - try { - m_log.trace("controller: Setup bar"); - m_bar->bootstrap(m_writeback || dump_wmname); - } catch (const std::exception& err) { - throw application_error("Failed to setup bar renderer: " + string{err.what()}); - } - - if (dump_wmname) { - std::cout << m_bar->settings().wmname << std::endl; - return; - } - - try { - if (m_writeback) { - 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("Failed to setup tray, disabling..."); - m_traymanager.reset(); - } - - m_log.trace("controller: Setup user-defined modules"); - bootstrap_modules(); - } - - /** - * Launch the controller - */ - auto run() { - assert(!m_connection.connection_has_error()); - - m_log.info("Starting application"); - m_running = true; - - install_sigmask(); - install_confwatch(); - - // Wait for term signal in separate thread - m_threads.emplace_back(thread(&controller::wait_for_signal, this)); - - // Activate traymanager in separate thread - if (!m_writeback && m_traymanager) { - m_threads.emplace_back(thread(&controller::activate_tray, this)); - } - - // Listen for X events in separate thread - if (!m_writeback) { - m_threads.emplace_back(thread(&controller::wait_for_xevent, this)); - } - - // Start event loop - if (m_eventloop) { - auto throttle_ms = m_conf.get("settings", "throttle-ms", 10); - auto throttle_limit = m_conf.get("settings", "throttle-limit", 5); - m_eventloop->run(chrono::duration(throttle_ms), throttle_limit); - } - - // Wake up signal thread - if (m_waiting) { - kill(getpid(), SIGTERM); - } - - uninstall_sigmask(); - uninstall_confwatch(); - - m_running = false; - - return !m_reload; - } + void bootstrap(bool writeback = false, bool dump_wmname = false); + bool run(); protected: - /** - * Install sigmask to prevent term signals from being raising - */ - void install_sigmask() { - m_log.trace("controller: Set pthread_sigmask to block term signals"); + void install_sigmask(); + void uninstall_sigmask(); - sigemptyset(&m_waitmask); - sigaddset(&m_waitmask, SIGINT); - sigaddset(&m_waitmask, SIGQUIT); - sigaddset(&m_waitmask, SIGTERM); - sigaddset(&m_waitmask, SIGUSR1); + void install_confwatch(); + void uninstall_confwatch(); - if (pthread_sigmask(SIG_BLOCK, &m_waitmask, nullptr) == -1) - throw system_error(); + void wait_for_signal(); + void wait_for_xevent(); - sigemptyset(&m_ignmask); - sigaddset(&m_ignmask, SIGPIPE); + void activate_tray(); + void bootstrap_modules(); - if (pthread_sigmask(SIG_BLOCK, &m_ignmask, nullptr) == -1) - throw system_error(); - } - - /** - * Uninstall sigmask to allow term signals - */ - void uninstall_sigmask() { - m_log.trace("controller: Set pthread_sigmask to unblock term signals"); - - if (pthread_sigmask(SIG_UNBLOCK, &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(); - } - }); - } - - void uninstall_confwatch() { - if (!m_confwatch) { - return; - } - - try { - m_log.info("Removing config watch"); - m_confwatch->remove(); - } catch (const system_error& err) { - } - } - - void wait_for_signal() { - m_log.trace("controller: Wait for signal"); - m_waiting = true; - - 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); - m_waiting = false; - - if (m_eventloop) { - m_eventloop->stop(); - } - } - - void wait_for_xevent() { - m_log.trace("controller: Listen for X events"); - - m_connection.flush(); - - while (m_running) { - shared_ptr evt; - - if (m_connection.connection_has_error()) { - break; - } - - if ((evt = m_connection.wait_for_event())) { - m_connection.dispatch_event(evt); - } - } - } - - void activate_tray() { - m_log.trace("controller: Activate tray manager"); - - try { - m_traymanager->activate(); - } catch (const std::exception& err) { - m_log.err(err.what()); - m_log.err("Failed to activate tray manager, disabling..."); - m_traymanager.reset(); - } - } - - /** - * Create and initialize bar modules - */ - void bootstrap_modules() { - const bar_settings bar{m_bar->settings()}; - string bs{m_conf.bar_section()}; - size_t module_count = 0; - - for (int i = 0; i < 3; i++) { - alignment align = static_cast(i + 1); - string confkey; - - switch (align) { - 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(bs, confkey, ""), ' ')) { - try { - auto type = m_conf.get("module/" + module_name, "type"); - module_t module; - - if (type == "internal/counter") - module.reset(new counter_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/backlight") - module.reset(new backlight_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/xbacklight") - module.reset(new xbacklight_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/battery") - module.reset(new battery_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/bspwm") - module.reset(new bspwm_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/cpu") - module.reset(new cpu_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/date") - module.reset(new date_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/memory") - module.reset(new memory_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/i3") - module.reset(new i3_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/mpd") - module.reset(new mpd_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/volume") - module.reset(new volume_module(bar, m_log, m_conf, module_name)); - else if (type == "internal/network") - module.reset(new network_module(bar, m_log, m_conf, module_name)); - else if (type == "custom/text") - module.reset(new text_module(bar, m_log, m_conf, module_name)); - else if (type == "custom/script") - module.reset(new script_module(bar, m_log, m_conf, module_name)); - else if (type == "custom/menu") - module.reset(new menu_module(bar, m_log, m_conf, module_name)); - else - throw application_error("Unknown module: " + module_name); - - module->set_update_cb(bind(&eventloop::enqueue, m_eventloop.get(), - eventloop::entry_t{static_cast(event_type::UPDATE)})); - module->set_stop_cb(bind(&eventloop::enqueue, m_eventloop.get(), - eventloop::entry_t{static_cast(event_type::CHECK)})); - - module->setup(); - - m_eventloop->add_module(align, move(module)); - - module_count++; - } catch (const module_error& err) { - continue; - } - } - } - - if (module_count == 0) - throw application_error("No modules created"); - } - - /** - * Callback for clicked bar actions - */ - void on_mouse_event(string input) { - eventloop::entry_t evt{static_cast(event_type::INPUT)}; - - if (input.length() > sizeof(evt.data)) { - m_log.warn("Ignoring input event (size)"); - } else { - snprintf(evt.data, sizeof(evt.data), "%s", input.c_str()); - m_eventloop->enqueue(evt); - } - } - - void on_unrecognized_action(string input) { - try { - if (m_command) { - m_log.warn("Terminating previous shell command"); - m_command->terminate(); - } - - m_log.info("Executing shell command: %s", input); - - m_command = command_util::make_command(input); - m_command->exec(); - m_command.reset(); - } catch (const application_error& err) { - m_log.err("controller: Error while forwarding input to shell -> %s", err.what()); - } - } - - void on_update() { - 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 (const auto& block : m_eventloop->modules()) { - string block_contents; - bool is_left = false; - bool is_center = false; - bool is_right = false; - - if (block.first == alignment::LEFT) - is_left = true; - else if (block.first == alignment::CENTER) - is_center = true; - else if (block.first == alignment::RIGHT) - is_right = true; - - for (const auto& module : block.second) { - auto module_contents = module->contents(); - - if (module_contents.empty()) - continue; - - if (!block_contents.empty() && !separator.empty()) - block_contents += separator; - - if (!(is_left && module == block.second.front())) - block_contents += string(margin_left, ' '); - - block_contents += module->contents(); - - if (!(is_right && module == block.second.back())) - block_contents += string(margin_right, ' '); - } - - if (block_contents.empty()) - continue; - - if (is_left) { - contents += "%{l}"; - contents += padding_left; - } else if (is_center) { - contents += "%{c}"; - } else if (is_right) { - contents += "%{r}"; - block_contents += padding_right; - } - - 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_writeback) { - std::cout << contents << std::endl; - } else { - m_bar->parse(contents); - } - } + void on_mouse_event(string input); + void on_unrecognized_action(string input); + void on_update(); private: connection& m_connection; @@ -558,10 +66,9 @@ class controller { sigset_t m_waitmask; sigset_t m_ignmask; - inotify_watch_t& m_confwatch; - vector m_threads; + inotify_util::watch_t& m_confwatch; command_util::command_t m_command; bool m_writeback = false; @@ -572,7 +79,7 @@ namespace { * Configure injection module */ template > - di::injector configure_controller(inotify_watch_t& confwatch) { + di::injector configure_controller(inotify_util::watch_t& confwatch) { // clang-format off return di::make_injector( di::bind<>().to(confwatch), diff --git a/include/components/eventloop.hpp b/include/components/eventloop.hpp index 17b3bab0..374ea8ff 100644 --- a/include/components/eventloop.hpp +++ b/include/components/eventloop.hpp @@ -3,11 +3,8 @@ #include #include "common.hpp" -#include "components/bar.hpp" #include "components/logger.hpp" #include "modules/meta.hpp" -#include "utils/command.hpp" -#include "utils/string.hpp" LEMONBUDDY_NS @@ -28,240 +25,32 @@ class eventloop { using entry_t = event; using queue_t = moodycamel::BlockingConcurrentQueue; - /** - * Construct eventloop - */ explicit eventloop(const logger& logger) : m_log(logger) {} - /** - * Deconstruct eventloop - */ - ~eventloop() noexcept { - for (auto&& block : m_modules) { - for (auto&& module : block.second) { - auto module_name = module->name(); - auto cleanup_ms = time_execution([&module] { - module->stop(); - module.reset(); - }); - m_log.trace("eventloop: Deconstruction of %s took %lu ms.", module_name, cleanup_ms); - } - } - } + ~eventloop() noexcept; - /** - * Set callback handler for UPDATE events - */ - void set_update_cb(callback<>&& cb) { - m_update_cb = forward(cb); - } + bool enqueue(const entry_t& i); + void run(chrono::duration timeframe, int limit); + void stop(); - /** - * Set callback handler for raw INPUT events - */ - void set_input_db(callback&& cb) { - m_unrecognized_input_cb = forward(cb); - } + void set_update_cb(callback<>&& cb); + void set_input_db(callback&& cb); - /** - * Get reference to module map - */ - modulemap_t& modules() { - return m_modules; - } + void add_module(const alignment pos, module_t&& module); - /** - * Add module to alignment block - */ - void add_module(const alignment pos, module_t&& module) { - modulemap_t::iterator it = m_modules.lower_bound(pos); - - if (it != m_modules.end() && !(m_modules.key_comp()(pos, it->first))) { - it->second.emplace_back(forward(module)); - } else { - vector vec; - vec.emplace_back(forward(module)); - m_modules.insert(it, modulemap_t::value_type(pos, move(vec))); - } - } - - /** - * Enqueue event - */ - bool enqueue(const entry_t& i) { - bool enqueued; - - if ((enqueued = m_queue.enqueue(i)) == false) { - m_log.warn("Failed to queue event (%d)", i.type); - } - - return enqueued; - } - - /** - * Start module threads and wait for events on the queue - * - * @param timeframe Time to wait for subsequent events - * @param limit Maximum amount of subsequent events to swallow within timeframe - */ - template - void run(chrono::duration timeframe, int limit) { - m_log.info("Starting event loop"); - m_running = true; - - // Start module threads - for (auto&& block : m_modules) { - for (auto&& module : block.second) { - try { - m_log.info("Starting %s", module->name()); - module->start(); - } catch (const application_error& err) { - m_log.err("Failed to start '%s' (reason: %s)", module->name(), err.what()); - } - } - } - - m_log.trace("eventloop: Enter loop"); - - while (m_running) { - entry_t evt, next{static_cast(event_type::NONE)}; - m_queue.wait_dequeue(evt); - - if (!m_running) { - break; - } - - if (match_event(evt, event_type::UPDATE)) { - int swallowed = 0; - while (swallowed++ < limit && m_queue.wait_dequeue_timed(next, timeframe)) { - if (match_event(next, event_type::QUIT)) { - evt = next; - break; - } else if (compare_events(evt, next)) { - m_log.trace("eventloop: Swallowing event within timeframe"); - evt = next; - } else { - break; - } - } - } - - forward_event(evt); - - if (match_event(next, event_type::NONE)) - continue; - if (compare_events(evt, next)) - continue; - - forward_event(next); - } - - m_log.trace("eventloop: Loop ended"); - } - - /** - * Stop main loop by enqueuing a QUIT event - */ - void stop() { - m_log.info("Stopping event loop"); - m_running = false; - enqueue({static_cast(event_type::QUIT)}); - } + modulemap_t& modules(); protected: - /** - * Test if event matches given type - */ - bool match_event(entry_t evt, event_type type) { - return static_cast(type) == evt.type; - } + void start_modules(); - /** - * Compare given events - */ - bool compare_events(entry_t evt, entry_t evt2) { - return evt.type == evt2.type; - } + bool match_event(entry_t evt, event_type type); + bool compare_events(entry_t evt, entry_t evt2); + void forward_event(entry_t evt); - /** - * Forward event to handler based on type - */ - void forward_event(entry_t evt) { - if (evt.type == static_cast(event_type::UPDATE)) { - on_update(); - } else if (evt.type == static_cast(event_type::INPUT)) { - on_input(string{evt.data}); - } else if (evt.type == static_cast(event_type::CHECK)) { - on_check(); - } else if (evt.type == static_cast(event_type::QUIT)) { - on_quit(); - } else { - m_log.warn("Unknown event type for enqueued event (%d)", evt.type); - } - } - - /** - * Handler for enqueued UPDATE events - */ - void on_update() { - m_log.trace("eventloop: Received UPDATE event"); - - if (m_update_cb) { - m_update_cb(); - } else { - m_log.warn("No callback to handle update"); - } - } - - /** - * Handler for enqueued INPUT events - */ - void on_input(string input) { - m_log.trace("eventloop: Received INPUT event"); - - for (auto&& block : m_modules) { - for (auto&& module : block.second) { - if (!module->receive_events()) - continue; - if (module->handle_event(input)) { - return; - } - } - } - - if (m_unrecognized_input_cb) { - m_unrecognized_input_cb(input); - } else { - m_log.warn("No callback to handle unrecognized input"); - } - } - - /** - * Handler for enqueued CHECK events - */ - void on_check() { - if (!m_running) { - return; - } - - for (auto&& block : m_modules) { - for (auto&& module : block.second) { - if (module->running()) - return; - } - } - - m_log.warn("No running modules..."); - stop(); - } - - /** - * Handler for enqueued QUIT events - */ - void on_quit() { - m_log.trace("eventloop: Received QUIT event"); - m_running = false; - } + void on_update(); + void on_input(string input); + void on_check(); + void on_quit(); private: const logger& m_log; diff --git a/include/components/logger.hpp b/include/components/logger.hpp index 3aa756e0..1bcb4da1 100644 --- a/include/components/logger.hpp +++ b/include/components/logger.hpp @@ -1,10 +1,8 @@ #pragma once #include -#include #include "common.hpp" -#include "utils/string.hpp" LEMONBUDDY_NS @@ -16,88 +14,44 @@ enum class loglevel { 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; -}; +loglevel parse_loglevel_name(string name); class logger { public: - /** - * Construct logger - */ - explicit logger(loglevel level) : m_level(level) { - if (isatty(m_fd)) { - // 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;33mwarn: \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(loglevel level); 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; - } + void verbosity(loglevel level); - /** - * Set output verbosity by loglevel name - */ - void verbosity(string level) { - verbosity(parse_loglevel_name(level)); - } + void verbosity(string level); /** * Output a trace message */ template -#ifdef DEBUG +#ifdef DEBUG_LOGGER void trace(string message, Args... args) const { output(loglevel::TRACE, message, args...); } #else - void trace(string, Args...) const {} +#ifdef VERBOSE_TRACELOG +#undef VERBOSE_TRACELOG +#endif + void trace(string, Args...) const { + } #endif /** * Output extra verbose trace message */ template -#ifdef ENABLE_VERBOSE_TRACELOG +#ifdef VERBOSE_TRACELOG void trace_x(string message, Args... args) const { output(loglevel::TRACE, message, args...); } #else - void trace_x(string, Args...) const {} + void trace_x(string, Args...) const { + } #endif /** diff --git a/include/components/parser.hpp b/include/components/parser.hpp index d965243b..b14ce73b 100644 --- a/include/components/parser.hpp +++ b/include/components/parser.hpp @@ -3,8 +3,6 @@ #include "common.hpp" #include "components/signals.hpp" #include "components/types.hpp" -#include "utils/math.hpp" -#include "utils/string.hpp" LEMONBUDDY_NS @@ -12,241 +10,17 @@ DEFINE_ERROR(unrecognized_token); 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 && g_signals::parser::color_change) - g_signals::parser::color_change(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 && g_signals::parser::color_change) - g_signals::parser::color_change(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 && g_signals::parser::color_change) { - g_signals::parser::color_change(gc::UL, parse_color(value, m_bar.linecolor)); - g_signals::parser::color_change(gc::OL, parse_color(value, m_bar.linecolor)); - } - break; - - case 'R': - if (g_signals::parser::color_change) { - g_signals::parser::color_change(gc::BG, m_bar.foreground); - g_signals::parser::color_change(gc::FG, m_bar.background); - } - break; - - case 'T': - if (data.find(" T") == string::npos && g_signals::parser::font_change) - g_signals::parser::font_change(parse_fontindex(value)); - break; - - case 'O': - if (g_signals::parser::pixel_offset) - g_signals::parser::pixel_offset(std::atoi(value.c_str())); - break; - - case 'l': - if (g_signals::parser::alignment_change) - g_signals::parser::alignment_change(alignment::LEFT); - break; - - case 'c': - if (g_signals::parser::alignment_change) - g_signals::parser::alignment_change(alignment::CENTER); - break; - - case 'r': - if (g_signals::parser::alignment_change) - g_signals::parser::alignment_change(alignment::RIGHT); - break; - - case '+': - if (g_signals::parser::attribute_set) - g_signals::parser::attribute_set(parse_attr(value[0])); - break; - - case '-': - if (g_signals::parser::attribute_unset) - g_signals::parser::attribute_unset(parse_attr(value[0])); - break; - - case '!': - if (g_signals::parser::attribute_toggle) - g_signals::parser::attribute_toggle(parse_attr(value[0])); - break; - - case 'A': - if (isdigit(data[0]) || data[0] == ':') { - value = parse_action_cmd(data); - mousebtn btn = parse_action_btn(data); - m_actions.push_back(static_cast(btn)); - - if (g_signals::parser::action_block_open) - g_signals::parser::action_block_open(btn, value); - - // make sure we strip the correct length (btn+wrapping colons) - if (data[0] != ':') - value += "0"; - value += "::"; - } else if (!m_actions.empty()) { - if (g_signals::parser::action_block_close) - g_signals::parser::action_block_close(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(sequence[n]) < 0x80 && ++n <= next_tag) - // ; - // if (g_signals::parser::ascii_text_write) - // g_signals::parser::ascii_text_write(data.substr(0, n)); - // return data.length(); - // } - - if (utf[0] < 0x80) { - if (g_signals::parser::ascii_text_write) - g_signals::parser::ascii_text_write(utf[0]); - return 1; - } else if ((utf[0] & 0xe0) == 0xc0) { // 2 byte utf-8 sequence - if (g_signals::parser::unicode_text_write) - g_signals::parser::unicode_text_write((utf[0] & 0x1f) << 6 | (utf[1] & 0x3f)); - return 2; - } else if ((utf[0] & 0xf0) == 0xe0) { // 3 byte utf-8 sequence - if (g_signals::parser::unicode_text_write) - g_signals::parser::unicode_text_write( - (utf[0] & 0xf) << 12 | (utf[1] & 0x3f) << 6 | (utf[2] & 0x3f)); - return 3; - } else if ((utf[0] & 0xf8) == 0xf0) { // 4 byte utf-8 sequence - if (g_signals::parser::unicode_text_write) - g_signals::parser::unicode_text_write(0xfffd); - return 4; - } else if ((utf[0] & 0xfc) == 0xf8) { // 5 byte utf-8 sequence - if (g_signals::parser::unicode_text_write) - g_signals::parser::unicode_text_write(0xfffd); - return 5; - } else if ((utf[0] & 0xfe) == 0xfc) { // 6 byte utf-8 sequence - if (g_signals::parser::unicode_text_write) - g_signals::parser::unicode_text_write(0xfffd); - return 6; - } else { // invalid utf-8 sequence - if (g_signals::parser::ascii_text_write) - g_signals::parser::ascii_text_write(utf[0]); - return 1; - } - } // }}} + void operator()(string data); + void codeblock(string data); + size_t text(string data); 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 (data[0] == ':') - return mousebtn::LEFT; - else if (isdigit(data[0])) - return static_cast(data[0] - '0'); - else if (!m_actions.empty()) - return static_cast(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), ':'); - } // }}} + color parse_color(string s, color fallback = color{0}); + int parse_fontindex(string s); + attribute parse_attr(const char s); + mousebtn parse_action_btn(string data); + string parse_action_cmd(string data); private: const bar_settings& m_bar; diff --git a/include/components/signals.hpp b/include/components/signals.hpp index 57bb22d9..9b3ca2b5 100644 --- a/include/components/signals.hpp +++ b/include/components/signals.hpp @@ -9,36 +9,29 @@ LEMONBUDDY_NS * @TODO: Allow multiple signal handlers */ namespace g_signals { - /** - * Signals used to communicate with the bar window - */ namespace bar { - static function action_click; - static function visibility_change; + extern callback action_click; + extern callback visibility_change; } - /** - * Signals used to communicate with the input parser - */ namespace parser { - static function alignment_change; - static function attribute_set; - static function attribute_unset; - static function attribute_toggle; - static function action_block_open; - static function action_block_close; - static function color_change; - static function font_change; - static function pixel_offset; - static function ascii_text_write; - static function unicode_text_write; + extern callback alignment_change; + extern callback attribute_set; + extern callback attribute_unset; + extern callback attribute_toggle; + extern callback action_block_open; + extern callback action_block_close; + extern callback color_change; + extern callback font_change; + extern callback pixel_offset; + extern callback ascii_text_write; + extern callback unicode_text_write; + extern callback string_write; } - /** - * Signals used to communicate with the tray manager - */ namespace tray { - static function report_slotcount; + extern callback report_slotcount; + extern callback clear_bg; } } diff --git a/include/components/types.hpp b/include/components/types.hpp index c380b8c3..792225ba 100644 --- a/include/components/types.hpp +++ b/include/components/types.hpp @@ -1,8 +1,8 @@ #pragma once #include "common.hpp" -#include "components/x11/color.hpp" -#include "components/x11/randr.hpp" +#include "x11/color.hpp" +#include "x11/randr.hpp" LEMONBUDDY_NS @@ -63,7 +63,6 @@ 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; @@ -73,11 +72,10 @@ struct tray_settings { spacing = o.spacing; slots = o.slots; sibling = o.sibling; - bg_pixmap = o.bg_pixmap; + background = o.background; return *this; } - uint32_t background; alignment align{alignment::NONE}; int16_t orig_x{0}; int16_t orig_y{0}; @@ -87,7 +85,7 @@ struct tray_settings { uint16_t spacing{0}; uint16_t slots{0}; uint32_t sibling{0}; - uint32_t bg_pixmap{0}; + uint32_t background{0}; }; struct border_settings { diff --git a/include/components/x11/atoms.hpp b/include/components/x11/atoms.hpp deleted file mode 100644 index 8427851c..00000000 --- a/include/components/x11/atoms.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include -#include - -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 MANAGER; -static xcb_atom_t WM_STATE; -static xcb_atom_t _NET_SYSTEM_TRAY_OPCODE; -static xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION; -static xcb_atom_t _NET_SYSTEM_TRAY_VISUAL; -static xcb_atom_t _NET_SYSTEM_TRAY_COLORS; -static xcb_atom_t WM_TAKE_FOCUS; -static xcb_atom_t Backlight; -static xcb_atom_t BACKLIGHT; - -// clang-format off -static cached_atom ATOMS[26] = { - {"_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}, - {"MANAGER", sizeof("MANAGER") - 1, &MANAGER}, - {"WM_STATE", sizeof("WM_STATE") - 1, &WM_STATE}, - {"_NET_SYSTEM_TRAY_OPCODE", sizeof("_NET_SYSTEM_TRAY_OPCODE") - 1, &_NET_SYSTEM_TRAY_OPCODE}, - {"_NET_SYSTEM_TRAY_ORIENTATION", sizeof("_NET_SYSTEM_TRAY_ORIENTATION") - 1, &_NET_SYSTEM_TRAY_ORIENTATION}, - {"_NET_SYSTEM_TRAY_VISUAL", sizeof("_NET_SYSTEM_TRAY_VISUAL") - 1, &_NET_SYSTEM_TRAY_VISUAL}, - {"_NET_SYSTEM_TRAY_COLORS", sizeof("_NET_SYSTEM_TRAY_COLORS") - 1, &_NET_SYSTEM_TRAY_COLORS}, - {"WM_TAKE_FOCUS", sizeof("WM_TAKE_FOCUS") - 1, &WM_TAKE_FOCUS}, - {"Backlight", sizeof("Backlight") - 1, &Backlight}, - {"BACKLIGHT", sizeof("BACKLIGHT") - 1, &BACKLIGHT}, -}; -// clang-format on diff --git a/include/components/x11/color.hpp b/include/components/x11/color.hpp deleted file mode 100644 index d3a0a94d..00000000 --- a/include/components/x11/color.hpp +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -#include - -#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 g_colorstore; - -class color { - public: - explicit color(string hex) : m_hex(string_util::upper(hex)) { - m_rgba.v = (strtoul(&hex[1], nullptr, 16)); - m_rgb = (m_rgba.v << 8) >> 8; - - // 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 = (strtoul(&m_hex[1], nullptr, 16)); - } - - uint32_t value() const { - return m_rgba.v; - } - - uint32_t rgb() const { - return m_rgb; - } - - uint32_t alpha() const { - return 0xFFFF & (((value() >> 24) << 8) | ((value() >> 24))); - } - - uint32_t red() const { - return 0xFFFF & (((value() >> 16) << 8) | ((value() >> 16))); - } - - uint32_t green() const { - return 0xFFFF & (((value() >> 8) << 8) | ((value() >> 8))); - } - - uint32_t blue() const { - return 0xFFFF & ((value() << 8) | value()); - } - - string hex_to_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_to_rgba() 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; - unsigned long m_rgb; - string m_hex; -}; - -static color g_colorblack{"#ff000000"}; -static color g_colorwhite{"#ffffffff"}; - -LEMONBUDDY_NS_END diff --git a/include/components/x11/connection.hpp b/include/components/x11/connection.hpp deleted file mode 100644 index 027c2f0b..00000000 --- a/include/components/x11/connection.hpp +++ /dev/null @@ -1,200 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#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; - -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()->present) - // throw application_error("Missing X extension: Damage"); - - // render().query_version(XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION); - // if (!extension()->present) - // throw application_error("Missing X extension: Render"); - - randr().query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION); - if (!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(7) << 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 - */ - auto make_client_message(xcb_atom_t type, xcb_window_t target) const { - auto client_message = memory_util::make_malloc_ptr(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 message, xcb_window_t target, - uint32_t event_mask = 0xFFFFFF, bool propagate = false) const { - const char* data = reinterpret_cast(message.get()); - send_event(propagate, target, event_mask, data); - 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_client_message(message, target, event); - } - - /** - * Try to get a visual type for the given screen that - * matches the given depth - */ - optional 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 - void attach_sink(Sink&& sink, registry::priority prio = 0) { - m_registry.attach(prio, forward(sink)); - } - - /** - * Detach sink from the registry - */ - template - void detach_sink(Sink&& sink, registry::priority prio = 0) { - m_registry.detach(prio, forward(sink)); - } - - /** - * Dispatch event through the registry - */ - void dispatch_event(const shared_ptr& evt) { - if (evt != nullptr) - m_registry.dispatch(evt); - } - - protected: - registry m_registry{*this}; - xcb_screen_t* m_screen = nullptr; -}; - -namespace { - /** - * Configure injection module - */ - template - di::injector configure_connection() { - return di::make_injector( - di::bind<>().to(factory::generic_singleton(xutils::get_connection()))); - } -} - -LEMONBUDDY_NS_END diff --git a/include/components/x11/fontmanager.hpp b/include/components/x11/fontmanager.hpp deleted file mode 100644 index aabeeaed..00000000 --- a/include/components/x11/fontmanager.hpp +++ /dev/null @@ -1,264 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#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 xft_widths; -static array 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 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; - -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(conn.default_screen()); - m_colormap = xlib::create_colormap(conn.default_screen()); - } - - ~fontmanager() { - XftColorFree(m_display, m_visual, m_colormap, &m_xftcolor); - XFreeColormap(m_display, m_colormap); - m_fonts.clear(); - } - - 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(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); - - XRenderColor color; - - color.red = (xcolor.red()) << 8; - color.green = (xcolor.green()) << 8; - color.blue = (xcolor.blue()) << 8; - color.alpha = xcolor.alpha() << 8; - - if (!XftColorAllocValue(m_display, m_visual, m_colormap, &color, &m_xftcolor)) - m_logger.err("Failed to allocate color '%s'", xcolor.hex_to_rgba()); - } // }}} - - 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); - if (query->char_infos_len == 0) { - m_logger.warn("X font '%s' does not contain any characters... (Verify the XLFD string)", fontname); - return false; - } - - 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; - - auto chars = query.char_infos(); - for (auto it = chars.begin(); it != chars.end(); it++) - fontptr->width_lut.emplace_back(forward(*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& 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 (static_cast(chr - font->char_min) >= font->width_lut.size()) - 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 m_fonts; - int m_fontindex = -1; - XftColor m_xftcolor; -}; - -namespace { - /** - * Configure injection module - */ - template > - di::injector configure_fontmanager() { - return di::make_injector(configure_connection(), configure_logger()); - } -} - -LEMONBUDDY_NS_END diff --git a/include/components/x11/tray.hpp b/include/components/x11/tray.hpp deleted file mode 100644 index 5290c7c3..00000000 --- a/include/components/x11/tray.hpp +++ /dev/null @@ -1,886 +0,0 @@ -#pragma once - -#include -#include - -#include "common.hpp" -#include "components/logger.hpp" -#include "components/signals.hpp" -#include "components/types.hpp" -#include "components/x11/connection.hpp" -#include "components/x11/xembed.hpp" -#include "utils/color.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 - -// 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(); - m_xembed->version = XEMBED_VERSION; - m_xembed->flags = XEMBED_MAPPED; - } - - ~trayclient() { - xembed::unembed(m_connection, window(), m_connection.root()); - } - - /** - * 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(); - } - - void configure_notify(int16_t x, int16_t y, uint16_t w, uint16_t h) { - auto notify = reinterpret_cast(calloc(32, 1)); - - notify->response_type = XCB_CONFIGURE_NOTIFY; - notify->event = m_window; - notify->window = m_window; - notify->override_redirect = false; - notify->above_sibling = XCB_NONE; - notify->x = x; - notify->y = y; - notify->width = w; - notify->height = h; - notify->border_width = 0; - - m_connection.send_event( - false, notify->event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(notify)); - m_connection.flush(); - - free(notify); - } - - protected: - connection& m_connection; - xcb_window_t m_window{0}; - shared_ptr m_xembed; - stateflag m_mapped{false}; -}; - -// }}} -// class definition : traymanager {{{ - -class traymanager - : public xpp::event::sink { - public: - explicit traymanager(connection& conn, const logger& logger) : m_connection(conn), m_log(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(); - } - - /** - * Activate systray management - */ - void activate() { - if (m_activated) { - return; - } - - if (m_tray == XCB_NONE) { - try { - create_window(); - set_wmhints(); - set_traycolors(); - } catch (const std::exception& err) { - m_log.err(err.what()); - m_log.err("Cannot activate traymanager... failed to setup window"); - return; - } - } - - m_log.info("Activating traymanager"); - m_activated = true; - - if (!m_sinkattached) { - m_connection.attach_sink(this, 2); - m_sinkattached = true; - } - - // Listen for visibility change events on the bar window - if (!m_restacked && !g_signals::bar::visibility_change) { - g_signals::bar::visibility_change = - bind(&traymanager::bar_visibility_change, this, std::placeholders::_1); - } - - // Attempt to get control of the systray selection then - // notify clients waiting for a manager. - acquire_selection(); - - // If replacing an existing manager or if re-activating from getting - // replaced, we delay the notification broadcast to allow the clients - // to get unembedded... - if (m_othermanager != XCB_NONE) - this_thread::sleep_for(1s); - notify_clients(); - - m_connection.flush(); - } - - /** - * Deactivate systray management - */ - void deactivate() { - if (!m_activated) { - return; - } - - m_log.info("Deactivating traymanager"); - m_activated = false; - - if (m_delayed_activation.joinable()) - m_delayed_activation.join(); - - if (g_signals::tray::report_slotcount) { - m_log.trace("tray: Report empty slotcount"); - g_signals::tray::report_slotcount(0); - } - - if (g_signals::bar::visibility_change) { - m_log.trace("tray: Clear callback handlers"); - g_signals::bar::visibility_change = nullptr; - } - - if (m_connection.get_selection_owner_unchecked(m_atom).owner() == m_tray) { - m_log.trace("tray: Unset selection owner"); - m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME); - } - - m_log.trace("tray: Unembed clients"); - m_clients.clear(); - - if (m_tray != XCB_NONE) { - if (m_mapped) { - m_log.trace("tray: Unmap window"); - m_connection.unmap_window(m_tray); - m_mapped = false; - } - - m_log.trace("tray: Destroy window"); - m_connection.destroy_window(m_tray); - m_tray = XCB_NONE; - m_hidden = false; - } - - m_connection.flush(); - } - - /** - * Reconfigure container window size and - * reposition embedded clients - */ - void reconfigure() { - // Ignore reconfigure requests when the - // tray window is in the pseudo-hidden state - if (m_hidden) - return; - if (m_tray == XCB_NONE) - return; - - 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; - } - } - - m_settings.slots = mapped_clients; - - try { - if (g_signals::tray::report_slotcount) - g_signals::tray::report_slotcount(mapped_clients); - - if (!width && m_mapped) { - m_connection.unmap_window_checked(m_tray); - return; - } else if (width && !m_mapped) { - m_connection.map_window_checked(m_tray); - m_lastwidth = 0; - return; - } else if (!width) { - return; - } else if ((width += m_settings.spacing) == m_lastwidth) { - return; - } - - m_lastwidth = width; - - // clear window to get rid of frozen artifacts - m_connection.clear_area(1, m_tray, 0, 0, 0, 0); - - // update window - const uint32_t val[2]{static_cast(calculate_x(width)), width}; - m_connection.configure_window(m_tray, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH, val); - } catch (const std::exception& err) { - m_log.err("Failed to configure tray window (%s)", err.what()); - } - - // 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) { - m_log.warn("Failed to reconfigure tray client, removing... (%s)", err.what()); - m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client)); - reconfigure(); - return; - } - } - } - - protected: - /** - * Signal handler connected to the bar window's visibility change signal. - * This is used as a fallback in case the window restacking fails. It will - * toggle the tray window whenever the visibility of the bar window changes. - */ - void bar_visibility_change(bool state) { - try { - // Ignore unchanged states - if (m_hidden == !state) - return; - - // Update the psuedo-state - m_hidden = !state; - - if (state && !m_mapped) - m_connection.map_window_checked(m_tray); - else if (!state && m_mapped) - m_connection.unmap_window_checked(m_tray); - } catch (const std::exception& err) { - m_log.warn("Failed to un-/map the tray window (%s)", err.what()); - } - } - - /** - * Calculate the tray window's horizontal position - */ - int16_t calculate_x(uint32_t width) 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); - else if (m_settings.align == alignment::CENTER) - x -= (width / 2) - (m_settings.width / 2); - 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 find_client(const xcb_window_t& win) { - for (auto&& client : m_clients) - if (client->match(win)) { - return shared_ptr{client.get(), null_deleter{}}; - } - return {}; - } - - /** - * Find the systray selection atom - */ - void query_atom() { - m_log.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(0); - auto y = calculate_y(); - auto scr = m_connection.screen(); - - m_tray = m_connection.generate_id(); - m_log.trace("tray: Create tray window %s, (%ix%i+%i+%i)", m_connection.id(m_tray), - m_settings.width, m_settings.height_fill, x, y); - - const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - const uint32_t values[4]{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_fill, 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, values); - - try { - // Put the tray window above the defined sibling in the window stack - if (m_settings.sibling != XCB_NONE) { - const uint32_t value_mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE; - const uint32_t value_list[2]{m_settings.sibling, XCB_STACK_MODE_ABOVE}; - m_connection.configure_window_checked(m_tray, value_mask, value_list); - m_connection.flush(); - m_restacked = true; - } - } catch (const std::exception& err) { - auto id = m_connection.id(m_settings.sibling); - m_log.trace("tray: Failed to put tray above %s in the stack (%s)", id, err.what()); - } - } - - /** - * Set window WM hints - */ - void set_wmhints() { - m_log.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_log.trace("tray: Set window WM_PROTOCOLS"); - vector 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_log.trace("tray: Set window _NET_WM_WINDOW_TYPE"); - vector 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_log.trace("tray: Set window _NET_WM_STATE"); - vector 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_log.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_log.trace("tray: Set window _NET_SYSTEM_TRAY_VISUAL"); - const uint32_t values2[1]{m_connection.screen()->root_visual}; - m_connection.change_property_checked(XCB_PROP_MODE_REPLACE, m_tray, - _NET_SYSTEM_TRAY_VISUAL, XCB_ATOM_VISUALID, 32, 1, values2); - - m_log.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); - } - - /** - * Set color atom used by clients when determing icon theme - */ - void set_traycolors() { - m_log.trace("tray: Set _NET_SYSTEM_TRAY_COLORS to %x", m_settings.background); - - auto c = color_util::make_32bit(m_settings.background); - auto r = color_util::red(c); - auto g = color_util::green(c); - auto b = color_util::blue(c); - - const uint32_t colors[12] = { - r, g, b, // normal - r, g, b, // error - r, g, b, // warning - r, g, b, // success - }; - - try { - m_connection.change_property_checked(XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_COLORS, - XCB_ATOM_CARDINAL, 32, 12, colors); - } catch (const std::exception& err) { - m_log.err("Failed to set tray colors"); - } - } - - /** - * 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_log.info("tray: Already managing the systray selection"); - return; - } else if ((m_othermanager = owner) != XCB_NONE) { - m_log.info("Replacing selection manager %s", m_connection.id(owner)); - } - - m_log.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"); - } - - /** - * Notify pending clients about the new systray MANAGER - */ - void notify_clients() { - m_log.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) { - try { - if (owner == XCB_NONE) - return; - m_log.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(owner, XCB_CW_EVENT_MASK, event_mask); - } catch (const xpp::x::error::window& err) { - m_log.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; - } - - /** - * Calculates a client's y position - */ - int calculate_client_ypos() { - return (m_settings.height_fill - m_settings.height) / 2; - } - - /** - * Process client docking request - */ - void process_docking_request(xcb_window_t win) { - auto client = find_client(win); - if (client) { - if (client->mapped()) { - m_log.trace("tray: Client %s is already embedded, skipping...", m_connection.id(win)); - } else { - m_log.trace("tray: Refresh _XEMBED_INFO"); - xembed::query(m_connection, win, client->xembed()); - - if ((client->xembed()->flags & XEMBED_MAPPED) == XEMBED_MAPPED) { - m_log.trace("tray: XEMBED_MAPPED flag set, map client window..."); - try { - m_connection.map_window_checked(client->window()); - } catch (const xpp::x::error::window& err) { - m_log.warn("Failed to map tray client, removing... (%s)", err.what()); - m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client)); - return; - } - } - } - - return; - } - - m_log.trace("tray: Process docking request from %s", m_connection.id(win)); - m_clients.emplace_back(make_shared(m_connection, win)); - client = m_clients.back(); - - try { - m_log.trace("tray: Get client _XEMBED_INFO"); - xembed::query(m_connection, win, client->xembed()); - } catch (const application_error& err) { - m_log.err(err.what()); - } catch (const xpp::x::error::window& err) { - m_log.warn("Failed to query for _XEMBED_INFO, removing client... (%s)", err.what()); - m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client)); - return; - } - - try { - m_log.trace("tray: Add tray client window to the save set"); - m_connection.change_save_set_checked(XCB_SET_MODE_INSERT, client->window()); - - m_log.trace("tray: Update tray client event mask"); - const uint32_t event_mask[]{XCB_BACK_PIXMAP_PARENT_RELATIVE, - XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY}; - m_connection.change_window_attributes_checked( - client->window(), XCB_CW_BACK_PIXMAP | XCB_CW_EVENT_MASK, event_mask); - - m_log.trace("tray: Reparent tray client"); - m_connection.reparent_window_checked( - client->window(), m_tray, m_settings.spacing, calculate_client_ypos()); - - m_log.trace("tray: Configure tray client size"); - const uint32_t values[]{m_settings.width, m_settings.height}; - m_connection.configure_window_checked( - client->window(), XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); - - m_log.trace("tray: Send embbeded notification to tray client"); - xembed::notify_embedded(m_connection, client->window(), m_tray, client->xembed()->version); - - m_log.trace("tray: Map tray client"); - m_connection.map_window_checked(client->window()); - } catch (const xpp::x::error::window& err) { - m_log.warn("Failed to map client, removing... (%s)", err.what()); - m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client)); - } - } - - /** - * Event callback : XCB_EXPOSE - */ - void handle(const evt::expose& evt) { - if (!m_activated) - return; - if (m_clients.empty()) - return; - m_log.trace("tray: Received expose event for %s", m_connection.id(evt->window)); - reconfigure(); - } - - /** - * Event callback : XCB_VISIBILITY_NOTIFY - */ - void handle(const evt::visibility_notify& evt) { - if (!m_activated || m_clients.empty()) - return; - if (m_clients.empty()) - return; - m_log.trace("tray: Received visibility_notify for %s", m_connection.id(evt->window)); - reconfigure(); - } - - /** - * Event callback : XCB_CLIENT_MESSAGE - */ - void handle(const evt::client_message& evt) { - if (!m_activated) - return; - - if (evt->type == _NET_SYSTEM_TRAY_OPCODE && evt->format == 32) { - m_log.trace("tray: Received client_message"); - - switch (evt->data.data32[1]) { - case SYSTEM_TRAY_REQUEST_DOCK: - try { - process_docking_request(evt->data.data32[2]); - } catch (const std::exception& err) { - auto id = m_connection.id(evt->data.data32[2]); - m_log.err("Error while processing docking request for %s (%s)", id, err.what()); - } - 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_log.warn("Received WM_DELETE"); - m_tray = XCB_NONE; - deactivate(); - } - } - } - - /** - * 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 (!m_activated) - return; - - auto client = find_client(evt->window); - if (client) { - m_log.trace("tray: Received configure_request for client %s", m_connection.id(evt->window)); - client->configure_notify(calculate_client_xpos(evt->window), calculate_client_ypos(), - m_settings.width, m_settings.height); - } - } - - /** - * @see tray_manager::handle(const evt::configure_request&); - */ - void handle(const evt::resize_request& evt) { - if (!m_activated) - return; - - auto client = find_client(evt->window); - if (client) { - m_log.trace("tray: Received resize_request for client %s", m_connection.id(evt->window)); - client->configure_notify(calculate_client_xpos(evt->window), calculate_client_ypos(), - m_settings.width, m_settings.height); - } - } - - /** - * Event callback : XCB_SELECTION_CLEAR - */ - void handle(const evt::selection_clear& evt) { - if (!m_activated) - return; - if (evt->selection != m_atom) - return; - if (evt->owner != m_tray) - return; - - try { - m_log.warn("Lost systray selection, deactivating..."); - m_othermanager = m_connection.get_selection_owner(m_atom)->owner; - track_selection_owner(m_othermanager); - } catch (const std::exception& err) { - m_log.err("Failed to get systray selection owner"); - m_othermanager = XCB_NONE; - } - - deactivate(); - } - - /** - * Event callback : XCB_PROPERTY_NOTIFY - */ - void handle(const evt::property_notify& evt) { - if (!m_activated) - return; - if (evt->atom != _XEMBED_INFO) - return; - - m_log.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_log.trace("tray: _XEMBED_INFO value has changed"); - - xembed::query(m_connection, win, xd); - m_log.trace("tray: _XEMBED_INFO[0]=%u _XEMBED_INFO[1]=%u", xd->version, xd->flags); - - try { - if (!client->mapped() && ((xd->flags & XEMBED_MAPPED) == XEMBED_MAPPED)) { - m_log.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_log.info("tray: Unmap client window: %s", m_connection.id(win)); - m_connection.unmap_window(win); - } - } catch (const xpp::x::error::window& err) { - m_log.warn("Failed to map/unmap client, removing... (%s)", err.what()); - m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client)); - } - } - - /** - * Event callback : XCB_REPARENT_NOTIFY - */ - void handle(const evt::reparent_notify& evt) { - if (!m_activated) - return; - if (evt->parent == m_tray) - return; - - auto client = find_client(evt->window); - - if (client) { - m_log.trace("tray: Received reparent_notify"); - m_log.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) { - if (!m_activated && evt->window == m_othermanager) { - m_log.trace("tray: Received destroy_notify"); - m_log.trace("tray: Systray selection is available... re-activating"); - activate(); - } else if (m_activated) { - auto client = find_client(evt->window); - - if (client) { - m_log.trace("tray: Received destroy_notify"); - m_log.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 (!m_activated) - return; - - if (evt->window == m_tray && !m_mapped) { - m_log.trace("tray: Received map_notify"); - if (m_mapped) - return; - m_log.trace("tray: Update container mapped flag"); - m_mapped = true; - reconfigure(); - } else { - auto client = find_client(evt->window); - if (client) { - m_log.trace("tray: Received map_notify"); - m_log.trace("tray: Set client mapped"); - client->mapped(true); - reconfigure(); - } - } - } - - /** - * Event callback : XCB_UNMAP_NOTIFY - */ - void handle(const evt::unmap_notify& evt) { - if (!m_activated) - return; - - if (evt->window == m_tray) { - m_log.trace("tray: Received unmap_notify"); - if (!m_mapped) - return; - m_log.trace("tray: Update container mapped flag"); - m_mapped = false; - reconfigure(); - } else { - auto client = find_client(evt->window); - - if (client) { - m_log.trace("tray: Received unmap_notify"); - m_log.trace("tray: Set client unmapped"); - client->mapped(true); - reconfigure(); - } - } - } - - private: - connection& m_connection; - const logger& m_log; - vector> 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_hidden{false}; - stateflag m_sinkattached{false}; - - thread m_delayed_activation; - - bool m_restacked = false; -}; - -// }}} - -namespace { - /** - * Configure injection module - */ - template > - di::injector configure_traymanager() { - return di::make_injector(configure_logger(), configure_connection()); - } -} - -LEMONBUDDY_NS_END diff --git a/include/components/x11/xlib.hpp b/include/components/x11/xlib.hpp deleted file mode 100644 index 0f50e019..00000000 --- a/include/components/x11/xlib.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include - -#include "common.hpp" - -LEMONBUDDY_NS - -namespace xlib { - static Display* g_display = nullptr; - static XVisualInfo g_visual_info; - - /** - * 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(int screen = 0) { - if (g_visual_info.visual == nullptr) { - XMatchVisualInfo(get_display(), screen, 32, TrueColor, &g_visual_info); - } - - return g_visual_info.visual; - } - - inline Colormap create_colormap(int screen = 0) { - return XCreateColormap(get_display(), XRootWindow(get_display(), screen), get_visual(), screen); - } - - /** - * 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(get_display()); - // } -} - -LEMONBUDDY_NS_END diff --git a/include/components/x11/xresources.hpp b/include/components/x11/xresources.hpp deleted file mode 100644 index 62338a56..00000000 --- a/include/components/x11/xresources.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include -#include - -#include "common.hpp" -#include "components/x11/xlib.hpp" - -LEMONBUDDY_NS - -class xresource_manager { - public: - explicit xresource_manager() { - XrmInitialize(); - - if (xlib::get_display() == nullptr) - return; - if ((m_manager = XResourceManagerString(xlib::get_display())) == nullptr) - return; - if ((m_db = XrmGetStringDatabase(m_manager)) == nullptr) - return; - } - - string get_string(string name) const { - return load_value(name, "String", 64); - } - - float get_float(string name) const { - return std::strtof(load_value(name, "String", 64).c_str(), 0); - } - - int get_int(string name) const { - return std::atoi(load_value(name, "String", 64).c_str()); - } - - protected: - string load_value(string key, string res_type, size_t n) const { - if (m_manager == nullptr) - return ""; - - char* type = nullptr; - XrmValue ret; - XrmGetResource(m_db, key.c_str(), res_type.c_str(), &type, &ret); - - if (ret.addr != nullptr && !std::strncmp(res_type.c_str(), type, n)) { - return {ret.addr}; - } - - return ""; - } - - private: - char* m_manager = nullptr; - XrmDatabase m_db; -}; - -namespace { - /** - * Configure injection module - */ - template - di::injector configure_xresource_manager() { - auto instance = factory::generic_singleton(); - return di::make_injector(di::bind<>().to(instance)); - } -} - -LEMONBUDDY_NS_END diff --git a/include/config.hpp.cmake b/include/config.hpp.cmake index 6b5c6e22..e025cc75 100644 --- a/include/config.hpp.cmake +++ b/include/config.hpp.cmake @@ -12,11 +12,12 @@ #cmakedefine01 ENABLE_NETWORK #cmakedefine01 ENABLE_I3 -#cmakedefine DISABLE_MODULES -#cmakedefine DISABLE_TRAY -#cmakedefine DISABLE_DRAW +#cmakedefine ENABLE_RANDR_EXT +#cmakedefine ENABLE_RENDER_EXT +#cmakedefine ENABLE_DAMAGE_EXT -#cmakedefine ENABLE_VERBOSE_TRACELOG +#cmakedefine DEBUG_LOGGER +#cmakedefine VERBOSE_TRACELOG #ifdef DEBUG #cmakedefine01 DRAW_CLICKABLE_AREA_HINTS diff --git a/include/drawtypes/animation.hpp b/include/drawtypes/animation.hpp index d80cec6b..3f5dd613 100644 --- a/include/drawtypes/animation.hpp +++ b/include/drawtypes/animation.hpp @@ -17,70 +17,25 @@ namespace drawtypes { , m_framecount(m_frames.size()) , m_lastupdate(chrono::system_clock::now()) {} - void add(icon_t&& frame) { - m_frames.emplace_back(forward(frame)); - m_framecount = m_frames.size(); - } - - icon_t get() { - tick(); - return m_frames[m_frame]; - } - - int framerate() { - return m_framerate_ms; - } - - operator bool() { - return !m_frames.empty(); - } + void add(icon_t&& frame); + icon_t get(); + int framerate(); + operator bool(); protected: + void tick(); + vector 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(now - m_lastupdate); - - if (diff.count() < m_framerate_ms) - return; - if (++m_frame >= m_framecount) - m_frame = 0; - - m_lastupdate = now; - } }; using animation_t = shared_ptr; - /** - * Create an animation by loading values - * from the configuration - */ - inline auto load_animation( - const config& conf, string section, string name = "animation", bool required = true) { - vector vec; - vector frames; - - name = string_util::ltrim(string_util::rtrim(name, '>'), '<'); - - if (required) - frames = conf.get_list(section, name); - else - frames = conf.get_list(section, name, {}); - - for (size_t i = 0; i < frames.size(); i++) - vec.emplace_back(forward( - load_optional_icon(conf, section, name + "-" + to_string(i), frames[i]))); - - auto framerate = conf.get(section, name + "-framerate", 1000); - - return animation_t{new animation(move(vec), framerate)}; - } + animation_t load_animation( + const config& conf, string section, string name = "animation", bool required = true); } LEMONBUDDY_NS_END diff --git a/include/drawtypes/iconset.hpp b/include/drawtypes/iconset.hpp index c55e6f99..4250f161 100644 --- a/include/drawtypes/iconset.hpp +++ b/include/drawtypes/iconset.hpp @@ -9,24 +9,10 @@ LEMONBUDDY_NS namespace drawtypes { class iconset : public non_copyable_mixin { public: - void add(string id, icon_t&& icon) { - m_icons.emplace(id, forward(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; - } + void add(string id, icon_t&& icon); + bool has(string id); + icon_t get(string id, string fallback_id = ""); + operator bool(); protected: map m_icons; diff --git a/include/drawtypes/label.hpp b/include/drawtypes/label.hpp index 82f4e890..a95603f0 100644 --- a/include/drawtypes/label.hpp +++ b/include/drawtypes/label.hpp @@ -6,10 +6,18 @@ LEMONBUDDY_NS +/** + * TODO: Remove icon_t + */ + namespace drawtypes { class label; - using icon = label; using label_t = shared_ptr