├── test └── unit_test │ ├── test_python3 │ ├── test.py │ ├── python3_test_dir │ │ └── hello_world.py │ └── test_python3.cc │ ├── test_dynlib │ ├── lib.c │ ├── Makefile │ └── test_dynlib.cc │ ├── run_tests.sh │ └── Makefile ├── screenshot.png ├── run_all_builds.sh ├── .gitmodules ├── CONTRIBUTING.md ├── src ├── modules │ ├── LoadPrinter.hpp │ ├── TimePrinter.hpp │ ├── BatteryPrinter.hpp │ ├── CustomPrinter.hpp │ ├── VolumePrinter.hpp │ ├── BacklightPrinter.hpp │ ├── MemoryUsagePrinter.hpp │ ├── TemperaturePrinter.hpp │ ├── NetworkInterfacesPrinter.hpp │ ├── VolumePrinter.cc │ ├── CustomPrinter.cc │ ├── NetworkInterfacesPrinter.cc │ ├── TemperaturePrinter.cc │ ├── TimePrinter.cc │ ├── BacklightPrinter.cc │ ├── BatteryPrinter.cc │ ├── LoadPrinter.cc │ ├── Base.hpp │ ├── MemoryUsagePrinter.cc │ └── Base.cc ├── alsa.h ├── error_handling.hpp ├── formatting │ ├── fmt_utility.cc │ ├── fmt_utility.hpp │ ├── fmt_config.cc │ ├── Conditional.cc │ ├── fmt_config.hpp │ ├── Conditional.hpp │ ├── printer.cc │ ├── LazyEval.hpp │ └── printer.hpp ├── help.h ├── Fd.cc ├── poller.h ├── _vimrc_local.vim ├── Fd.hpp ├── handle_click_events.h ├── mem_size_t.hpp ├── Backlight.hpp ├── cxx_utility.cc ├── Callback │ ├── dynlib.cc │ ├── dynlib.hpp │ ├── Callable.cc │ ├── Callable.hpp │ ├── python3.hpp │ └── python3.cc ├── Battery.hpp ├── Backlight.cc ├── poller.c ├── alsa.c ├── sensors.hpp ├── process_configuration.h ├── mem_size_t.cc ├── Makefile ├── networking.hpp ├── utility.h ├── sensors.cc ├── swaystatus.cc ├── Battery.cc ├── handle_click_events.cc ├── process_configuration.c ├── utility.c └── networking.cc ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── LICENSE ├── draft_github_release.sh ├── example-config.json └── README.md /test/unit_test/test_python3/test.py: -------------------------------------------------------------------------------- 1 | def identity(a): 2 | return a 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NobodyXu/swaystatus/HEAD/screenshot.png -------------------------------------------------------------------------------- /run_all_builds.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | cd $(dirname $0)/src 4 | exec make all_builds -j $(nproc) 5 | -------------------------------------------------------------------------------- /test/unit_test/test_python3/python3_test_dir/hello_world.py: -------------------------------------------------------------------------------- 1 | def hello(): 2 | return "Hello, world!" 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/dep/fmt"] 2 | path = src/formatting/fmt 3 | url = https://github.com/fmtlib/fmt.git 4 | -------------------------------------------------------------------------------- /test/unit_test/test_dynlib/lib.c: -------------------------------------------------------------------------------- 1 | int f1() 2 | { 3 | return 1; 4 | } 5 | 6 | int f2(int *param) 7 | { 8 | return *param; 9 | } 10 | -------------------------------------------------------------------------------- /test/unit_test/test_dynlib/Makefile: -------------------------------------------------------------------------------- 1 | CC := clang 2 | 3 | all: libtest_lib.so 4 | mkdir -p test_dir/ 5 | cp $< test_dir/libtest_lib2.so 6 | 7 | libtest_lib.so: lib.c Makefile 8 | $(CC) -fPIC -shared $< -o $@ 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | - To implement new function for printing system information, please create `modules/print_.h` and `modules/print_.cc`. 2 | - Write code as simple as possible 3 | - Do not `#include` external library unless absolutely necessary 4 | -------------------------------------------------------------------------------- /src/modules/LoadPrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_LoadPrinter_H__ 2 | # define __swaystatus_LoadPrinter_H__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeLoadPrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/modules/TimePrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_TimePrinter_HPP__ 2 | # define __swaystatus_TimePrinter_HPP__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeTimePrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/modules/BatteryPrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_BatteryPrinter_H__ 2 | # define __swaystatus_BatteryPrinter_H__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeBatteryPrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/modules/CustomPrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_CustomPrinter_HPP__ 2 | # define __swaystatus_CustomPrinter_HPP__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeCustomPrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/modules/VolumePrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_VolumePrinter_HPP__ 2 | # define __swaystatus_VolumePrinter_HPP__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeVolumePrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/modules/BacklightPrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_BacklightPrinter_H__ 2 | # define __swaystatus_BacklightPrinter_H__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeBacklightPrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /test/unit_test/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exit_code=0 4 | 5 | for each in $@; do 6 | echo -e '\nRunning' $each ...'\n' 7 | 8 | cd $(dirname $each) 9 | ./$(basename $each) 10 | 11 | [ $? -ne 0 ] && exit_code=1 12 | 13 | cd .. 14 | 15 | echo 16 | done 17 | 18 | exit $exit_code 19 | -------------------------------------------------------------------------------- /src/modules/MemoryUsagePrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_MemoryUsagePrinter_HPP__ 2 | # define __swaystatus_MemoryUsagePrinter_HPP__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeMemoryUsagePrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/modules/TemperaturePrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_TemperaturePrinter_HPP__ 2 | # define __swaystatus_TemperaturePrinter_HPP__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeTemperaturePrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/modules/NetworkInterfacesPrinter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_NetworkInterfacesPrinter_HPP__ 2 | # define __swaystatus_NetworkInterfacesPrinter_HPP__ 3 | 4 | # include "Base.hpp" 5 | 6 | namespace swaystatus::modules { 7 | std::unique_ptr makeNetworkInterfacesPrinter(void *config); 8 | } /* namespace swaystatus::modules */ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/alsa.h: -------------------------------------------------------------------------------- 1 | /** 2 | * functions relating to alsa are put here since alsa/asoundlib.h doesn't work well with C++ 3 | */ 4 | #ifndef __swaystatus_alsa_H__ 5 | # define __swaystatus_alsa_H__ 6 | 7 | # ifdef __cplusplus 8 | extern "C" { 9 | # endif 10 | 11 | void initialize_alsa_lib(const char *mix_name, const char *card); 12 | 13 | void update_volume(); 14 | long get_audio_volume(); 15 | 16 | # ifdef __cplusplus 17 | } 18 | # endif 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/error_handling.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_error_handling_HPP__ 2 | # define __swaystatus_error_handling_HPP__ 3 | 4 | # ifdef __cplusplus 5 | # if (defined(__GNUC__) && defined(__EXCEPTIONS)) || FMT_MSC_VER && _HAS_EXCEPTIONS 6 | # define CXX_HAS_EXCEPTION 7 | # endif 8 | # endif 9 | 10 | # ifdef CXX_HAS_EXCEPTION 11 | # define TRY try 12 | /** 13 | * Usage: 14 | * CATCH (const std::exception &e) { 15 | * // do anything here 16 | * }; 17 | */ 18 | # define CATCH catch 19 | # else 20 | # define TRY 21 | # define CATCH [&] 22 | # endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/formatting/fmt_utility.cc: -------------------------------------------------------------------------------- 1 | #include "fmt_utility.hpp" 2 | 3 | namespace swaystatus { 4 | format_parse_context_it find_end_of_format(format_parse_context &ctx) 5 | { 6 | size_t level = 1; 7 | 8 | for (auto it = ctx.begin(); it != ctx.end(); ++it) { 9 | if (*it == '{') 10 | ++level; 11 | if (*it == '}') 12 | --level; 13 | if (level == 0) 14 | return it; 15 | } 16 | 17 | FMT_THROW(fmt::format_error("invalid format: Unterminated '{'")); 18 | __builtin_unreachable(); 19 | } 20 | } /* namespace swaystatus */ 21 | -------------------------------------------------------------------------------- /src/help.h: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_help_H__ 2 | # define __swaystatus_help_H__ 3 | 4 | # ifdef __cplusplus 5 | extern "C" { 6 | # endif 7 | 8 | static const char * const help = 9 | "Usage: swaystatus [options] configuration_filename\n\n" 10 | " --help Show help message and exit\n" 11 | " --interval=unsigned_msec Specify update interval in milliseconds, must be an unsigner " 12 | "integer.\n" 13 | " By default, the interval is set to 1000 ms.\n\n"; 14 | 15 | # ifdef __cplusplus 16 | } 17 | # endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/formatting/fmt_utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_fmt_utility_HPP__ 2 | # define __swaystatus_fmt_utility_HPP__ 3 | 4 | # include "fmt_config.hpp" 5 | # include "fmt/include/fmt/format.h" 6 | 7 | namespace swaystatus { 8 | using fmt::format_parse_context; 9 | using format_parse_context_it = typename format_parse_context::iterator; 10 | 11 | /** 12 | * Usage: 13 | * auto it = ctx.begin(), end = ctx.end(); 14 | * if (it == end) 15 | * return it; 16 | * 17 | * end = find_end_of_format(ctx); 18 | */ 19 | format_parse_context_it find_end_of_format(format_parse_context &ctx); 20 | } /* namespace swaystatus */ 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /test/unit_test/test_dynlib/test_dynlib.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../../src/utility.h" 6 | #include "../../../src/Callback/dynlib.hpp" 7 | 8 | using namespace swaystatus; 9 | 10 | int main() 11 | { 12 | { 13 | char *path = realpath_checked("test_dir"); 14 | setup_dlpath(path); 15 | std::free(path); 16 | } 17 | 18 | int val = 2; 19 | 20 | assert((CFunction{"libtest_lib.so", "f1"}()) == 1); 21 | assert((CFunction{"libtest_lib.so", "f2"}(val)) == val); 22 | 23 | assert((CFunction{"libtest_lib2.so", "f1"}()) == 1); 24 | assert((CFunction{"libtest_lib2.so", "f2"}(val)) == val); 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /src/Fd.cc: -------------------------------------------------------------------------------- 1 | #include /* For close */ 2 | 3 | #include "Fd.hpp" 4 | 5 | namespace swaystatus { 6 | Fd::Fd(int fd_arg) noexcept: 7 | fd{fd_arg} 8 | {} 9 | 10 | Fd::Fd(Fd &&other) noexcept: 11 | fd{other.fd} 12 | { 13 | other.fd = -1; 14 | } 15 | 16 | Fd& Fd::operator = (Fd &&other) noexcept 17 | { 18 | destroy(); 19 | fd = other.fd; 20 | other.fd = -1; 21 | 22 | return *this; 23 | } 24 | 25 | void Fd::destroy() noexcept 26 | { 27 | if (*this) 28 | close(fd); 29 | } 30 | 31 | Fd::~Fd() 32 | { 33 | destroy(); 34 | } 35 | 36 | Fd::operator bool () const noexcept 37 | { 38 | return fd != -1; 39 | } 40 | int Fd::get() const noexcept 41 | { 42 | return fd; 43 | } 44 | } /* namespace swaystatus */ 45 | -------------------------------------------------------------------------------- /src/poller.h: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_poller_HPP__ 2 | # define __swaystatus_poller_HPP__ 3 | 4 | # ifdef __cplusplus 5 | extern "C" { 6 | # endif 7 | 8 | void init_poller(); 9 | 10 | enum Event { 11 | read_ready = 1 << 0, 12 | /** 13 | * error, hup and invalid_fd can be set in event for the poller_callback, 14 | * no matter it is registed with it or not. 15 | */ 16 | error = 1 << 2, 17 | hup = 1 << 3, 18 | invalid_fd = -1, 19 | }; 20 | typedef void (*poller_callback)(int fd, enum Event events, void *data); 21 | 22 | void request_polling(int fd, enum Event events, poller_callback callback, void *data); 23 | 24 | void perform_polling(int timeout); 25 | 26 | # ifdef __cplusplus 27 | } 28 | # endif 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/_vimrc_local.vim: -------------------------------------------------------------------------------- 1 | let g:ale_linter_aliases = {'h': 'c', 'hpp': 'cpp', 'cc': 'cpp'} 2 | 3 | let g:ale_c_parse_makefile = 1 4 | let g:ale_c_parse_compile_commands = 1 5 | 6 | let include_path = system("pkg-config --cflags alsa json-c") 7 | 8 | let g:ale_c_clangd_options = '-std=c11' . expand(include_path) 9 | let g:ale_c_clangtidy_options = "-std=c11 " . expand(include_path) 10 | let g:ale_c_clangcheck_options = "--extra-arg=-std=c11 " . expand(include_path) 11 | 12 | let g:ale_cpp_clangd_options = '-std=c++17' . expand(include_path) 13 | let g:ale_cpp_clangtidy_options = "-std=c++17 " . expand(include_path) 14 | let g:ale_cpp_clangcheck_options = "--extra-arg=-std=c++17 " . expand(include_path) 15 | let g:ale_cpp_cppcheck_options = "--std=c++17 " . expand(include_path) 16 | -------------------------------------------------------------------------------- /src/formatting/fmt_config.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../utility.h" 5 | 6 | #include "fmt_config.hpp" 7 | 8 | namespace swaystatus { 9 | static const char *module = nullptr; 10 | void fmt_set_calling_module(const char *module_arg) noexcept 11 | { 12 | (void) module_arg; 13 | #ifndef CXX_HAS_EXCEPTION 14 | module = module_arg; 15 | #endif 16 | } 17 | 18 | [[noreturn]] 19 | void fmt_throw_impl(const std::exception &e, const char *func, int line, const char *file) noexcept 20 | { 21 | std::fprintf(stderr, 22 | "Error when formatting in module %s: %s in %s, %d, %s\n", 23 | module ? module : "nullptr", e.what(), func, line, file); 24 | stack_bt(); 25 | std::exit(1); 26 | } 27 | } /* namespace swaystatus */ 28 | -------------------------------------------------------------------------------- /src/Fd.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_Fd_HPP__ 2 | # define __swaystatus_Fd_HPP__ 3 | 4 | namespace swaystatus { 5 | class Fd { 6 | int fd = -1; 7 | 8 | public: 9 | Fd() = default; 10 | 11 | /** 12 | * @param must be a valid file descripter 13 | */ 14 | Fd(int fd) noexcept; 15 | 16 | Fd(const Fd&) = delete; 17 | Fd(Fd&&) noexcept; 18 | 19 | Fd& operator = (const Fd&) = delete; 20 | Fd& operator = (Fd&&) noexcept; 21 | 22 | void destroy() noexcept; 23 | 24 | ~Fd(); 25 | 26 | /** 27 | * @return true if Fd contains a valid file descripter 28 | */ 29 | explicit operator bool () const noexcept; 30 | /** 31 | * @return valid fd if this->operator bool() returns true. 32 | */ 33 | int get() const noexcept; 34 | }; 35 | } /* namespace swaystatus */ 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | */swaystatus 55 | 56 | *_build/ 57 | 58 | compile_flags.txt 59 | compile_commands.json 60 | 61 | test/unit_test/build_dir 62 | test/unit_test/test_python3.plist 63 | 64 | # Ignore python 65 | __pycache__ 66 | -------------------------------------------------------------------------------- /src/formatting/Conditional.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fmt_utility.hpp" 4 | #include "Conditional.hpp" 5 | 6 | using formatter = fmt::formatter; 7 | 8 | using swaystatus::find_end_of_format; 9 | 10 | auto formatter::parse(format_parse_context &ctx) -> format_parse_context_it 11 | { 12 | /* 13 | * This function assumes that [it, end) points to the format passed from swaystatus::vprint 14 | */ 15 | auto it = ctx.begin(), end = ctx.end(); 16 | if (it == end) 17 | return it; 18 | 19 | end = find_end_of_format(ctx); 20 | 21 | if_true_str = std::string_view{it, static_cast(end - it)}; 22 | 23 | return end; 24 | } 25 | 26 | auto formatter::format(const Conditional &cond, format_context &ctx) -> format_context_it 27 | { 28 | if (cond) 29 | return vformat_to(ctx.out(), if_true_str, ctx.args()); 30 | else 31 | return ctx.out(); 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Actual behavior** 24 | A clear and concise description of what have actually happened. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - The linux flavor you use 31 | - `swaystatus` version 32 | - `swaystatus` configuration 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jiahao XU 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/handle_click_events.h: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_handle_click_events_H__ 2 | # define __swaystatus_handle_click_events_H__ 3 | 4 | # include 5 | # include 6 | 7 | # define CALLBACK_CNT 12 8 | 9 | # ifdef __cplusplus 10 | extern "C" { 11 | 12 | enum class ClickHandlerRequest: uint8_t { 13 | none = 0, 14 | update = 1, 15 | reload = 2, 16 | }; 17 | 18 | inline uint8_t operator & (const ClickHandlerRequest &x, const ClickHandlerRequest &y) noexcept 19 | { 20 | return static_cast(x) & static_cast(y); 21 | } 22 | 23 | # endif 24 | 25 | void init_click_events_handling(); 26 | 27 | /** 28 | * @param click_event_handler_config if equals to NULL, return without doing anything. 29 | * @return NULL if click_event_handler_config is NULL, otherwise it will be the events 30 | * requested by the callback (a bitwise or of all return value) 31 | * 32 | * Be sure to set the *(ret ptr) to 0 after you processed all events in it. 33 | */ 34 | uint8_t* add_click_event_handler(const char *name, const void *click_event_handler_config); 35 | 36 | # ifdef __cplusplus 37 | } 38 | # endif 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/formatting/fmt_config.hpp: -------------------------------------------------------------------------------- 1 | #include "../error_handling.hpp" 2 | 3 | #include 4 | 5 | #ifndef FMT_HEADER_ONLY 6 | # define FMT_HEADER_ONLY 7 | #endif 8 | 9 | #ifndef FMT_USE_FLOAT 10 | # define FMT_USE_FLOAT 0 11 | #endif 12 | 13 | #ifndef FMT_USE_DOUBLE 14 | # define FMT_USE_DOUBLE 0 15 | #endif 16 | 17 | #ifndef FMT_USE_LONG_DOUBLE 18 | # define FMT_USE_LONG_DOUBLE 0 19 | #endif 20 | 21 | # ifndef FMT_STATIC_THOUSANDS_SEPARATOR 22 | # define FMT_STATIC_THOUSANDS_SEPARATOR ',' 23 | # endif 24 | 25 | #ifndef __swaystatus_fmt_config_HPP__ 26 | # define __swaystatus_fmt_config_HPP__ 27 | 28 | namespace swaystatus { 29 | /** 30 | * @param module nullptr to mark the completion current module 31 | */ 32 | void fmt_set_calling_module(const char *module) noexcept; 33 | 34 | [[noreturn]] 35 | void fmt_throw_impl(const std::exception &e, const char *func, int line, const char *file) noexcept; 36 | } /* namespace swaystatus */ 37 | 38 | #endif 39 | 40 | // Check if exceptions are disabled, copied from fmt 41 | #ifndef CXX_HAS_EXCEPTION 42 | # define FMT_THROW(x) ::swaystatus::fmt_throw_impl((x), __PRETTY_FUNCTION__, __LINE__, __FILE__) 43 | #endif 44 | -------------------------------------------------------------------------------- /src/formatting/Conditional.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Conditional is an extension to fmt 3 | * 4 | * Format: "{format_arg_name:format_str}" 5 | * 6 | * Yes, its'right: Conditional supports recursive format_str. 7 | */ 8 | 9 | #ifndef __swaystatus_conditional_H__ 10 | # define __swaystatus_conditional_H__ 11 | 12 | # include "fmt_config.hpp" 13 | 14 | # include 15 | # include 16 | 17 | # include "fmt/include/fmt/format.h" 18 | 19 | namespace swaystatus { 20 | struct Conditional { 21 | bool value; 22 | 23 | constexpr operator bool () const noexcept 24 | { 25 | return value; 26 | } 27 | }; 28 | } 29 | 30 | 31 | template <> 32 | struct fmt::formatter 33 | { 34 | using Conditional = swaystatus::Conditional; 35 | 36 | using format_parse_context_it = typename format_parse_context::iterator; 37 | using format_context_it = typename format_context::iterator; 38 | 39 | std::string_view if_true_str = ""; 40 | 41 | auto parse(format_parse_context &ctx) -> format_parse_context_it; 42 | auto format(const Conditional &cond, format_context &ctx) -> format_context_it; 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/modules/VolumePrinter.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../process_configuration.h" 4 | #include "../alsa.h" 5 | 6 | #include "VolumePrinter.hpp" 7 | 8 | using namespace std::literals; 9 | 10 | namespace swaystatus::modules { 11 | class VolumePrinter: public Base { 12 | ; 13 | 14 | public: 15 | VolumePrinter(void *config, const char *mix_name, const char *card): 16 | Base{ 17 | config, "VolumePrinter"sv, 18 | 1, "vol {volume}%", nullptr, 19 | "mix_name", "card" 20 | } 21 | { 22 | initialize_alsa_lib(mix_name, card); 23 | } 24 | 25 | void update() 26 | { 27 | update_volume(); 28 | } 29 | void do_print(const char *format) 30 | { 31 | print(format, fmt::arg("volume", get_audio_volume())); 32 | } 33 | void reload() 34 | {} 35 | }; 36 | 37 | std::unique_ptr makeVolumePrinter(void *config) 38 | { 39 | std::unique_ptr mix_name{get_property(config, "mix_name", "Master")}; 40 | std::unique_ptr card {get_property(config, "card", "default")}; 41 | 42 | return std::make_unique(config, mix_name.get(), card.get()); 43 | } 44 | } /* namespace swaystatus::modules */ 45 | -------------------------------------------------------------------------------- /src/mem_size_t.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_mem_size_t_H__ 2 | # define __swaystatus_mem_size_t_H__ 3 | 4 | # include "formatting/fmt_config.hpp" 5 | 6 | # include 7 | # include "formatting/fmt/include/fmt/format.h" 8 | 9 | namespace swaystatus { 10 | struct mem_size_t { 11 | size_t bytes; 12 | }; 13 | } 14 | 15 | /** 16 | * Expected replacement field for mem_size_t: "{A|K|M|G|T|P|E|Z|Y} {integer format}" 17 | */ 18 | template <> 19 | struct fmt::formatter: fmt::formatter { 20 | using format_parse_context = fmt::format_parse_context; 21 | using format_context = fmt::format_context; 22 | using mem_size_t = swaystatus::mem_size_t; 23 | 24 | using format_parse_context_it = typename format_parse_context::iterator; 25 | using format_context_it = typename format_context::iterator; 26 | 27 | char presentation = 'A'; 28 | 29 | auto format_impl(size_t mem, const char *unit, format_context &ctx) -> format_context_it; 30 | auto auto_format(size_t mem, format_context &ctx) -> format_context_it; 31 | 32 | auto parse(format_parse_context &ctx) -> format_parse_context_it; 33 | auto format(const mem_size_t &sz, format_context &ctx) -> format_context_it; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /test/unit_test/Makefile: -------------------------------------------------------------------------------- 1 | CXX := clang++ 2 | 3 | CFLAGS := -Og -g -Wall 4 | 5 | LIBS := $(shell pkg-config --libs alsa json-c) -ldl -lsensors 6 | LIBS += $(shell python3-config --ldflags --embed) 7 | 8 | ## Objects to build 9 | C_SRCS := $(shell find -name 'test_*.c') 10 | C_OBJS := $(C_SRCS:.c=.out) 11 | 12 | CXX_SRCS := $(shell find -name 'test_*.cc') 13 | CXX_OBJS := $(CXX_SRCS:.cc=.out) 14 | 15 | OBJS := $(C_OBJS) $(CXX_OBJS) 16 | 17 | DEPS := $(filter-out build_dir/swaystatus.o, $(shell find build_dir/ -name '*.o')) 18 | 19 | ## Build rules 20 | all: 21 | mkdir -p build_dir 22 | mkdir -p build_dir/Callback 23 | mkdir -p build_dir/formatting 24 | mkdir -p build_dir/modules 25 | BUILD_DIR=${PWD}/build_dir PYTHON=true DEBUG=true $(MAKE) -C ../../src/ 26 | $(MAKE) build 27 | ./run_tests.sh $(OBJS) 28 | 29 | build: $(OBJS) 30 | 31 | %.out: %.cc $(DEPS) Makefile 32 | $(CXX) -std=c++17 $(CXXFLAGS) $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ $< $(DEPS) 33 | # Allow subdir to have their own Makefile for building like library. 34 | ([ -f "$$(dirname $<)/Makefile" ] && $(MAKE) -C $$(dirname $<)) || echo 35 | 36 | compile_commands.json: Makefile 37 | compiledb make build -j $(shell nproc) 38 | 39 | clean: 40 | rm -rf $(OBJS) build_dir/ 41 | 42 | .PHONY: all build clean 43 | -------------------------------------------------------------------------------- /src/formatting/printer.cc: -------------------------------------------------------------------------------- 1 | #include "fmt_config.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "fmt/include/fmt/format.h" 9 | 10 | #include "../utility.h" 11 | #include "printer.hpp" 12 | 13 | static fmt::basic_memory_buffer out; 14 | 15 | extern "C" { 16 | void print_str(const char *str) 17 | { 18 | /* len, excluding the terminating null byte */ 19 | const size_t len = strlen(str); 20 | 21 | print_str2(str, len); 22 | } 23 | void print_str2(const char *str, size_t len) 24 | { 25 | out.append(str, str + len); 26 | } 27 | 28 | void flush() 29 | { 30 | const char *data = out.data(); 31 | const size_t sz = out.size(); 32 | 33 | for (size_t cnt = 0; cnt != sz; ) { 34 | ssize_t ret = write_autorestart( 35 | 1, 36 | data + cnt, 37 | std::min(sz - cnt, static_cast(SSIZE_MAX)) 38 | ); 39 | if (ret < 0) 40 | err(1, "%s on %s failed", "write", "fd 1"); 41 | cnt += ret; 42 | } 43 | 44 | out.clear(); 45 | } 46 | } 47 | 48 | namespace swaystatus { 49 | void vprint(fmt::string_view format, fmt::format_args args) 50 | { 51 | fmt::vformat_to(out, format, args); 52 | } 53 | } /* End of namespace swaystatus */ 54 | -------------------------------------------------------------------------------- /src/formatting/LazyEval.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_LazyEval_H__ 2 | # define __swaystatus_LazyEval_H__ 3 | 4 | # include "fmt_config.hpp" 5 | 6 | # include 7 | # include "fmt/include/fmt/format.h" 8 | 9 | namespace swaystatus { 10 | template 11 | struct LazyEval { 12 | static_assert(std::is_invocable_v); 13 | 14 | using result_type = std::invoke_result_t; 15 | 16 | F f; 17 | 18 | decltype(auto) evaluate() const noexcept 19 | { 20 | return f(); 21 | } 22 | }; 23 | template 24 | LazyEval(F f) -> LazyEval; 25 | } 26 | 27 | template 28 | struct fmt::formatter>: 29 | fmt::formatter::result_type> 30 | { 31 | using parent_type = fmt::formatter::result_type>; 32 | 33 | using format_parse_context = fmt::format_parse_context; 34 | using format_context = fmt::format_context; 35 | using LazyEval = swaystatus::LazyEval; 36 | 37 | using format_parse_context_it = typename format_parse_context::iterator; 38 | using format_context_it = typename format_context::iterator; 39 | 40 | auto format(const LazyEval &val, format_context &ctx) -> format_context_it 41 | { 42 | return parent_type::format(val.evaluate(), ctx); 43 | } 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/Backlight.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_Backlight_HPP__ 2 | # define __swaystatus_Backlight_HPP__ 3 | 4 | # include 5 | # include 6 | # include 7 | 8 | # include "Fd.hpp" 9 | 10 | namespace swaystatus { 11 | class Backlight { 12 | public: 13 | static constexpr const char * const path = "/sys/class/backlight/"; 14 | 15 | // instance variables 16 | std::string filename; 17 | /** 18 | * value read from max_brightness 19 | */ 20 | std::uintmax_t max_brightness; 21 | /** 22 | * opened file of /sys/class/backlight/{BacklightDevice}/brightness 23 | */ 24 | Fd fd; 25 | /** 26 | * cached brightness 27 | */ 28 | std::uintmax_t brightness; 29 | 30 | // methods 31 | std::uintmax_t calculate_brightness(); 32 | 33 | public: 34 | Backlight() = delete; 35 | 36 | Backlight(int path_fd, const char *filename_arg); 37 | 38 | Backlight(const Backlight&) = delete; 39 | Backlight(Backlight&&) = default; 40 | 41 | Backlight& operator = (const Backlight&) = delete; 42 | Backlight& operator = (Backlight&&) = delete; 43 | 44 | ~Backlight() = default; 45 | 46 | void update_brightness(); 47 | 48 | auto get_device_name() const noexcept -> std::string_view; 49 | auto get_brightness() const noexcept -> std::uintmax_t; 50 | auto get_max_brightness() const noexcept -> std::uintmax_t; 51 | }; 52 | } /* namespace swaystatus */ 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/modules/CustomPrinter.cc: -------------------------------------------------------------------------------- 1 | #include "../process_configuration.h" 2 | #include "../Callback/Callable.hpp" 3 | 4 | #include "CustomPrinter.hpp" 5 | 6 | namespace swaystatus::modules { 7 | class CustomPrinter: public Base { 8 | /* Ret type, param .. */ 9 | swaystatus::Callable update_callback; 10 | swaystatus::Callable do_print_callback; 11 | 12 | public: 13 | CustomPrinter( 14 | void *config, 15 | Callable_base &&update_callback_base, 16 | Callable_base &&do_print_callback_base 17 | ): 18 | Base{config, "custom", 1, "", nullptr, "click_event_handler", "update_callback", "do_print_callback"}, 19 | update_callback{std::move(update_callback_base)}, 20 | do_print_callback{std::move(do_print_callback_base)} 21 | {} 22 | 23 | ~CustomPrinter() = default; 24 | 25 | void update() 26 | { 27 | update_callback(); 28 | } 29 | void do_print(const char *format) 30 | { 31 | (void) format; 32 | 33 | print_str2(do_print_callback()); 34 | } 35 | void reload() 36 | { 37 | ; 38 | } 39 | }; 40 | 41 | std::unique_ptr makeCustomPrinter(void *config) { 42 | return std::make_unique( 43 | config, 44 | Callable_base("custom_block", get_callable(config, "update_callback")), 45 | Callable_base("custom_block", get_callable(config, "do_print_callback")) 46 | ); 47 | } 48 | } /* namespace swaystatus::modules */ 49 | -------------------------------------------------------------------------------- /src/cxx_utility.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include /* For SSIZE_MAX and realpath */ 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "utility.h" 12 | 13 | extern "C" { 14 | void set_terminate_handler(void (*handler)()) 15 | { 16 | std::set_terminate(handler); 17 | } 18 | } 19 | 20 | namespace swaystatus { 21 | std::string getcwd_checked() 22 | { 23 | std::string cwd; 24 | 25 | char *result; 26 | do { 27 | cwd.resize(cwd.size() + 200); 28 | } while ((result = getcwd(cwd.data(), cwd.size())) == NULL && errno == ERANGE); 29 | 30 | if (result == nullptr) 31 | err(1, "%s failed", "getcwd"); 32 | 33 | cwd.resize(std::strlen(cwd.data())); 34 | 35 | return cwd; 36 | } 37 | 38 | ssize_t asreadall(int fd, std::string &buffer) 39 | { 40 | if (buffer.size() < buffer.capacity()) 41 | buffer.resize(buffer.capacity()); 42 | 43 | size_t bytes = 0; 44 | for (ssize_t ret; ; bytes += ret) { 45 | if (buffer.size() == bytes) 46 | buffer.resize(buffer.size() * 2 + 100); 47 | 48 | auto *ptr = buffer.data(); 49 | auto size = buffer.size(); 50 | 51 | ret = read_autorestart(fd, ptr + bytes, min_unsigned(size - bytes, SSIZE_MAX)); 52 | if (ret == 0) 53 | break; 54 | if (ret == -1) 55 | return -1; 56 | } 57 | 58 | buffer.resize(bytes); 59 | 60 | return bytes; 61 | } 62 | } /* namespace swaystatus */ 63 | -------------------------------------------------------------------------------- /draft_github_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | get_libc_name() { 4 | libc=$(ldd src/release_build/swaystatus | grep libc | cut -d '>' -f 2 | sed 's/(.*)$//') 5 | output=$($libc 2>&1) 6 | if echo "$output" | grep -q 'GNU C Library'; then 7 | version="$(echo $output | grep -o 'release version [0-9]*\.[0-9]*' | sed -e 's/release version //')" 8 | echo "glibc-$version" 9 | elif echo "$output" | grep -q 'musl libc'; then 10 | version="$(echo $output | grep -o 'Version [0-9]*\.[0-9]*\.[0-9]*' | sed -e 's/Version //')" 11 | echo "musl-$version" 12 | else 13 | echo unknown libc! >&2 14 | exit 1 15 | fi 16 | } 17 | 18 | cd $(dirname $0) 19 | 20 | label_postfix="$(arch)-$(uname)-$(get_libc_name)" 21 | 22 | if [ $# -lt 1 ] || [ $# -gt 2 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then 23 | echo "Usage: $0 [Release notes]" >&2 24 | exit 1 25 | fi 26 | 27 | tag=$1 28 | if [ $# -eq 2 ]; then 29 | changelogOption="-n" 30 | changelogValue="$2" 31 | else 32 | changelogFile=$(mktemp) 33 | "$EDITOR" "$changelogFile" 34 | 35 | changelogOption="-F" 36 | changelogValue="$changelogFile" 37 | fi 38 | 39 | ./run_all_builds.sh 40 | 41 | cp src/release_build/swaystatus "/tmp/swaystatus-with-python-support-$label_postfix" 42 | cp src/release_no_py_build/swaystatus "/tmp/swaystatus-no-python-support-$label_postfix" 43 | 44 | git push 45 | exec gh release create "$tag" \ 46 | --prerelease \ 47 | $changelogOption "$changelogValue" \ 48 | -t "swaystatus $tag" \ 49 | "/tmp/swaystatus-with-python-support-$label_postfix" \ 50 | "/tmp/swaystatus-no-python-support-$label_postfix" 51 | -------------------------------------------------------------------------------- /src/Callback/dynlib.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "../utility.h" 6 | #include "dynlib.hpp" 7 | 8 | #include 9 | #include 10 | 11 | std::array dlpaths; 12 | 13 | extern "C" { 14 | void setup_dlpath(const char *path) 15 | { 16 | dlpaths[0] = swaystatus::getcwd_checked(); 17 | dlpaths[0].shrink_to_fit(); 18 | 19 | if (path) 20 | dlpaths[1] = path; 21 | } 22 | 23 | void* dload_symbol(const char *filename, const char *symbol_name) 24 | { 25 | /* Clear any previous error */ 26 | dlerror(); 27 | 28 | void *handle = dlopen(filename, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); 29 | if (handle == nullptr) { 30 | std::string path; 31 | 32 | for (const auto &dlpath: dlpaths) { 33 | if (dlpath.size() == 0) 34 | continue; 35 | 36 | path = dlpath + '/' + filename; 37 | handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); 38 | path.clear(); 39 | 40 | if (handle) 41 | break; 42 | } 43 | if (handle == nullptr) 44 | errx(1, "%s on %s failed: %s", "dload_symbol", filename, dlerror()); 45 | } 46 | 47 | void *sym = dlsym(handle, symbol_name); 48 | const char *error = dlerror(); 49 | if (sym == nullptr) { 50 | if (error) 51 | errx(1, "%s on %s failed: %s", "dlsym", symbol_name, error); 52 | else 53 | errx( 54 | 1, 55 | "%s on %s failed: %s", "dlsym", symbol_name, 56 | "It is an undefined weak symboll/it is placed at address 0/IFUNC returns NULL" 57 | ); 58 | } 59 | 60 | return sym; 61 | } 62 | } /* extern "C" */ 63 | -------------------------------------------------------------------------------- /src/modules/NetworkInterfacesPrinter.cc: -------------------------------------------------------------------------------- 1 | #include "../formatting/Conditional.hpp" 2 | #include "../networking.hpp" 3 | 4 | #include "NetworkInterfacesPrinter.hpp" 5 | 6 | using namespace std::literals; 7 | 8 | namespace swaystatus::modules { 9 | class NetworkInterfacesPrinter: public Base { 10 | Interfaces interfaces; 11 | 12 | public: 13 | NetworkInterfacesPrinter(void *config): 14 | Base{ 15 | config, "NetworkInterfacesPrinter"sv, 16 | 60 * 2, 17 | "{is_connected:{per_interface_fmt_str:" 18 | "{name} {is_dhcp:DHCP }in: {rx_bytes} out: {tx_bytes} " 19 | "{ipv4_addrs:1} {ipv6_addrs:1}" 20 | "}}", 21 | "{is_connected:{per_interface_fmt_str:" 22 | "{name}" 23 | "}}" 24 | } 25 | {} 26 | 27 | void update() 28 | { 29 | interfaces.update(); 30 | } 31 | void do_print(const char *format) 32 | { 33 | print( 34 | format, 35 | fmt::arg("is_not_connected", Conditional{interfaces.is_empty()}), 36 | fmt::arg("is_connected", Conditional{!interfaces.is_empty()}), 37 | fmt::arg("per_interface_fmt_str", interfaces) 38 | ); 39 | } 40 | void reload() 41 | { 42 | /* 43 | * Since each call t interfaces.update() would read all network interfaces again, 44 | * reload() needs to do nothing here as Base::update_and_print() will always 45 | * call update() after reload(). 46 | */ 47 | } 48 | }; 49 | 50 | std::unique_ptr makeNetworkInterfacesPrinter(void *config) 51 | { 52 | return std::make_unique(config); 53 | } 54 | } /* namespace swaystatus::modules */ 55 | -------------------------------------------------------------------------------- /test/unit_test/test_python3/test_python3.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define USE_PYTHON 6 | #include "../../../src/utility.h" 7 | #include "../../../src/Callback/python3.hpp" 8 | 9 | using namespace swaystatus::python; 10 | 11 | int main() 12 | { 13 | // Initialize libpython 14 | { 15 | char *path = realpath_checked("python3_test_dir"); 16 | setup_pythonpath(path); 17 | 18 | MainInterpreter::load_libpython3(); 19 | std::free(path); 20 | } 21 | 22 | std::printf("%s = %s\n\n", "PYTHONPATH", getenv("PYTHONPATH")); 23 | 24 | { 25 | auto scope = MainInterpreter::get().acquire(); 26 | static_cast(scope); 27 | 28 | Compiled compiled{ 29 | "print_sys_path", 30 | 31 | "import sys\n" 32 | "def f():\n" 33 | " return str(sys.path)\n" 34 | "\n" 35 | }; 36 | Module print_sys_path{"print_sys_path", compiled}; 37 | Callable f{print_sys_path.getattr("f")}; 38 | str sys_path{f()}; 39 | std::printf("sys.path = %s\n\n", sys_path.get_view().data()); 40 | 41 | Module hello_world{"hello_world"}; 42 | Callable hello{hello_world.getattr("hello")}; 43 | 44 | assert(str{hello()}.get_view() == "Hello, world!"); 45 | 46 | Module test{"test"}; 47 | Callable identity{test.getattr("identity")}; 48 | Callable identity2{test.getattr("identity")}; 49 | 50 | ssize_t val; 51 | assert(identity(2021).to_ssize_t(&val)); 52 | assert(val == 2021); 53 | 54 | assert(identity2(2021) == 2021); 55 | } 56 | 57 | ; 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /src/modules/TemperaturePrinter.cc: -------------------------------------------------------------------------------- 1 | #include "../sensors.hpp" 2 | 3 | #include "TemperaturePrinter.hpp" 4 | 5 | using namespace std::literals; 6 | 7 | namespace swaystatus::modules { 8 | class TemperaturePrinter: public Base { 9 | Sensors sensors; 10 | typename Sensors::const_iterator reading_it; 11 | 12 | public: 13 | TemperaturePrinter(void *config): 14 | Base{ 15 | config, "TemperaturePrinter"sv, 16 | 5, "{prefix} {reading_number}th sensor: {reading_temp}°C", nullptr 17 | }, 18 | /** 19 | * Here, reading_it = sensors.begin() = sensor.end() 20 | */ 21 | reading_it{sensors.begin()} 22 | {} 23 | 24 | void update() 25 | { 26 | ++reading_it; 27 | if (reading_it >= sensors.end()) { 28 | sensors.update(); 29 | reading_it = sensors.begin(); 30 | } 31 | } 32 | void do_print(const char *format) 33 | { 34 | auto &sensor = *reading_it->sensor; 35 | auto &bus = sensor.bus; 36 | auto &reading = *reading_it; 37 | 38 | print( 39 | format, 40 | fmt::arg("prefix", sensor.prefix), 41 | fmt::arg("path", sensor.path), 42 | fmt::arg("addr", sensor.addr), 43 | fmt::arg("bus_type", bus.type), 44 | fmt::arg("bus_nr", bus.nr), 45 | 46 | fmt::arg("reading_number", reading.number), 47 | fmt::arg("reading_temp", reading.temp) 48 | ); 49 | } 50 | void reload() 51 | { 52 | sensors.reload(); 53 | } 54 | }; 55 | 56 | std::unique_ptr makeTemperaturePrinter(void *config) 57 | { 58 | return std::make_unique(config); 59 | } 60 | } /* namespace swaystatus::modules */ 61 | -------------------------------------------------------------------------------- /src/modules/TimePrinter.cc: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200112L /* For localtime_r */ 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "TimePrinter.hpp" 10 | 11 | using namespace std::literals; 12 | 13 | namespace swaystatus::modules { 14 | class TimePrinter: public Base { 15 | struct tm local_time; 16 | 17 | public: 18 | TimePrinter(void *config): 19 | Base{ 20 | config, "TimePrinter"sv, 21 | 1, "%Y-%m-%d %T", nullptr 22 | } 23 | {} 24 | 25 | void update() 26 | { 27 | /* 28 | * time technically can't fail as long as the first arg is set to nullptr 29 | */ 30 | const time_t epoch = time(nullptr); 31 | if (localtime_r(&epoch, &local_time) == nullptr) 32 | errx(1, "%s failed %s", "localtime_r", "due to time(nullptr) has failed"); 33 | } 34 | void do_print(const char *format) 35 | { 36 | errno = 0; 37 | 38 | /* 39 | * allocate a big enough buffer to make sure strftime never fails 40 | */ 41 | char buffer[4096]; 42 | size_t cnt = strftime(buffer, sizeof(buffer), format, &local_time); 43 | if (cnt == 0) { 44 | if (errno != 0) 45 | err(1, "strftime failed"); 46 | else 47 | errx(1, "strftime returns 0: Your format string generate string longer than 4096 " 48 | "which is larger than the buffer"); 49 | } 50 | 51 | print_str2(buffer, cnt); 52 | } 53 | void reload() 54 | {} 55 | }; 56 | 57 | std::unique_ptr makeTimePrinter(void *config) 58 | { 59 | return std::make_unique(config); 60 | } 61 | } /* namespace swaystatus::modules */ 62 | -------------------------------------------------------------------------------- /src/Battery.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_Battery_HPP__ 2 | # define __swaystatus_Battery_HPP__ 3 | 4 | # include 5 | # include 6 | # include 7 | # include 8 | 9 | # include "Fd.hpp" 10 | 11 | # include "formatting/LazyEval.hpp" 12 | # include "formatting/Conditional.hpp" 13 | 14 | # include "formatting/fmt/include/fmt/format.h" 15 | 16 | namespace swaystatus { 17 | class Battery { 18 | std::string battery_device; 19 | Fd uevent_fd; 20 | 21 | std::string buffer; 22 | 23 | protected: 24 | Battery(int path_fd, std::string &&battery_device); 25 | 26 | public: 27 | static constexpr const auto *power_supply_path = "/sys/class/power_supply/"; 28 | 29 | static auto makeBattery(int path_fd, std::string_view device, std::string_view excluded_model) 30 | -> std::optional; 31 | 32 | Battery(Battery&&) = default; 33 | 34 | Battery& operator = (const Battery&) = delete; 35 | Battery& operator = (Battery&&) = delete; 36 | 37 | ~Battery() = default; 38 | 39 | void read_battery_uevent(); 40 | 41 | auto get_property(std::string_view name) const noexcept -> std::string_view; 42 | }; 43 | 44 | class Batteries { 45 | std::vector batteries; 46 | 47 | public: 48 | Batteries() = default; 49 | }; 50 | } /* namespace swaystatus */ 51 | 52 | template <> 53 | struct fmt::formatter> 54 | { 55 | using Batteries = std::vector; 56 | 57 | using format_parse_context_it = typename format_parse_context::iterator; 58 | using format_context_it = typename format_context::iterator; 59 | 60 | std::string_view fmt_str = ""; 61 | 62 | auto parse(format_parse_context &ctx) -> format_parse_context_it; 63 | auto format(const Batteries &batteries, format_context &ctx) -> format_context_it; 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /example-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "Any variable starts with '_' is a comment", 3 | 4 | "order": [ 5 | "battery", "brightness", "volume", "memory_usage", "network_interface", "load", "time", "custom" 6 | ], 7 | 8 | "battery": { 9 | "format": 10 | "{has_battery:{per_battery_fmt_str:{is_full:Battery is Full}{is_charging:charging {capacity}%}{is_discharging:discharging {capacity}%}}}" 11 | }, 12 | "memory_usage": { 13 | "format": " Free {MemFree}/Total {MemTotal} Free {SwapFree}/{SwapTotal}" 14 | }, 15 | "brightness": { 16 | "format": "{has_multiple_backlight_devices:{backlight_device} }{brightness}" 17 | }, 18 | "volume": { 19 | "format": "volume: {volume}%" 20 | }, 21 | "load": { 22 | "format": 23 | "1m {loadavg_1m} kthreads:{running_kthreads_cnt}/{total_kthreads_cnt} {last_created_process_pid}" 24 | }, 25 | "network_interface": { 26 | "format": "{is_connected:{per_interface_fmt_str:{name} {ipv4_addrs:1} {ipv6_addrs:1}}}", 27 | "short_format": "{is_connected:{per_interface_fmt_str:{name}}}", 28 | "update_interval": 20, 29 | "__update_interval__": [ 30 | "Setting update_interval to 20 means it will be updated per 20sec by default", 31 | "if cmdline arg \"--interval\" is not specified" 32 | ] 33 | }, 34 | "custom": { 35 | "update_callback": { 36 | "type": "python", 37 | "module_name": "inline_code", 38 | "code": "import sys\ncounter = 0\ndef handler():\n global counter\n counter += 1\n\ndef get_counter():\n return counter", 39 | "function_name": "handler" 40 | }, 41 | "do_print_callback": { 42 | "type": "python", 43 | "module_name": "inline_code2", 44 | "code": "from inline_code import get_counter\ndef handler():\n return str(get_counter())", 45 | "function_name": "handler" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/formatting/printer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_printer_HPP__ 2 | # define __swaystatus_printer_HPP__ 3 | 4 | # include 5 | 6 | # ifdef __cplusplus 7 | # include "fmt_config.hpp" 8 | # include "fmt/include/fmt/core.h" 9 | # endif 10 | 11 | # ifdef __cplusplus 12 | extern "C" { 13 | # endif 14 | 15 | /** 16 | * @param str must not be NULL 17 | */ 18 | void print_str(const char *str); 19 | /** 20 | * @param str must not be NULL 21 | */ 22 | void print_str2(const char *str, size_t len); 23 | /** 24 | * Flush the buffer of stdout, not thread safe. 25 | */ 26 | void flush(); 27 | 28 | # ifdef __cplusplus 29 | } 30 | # endif 31 | 32 | # ifdef __cplusplus 33 | # define print_literal_str(literal) swaystatus::print_str2((literal), sizeof(literal) - 1) 34 | # else 35 | # define print_literal_str(literal) print_str2((literal), sizeof(literal) - 1) 36 | # endif 37 | 38 | # ifdef __cplusplus 39 | # include 40 | 41 | namespace swaystatus { 42 | /** 43 | * @param str must not be nullptr 44 | */ 45 | inline void print_str(const char *str) 46 | { 47 | ::print_str(str); 48 | } 49 | /** 50 | * @param str must not be nullptr 51 | */ 52 | inline void print_str2(const char *str, size_t len) 53 | { 54 | ::print_str2(str, len); 55 | } 56 | 57 | inline void print_str2(std::string_view sv) 58 | { 59 | ::print_str2(sv.data(), sv.size()); 60 | } 61 | 62 | /** 63 | * Prints to the buffer of stdout, but does not flush the buffer, not thread safe. 64 | */ 65 | void vprint(fmt::string_view format, fmt::format_args args); 66 | 67 | /** 68 | * Prints to the buffer of stdout, but does not flush the buffer, not thread safe. 69 | */ 70 | template 71 | void print(const S &format, Args &&...args) 72 | { 73 | swaystatus::vprint(format, fmt::make_args_checked(format, args...)); 74 | } 75 | 76 | /** 77 | * Flush the buffer of stdout, not thread safe. 78 | */ 79 | inline void flush() 80 | { 81 | ::flush(); 82 | } 83 | } /* End of namespace swaystatus */ 84 | # endif 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /src/Backlight.cc: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE /* For macro constants of struct dirent::d_type and struct timespec */ 2 | 3 | #include 4 | 5 | #include /* For O_RDONLY */ 6 | #include /* For close and lseek */ 7 | 8 | #include 9 | 10 | #include "utility.h" 11 | 12 | #include "Backlight.hpp" 13 | 14 | namespace swaystatus { 15 | Backlight::Backlight(int path_fd, const char *filename_arg): 16 | filename{filename_arg} 17 | { 18 | auto &buffer = filename; 19 | auto filename_sz = filename.size(); 20 | 21 | { 22 | buffer.append("/max_brightness"); 23 | int fd = openat_checked(path, path_fd, buffer.c_str(), O_RDONLY); 24 | buffer.resize(filename_sz); 25 | 26 | const char *failed_part = readall_as_uintmax(fd, &max_brightness); 27 | if (failed_part) 28 | err(1, "%s on %s%s/%s failed", failed_part, path, buffer.c_str(), "max_brightness"); 29 | close(fd); 30 | } 31 | 32 | buffer.append("/brightness"); 33 | this->fd = openat_checked(path, path_fd, buffer.c_str(), O_RDONLY); 34 | buffer.resize(filename_sz); 35 | 36 | buffer.shrink_to_fit(); 37 | } 38 | 39 | std::uintmax_t Backlight::calculate_brightness() 40 | { 41 | uintmax_t val; 42 | const char *failed_part = readall_as_uintmax(fd.get(), &val); 43 | if (failed_part) 44 | err(1, "%s on %s%s/%s failed", failed_part, path, filename.c_str(), "brightness"); 45 | 46 | if (lseek(fd.get(), 0, SEEK_SET) == (off_t) -1) 47 | err(1, "%s on %s%s/%s failed", "lseek", path, filename.c_str(), "brightness"); 48 | 49 | return 100 * val / max_brightness; 50 | } 51 | void Backlight::update_brightness() 52 | { 53 | brightness = calculate_brightness(); 54 | } 55 | 56 | auto Backlight::get_device_name() const noexcept -> std::string_view 57 | { 58 | return filename; 59 | } 60 | auto Backlight::get_brightness() const noexcept -> std::uintmax_t 61 | { 62 | return brightness; 63 | } 64 | auto Backlight::get_max_brightness() const noexcept -> std::uintmax_t 65 | { 66 | return max_brightness; 67 | } 68 | } /* namespace swaystatus */ 69 | -------------------------------------------------------------------------------- /src/poller.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "utility.h" 7 | #include "poller.h" 8 | 9 | struct callback_storer { 10 | poller_callback callback; 11 | void *data; 12 | }; 13 | 14 | static struct pollfd *fds; 15 | static nfds_t nfds; 16 | static struct callback_storer *callbacks; 17 | 18 | void init_poller() 19 | {} 20 | 21 | static short fromEvent(enum Event events) 22 | { 23 | short result = 0; 24 | if (events & read_ready) 25 | result |= POLLIN; 26 | return result; 27 | } 28 | static enum Event toEvent(short events) 29 | { 30 | if (events & POLLNVAL) 31 | return invalid_fd; 32 | 33 | enum Event result = 0; 34 | 35 | if (events & POLLIN) 36 | result |= read_ready; 37 | if (events & POLLERR) 38 | result |= error; 39 | if (events & POLLHUP) 40 | result |= hup; 41 | 42 | return result; 43 | } 44 | 45 | void request_polling(int fd, enum Event events, poller_callback callback, void *data) 46 | { 47 | ++nfds; 48 | reallocate(fds, nfds); 49 | reallocate(callbacks, nfds); 50 | 51 | fds[nfds - 1] = (struct pollfd){ 52 | .fd = fd, 53 | .events = fromEvent(events) 54 | }; 55 | callbacks[nfds - 1] = (struct callback_storer){ 56 | .callback = callback, 57 | .data = data 58 | }; 59 | } 60 | 61 | static nfds_t skip_empty_revents(nfds_t i) 62 | { 63 | for (; fds[i].revents == 0; ++i) 64 | ; 65 | return i; 66 | } 67 | 68 | void perform_polling(int timeout) 69 | { 70 | if (nfds == 0) 71 | return; 72 | 73 | int result = 0; 74 | do { 75 | result = poll(fds, nfds, timeout); 76 | } while (result == -1 && errno == EINTR); 77 | /* Use < 0 here to tell the compiler that in the loop below, result cannot be less than 0 */ 78 | if (result < 0) 79 | err(1, "%s failed", "poll"); 80 | 81 | nfds_t i = 0; 82 | for (int cnt = 0; cnt != result; ++cnt) { 83 | i = skip_empty_revents(i); 84 | 85 | struct callback_storer *storer = &callbacks[i]; 86 | storer->callback(fds[i].fd, toEvent(fds[i].revents), storer->data); 87 | 88 | ++i; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Callback/dynlib.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_dylib_HPP__ 2 | # define __swaystatus_dylib_HPP__ 3 | 4 | # ifdef __cplusplus 5 | extern "C" { 6 | # endif 7 | 8 | /** 9 | * @param path can be NULL 10 | * 11 | * setup_dlpath should be called before chdir is called. 12 | */ 13 | void setup_dlpath(const char *path); 14 | 15 | void* dload_symbol(const char *filename, const char *symbol_name); 16 | 17 | # ifdef __cplusplus 18 | } /* extern "C" */ 19 | 20 | # include 21 | 22 | namespace swaystatus { 23 | namespace impl { 24 | template 25 | struct ref2ptr { 26 | using type = T; 27 | 28 | static T cast(T val) noexcept 29 | { 30 | return val; 31 | } 32 | }; 33 | template 34 | struct ref2ptr { 35 | using type = T*; 36 | 37 | static T* cast(T &val) noexcept 38 | { 39 | return &val; 40 | } 41 | }; 42 | template 43 | struct ref2ptr { 44 | using type = T*; 45 | 46 | static_assert(!std::is_fundamental_v>); 47 | 48 | static T* cast(T &val) noexcept 49 | { 50 | return &val; 51 | } 52 | }; 53 | 54 | template 55 | using ref2ptr_t = typename ref2ptr::type; 56 | } /* namespace impl */ 57 | 58 | /** 59 | * class CFunction would automatically translate C++ function param to C param 60 | * 61 | * It will translate all reference to pointer, but it would not translate class 62 | * Thus all Args must be fundamental types or aggregates. 63 | */ 64 | template 65 | class CFunction { 66 | using Fp = Ret (*)(impl::ref2ptr_t...); 67 | 68 | Fp ptr; 69 | 70 | public: 71 | CFunction(const char *filename, const char *symbol_name): 72 | ptr{reinterpret_cast(dload_symbol(filename, symbol_name))} 73 | {} 74 | 75 | CFunction(void *symbol): 76 | ptr{reinterpret_cast(symbol)} 77 | {} 78 | 79 | CFunction(const CFunction&) = default; 80 | CFunction& operator = (const CFunction&) = default; 81 | 82 | decltype(auto) operator () (Args ...args) 83 | { 84 | return ptr(impl::ref2ptr::cast(args)...); 85 | } 86 | }; 87 | 88 | } /* namespace swaystatus */ 89 | # endif 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/modules/BacklightPrinter.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../utility.h" 5 | #include "../Backlight.hpp" 6 | #include "../formatting/Conditional.hpp" 7 | 8 | #include "BacklightPrinter.hpp" 9 | 10 | using namespace std::literals; 11 | 12 | namespace swaystatus::modules { 13 | class BacklightPrinter: public Base { 14 | std::vector backlights; 15 | 16 | void load() 17 | { 18 | visit_all_subdirs( 19 | Backlight::path, 20 | [](int path_fd, const char *d_name, va_list ap) 21 | { 22 | va_list args; 23 | va_copy(args, ap); 24 | va_arg(args, std::vector*)->emplace_back(path_fd, d_name); 25 | va_end(args); 26 | }, 27 | &backlights 28 | ); 29 | 30 | backlights.shrink_to_fit(); 31 | } 32 | 33 | public: 34 | BacklightPrinter(void *config): 35 | Base{ 36 | config, "BacklightPrinter"sv, 37 | 1, "{backlight_device}: {brightness}", nullptr 38 | } 39 | { 40 | load(); 41 | } 42 | 43 | ~BacklightPrinter() = default; 44 | 45 | void update() 46 | { 47 | for (Backlight &backlight: backlights) 48 | backlight.update_brightness(); 49 | } 50 | void do_print(const char *format) 51 | { 52 | std::size_t i = 0; 53 | for (const Backlight &backlight: backlights) { 54 | print( 55 | format, 56 | fmt::arg("backlight_device", backlight.get_device_name()), 57 | fmt::arg("brightness", backlight.get_brightness()), 58 | fmt::arg("max_brightness", backlight.get_max_brightness()), 59 | fmt::arg("has_multiple_backlight_devices", Conditional{backlights.size() != 1}) 60 | ); 61 | 62 | if (i++ != backlights.size()) 63 | print_literal_str(" "); 64 | } 65 | } 66 | void reload() 67 | { 68 | backlights.clear(); 69 | load(); 70 | } 71 | }; 72 | 73 | std::unique_ptr makeBacklightPrinter(void *config) 74 | { 75 | return std::make_unique(config); 76 | } 77 | } /* namespace swaystatus::modules */ 78 | -------------------------------------------------------------------------------- /src/Callback/Callable.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "Callable.hpp" 6 | 7 | namespace swaystatus { 8 | static const char *get_str_from_config( 9 | const char *name, 10 | const json_object *callable_config, 11 | const char *attr 12 | ) 13 | { 14 | struct json_object *val; 15 | if (!json_object_object_get_ex(callable_config, attr, &val)) 16 | errx(1, "Attr %s.click_event_handler/callback.%s %s", name, attr, "is missing"); 17 | 18 | if (json_object_get_type(val) != json_type_string) 19 | errx(1, "Attr %s.click_event_handler/callback.%s %s", name, attr, "contains invalid value"); 20 | 21 | return json_object_get_string(val); 22 | } 23 | 24 | Callable_base::Callable_base(const char *name, const void *callable_config_arg) 25 | { 26 | auto *callable_config = static_cast(callable_config_arg); 27 | const char *type = get_str_from_config(name, callable_config, "type"); 28 | 29 | auto get_str = [&](const char *attr) 30 | { 31 | return get_str_from_config(name, callable_config, attr); 32 | }; 33 | 34 | auto *module_name = get_str("module_name"); 35 | auto *function_name = get_str("function_name"); 36 | 37 | if (std::strcmp(type, "python") == 0) { 38 | #ifdef USE_PYTHON 39 | python::MainInterpreter::load_libpython3(); 40 | auto scope = python::MainInterpreter::get().acquire(); 41 | 42 | auto module = [&]{ 43 | struct json_object *code; 44 | if (json_object_object_get_ex(callable_config, "code", &code)) { 45 | python::Compiled compiled{module_name, get_str("code")}; 46 | return python::Module{module_name, compiled}; 47 | } else 48 | return python::Module{module_name}; 49 | }(); 50 | 51 | v.emplace(module.getattr(function_name)); 52 | #else 53 | errx(1, "Click events specified using python callback, but feature python is not " 54 | "supported."); 55 | #endif 56 | } else if (std::strcmp(type, "dylib") == 0) { 57 | v.emplace(dload_symbol(module_name, function_name)); 58 | } else 59 | errx(1, "Attr %s.click_event_handler.%s %s", name, "type", "contains invalid value"); 60 | } 61 | } /* namespace swaystatus */ 62 | -------------------------------------------------------------------------------- /src/alsa.c: -------------------------------------------------------------------------------- 1 | /** 2 | * The file is adatped from: 3 | * https://stackoverflow.com/questions/7657624/get-master-sound-volume-in-c-in-linux, which uses 4 | * WTFPL license. 5 | */ 6 | 7 | /** 8 | * Fix redefinition of timespec and timeval in alsa/global.h. 9 | * 10 | * Learnt from: 11 | * https://stackoverflow.com/questions/32672333/including-alsa-asoundlib-h-and-sys-time-h-results-in-multiple-definition-co 12 | */ 13 | #define _POSIX_C_SOURCE 200809L 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include "alsa.h" 21 | 22 | static snd_mixer_t *handle; 23 | static snd_mixer_elem_t *elem; 24 | static long volume; 25 | 26 | static long calculate_audio_volume(); 27 | 28 | void initialize_alsa_lib(const char *mix_name, const char *card) 29 | { 30 | snd_mixer_selem_id_t *sid; 31 | snd_mixer_selem_id_alloca(&sid); 32 | 33 | snd_mixer_selem_id_set_index(sid, 0); 34 | snd_mixer_selem_id_set_name(sid, mix_name); 35 | 36 | if (snd_mixer_open(&handle, 0) < 0) 37 | errx(1, "%s failed", "snd_mixer_open"); 38 | 39 | if (snd_mixer_attach(handle, card) < 0) 40 | errx(1, "%s failed", "snd_mixer_attach"); 41 | 42 | if (snd_mixer_selem_register(handle, NULL, NULL) < 0) 43 | errx(1, "%s failed", "snd_mixer_selem_register"); 44 | 45 | if (snd_mixer_load(handle) < 0) 46 | errx(1, "%s failed", "snd_mixer_load"); 47 | 48 | elem = snd_mixer_find_selem(handle, sid); 49 | if (!elem) 50 | errx(1, "%s failed", "snd_mixer_find_selem"); 51 | 52 | if (snd_config_update_free_global() < 0) 53 | errx(1, "%s failed", "snd_config_update_free_global"); 54 | 55 | volume = calculate_audio_volume(); 56 | } 57 | 58 | static long calculate_audio_volume() 59 | { 60 | long minv, maxv; 61 | snd_mixer_selem_get_playback_volume_range(elem, &minv, &maxv); 62 | 63 | long vol; 64 | if (snd_mixer_selem_get_playback_volume(elem, 0, &vol) < 0) 65 | errx(1, "%s failed", "snd_mixer_selem_get_playback_volume"); 66 | 67 | /* make the vol bound to range [0, 100] */ 68 | vol -= minv; 69 | maxv -= minv; 70 | return 100 * vol / maxv; 71 | } 72 | 73 | void update_volume() 74 | { 75 | if (snd_mixer_wait(handle, 0) == 0) { 76 | if (snd_mixer_handle_events(handle) < 0) 77 | errx(1, "%s failed", "snd_mixer_handle_events"); 78 | 79 | volume = calculate_audio_volume(); 80 | } 81 | } 82 | long get_audio_volume() 83 | { 84 | return volume; 85 | } 86 | -------------------------------------------------------------------------------- /src/modules/BatteryPrinter.cc: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE /* For macro constants of struct dirent::d_type */ 2 | #ifndef _GNU_SOURCE 3 | # define _GNU_SOURCE /* For strchrnul */ 4 | #endif 5 | 6 | #include 7 | 8 | #include "../utility.h" 9 | #include "../process_configuration.h" 10 | #include "../Battery.hpp" 11 | 12 | #include "BatteryPrinter.hpp" 13 | 14 | using namespace std::literals; 15 | 16 | namespace swaystatus::modules { 17 | class BatteryPrinter: public Base { 18 | std::unique_ptr excluded_model; 19 | std::vector batteries; 20 | 21 | void load() 22 | { 23 | std::string_view excluded_model_sv; 24 | if (excluded_model) 25 | excluded_model_sv = excluded_model.get(); 26 | 27 | visit_all_subdirs( 28 | Battery::power_supply_path, 29 | [](int path_fd, const char *d_name, va_list ap) 30 | { 31 | va_list args; 32 | va_copy(args, ap); 33 | 34 | auto &excluded_model = *va_arg(args, std::string_view*); 35 | auto &batteries = *va_arg(args, std::vector*); 36 | 37 | auto result = Battery::makeBattery(path_fd, d_name, excluded_model); 38 | if (result) 39 | batteries.push_back(std::move(*result)); 40 | 41 | va_end(args); 42 | }, 43 | &excluded_model_sv, &batteries 44 | ); 45 | 46 | batteries.shrink_to_fit(); 47 | } 48 | 49 | public: 50 | BatteryPrinter(void *config, std::unique_ptr &&excluded_model_arg): 51 | Base{ 52 | config, "BatteryPrinter"sv, 53 | 3, "{has_battery:{per_battery_fmt_str:{status} {capacity}%}}", nullptr, 54 | "excluded_model" 55 | }, 56 | excluded_model{std::move(excluded_model_arg)} 57 | { 58 | load(); 59 | } 60 | 61 | ~BatteryPrinter() = default; 62 | 63 | void update() 64 | { 65 | for (Battery &bat: batteries) 66 | bat.read_battery_uevent(); 67 | } 68 | void do_print(const char *format) 69 | { 70 | print( 71 | format, 72 | fmt::arg("has_battery", swaystatus::Conditional{batteries.size() != 0}), 73 | fmt::arg("per_battery_fmt_str", batteries) 74 | ); 75 | } 76 | void reload() 77 | { 78 | batteries.clear(); 79 | load(); 80 | } 81 | }; 82 | 83 | std::unique_ptr makeBatteryPrinter(void *config) 84 | { 85 | return std::make_unique( 86 | config, 87 | std::unique_ptr{get_property(config, "excluded_model", nullptr)} 88 | ); 89 | } 90 | } /* namespace swaystatus::modules */ 91 | -------------------------------------------------------------------------------- /src/sensors.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_sensors_HPP__ 2 | # define __swaystatus_sensors_HPP__ 3 | 4 | # include "formatting/fmt_config.hpp" 5 | 6 | # include 7 | # include 8 | # include 9 | # include 10 | # include 11 | # include 12 | 13 | # include "formatting/fmt/include/fmt/format.h" 14 | 15 | namespace swaystatus { 16 | struct sensor_bus_type { 17 | short val; 18 | }; 19 | struct sensor_bus_id { 20 | sensor_bus_type type; 21 | short nr; 22 | }; 23 | 24 | class Sensors; 25 | struct Sensor { 26 | std::string prefix; 27 | std::string path; 28 | int addr; 29 | 30 | sensor_bus_id bus; 31 | 32 | Sensor(const char *prefix, const char *path, int addr, short type, short nr) noexcept; 33 | 34 | void update(Sensors &sensors); 35 | auto get_adapter_name() const noexcept -> std::string_view; 36 | }; 37 | 38 | struct sensor_reading { 39 | Sensor *sensor; 40 | 41 | int number; 42 | 43 | sensor_reading(Sensor *sensor, int number) noexcept; 44 | 45 | /** 46 | * It is unlikely that a compuer can run under temperature outside of range 47 | * [-126, 125] 48 | * 49 | * It might be a good idea in future to add field temp_critical_alarm so that 50 | * swaystatus can set urgent according to that 51 | * 52 | * There's also other interesting meteric, listed in /usr/include/sensors/sensors.h 53 | */ 54 | std::int8_t temp = std::numeric_limits::min(); 55 | }; 56 | 57 | class Sensors { 58 | std::vector sensors; 59 | std::vector readings; 60 | 61 | friend Sensor; 62 | 63 | public: 64 | /** 65 | * Initialize libsensors and get a list of sensors on the system 66 | * You need to call update() after ctor to fetch the readings, otherwise begin() == end(). 67 | */ 68 | Sensors(); 69 | 70 | Sensors(const Sensors&) = delete; 71 | Sensors(Sensors&&) = delete; 72 | 73 | Sensors& operator = (const Sensors&) = delete; 74 | Sensors& operator = (Sensors&&) = delete; 75 | 76 | void reload(); 77 | 78 | void update(); 79 | 80 | using const_iterator = std::vector::const_iterator; 81 | 82 | auto begin() const noexcept -> const_iterator; 83 | auto end() const noexcept -> const_iterator; 84 | 85 | auto cbegin() const noexcept -> const_iterator; 86 | auto cend() const noexcept -> const_iterator; 87 | }; 88 | } /* namespace swaystatus */ 89 | 90 | template <> 91 | struct fmt::formatter: 92 | fmt::formatter 93 | { 94 | using sensor_bus_type = swaystatus::sensor_bus_type; 95 | 96 | using format_context_it = typename format_context::iterator; 97 | 98 | auto format(const sensor_bus_type &type, format_context &ctx) -> format_context_it; 99 | }; 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /src/process_configuration.h: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_process_configuration_H__ 2 | # define __swaystatus_process_configuration_H__ 3 | 4 | # ifdef __cplusplus 5 | extern "C" { 6 | # endif 7 | 8 | # include 9 | # include 10 | # include 11 | # include 12 | 13 | /** 14 | * load and verify the configuration. 15 | */ 16 | void* load_config(const char *filename); 17 | 18 | /** 19 | * @param config can be NULL 20 | * @param name must not be NULL, can be battery, network_interface, etc. 21 | * @return If config == NULL, then return NULL. If the module config does not exist, return NULL. 22 | * If the module config is a boolean, return NULL 23 | * Otherwise a non-NULL value. 24 | */ 25 | void* get_module_config(void *config, const char *name); 26 | 27 | /** 28 | * @param moduleOrder would be filled with names of the module 29 | * It is valid as long as the config is not freed. 30 | * @param len length of moduleOrder. If the order specified in config is greater than len, 31 | * then it is silently truncated. 32 | * @return past-the-end iterator of moduleOrder if property order present in config, 33 | * otherwise NULL. 34 | */ 35 | const char** get_module_order(void *config, const char* moduleOrder[], size_t len); 36 | 37 | /** 38 | * @return false if the module is explicitly disabled, else true 39 | */ 40 | bool is_block_printer_enabled(const void *config, const char *name); 41 | 42 | void free_config(void *config); 43 | 44 | /** 45 | * The 5 getters below are used in 'modules/\*Printer.cc' only 46 | */ 47 | 48 | /** 49 | * @return heap-allocated string 50 | */ 51 | const char* get_property(const void *module_config, const char *property, const char *default_val); 52 | /** 53 | * @return heap-allocated string 54 | */ 55 | const char* get_format(const void *module_config, const char *default_val); 56 | const char* get_short_format(const void *module_config, const char *default_val); 57 | /** 58 | * @param module_name used only for printing err msg 59 | */ 60 | uint32_t get_update_interval(const void *module_config, const char *module_name, uint32_t default_val); 61 | 62 | const void* get_callable(const void *module_config, const char *property_name); 63 | const void* get_click_event_handler(const void *module_config); 64 | 65 | /** 66 | * @param n number of variadic args 67 | * @param args should be properties to be removed before converting this module_config 68 | * the a string `"property0": val, ...`, suitable for printing a json directly 69 | * Eg: "\"border_top\":2,\"borer_left\":3" 70 | * @return If user hasn't specialized any property, then NULL 71 | * 72 | * If user hasn't specified "separator", then it would be set to true. 73 | */ 74 | const char* get_user_specified_property_str_impl(void *module_config, unsigned n, /* args */ ...); 75 | const char* get_user_specified_property_str_impl2(void *module_config, unsigned n, va_list ap); 76 | 77 | # ifdef __cplusplus 78 | } 79 | # endif 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/modules/LoadPrinter.cc: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE /* For strsep */ 2 | #define _POSIX_C_SOURCE 200809L /* For AT_FDCWD */ 3 | 4 | #include 5 | 6 | #include /* For lseek */ 7 | #include /* For AT_FDCWD and O_RDONLY */ 8 | 9 | #include "../utility.h" 10 | #include "../Fd.hpp" 11 | 12 | #include "LoadPrinter.hpp" 13 | 14 | using namespace std::literals; 15 | 16 | namespace swaystatus::modules { 17 | class LoadPrinter: public Base { 18 | Fd load_fd; 19 | /* 20 | * 100-long buffer should be enough for /proc/loadavg 21 | */ 22 | char buffer[100]; 23 | const char* statistics[6]; 24 | 25 | /** 26 | * @pre str != NULL, statistics != NULL 27 | */ 28 | void split(char *str, const char *statistics[5]) 29 | { 30 | size_t i = 0; 31 | 32 | do { 33 | statistics[i++] = strsep(&str, " "); 34 | } while (str); 35 | 36 | if (i != 5) 37 | errx(1, "%s on %s failed", "Assumption", loadavg_path); 38 | } 39 | void parse_loadavg(char *str, const char *statistics[6]) 40 | { 41 | char *delimiter = strchr(str, '/'); 42 | if (!delimiter) 43 | errx(1, "%s on %s failed", "Assumption", loadavg_path); 44 | 45 | split(str, statistics); 46 | 47 | statistics[5] = statistics[4]; 48 | statistics[4] = delimiter + 1; 49 | *delimiter = '\0'; 50 | } 51 | 52 | void update_load() 53 | { 54 | ssize_t cnt = readall(load_fd.get(), buffer, sizeof(buffer) - 1); 55 | if (cnt == -1) 56 | err(1, "%s on %s failed", "readall", loadavg_path); 57 | if (cnt == 0 || cnt == sizeof(buffer) - 1) 58 | errx(1, "%s on %s failed", "Assumption", loadavg_path); 59 | buffer[cnt] = '\0'; 60 | 61 | if (lseek(load_fd.get(), 0, SEEK_SET) == (off_t) -1) 62 | err(1, "%s on %s failed", "lseek", loadavg_path); 63 | 64 | parse_loadavg(buffer, statistics); 65 | } 66 | 67 | public: 68 | static constexpr const char * const loadavg_path = "/proc/loadavg"; 69 | 70 | LoadPrinter(void *config): 71 | Base{ 72 | config, "LoadPrinter"sv, 73 | 60, "1m: {loadavg_1m} 5m: {loadavg_5m} 15m: {loadavg_15m}", nullptr 74 | }, 75 | load_fd{openat_checked("", AT_FDCWD, loadavg_path, O_RDONLY)} 76 | {} 77 | 78 | void update() 79 | { 80 | update_load(); 81 | } 82 | void do_print(const char *format) 83 | { 84 | print( 85 | format, 86 | fmt::arg("loadavg_1m", statistics[0]), 87 | fmt::arg("loadavg_5m", statistics[1]), 88 | fmt::arg("loadavg_15m", statistics[2]), 89 | fmt::arg("running_kthreads_cnt", statistics[3]), 90 | fmt::arg("total_kthreads_cnt", statistics[4]), 91 | fmt::arg("last_created_process_pid", statistics[5]) 92 | ); 93 | } 94 | void reload() 95 | {} 96 | }; 97 | 98 | std::unique_ptr makeLoadPrinter(void *config) 99 | { 100 | return std::make_unique(config); 101 | } 102 | } /* namespace swaystatus::modules */ 103 | -------------------------------------------------------------------------------- /src/mem_size_t.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "mem_size_t.hpp" 4 | 5 | using formatter = fmt::formatter; 6 | 7 | auto formatter::parse(format_parse_context &ctx) -> format_parse_context_it 8 | { 9 | auto it = ctx.begin(), end = ctx.end(); 10 | if (it == end) 11 | return it; 12 | 13 | switch (*it) { 14 | default: 15 | FMT_THROW(format_error("invalid format")); 16 | 17 | case 'A': 18 | case 'K': 19 | case 'M': 20 | case 'G': 21 | case 'T': 22 | case 'P': 23 | case 'E': 24 | case 'Z': 25 | case 'Y': 26 | presentation = *it; 27 | /* If no terminating '}' is present */ 28 | if (++it == end) 29 | FMT_THROW(format_error("invalid format: Unterminated '{'")); 30 | } 31 | 32 | if (*it == '}') 33 | return it; 34 | 35 | if (*it != ' ') 36 | FMT_THROW(format_error("invalid format")); 37 | ++it; 38 | 39 | ctx.advance_to(it); 40 | return fmt::formatter::parse(ctx); 41 | } 42 | 43 | static const char* get_unit(size_t ratio) 44 | { 45 | switch (ratio) { 46 | case 1: 47 | return "B"; 48 | case 2: 49 | return "K"; 50 | case 3: 51 | return "M"; 52 | case 4: 53 | return "G"; 54 | case 5: 55 | return "T"; 56 | case 6: 57 | return "P"; 58 | case 7: 59 | return "E"; 60 | case 8: 61 | return "Z"; 62 | case 9: 63 | return "Y"; 64 | 65 | default: 66 | return "Invalid unit"; 67 | } 68 | } 69 | auto formatter::format_impl(size_t mem, const char *unit, format_context &ctx) -> format_context_it 70 | { 71 | auto it = fmt::formatter::format(mem, ctx); 72 | return fmt::format_to(it, "{}", unit); 73 | } 74 | auto formatter::auto_format(size_t mem, format_context &ctx) -> format_context_it 75 | { 76 | size_t ratio = 1; 77 | for (; ratio < 9 && mem > 1024; ratio += 1) 78 | mem /= 1024; 79 | 80 | return format_impl(mem, get_unit(ratio), ctx); 81 | } 82 | auto formatter::format(const mem_size_t &sz, format_context &ctx) -> format_context_it 83 | { 84 | size_t mem = sz.bytes; 85 | 86 | switch (presentation) { 87 | case 'B': 88 | return format_impl(mem, "B", ctx); 89 | 90 | case 'K': 91 | return format_impl(mem / 1024, "K", ctx); 92 | 93 | case 'M': 94 | return format_impl(mem / 1024 / 1024, "M", ctx); 95 | 96 | case 'G': 97 | return format_impl(mem / 1024 / 1024 / 1024, "G", ctx); 98 | 99 | case 'T': 100 | return format_impl(mem / 1024 / 1024 / 1024 / 1024, "T", ctx); 101 | 102 | case 'P': 103 | return format_impl(mem / 1024 / 1024 / 1024 / 1024 / 1024, "P", ctx); 104 | 105 | case 'E': 106 | return format_impl(mem / 1024 / 1024 / 1024 / 1024 / 1024 / 1024, "E", ctx); 107 | 108 | case 'Z': 109 | return format_impl(mem / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024, "Z", ctx); 110 | 111 | case 'Y': 112 | return format_impl(mem / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024, "Y", ctx); 113 | 114 | case 'A': 115 | return auto_format(mem, ctx); 116 | 117 | default: 118 | assert(false); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/modules/Base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_modules_Base_HPP__ 2 | # define __swaystatus_modules_Base_HPP__ 3 | 4 | # include 5 | # include 6 | # include 7 | # include 8 | # include 9 | # include 10 | 11 | # include "../formatting/printer.hpp" 12 | 13 | namespace swaystatus::modules { 14 | namespace impl { 15 | template 16 | struct is_cstr: public std::true_type {}; 17 | 18 | template 19 | struct is_cstr 20 | { 21 | constexpr bool operator () () const noexcept 22 | { 23 | /** 24 | * Use if constexpr to avoid instantiation if possible. 25 | */ 26 | if constexpr(std::is_same_v, const char*>) 27 | return is_cstr{}(); 28 | else 29 | return false; 30 | } 31 | }; 32 | 33 | template 34 | inline constexpr const bool is_cstr_v = is_cstr{}(); 35 | } /* namespace impl */ 36 | 37 | class Base { 38 | // instance variables 39 | const std::string_view module_name; 40 | 41 | const std::unique_ptr full_text_format; 42 | const std::unique_ptr short_text_format; 43 | 44 | std::uint32_t cycle_cnt; 45 | const std::uint32_t interval; 46 | 47 | std::unique_ptr user_specified_properties_str; 48 | std::size_t user_specified_properties_str_len; 49 | 50 | std::uint8_t * const requested_events; 51 | 52 | // instance methods 53 | 54 | /** 55 | * @param name need to be null-terminated 56 | */ 57 | void print_fmt(std::string_view name, const char *format); 58 | 59 | protected: 60 | Base() = delete; 61 | 62 | Base(const Base&) = delete; 63 | Base(Base&&) = delete; 64 | 65 | Base& operator = (const Base&) = delete; 66 | Base& operator = (Base&&) = delete; 67 | 68 | /** 69 | * @param module_name_arg should be null-terminated 70 | * @param n number of variadic args 71 | * @param args should be properties to be removed before converting this module_config 72 | * the a string `"property0": val, ...`, suitable for printing a json directly 73 | * Eg: "\"border_top\":2,\"borer_left\":3" 74 | * 75 | * This ctor invokes uses n and args to invoke get_user_specified_property_str_impl2. 76 | */ 77 | Base(void *config, std::string_view module_name_arg, 78 | std::uint32_t default_interval, 79 | const char *default_full_format, const char *default_short_format, 80 | unsigned n, ...); 81 | 82 | /** 83 | * Convenient wrapper 84 | */ 85 | template >> 86 | Base( 87 | void *config, std::string_view module_name_arg, 88 | std::uint32_t default_interval, 89 | const char *default_full_format, const char *default_short_format, 90 | Args &&...args 91 | ): 92 | Base{ 93 | config, module_name_arg, 94 | default_interval, default_full_format, default_short_format, 95 | sizeof...(args), std::forward(args)... 96 | } 97 | {} 98 | 99 | virtual void update() = 0; 100 | virtual void do_print(const char *format) = 0; 101 | virtual void reload() = 0; 102 | 103 | public: 104 | /** 105 | * The first call to update_and_print will always trigger update 106 | * Immediately after reload(), update() will be called. 107 | */ 108 | void update_and_print(); 109 | 110 | virtual ~Base() = default; 111 | }; 112 | 113 | auto makeModules(void *config) -> std::vector>; 114 | } /* namespace swaystatus::modules */ 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Path 2 | BUILD_DIR ?= . 3 | TARGET_DIR := /usr/local 4 | 5 | # Features 6 | PYTHON ?= true 7 | DEBUG ?= false 8 | EXCEPTION ?= true 9 | 10 | # compiler and flags 11 | CC := clang 12 | CXX := clang++ 13 | 14 | CFLAGS := -Wall -Wextra -Werror 15 | 16 | ifeq ($(DEBUG), false) 17 | CFLAGS += -O3 -flto 18 | CFLAGS += -fno-asynchronous-unwind-tables -fno-unwind-tables 19 | 20 | CXXFLAGS := -fno-rtti 21 | 22 | LDFLAGS := -march=native -s -flto -fuse-ld=lld -Wl,-icf=all,--gc-sections,--plugin-opt=O3,-O3,--as-needed 23 | else 24 | CFLAGS += -Og -g 25 | endif 26 | 27 | ifeq ($(EXCEPTION), false) 28 | CXXFLAGS += -fno-exceptions 29 | endif 30 | 31 | CFLAGS += $(shell pkg-config --cflags alsa json-c) 32 | LIBS := $(shell pkg-config --libs alsa json-c) -ldl -lsensors 33 | 34 | ifeq ($(PYTHON), true) 35 | 36 | ifneq ($(shell python3 -c 'import sys; print(sys.version_info >= (3, 7))'), True) 37 | $(error Minimum python3 version is 3.7) 38 | endif 39 | 40 | CFLAGS += $(shell python3-config --includes) -DUSE_PYTHON 41 | LIBS += $(shell python3-config --ldflags --embed) 42 | endif 43 | 44 | ## Objects to build 45 | C_SRCS := $(shell find -maxdepth 2 -name '*.c') 46 | C_OBJS := $(C_SRCS:.c=.o) 47 | 48 | CXX_SRCS := $(shell find -maxdepth 2 -name '*.cc') 49 | CXX_OBJS := $(CXX_SRCS:.cc=.o) 50 | 51 | OBJS := $(addprefix $(BUILD_DIR)/, $(C_OBJS) $(CXX_OBJS)) 52 | 53 | .DEFAULT_GOAL := $(BUILD_DIR)/swaystatus 54 | 55 | ## Automatic dependency building 56 | DEPFLAGS = -MT $@ -MMD -MP -MF $(BUILD_DIR)/$*.Td 57 | DEPFILES := $(OBJS:%.o=%.d) 58 | 59 | ## Dummy target for $(DEPFILES) when they are not present. 60 | $(DEPFILES): 61 | ## Use wildcard so that nonexisitent dep files are ignored. 62 | include $(wildcard $(DEPFILES)) 63 | 64 | ## Build rules 65 | .SECONDEXPANSION: 66 | 67 | DEBUG: 68 | @echo "C_SRCS=$(C_SRCS), C_OBJS=$(C_OBJS)" 69 | @echo "CXX_SRCS=$(CXX_SRCS), CXX_OBJS=$(CXX_OBJS)" 70 | @echo "OBJS=$(OBJS)" 71 | 72 | $(BUILD_DIR)/swaystatus: $(OBJS) 73 | $(CXX) $(LDFLAGS) $(LIBS) $^ -o $@ 74 | 75 | $(BUILD_DIR)/%.o: %.c $$(wildcard %.h) Makefile 76 | $(CC) -std=c11 -c $(CFLAGS) $(DEPFLAGS) -o $@ $< 77 | mv -f $(BUILD_DIR)/$*.Td $(BUILD_DIR)/$*.d && touch $@ 78 | 79 | $(BUILD_DIR)/%.o: %.cc $$(wildcard %.h) $$(wildcard %.hpp) Makefile 80 | $(CXX) -std=c++17 -c $(CXXFLAGS) $(CFLAGS) $(DEPFLAGS) -o $@ $< 81 | mv -f $(BUILD_DIR)/$*.Td $(BUILD_DIR)/$*.d && touch $@ 82 | 83 | compile_flags.txt: Makefile 84 | echo '-xc++' $(CXXFLAGS) $(CFLAGS) | sed 's/-I/-I /g' | xargs -n1 echo | tee $@ 85 | 86 | compile_commands.json: Makefile 87 | rm -rf /tmp/swaystatus_build_dir/ 88 | mkdir -p /tmp/swaystatus_build_dir/ 89 | BUILD_DIR=/tmp/swaystatus_build_dir/ PYTHON=true DEBUG=true compiledb make -j $(shell nproc) 90 | rm -rf /tmp/swaystatus_build_dir/ 91 | 92 | ### Convenient target for automatically running debug/release builds 93 | ### Move from run_all_builds.sh to further parallel the builds 94 | all_builds: release_build release_no_py_build 95 | 96 | debug_build: 97 | mkdir -p $@ 98 | mkdir -p $@/Callback 99 | mkdir -p $@/formatting 100 | mkdir -p $@/modules 101 | $(MAKE) BUILD_DIR=$@ DEBUG=true PYTHON=true 102 | 103 | release_build: 104 | mkdir -p $@ 105 | mkdir -p $@/Callback 106 | mkdir -p $@/formatting 107 | mkdir -p $@/modules 108 | $(MAKE) BUILD_DIR=$@ DEBUG=false PYTHON=true 109 | 110 | release_no_py_build: 111 | mkdir -p $@ 112 | mkdir -p $@/Callback 113 | mkdir -p $@/formatting 114 | mkdir -p $@/modules 115 | $(MAKE) BUILD_DIR=$@ DEBUG=false PYTHON=false 116 | 117 | install: $(BUILD_DIR)/swaystatus 118 | cp -f $(BUILD_DIR)/swaystatus $(TARGET_DIR)/bin/ 119 | 120 | install_release_build: release_build 121 | $(MAKE) BUILD_DIR=release_build DEBUG=false PYTHON=true install 122 | 123 | clean: 124 | rm -rf $(OBJS) $(BUILD_DIR)/swaystatus $(DEPFILES) 125 | 126 | .PHONY: DEBUG all_builds debug_build release_build release_no_py_build install install_release_build clean 127 | -------------------------------------------------------------------------------- /src/Callback/Callable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_Callable_HPP__ 2 | # define __swaystatus_Callable_HPP__ 3 | 4 | # include 5 | 6 | # include 7 | 8 | # include "../utility.h" 9 | # include "python3.hpp" 10 | # include "dynlib.hpp" 11 | 12 | namespace swaystatus { 13 | namespace impl { 14 | template 15 | struct is_valid_callable_args_t { 16 | constexpr bool operator () () const noexcept 17 | { 18 | return true; 19 | } 20 | }; 21 | 22 | template 23 | struct is_valid_callable_args_t { 24 | constexpr bool operator () () const noexcept 25 | { 26 | if constexpr(std::is_fundamental_v< rm_cvref_t > && std::is_rvalue_reference_v) 27 | return false; 28 | else 29 | return is_valid_callable_args_t{}(); 30 | } 31 | }; 32 | 33 | template 34 | constexpr bool is_valid_callable_args() noexcept 35 | { 36 | return is_valid_callable_args_t{}(); 37 | } 38 | } /* namespace impl */ 39 | 40 | class Callable_base { 41 | std::variant< 42 | std::monostate, 43 | /* pointer to the c function */ 44 | void* 45 | # ifdef USE_PYTHON 46 | , python::Callable_base 47 | # endif 48 | > v; 49 | 50 | template 51 | friend class Callable; 52 | 53 | public: 54 | Callable_base() = default; 55 | 56 | Callable_base(const char *name, const void *callable_config); 57 | 58 | Callable_base(Callable_base&&) = default; 59 | Callable_base& operator = (Callable_base&&) = default; 60 | 61 | ~Callable_base() = default; 62 | }; 63 | 64 | template 65 | class Callable { 66 | static_assert(impl::is_valid_callable_args()); 67 | 68 | struct monostate { 69 | Ret operator () (Args ...args) const noexcept 70 | { 71 | auto dummy = [](auto &&arg) noexcept 72 | { 73 | (void) arg; 74 | return 0; 75 | }; 76 | const int dummy_array[] = {dummy(std::forward(args))...}; 77 | (void) dummy_array; 78 | 79 | if constexpr(!std::is_void_v) 80 | return Ret{}; 81 | } 82 | }; 83 | 84 | # ifdef USE_PYTHON 85 | using py_callback = python::Callable< 86 | std::conditional_t, python::None, Ret>, 87 | Args... 88 | >; 89 | # endif 90 | 91 | std::variant< 92 | monostate, 93 | CFunction 94 | # ifdef USE_PYTHON 95 | , py_callback 96 | # endif 97 | > v; 98 | 99 | public: 100 | Callable() = default; 101 | 102 | Callable(Callable_base &&base) 103 | { 104 | # ifdef USE_PYTHON 105 | if (auto p = std::get_if(&base.v); p) { 106 | v.template emplace(std::move(*p)); 107 | } else 108 | # endif 109 | if (auto p = std::get_if(&base.v); p) { 110 | v.template emplace>(*p); 111 | } 112 | } 113 | 114 | Callable(Callable&&) = default; 115 | Callable& operator = (Callable&&) = default; 116 | 117 | ~Callable() = default; 118 | 119 | auto operator () (Args ...args) -> Ret 120 | { 121 | # ifdef USE_PYTHON 122 | auto scope = std::holds_alternative(v) ? 123 | python::MainInterpreter::get().acquire() : 124 | python::Interpreter::GIL_scoped(nullptr); 125 | # endif 126 | 127 | if constexpr(std::is_void_v) { 128 | std::visit([&](auto &&f) { 129 | f(std::forward(args)...); 130 | }, v); 131 | } else { 132 | return std::visit([&](auto &&f) 133 | { 134 | return f(std::forward(args)...); 135 | }, v); 136 | } 137 | } 138 | }; 139 | } /* namespace swaystatus */ 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /src/modules/MemoryUsagePrinter.cc: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L /* For AT_FDCWD */ 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "../utility.h" 12 | #include "../Fd.hpp" 13 | 14 | #include "../formatting/printer.hpp" 15 | #include "../formatting/LazyEval.hpp" 16 | #include "../mem_size_t.hpp" 17 | #include "MemoryUsagePrinter.hpp" 18 | 19 | using namespace std::literals; 20 | 21 | namespace swaystatus::modules { 22 | class MemoryUsagePrinter: public Base { 23 | static constexpr const char * const path = "/proc/meminfo"; 24 | 25 | Fd meminfo_fd; 26 | std::size_t memtotal = -1; 27 | std::string buffer; 28 | 29 | void read_meminfo() 30 | { 31 | ssize_t cnt = asreadall(meminfo_fd.get(), buffer); 32 | if (cnt < 0) 33 | err(1, "%s on %s failed", "read", path); 34 | 35 | if (lseek(meminfo_fd.get(), 0, SEEK_SET) == (off_t) -1) 36 | err(1, "%s on %s failed", "lseek", path); 37 | } 38 | char* skip_space(char *str) 39 | { 40 | while (*str == ' ') 41 | ++str; 42 | return str; 43 | } 44 | 45 | /** 46 | * @pre must call read_meminfo() before calling get_memusage. 47 | * @param element_sz excluding terminating null byte 48 | * @return in byte 49 | */ 50 | std::size_t get_memusage(std::string_view element) 51 | { 52 | char *line; 53 | do { 54 | line = strstr(buffer.data(), element.data()); 55 | if (!line) 56 | return -1; 57 | } while (line[element.size()] != ':'); 58 | 59 | line += element.size() + 1; 60 | line = skip_space(line); 61 | 62 | errno = 0; 63 | char *endptr; 64 | uintmax_t val = strtoumax(line, &endptr, 10); 65 | if (errno == ERANGE) 66 | err(1, "%s on %s failed", "strtoumax", "/proc/meminfo"); 67 | if (strncmp(endptr, " kB\n", 4) != 0) 68 | errx(1, "%s on %s failed", "Assumption", "/proc/meminfo"); 69 | 70 | return val * 1000; 71 | } 72 | 73 | auto get_memusage_lazy(std::string_view element) 74 | { 75 | return swaystatus::LazyEval{[=]() noexcept { 76 | return mem_size_t{get_memusage(element)}; 77 | }}; 78 | } 79 | 80 | public: 81 | MemoryUsagePrinter(void *config): 82 | Base{ 83 | config, "MemoryUsagePrinter"sv, 84 | 10, "Mem Free={MemFree}/Total={MemTotal}", nullptr 85 | }, 86 | meminfo_fd{openat_checked("", AT_FDCWD, path, O_RDONLY)} 87 | {} 88 | 89 | void update() 90 | { 91 | read_meminfo(); 92 | if (UNLIKELY(memtotal == static_cast(-1) )) 93 | memtotal = get_memusage("MemTotal"sv); 94 | } 95 | void do_print(const char *format) 96 | { 97 | print( 98 | format, 99 | fmt::arg("MemFree", get_memusage_lazy("MemFree")), 100 | fmt::arg("MemAvailable", get_memusage_lazy("MemAvailable")), 101 | fmt::arg("Buffers", get_memusage_lazy("Buffers")), 102 | fmt::arg("Cached", get_memusage_lazy("Cached")), 103 | fmt::arg("SwapCached", get_memusage_lazy("SwapCached")), 104 | fmt::arg("Active", get_memusage_lazy("Active")), 105 | fmt::arg("Inactive", get_memusage_lazy("Inactive")), 106 | fmt::arg("Mlocked", get_memusage_lazy("Mlocked")), 107 | fmt::arg("SwapTotal", get_memusage_lazy("SwapTotal")), 108 | fmt::arg("SwapFree", get_memusage_lazy("SwapFree")), 109 | fmt::arg("Dirty", get_memusage_lazy("Dirty")), 110 | fmt::arg("Writeback", get_memusage_lazy("Writeback")), 111 | fmt::arg("AnonPages", get_memusage_lazy("AnonPages")), 112 | fmt::arg("Mapped", get_memusage_lazy("Mapped")), 113 | fmt::arg("Shmem", get_memusage_lazy("Shmem")), 114 | fmt::arg("MemTotal", mem_size_t{memtotal}) 115 | ); 116 | } 117 | void reload() 118 | {} 119 | }; 120 | 121 | std::unique_ptr makeMemoryUsagePrinter(void *config) 122 | { 123 | return std::make_unique(config); 124 | } 125 | } /* namespace swaystatus::modules */ 126 | -------------------------------------------------------------------------------- /src/networking.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_networking_H__ 2 | # define __swaystatus_networking_H__ 3 | 4 | # include "formatting/fmt_config.hpp" 5 | 6 | # include 7 | # include 8 | # include 9 | # include 10 | 11 | # include 12 | # include 13 | # include 14 | # include 15 | 16 | # include "formatting/fmt/include/fmt/format.h" 17 | 18 | namespace swaystatus { 19 | using ipv4_addr = struct in_addr; 20 | using ipv6_addr = struct in6_addr; 21 | 22 | using interface_stats = struct rtnl_link_stats; 23 | 24 | struct ip_addrs {}; 25 | /** 26 | * It is unlikely for one computer to have more than 8 addresses. 27 | */ 28 | struct ipv4_addrs: ip_addrs { 29 | using const_iterator = typename std::array::const_iterator; 30 | 31 | std::size_t cnt = 0; 32 | std::array array; 33 | 34 | void add(const struct sockaddr *src_addr) noexcept; 35 | void reset() noexcept; 36 | 37 | auto begin() const noexcept -> const_iterator; 38 | auto end() const noexcept -> const_iterator; 39 | }; 40 | struct ipv6_addrs: ip_addrs { 41 | using const_iterator = typename std::array::const_iterator; 42 | 43 | std::size_t cnt = 0; 44 | std::array array; 45 | 46 | void add(const struct sockaddr *src_addr) noexcept; 47 | void reset() noexcept; 48 | 49 | auto begin() const noexcept -> const_iterator; 50 | auto end() const noexcept -> const_iterator; 51 | }; 52 | struct Interface { 53 | static interface_stats get_empty_stats() noexcept; 54 | 55 | std::string name; 56 | unsigned int flags; /* Flags from SIOCGIFFLAGS */ 57 | 58 | interface_stats stat = get_empty_stats(); 59 | 60 | ipv4_addrs ipv4_addrs_v; 61 | ipv6_addrs ipv6_addrs_v; 62 | 63 | /** 64 | * operator == and != are used to find the interface. 65 | */ 66 | bool operator == (std::string_view interface_name) const noexcept; 67 | bool operator != (std::string_view interface_name) const noexcept; 68 | 69 | void reset() noexcept; 70 | }; 71 | 72 | class Interfaces { 73 | std::size_t cnt = 0; 74 | /** 75 | * It is unlikely for one computer to have more than 8 network interfaces. 76 | */ 77 | std::array interfaces; 78 | 79 | public: 80 | using iterator = typename std::array::iterator; 81 | using const_iterator = typename std::array::const_iterator; 82 | 83 | Interfaces() = default; 84 | ~Interfaces() = default; 85 | 86 | auto operator [] (std::string_view name) noexcept -> iterator; 87 | 88 | bool is_empty() const noexcept; 89 | auto size() const noexcept -> std::size_t; 90 | 91 | auto begin() noexcept -> iterator; 92 | auto end() noexcept -> iterator; 93 | 94 | auto begin() const noexcept -> const_iterator; 95 | auto end() const noexcept -> const_iterator; 96 | 97 | auto cbegin() const noexcept -> const_iterator; 98 | auto cend() const noexcept -> const_iterator; 99 | 100 | void update(); 101 | void clear() noexcept; 102 | }; 103 | } 104 | 105 | template <> 106 | struct fmt::formatter 107 | { 108 | using Interfaces = swaystatus::Interfaces; 109 | 110 | using format_parse_context_it = typename format_parse_context::iterator; 111 | using format_context_it = typename format_context::iterator; 112 | 113 | std::string_view fmt_str = ""; 114 | 115 | auto parse(format_parse_context &ctx) -> format_parse_context_it; 116 | auto format(const Interfaces &interfaces, format_context &ctx) -> format_context_it; 117 | }; 118 | 119 | template <> 120 | struct fmt::formatter 121 | { 122 | using ipv4_addrs = swaystatus::ipv4_addrs; 123 | 124 | using format_parse_context_it = typename format_parse_context::iterator; 125 | using format_context_it = typename format_context::iterator; 126 | 127 | std::size_t limit = -1; 128 | 129 | auto parse(format_parse_context &ctx) -> format_parse_context_it; 130 | auto format(const ipv4_addrs &addrs, format_context &ctx) -> format_context_it; 131 | }; 132 | 133 | template <> 134 | struct fmt::formatter 135 | { 136 | using ipv6_addrs = swaystatus::ipv6_addrs; 137 | 138 | using format_parse_context_it = typename format_parse_context::iterator; 139 | using format_context_it = typename format_context::iterator; 140 | 141 | std::size_t limit = -1; 142 | 143 | auto parse(format_parse_context &ctx) -> format_parse_context_it; 144 | auto format(const ipv6_addrs &addrs, format_context &ctx) -> format_context_it; 145 | }; 146 | 147 | #endif 148 | -------------------------------------------------------------------------------- /src/utility.h: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_utility_H__ 2 | # define __swaystatus_utility_H__ 3 | 4 | # include 5 | # include 6 | # include 7 | # include 8 | # include 9 | 10 | # ifdef __GNUC__ 11 | # define LIKELY(expr) (__builtin_expect((expr), 1)) 12 | # define UNLIKELY(expr) (__builtin_expect((expr), 0)) 13 | # else 14 | # define LIKELY(expr) (expr) 15 | # define UNLIKELY(expr) (expr) 16 | # endif 17 | 18 | # ifdef __cplusplus 19 | extern "C" { 20 | # endif 21 | 22 | uintmax_t min_unsigned(uintmax_t x, uintmax_t y); 23 | 24 | void* malloc_checked(size_t size); 25 | 26 | void reallocarray_checked(void **ptr, size_t nmemb, size_t size); 27 | 28 | /** 29 | * @param ptr must be l-value 30 | */ 31 | #define reallocate(ptr, n) \ 32 | reallocarray_checked((void**) &(ptr), (n), sizeof(*ptr)) 33 | 34 | char* strdup_checked(const char *s); 35 | 36 | /** 37 | * @return heap allocated string 38 | */ 39 | char* escape_quotation_marks(const char *fmt); 40 | 41 | char* realpath_checked(const char *path); 42 | 43 | void setenv_checked(const char *name, const char *value, int overwrite); 44 | 45 | /** 46 | * @param msec milliseconds 47 | * NOTE that msleep does not restart on interruption. 48 | */ 49 | void msleep(uintmax_t msec); 50 | 51 | /** 52 | * @param msec milliseconds 53 | * @return a pollable fd. 54 | * 55 | * The initial expire of the timer is ASAP (1 ns) 56 | * 57 | * Check man page of timefd_create for how to use the return value of this API. 58 | */ 59 | int create_pollable_monotonic_timer(uintmax_t msec); 60 | /** 61 | * @param timerfd must be ret val of create_pollable_monotonic_timer and 62 | * in poller.h:Event::read_ready state. 63 | * @return number of time the timer has fired. 64 | */ 65 | uint64_t read_timer(int timerfd); 66 | 67 | void set_terminate_handler(void (*handler)()); 68 | 69 | void sigaction_checked_impl(int sig, const char *signame, void (*sighandler)(int signum)); 70 | 71 | # define sigaction_checked(sig, sighandler) sigaction_checked_impl((sig), # sig, (sighandler)) 72 | 73 | /** 74 | * close all fd except for 0, 1 and 2. 75 | */ 76 | void close_all(); 77 | 78 | /** 79 | * @param dir should end with '/' or "" 80 | * @param dirfd if dir == "", then dirfd got to be AT_FDCWD. 81 | * @param flags all fd will be opend with O_CLOEXEC. 82 | */ 83 | int openat_checked(const char *dir, int dirfd, const char *path, int flags); 84 | 85 | void set_fd_non_blocking(int fd); 86 | 87 | ssize_t read_autorestart(int fd, void *buf, size_t count); 88 | ssize_t write_autorestart(int fd, const void *buf, size_t count); 89 | 90 | /** 91 | * @return If equal to len, then the file is bigger than expected. 92 | * -1 if read failed, error code is stored in errno 93 | * 0 if EOF or EAGAIN/EWOULDBLOCK 94 | * 95 | * readall read all data from fd until EOF or EAGAIN/EWOULDBLOCK returned or 96 | * the buffer is full. 97 | */ 98 | ssize_t readall(int fd, void *buffer, size_t len); 99 | /** 100 | * @param buffer it must point to a heap allocated buffer, can be NULL 101 | * @param len must point to length of *buffer, can be 0 102 | * @return -1 if read failed, error code is stored in errno. 103 | * 104 | * If buffer isn't large enough, then asreadall will realloc it 105 | * The read in buffer will be zero-terminated. 106 | */ 107 | ssize_t asreadall(int fd, char **buffer, size_t *len); 108 | 109 | /** 110 | * @return function name/assumption that failed or NULL if succeeded. 111 | */ 112 | const char* readall_as_uintmax(int fd, uintmax_t *val); 113 | 114 | /** 115 | * @param dir should end with '/' 116 | * @param path should be relative, eitherwise the error message will be confusing. 117 | * 118 | * isdir follows symlink 119 | */ 120 | int isdir(const char *dir, int dirfd, const char *path); 121 | 122 | void stack_bt(); 123 | 124 | typedef void (*subdir_visiter)(int path_fd, const char *d_name, va_list ap); 125 | /** 126 | * Wrapper function for readdir 127 | */ 128 | void visit_all_subdirs(const char *path, subdir_visiter visiter, ...); 129 | 130 | # ifdef __cplusplus 131 | } 132 | 133 | # include 134 | # include 135 | # include 136 | 137 | namespace swaystatus { 138 | std::string getcwd_checked(); 139 | 140 | /** 141 | * @param buffer it must point to an empty std::string 142 | * @return -1 if read failed, error code is stored in errno. 143 | * 144 | * If buffer isn't large enough, then asreadall will resize it 145 | */ 146 | ssize_t asreadall(int fd, std::string &buffer); 147 | 148 | constexpr bool is_all_true(std::initializer_list list) 149 | { 150 | for (bool each: list) { 151 | if (!each) 152 | return false; 153 | } 154 | return true; 155 | } 156 | 157 | template 158 | using rm_cvref_t = std::remove_cv_t>; 159 | } /* namespace swaystatus */ 160 | # endif 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /src/sensors.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from: 3 | * https://stackoverflow.com/questions/8556551/has-anyone-been-able-to-use-libsensors-properly 4 | */ 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "sensors.hpp" 11 | 12 | namespace swaystatus { 13 | static auto sensor_get_int8_val(const sensors_chip_name &cn, const sensors_subfeature *subf) 14 | { 15 | double val; 16 | if (sensors_get_value(&cn, subf->number, &val) < 0) 17 | errx(1, "%s failed", "sensors_get_value"); 18 | 19 | return static_cast(std::lround(val)); 20 | } 21 | 22 | Sensor::Sensor(const char *prefix, const char *path, int addr, short type, short nr) noexcept: 23 | prefix{prefix}, path{path}, addr{addr}, bus{sensor_bus_type{type}, nr} 24 | {} 25 | 26 | auto Sensor::get_adapter_name() const noexcept -> std::string_view 27 | { 28 | const sensors_bus_id bus_id = { 29 | .type = bus.type.val, 30 | .nr = bus.nr 31 | }; 32 | const char *ret = sensors_get_adapter_name(&bus_id); 33 | if (ret) 34 | return ret; 35 | else 36 | return {}; 37 | } 38 | void Sensor::update(Sensors &sensors) 39 | { 40 | /* 41 | * The const_cast is used only to maintain compatibility with the sensors_bus_id defined in 42 | * sensors/sensors.h 43 | */ 44 | const sensors_chip_name cn = { 45 | .prefix = const_cast(prefix.c_str()), 46 | .bus = sensors_bus_id{ 47 | .type = bus.type.val, 48 | .nr = bus.nr 49 | }, 50 | .addr = addr, 51 | .path = const_cast(path.c_str()) 52 | }; 53 | 54 | sensors_feature const *feat; 55 | int f = 0; 56 | 57 | while ((feat = sensors_get_features(&cn, &f)) != nullptr) { 58 | if (feat->type != SENSORS_FEATURE_TEMP) 59 | continue; 60 | 61 | auto &reading = sensors.readings.emplace_back(this, feat->number); 62 | 63 | sensors_subfeature const *subf; 64 | int s = 0; 65 | 66 | std::uint8_t cnt = 0; 67 | while ((subf = sensors_get_all_subfeatures(&cn, feat, &s)) != nullptr) { 68 | if ((subf->flags & SENSORS_MODE_R) == 0) 69 | continue; 70 | 71 | std::int8_t *result; 72 | switch (subf->type) { 73 | default: 74 | continue; 75 | 76 | case SENSORS_SUBFEATURE_TEMP_INPUT: 77 | result = &reading.temp; 78 | break; 79 | } 80 | 81 | *result = sensor_get_int8_val(cn, subf); 82 | ++cnt; 83 | } 84 | 85 | /** 86 | * Check if the reading contains any valid data 87 | * 88 | * On my computer, BAT0 always return subfeatures that have no TEMP_INPUT, 89 | * so this is necessary. 90 | */ 91 | if (cnt == 0) 92 | sensors.readings.pop_back(); 93 | } 94 | } 95 | 96 | sensor_reading::sensor_reading(Sensor *sensor, int number) noexcept: 97 | sensor{sensor}, number{number} 98 | {} 99 | 100 | Sensors::Sensors() 101 | { 102 | if (sensors_init(nullptr) != 0) 103 | errx(1, "%s failed", "sensors_init"); 104 | reload(); 105 | } 106 | 107 | void Sensors::reload() 108 | { 109 | sensors.clear(); 110 | readings.clear(); 111 | 112 | sensors_chip_name const * cn; 113 | int c = 0; 114 | while ((cn = sensors_get_detected_chips(NULL, &c)) != 0) { 115 | const auto &bus = cn->bus; 116 | sensors.emplace_back(cn->prefix, cn->path, cn->addr, bus.type, bus.nr); 117 | } 118 | 119 | sensors.shrink_to_fit(); 120 | } 121 | 122 | void Sensors::update() 123 | { 124 | readings.clear(); 125 | 126 | for (auto &sensor: sensors) 127 | sensor.update(*this); 128 | 129 | readings.shrink_to_fit(); 130 | } 131 | 132 | auto Sensors::begin() const noexcept -> const_iterator 133 | { 134 | return readings.begin(); 135 | } 136 | auto Sensors::end() const noexcept -> const_iterator 137 | { 138 | return readings.end(); 139 | } 140 | 141 | auto Sensors::cbegin() const noexcept -> const_iterator 142 | { 143 | return readings.begin(); 144 | } 145 | auto Sensors::cend() const noexcept -> const_iterator 146 | { 147 | return readings.end(); 148 | } 149 | } /* namespace swaystatus */ 150 | 151 | using formatter = fmt::formatter; 152 | 153 | static auto bus_type_to_str(const swaystatus::sensor_bus_type &type) noexcept -> std::string_view 154 | { 155 | switch (type.val) { 156 | #define CASE(name) case SENSORS_BUS_TYPE_ ## name: return (# name) 157 | 158 | CASE(ANY); 159 | CASE(I2C); 160 | CASE(ISA); 161 | CASE(PCI); 162 | CASE(SPI); 163 | CASE(VIRTUAL); 164 | CASE(ACPI); 165 | CASE(HID); 166 | CASE(MDIO); 167 | CASE(SCSI); 168 | 169 | #undef CASE 170 | 171 | default: 172 | return "UNKNOWN"; 173 | } 174 | } 175 | auto formatter::format(const sensor_bus_type &type, format_context &ctx) -> format_context_it 176 | { 177 | return fmt::formatter::format(bus_type_to_str(type), ctx); 178 | } 179 | -------------------------------------------------------------------------------- /src/swaystatus.cc: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | # define _GNU_SOURCE /* For RTLD_DEFAULT */ 3 | #endif 4 | #define _DEFAULT_SOURCE /* For nice */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include /* For malloc_trim */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include "help.h" 22 | #include "utility.h" 23 | #include "formatting/printer.hpp" 24 | #include "Callback/python3.hpp" 25 | #include "process_configuration.h" 26 | #include "modules/Base.hpp" 27 | #include "poller.h" 28 | 29 | namespace modules = swaystatus::modules; 30 | 31 | #define starts_with(str, prefix) (strncmp((str), (prefix), sizeof(prefix) - 1) == 0) 32 | 33 | static void terminate_handler() 34 | { 35 | stack_bt(); 36 | _exit(1); 37 | } 38 | static void sigabort_handler(int sig) 39 | { 40 | (void) sig; 41 | stack_bt(); 42 | } 43 | 44 | static bool reload_requested; 45 | static void handle_reload_request(int sig) 46 | { 47 | (void) sig; 48 | reload_requested = true; 49 | } 50 | 51 | static void print_blocks(int fd, enum Event events, void *data) 52 | { 53 | static const uintmax_t trim_interval = 3660; 54 | /** 55 | * trim heap right after first loop is done to give back memory 56 | * used during initialization time. 57 | */ 58 | static uintmax_t cycle_cnt = trim_interval - 1; 59 | 60 | (void) events; 61 | 62 | auto &modules = *static_cast>*>(data); 63 | 64 | print_literal_str("["); 65 | 66 | for (auto &module: modules) 67 | module->update_and_print(); 68 | 69 | /* Print dummy */ 70 | print_literal_str("{}],\n"); 71 | flush(); 72 | 73 | read_timer(fd); 74 | 75 | if (++cycle_cnt == trim_interval) { 76 | malloc_trim(4096 * 3); 77 | cycle_cnt = 0; 78 | } 79 | } 80 | 81 | int main(int argc, char* argv[]) 82 | { 83 | close_all(); 84 | 85 | /* Force dynamic linker to load function backtrace */ 86 | if (dlsym(RTLD_DEFAULT, "backtrace") == NULL) 87 | err(1, "%s on %s failed", "dlsym", "backtrace"); 88 | 89 | nice(19); 90 | 91 | set_terminate_handler(terminate_handler); 92 | sigaction_checked(SIGABRT, sigabort_handler); 93 | sigaction_checked(SIGUSR1, handle_reload_request); 94 | 95 | bool is_reload = false; 96 | const char *config_filename = NULL; 97 | 98 | /* Default interval is 1 second */ 99 | uintmax_t interval = 1000; 100 | 101 | void *config = NULL; 102 | 103 | for (int i = 1; i != argc; ++i) { 104 | if (strcmp(argv[i], "--help") == 0) { 105 | fputs(help, stderr); 106 | exit(1); 107 | } else if (starts_with(argv[i], "--interval=")) { 108 | char *endptr; 109 | errno = 0; 110 | interval = strtoumax(argv[i] + sizeof("--interval=") - 1, &endptr, 10); 111 | if (errno == ERANGE) 112 | err(1, "Invalid argument %s%s", argv[i], ""); 113 | else if (*endptr != '\0') 114 | errx(1, "Invalid argument %s%s", argv[i], ": Contains non-digit character"); 115 | } else if (strcmp(argv[i], "--reload") == 0) { 116 | is_reload = true; 117 | } else { 118 | if (config) 119 | errx(1, "Error: configuration file is specified twice"); 120 | config_filename = realpath_checked(argv[i]); 121 | config = load_config(argv[i]); 122 | } 123 | } 124 | 125 | init_poller(); 126 | 127 | #ifdef USE_PYTHON 128 | if (!is_reload) { 129 | if (config_filename) { 130 | char *file = strdup_checked(config_filename); 131 | char *path = dirname(file); 132 | 133 | setup_pythonpath(path); 134 | 135 | free(file); 136 | } else 137 | setup_pythonpath(NULL); 138 | } 139 | #endif 140 | 141 | auto modules = modules::makeModules(config); 142 | 143 | free_config(config); 144 | 145 | if (chdir("/") < 0) 146 | err(1, "%s failed", "chdir(\"/\")"); 147 | 148 | if (!is_reload) { 149 | /* Print header */ 150 | print_literal_str("{\"version\":1,\"click_events\":true}\n"); 151 | 152 | flush(); 153 | 154 | /* Begin an infinite array */ 155 | print_literal_str("[\n"); 156 | flush(); 157 | } 158 | 159 | int timerfd = create_pollable_monotonic_timer(interval); 160 | request_polling(timerfd, read_ready, print_blocks, &modules); 161 | 162 | do { 163 | perform_polling(-1); 164 | } while (!reload_requested); 165 | 166 | if (reload_requested) { 167 | char buffer[4096]; 168 | if (snprintf(buffer, sizeof(buffer), "--interval=%" PRIuMAX, interval) < 0) 169 | err(1, "%s on %s failed", "snprintf", "char buffer[4096]"); 170 | 171 | execl("/proc/self/exe", "/proc/self/exe", buffer, "--reload", config_filename, NULL); 172 | err(1, "%s on %s failed", "execv", "/proc/self/exe"); 173 | } 174 | 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /src/Battery.cc: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | # define _GNU_SOURCE /* For strchrnul, strcasestr */ 3 | #endif 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include /* For O_RDONLY */ 11 | #include /* For close, lseek and fstat */ 12 | 13 | #include 14 | 15 | #include "formatting/fmt/include/fmt/core.h" 16 | 17 | #include "utility.h" 18 | #include "formatting/fmt_utility.hpp" 19 | 20 | #include "Battery.hpp" 21 | 22 | namespace swaystatus { 23 | auto Battery::makeBattery(int path_fd, std::string_view device, std::string_view excluded_model) -> 24 | std::optional 25 | { 26 | std::size_t device_name_len = device.size(); 27 | 28 | std::string path(device); 29 | path.reserve(device_name_len + 1 + sizeof("uevent")); 30 | 31 | path.append("/type"); 32 | 33 | int fd = openat_checked(power_supply_path, path_fd, path.c_str(), O_RDONLY); 34 | 35 | char type[sizeof("Battery")]; 36 | ssize_t bytes = readall(fd, type, sizeof(type) - 1); 37 | if (bytes < 0) 38 | err(1, "%s on %s%s failed", "readall", power_supply_path, path.c_str()); 39 | if (bytes == 0) 40 | errx(1, "%s on %s%s failed", "Assumption", power_supply_path, path.c_str()); 41 | type[bytes] = '\0'; 42 | 43 | close(fd); 44 | 45 | if (std::strncmp("Battery", type, sizeof("Battery") - 1) != 0) 46 | return {std::nullopt}; 47 | 48 | path.resize(device_name_len); 49 | 50 | auto battery = Battery{path_fd, std::move(path)}; 51 | 52 | battery.read_battery_uevent(); 53 | if (battery.get_property("model_name") == excluded_model) 54 | return std::nullopt; 55 | 56 | return battery; 57 | } 58 | 59 | Battery::Battery(int path_fd, std::string &&device): 60 | battery_device{std::move(device)} 61 | { 62 | auto device_sz = battery_device.size(); 63 | 64 | auto &buffer = battery_device; 65 | buffer.append("/uevent"); 66 | uevent_fd = openat_checked(power_supply_path, path_fd, buffer.c_str(), O_RDONLY); 67 | battery_device.resize(device_sz); 68 | battery_device.shrink_to_fit(); 69 | } 70 | 71 | void Battery::read_battery_uevent() 72 | { 73 | buffer.clear(); 74 | 75 | ssize_t cnt = asreadall(uevent_fd.get(), buffer); 76 | if (cnt < 0) 77 | err(1, "%s on %s%s/%s failed", "read", power_supply_path, battery_device.c_str(), "uevent"); 78 | 79 | if (lseek(uevent_fd.get(), 0, SEEK_SET) == static_cast(-1)) 80 | err(1, "%s on %s%s/%s failed", "lseek", power_supply_path, battery_device.c_str(), "uevent"); 81 | } 82 | 83 | auto Battery::get_property(std::string_view name) const noexcept -> std::string_view 84 | { 85 | if (!uevent_fd) 86 | return {}; 87 | 88 | std::size_t name_len = name.size(); 89 | 90 | char *substr = const_cast(buffer.c_str()); 91 | for (; ;) { 92 | substr = strcasestr(substr, name.data()); 93 | if (!substr) 94 | return "nullptr"; 95 | if (substr[name_len] == '=') 96 | break; 97 | substr += name_len; 98 | } 99 | 100 | const char *value = substr + name_len + 1; 101 | const char *end = strchrnul(substr, '\n'); 102 | 103 | return {value, static_cast(end - value)}; 104 | } 105 | } /* namespace swaystatus */ 106 | 107 | using Batteries_formatter = fmt::formatter>; 108 | 109 | auto Batteries_formatter::parse(format_parse_context &ctx) -> format_parse_context_it 110 | { 111 | auto it = ctx.begin(), end = ctx.end(); 112 | if (it == end) 113 | return it; 114 | 115 | end = swaystatus::find_end_of_format(ctx); 116 | 117 | fmt_str = std::string_view{it, static_cast(end - it)}; 118 | 119 | return end; 120 | } 121 | auto Batteries_formatter::format(const Batteries &batteries, format_context &ctx) 122 | -> format_context_it 123 | { 124 | auto out = ctx.out(); 125 | 126 | if (fmt_str.size() == 0) 127 | return out; 128 | 129 | for (const swaystatus::Battery &battery: batteries) { 130 | auto get_bat_property_lazy = [&battery](std::string_view name) noexcept 131 | { 132 | return swaystatus::LazyEval{[&battery, name]() noexcept 133 | { 134 | return battery.get_property(name); 135 | }}; 136 | }; 137 | auto get_conditional_lazy = [&battery](std::string_view name, std::string_view val) noexcept 138 | { 139 | return swaystatus::LazyEval{[&battery, name, val]() noexcept 140 | { 141 | return swaystatus::Conditional{battery.get_property(name) == val}; 142 | }}; 143 | }; 144 | 145 | out = format_to( 146 | out, 147 | 148 | fmt_str, 149 | 150 | fmt::arg("type", "battery"), 151 | 152 | #define ARG(literal) fmt::arg((literal), get_bat_property_lazy(literal)) 153 | ARG("name"), 154 | ARG("present"), 155 | ARG("technology"), 156 | 157 | ARG("model_name"), 158 | ARG("manufacturer"), 159 | ARG("serial_number"), 160 | 161 | ARG("status"), 162 | 163 | ARG("cycle_count"), 164 | 165 | ARG("voltage_min_design"), 166 | ARG("voltage_now"), 167 | 168 | ARG("charge_full_design"), 169 | ARG("charge_full"), 170 | ARG("charge_now"), 171 | 172 | ARG("capacity"), 173 | ARG("capacity_level"), 174 | #undef ARG 175 | 176 | fmt::arg("is_charging", get_conditional_lazy("status", "Charging")), 177 | fmt::arg("is_discharging", get_conditional_lazy("status", "Discharging")), 178 | fmt::arg("is_not_charging", get_conditional_lazy("status", "Not charging")), 179 | fmt::arg("is_full", get_conditional_lazy("status", "Full")) 180 | ); 181 | } 182 | 183 | return out; 184 | } 185 | -------------------------------------------------------------------------------- /src/handle_click_events.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "poller.h" 14 | #include "utility.h" 15 | #include "Callback/Callable.hpp" 16 | #include "handle_click_events.h" 17 | 18 | extern "C" { 19 | struct Pos { 20 | std::uint64_t x; 21 | std::uint64_t y; 22 | }; 23 | typedef struct Pos ClickPos; 24 | typedef struct Pos BlockSize; 25 | } /* extern "C" */ 26 | 27 | std::tuple to_unpackable(const struct Pos &pos) 28 | { 29 | return {pos.x, pos.y}; 30 | } 31 | 32 | struct Callback { 33 | const char *name; 34 | swaystatus::Callable callable; 43 | std::uint8_t requested_events = 0; 44 | 45 | auto operator () ( 46 | const char *instance, 47 | const ClickPos &pos, 48 | std::uint64_t button, 49 | std::uint64_t event, 50 | const ClickPos &relative_pos, 51 | const BlockSize &size 52 | ) 53 | { 54 | requested_events |= callable(instance, pos, button, event, relative_pos, size); 55 | } 56 | }; 57 | 58 | static struct json_tokener *parser; 59 | static Callback callbacks[CALLBACK_CNT]; 60 | static std::size_t callback_cnt; 61 | 62 | static void click_events_handler(int fd, enum Event events, void *data); 63 | 64 | extern "C" { 65 | void init_click_events_handling() 66 | { 67 | set_fd_non_blocking(0); 68 | 69 | request_polling(0, read_ready, click_events_handler, NULL); 70 | } 71 | 72 | uint8_t* add_click_event_handler(const char *name, const void *click_event_handler_config) 73 | { 74 | if (click_event_handler_config == NULL) 75 | return NULL; 76 | 77 | auto &callback = callbacks[callback_cnt++]; 78 | 79 | callback.name = name; 80 | callback.callable = swaystatus::Callable_base(name, click_event_handler_config); 81 | 82 | if (parser == nullptr) { 83 | parser = json_tokener_new(); 84 | if (parser == NULL) 85 | errx(1, "%s failed", "json_tokener_new"); 86 | } 87 | 88 | return &callback.requested_events; 89 | } 90 | } /* extern "C" */ 91 | 92 | static void click_event_handler(const struct json_object *event); 93 | static void click_events_handler(int fd, enum Event events, void *data) 94 | { 95 | (void) data; 96 | 97 | if (events == invalid_fd) 98 | errx(1, "fd %d %s", fd, "is invalid"); 99 | if (events == hup) 100 | /* swaybar is being reloaded */ 101 | return; 102 | if (events == error) 103 | errx(1, "fd %d %s", fd, "errored"); 104 | 105 | char buffer[4096]; 106 | /** 107 | * json_tokener_parse_ex cannot accept buffer longer than INT32_MAX 108 | */ 109 | _Static_assert(sizeof(buffer) <= INT32_MAX, ""); 110 | 111 | static bool first_read = true; 112 | 113 | ssize_t len = readall(0, buffer, sizeof(buffer)); 114 | if (len < 0) 115 | err(1, "%s on %s failed", "read", "stdin"); 116 | if (len == 0) 117 | return; 118 | buffer[len] = '\0'; 119 | if (first_read) { 120 | /** 121 | * The input is an infinite JSON array, so has to ignore the first 122 | * character '[', otherwise json_tokener_parse_ex would never 123 | * return any object. 124 | */ 125 | buffer[0] = ' '; 126 | first_read = false; 127 | } 128 | 129 | if (callback_cnt == 0) 130 | return; 131 | 132 | std::size_t i = 0; 133 | do { 134 | /** 135 | * Ignore ',' and space from the infinite JSON array 136 | */ 137 | while (buffer[i] == ',' || buffer[i] == '\n' || buffer[i] == ' ' || buffer[i] == '\t') 138 | ++i; 139 | 140 | struct json_object *event = json_tokener_parse_ex(parser, buffer + i, len - i); 141 | enum json_tokener_error jerr = json_tokener_get_error(parser); 142 | size_t bytes_processed = json_tokener_get_parse_end(parser); 143 | 144 | if (event != nullptr) { 145 | click_event_handler(event); 146 | json_object_put(event); 147 | } else if (jerr != json_tokener_continue) { 148 | errx( 149 | 1, 150 | "%s on %s failed: %s", 151 | "json_tokener_parse_ex", 152 | "content read from stdin", 153 | json_tokener_error_desc(jerr) 154 | ); 155 | } 156 | 157 | i += bytes_processed; 158 | } while (i != (size_t) len); 159 | } 160 | static void click_event_handler(const struct json_object *event) 161 | { 162 | // swaybar-protocol requires the input to be an infinite array of json objects, 163 | // and each `event` should be the individual object. 164 | // 165 | // If it is not an object, then it is likely that `swaystatus` reloaded and the input isn't 166 | // completed processed before exec. 167 | if (json_object_get_type(event) != json_type_object) 168 | return; 169 | 170 | auto get_str = [&](const char *key) 171 | { 172 | return json_object_get_string(json_object_object_get(event, key)); 173 | }; 174 | auto get_int = [&](const char *key) 175 | { 176 | return json_object_get_uint64(json_object_object_get(event, key)); 177 | }; 178 | 179 | const char *name = get_str("name"); 180 | auto it = std::find_if(callbacks, callbacks + callback_cnt, [&](const auto &val) 181 | { 182 | return std::strcmp(val.name, name) == 0; 183 | }); 184 | if (it == callbacks + callback_cnt) 185 | return; 186 | 187 | auto &callback = *it; 188 | callback( 189 | get_str("instance"), 190 | ClickPos{get_int("x"), get_int("y")}, 191 | get_int("button"), 192 | get_int("event"), 193 | ClickPos{get_int("relative_x"), get_int("relative_y")}, 194 | BlockSize{get_int("width"), get_int("height")} 195 | ); 196 | } 197 | -------------------------------------------------------------------------------- /src/modules/Base.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "../error_handling.hpp" 9 | #include "../process_configuration.h" 10 | #include "../handle_click_events.h" 11 | #include "../formatting/printer.hpp" 12 | 13 | #include "Base.hpp" 14 | 15 | #include "BacklightPrinter.hpp" 16 | #include "BatteryPrinter.hpp" 17 | #include "LoadPrinter.hpp" 18 | #include "MemoryUsagePrinter.hpp" 19 | #include "NetworkInterfacesPrinter.hpp" 20 | #include "TemperaturePrinter.hpp" 21 | #include "TimePrinter.hpp" 22 | #include "VolumePrinter.hpp" 23 | #include "CustomPrinter.hpp" 24 | 25 | using namespace std::literals; 26 | 27 | namespace swaystatus::modules { 28 | Base::Base( 29 | void *config, std::string_view module_name_arg, 30 | std::uint32_t default_interval, 31 | const char *default_full_format, const char *default_short_format, 32 | unsigned n, ... 33 | ): 34 | module_name{module_name_arg}, 35 | full_text_format{get_format(config, default_full_format)}, 36 | short_text_format{get_short_format(config, default_short_format)}, 37 | interval{get_update_interval(config, module_name_arg.data(), default_interval)}, 38 | requested_events{add_click_event_handler(module_name.data(), get_click_event_handler(config))} 39 | { 40 | this->cycle_cnt = interval - 1; 41 | 42 | std::va_list ap; 43 | va_start(ap, n); 44 | user_specified_properties_str.reset(get_user_specified_property_str_impl2(config, n, ap)); 45 | if (user_specified_properties_str) 46 | user_specified_properties_str_len = std::strlen(user_specified_properties_str.get()); 47 | va_end(ap); 48 | } 49 | 50 | void Base::update_and_print() 51 | { 52 | if (requested_events) { 53 | const ClickHandlerRequest requests{*requested_events}; 54 | *requested_events = static_cast(ClickHandlerRequest::none); 55 | 56 | if (requests & ClickHandlerRequest::reload) { 57 | reload(); 58 | update(); 59 | } 60 | if (requests & ClickHandlerRequest::update) 61 | update(); 62 | } 63 | 64 | if (++cycle_cnt == interval) { 65 | cycle_cnt = 0; 66 | update(); 67 | } 68 | 69 | print_literal_str("{\"name\":\""); 70 | print_str2(module_name); 71 | print_literal_str("\",\"instance\":\"0\","); 72 | 73 | print_fmt("full_text"sv, full_text_format.get()); 74 | if (short_text_format) 75 | print_fmt("short_text"sv, short_text_format.get()); 76 | 77 | if (user_specified_properties_str) 78 | print_str2(user_specified_properties_str.get(), user_specified_properties_str_len); 79 | else 80 | print_literal_str("\"separator\":true"); 81 | 82 | print_literal_str("},"); 83 | } 84 | void Base::print_fmt(std::string_view name, const char *format) 85 | { 86 | print_literal_str("\""); 87 | print_str2(name); 88 | print_literal_str("\":\""); 89 | 90 | TRY { 91 | fmt_set_calling_module(module_name.data()); 92 | do_print(format); 93 | fmt_set_calling_module(nullptr); 94 | } CATCH (const std::exception &e) { 95 | errx(1, "Failed to print %s format in %s: %s", 96 | name.data(), module_name.data(), e.what()); 97 | }; 98 | 99 | print_literal_str("\","); 100 | } 101 | 102 | static constexpr const char * const default_order[] = { 103 | "brightness", 104 | "battery", 105 | "load", 106 | "memory_usage", 107 | "network_interface", 108 | "sensors", 109 | "time", 110 | "volume", 111 | "custom", 112 | }; 113 | static constexpr auto default_order_len = sizeof(default_order) / sizeof(const char*); 114 | static_assert(CALLBACK_CNT >= default_order_len); 115 | 116 | static constexpr const std::size_t default_index[] = { 117 | 0, 118 | 1, 119 | 2, 120 | 3, 121 | 4, 122 | 5, 123 | 6, 124 | 7, 125 | }; 126 | static constexpr auto default_index_len = sizeof(default_index) / sizeof(std::size_t); 127 | static_assert(default_order_len >= default_index_len); 128 | 129 | using Factory = std::unique_ptr (*)(void *config); 130 | static constexpr const Factory factories[] = { 131 | makeBacklightPrinter, 132 | makeBatteryPrinter, 133 | makeLoadPrinter, 134 | makeMemoryUsagePrinter, 135 | makeNetworkInterfacesPrinter, 136 | makeTemperaturePrinter, 137 | makeTimePrinter, 138 | makeVolumePrinter, 139 | makeCustomPrinter, 140 | }; 141 | static_assert(default_order_len == sizeof(factories) / sizeof(Factory)); 142 | 143 | static auto makeModules(void *config, const std::size_t indexes[], std::size_t len) 144 | -> std::vector> 145 | { 146 | std::vector> modules; 147 | 148 | for (std::size_t i = 0; i != len; ++i) { 149 | auto index = indexes[i]; 150 | 151 | if (!is_block_printer_enabled(config, default_order[index])) 152 | continue; 153 | 154 | Factory factory = factories[index]; 155 | void *module_config = get_module_config(config, default_order[index]); 156 | modules.push_back( factory(module_config) ); 157 | } 158 | 159 | return modules; // C++17 guaranteed NRVO 160 | } 161 | auto makeModules(void *config) -> std::vector> 162 | { 163 | init_click_events_handling(); 164 | 165 | std::vector> modules; 166 | 167 | const char *buffer[20]; 168 | _Static_assert(sizeof(buffer) / sizeof(const char*) >= default_order_len); 169 | 170 | auto *end = get_module_order(config, buffer, sizeof(buffer) / sizeof(const char*)); 171 | if (end) { 172 | std::size_t len = end - buffer; 173 | 174 | std::size_t index_buffer[20]; 175 | for (std::size_t i = 0; i != len; ++i) { 176 | auto it = std::find_if( 177 | default_order, default_order + default_order_len, 178 | [&](const char *elem) 179 | { 180 | return std::strcmp(buffer[i], elem) == 0; 181 | } 182 | ); 183 | if (it == default_order + default_order_len) 184 | errx(1, "Unknown module name %s", buffer[i]); 185 | 186 | index_buffer[i] = it - default_order; 187 | } 188 | 189 | modules = makeModules(config, index_buffer, len); 190 | } else 191 | modules = makeModules(config, default_index, default_index_len); 192 | 193 | return modules; 194 | } 195 | } /* namespace swaystatus::modules */ 196 | -------------------------------------------------------------------------------- /src/process_configuration.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "utility.h" 14 | #include "handle_click_events.h" 15 | #include "process_configuration.h" 16 | 17 | static const int json2str_flag = JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOZERO; 18 | 19 | void* load_config(const char *filename) 20 | { 21 | struct json_object *config = json_object_from_file(filename); 22 | if (!config) 23 | errx(1, "%s on %s failed: %s", "json_object_from_file", filename, json_util_get_last_err()); 24 | 25 | return config; 26 | } 27 | void free_config(void *config) 28 | { 29 | json_object_put(config); 30 | } 31 | 32 | void* get_module_config(void *config, const char *name) 33 | { 34 | if (!config) 35 | return NULL; 36 | 37 | struct json_object *module; 38 | if (!json_object_object_get_ex(config, name, &module)) 39 | return NULL; 40 | 41 | if (json_object_get_type(module) == json_type_boolean) 42 | return NULL; 43 | 44 | return module; 45 | } 46 | 47 | const char** get_module_order(void *config, const char* moduleOrder[], size_t len) 48 | { 49 | if (config == NULL) 50 | return NULL; 51 | 52 | struct json_object *order; 53 | if (!json_object_object_get_ex(config, "order", &order)) 54 | return NULL; 55 | 56 | size_t out = 0; 57 | size_t n = json_object_array_length(order); 58 | n = n > len ? len : n; 59 | for (size_t i = 0; i != n; i++) { 60 | moduleOrder[out++] = json_object_get_string(json_object_array_get_idx(order, i)); 61 | } 62 | 63 | return moduleOrder + out; 64 | } 65 | 66 | bool is_block_printer_enabled(const void *config, const char *name) 67 | { 68 | if (config == NULL) 69 | return true; 70 | 71 | struct json_object *val; 72 | if (!json_object_object_get_ex(config, name, &val)) 73 | return true; 74 | if (json_object_get_type(val) == json_type_object) 75 | return true; 76 | 77 | return json_object_get_boolean(val); 78 | } 79 | 80 | const char* get_property_impl(const void *module_config, const char *property) 81 | { 82 | if (!module_config) 83 | return NULL; 84 | 85 | struct json_object *value; 86 | if (!json_object_object_get_ex(module_config, property, &value)) 87 | return NULL; 88 | 89 | return json_object_get_string(value); 90 | } 91 | const char* get_property(const void *module_config, const char *property, const char *default_val) 92 | { 93 | const char *result = get_property_impl(module_config, property); 94 | if (result == NULL) { 95 | if (default_val) 96 | return strdup_checked(default_val); 97 | return NULL; 98 | } else 99 | return strdup_checked(result); 100 | } 101 | const char* get_format(const void *module_config, const char *default_val) 102 | { 103 | const char *fmt = get_property_impl(module_config, "format"); 104 | if (fmt) { 105 | return escape_quotation_marks(fmt); 106 | } else { 107 | if (default_val) 108 | return strdup_checked(default_val); 109 | return NULL; 110 | } 111 | } 112 | const char* get_short_format(const void *module_config, const char *default_val) 113 | { 114 | const char *fmt = get_property_impl(module_config, "short_format"); 115 | if (fmt) { 116 | return escape_quotation_marks(fmt); 117 | } else { 118 | if (default_val) 119 | return strdup_checked(default_val); 120 | return NULL; 121 | } 122 | } 123 | uint32_t get_update_interval(const void *module_config, const char *name, uint32_t default_val) 124 | { 125 | if (!module_config) 126 | return default_val; 127 | 128 | struct json_object *value; 129 | if (!json_object_object_get_ex(module_config, "update_interval", &value)) 130 | return default_val; 131 | 132 | errno = 0; 133 | int64_t interval = json_object_get_int64(value); 134 | if (errno != 0) 135 | err(1, "%s on %s.%s%s", "json_object_get_uint64", name, "update_interval", " failed"); 136 | if (interval > UINT32_MAX) 137 | errx(1, "%s on %s.%s%s", "Value too large", name, "update_interval", ""); 138 | if (interval < 0) 139 | errx(1, "%s on %s.%s%s", "Negative number is not accepted", name, "update_interval", ""); 140 | 141 | return interval; 142 | } 143 | 144 | static int has_seperator(const struct json_object *properties) 145 | { 146 | struct json_object *separator; 147 | return json_object_object_get_ex(properties, "separator", &separator); 148 | } 149 | const char* get_user_specified_property_str_impl(void *module_config, unsigned n, ...) 150 | { 151 | va_list ap; 152 | va_start(ap, n); 153 | const char *ret = get_user_specified_property_str_impl2(module_config, n, ap); 154 | va_end(ap); 155 | return ret; 156 | } 157 | const char* get_user_specified_property_str_impl2(void *module_config, unsigned n, va_list ap) 158 | { 159 | if (!module_config) 160 | return NULL; 161 | 162 | va_list args; 163 | va_copy(args, ap); 164 | 165 | json_object_object_del(module_config, "format"); 166 | json_object_object_del(module_config, "short_format"); 167 | json_object_object_del(module_config, "update_interval"); 168 | json_object_object_del(module_config, "click_event_handler"); 169 | for (unsigned i = 0; i != n; ++i) { 170 | json_object_object_del(module_config, va_arg(args, const char*)); 171 | } 172 | 173 | va_end(args); 174 | 175 | if (json_object_object_length(module_config) == 0) 176 | return NULL; 177 | 178 | size_t json_str_len; 179 | const char *json_str = json_object_to_json_string_length( 180 | module_config, 181 | json2str_flag, 182 | &json_str_len 183 | ); 184 | 185 | size_t size = json_str_len; 186 | 187 | #define DEFAULT_PROPERTY "\"separator\":true" 188 | 189 | const int has_sep = has_seperator(module_config); 190 | if (!has_sep) 191 | size += /* For the comma */ 1 + sizeof(DEFAULT_PROPERTY) - 1; 192 | 193 | size = size - /* Remove '{' and '}' */ 2 + 1; 194 | char *ret = malloc_checked(size); 195 | memcpy(ret, json_str + 1, json_str_len - 2); 196 | if (!has_sep) { 197 | char *dest = ret + json_str_len - 2; 198 | *dest++ = ','; 199 | memcpy(dest, DEFAULT_PROPERTY, sizeof(DEFAULT_PROPERTY) - 1); 200 | } 201 | ret[size - 1] = '\0'; 202 | 203 | return ret; 204 | } 205 | 206 | const void* get_callable(const void *module_config, const char *property_name) 207 | { 208 | struct json_object *callable_config; 209 | if (!json_object_object_get_ex(module_config, property_name, &callable_config)) 210 | return NULL; 211 | return callable_config; 212 | } 213 | const void* get_click_event_handler(const void *module_config) 214 | { 215 | return get_callable(module_config, "click_event_handler"); 216 | } 217 | -------------------------------------------------------------------------------- /src/utility.c: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE /* For reallocarray, realpath and struct dirent::d_type */ 2 | #define _POSIX_C_SOURCE 200809L /* For openat, fstatat, sigaction */ 3 | #define _XOPEN_SOURCE 500 /* For realpath */ 4 | 5 | #include 6 | #include /* For SSIZE_MAX and realpath */ 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include /* For timefd_create and timefd_settime */ 19 | #include 20 | #include 21 | #include 22 | 23 | #include "utility.h" 24 | 25 | uintmax_t min_unsigned(uintmax_t x, uintmax_t y) 26 | { 27 | return x < y ? x : y; 28 | } 29 | 30 | void* malloc_checked(size_t size) 31 | { 32 | void *ret = malloc(size); 33 | if (!ret) 34 | err(1, "%s failed", "malloc"); 35 | return ret; 36 | } 37 | void reallocarray_checked(void **ptr, size_t nmemb, size_t size) 38 | { 39 | void *newp = reallocarray(*ptr, nmemb, size); 40 | if (!newp) 41 | err(1, "%s failed", "reallocarray"); 42 | *ptr = newp; 43 | } 44 | 45 | char* strdup_checked(const char *s) 46 | { 47 | char *ret = strdup(s); 48 | if (!ret) 49 | err(1, "%s failed", "strdup"); 50 | return ret; 51 | } 52 | 53 | char* escape_quotation_marks(const char *fmt) 54 | { 55 | size_t fmt_len = strlen(fmt); 56 | 57 | /** 58 | * In the worst case scenario, every character is '"', thus the space required 59 | * is fmt_len * 2. 60 | */ 61 | char *santilized = malloc(fmt_len * 2 * sizeof(char) + 1); 62 | 63 | size_t out = 0; 64 | for (size_t i = 0; i != fmt_len; ++i) { 65 | if (fmt[i] == '\"') { 66 | santilized[out++] = '\\'; 67 | santilized[out++] = '\"'; 68 | } else { 69 | santilized[out++] = fmt[i]; 70 | } 71 | } 72 | santilized[out++] = '\0'; 73 | reallocate(santilized, out); 74 | 75 | return santilized; 76 | } 77 | 78 | char* realpath_checked(const char *path) 79 | { 80 | char *ret = realpath(path, NULL); 81 | if (ret == NULL) 82 | err(1, "%s on %s failed", "realpath", path); 83 | return ret; 84 | } 85 | 86 | void setenv_checked(const char *name, const char *value, int overwrite) 87 | { 88 | if (setenv(name, value, overwrite) < 0) 89 | err(1, "%s failed", "setenv"); 90 | } 91 | 92 | void msleep(uintmax_t msec) 93 | { 94 | if (usleep(msec * 1000) && errno == EINVAL) 95 | err(1, "%s failed", "usleep"); 96 | } 97 | 98 | int create_pollable_monotonic_timer(uintmax_t msec) 99 | { 100 | int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); 101 | if (timerfd == -1) 102 | err(1, "%s failed", "timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)"); 103 | 104 | struct itimerspec spec = { 105 | .it_interval = { 106 | .tv_sec = msec / 1000, 107 | .tv_nsec = (msec % 1000) * 1000 * 1000 108 | }, 109 | /** 110 | * In order to arm the timer, either it_value.tv_sec or it_value.tv_nsec has to be non-zero 111 | * However, I'd like the initial expire of the timer to be ASAP, so tv_nsec is set to 112 | * 1 while tv_sec is set to 0. 113 | */ 114 | .it_value = { 115 | .tv_sec = 0, 116 | .tv_nsec = 1 117 | } 118 | }; 119 | int result = timerfd_settime(timerfd, 0, &spec, NULL); 120 | if (result == -1) 121 | err(1, "%s failed", "timerfd_settime"); 122 | 123 | return timerfd; 124 | } 125 | uint64_t read_timer(int timerfd) 126 | { 127 | uint64_t ret; 128 | 129 | ssize_t bytes = read_autorestart(timerfd, (char*) &ret, sizeof(ret)); 130 | if (bytes == -1) 131 | err(1, "%s on %s failed", "read_autorestart", "timerfd"); 132 | 133 | return ret; 134 | } 135 | 136 | void sigaction_checked_impl(int sig, const char *signame, void (*sighandler)(int signum)) 137 | { 138 | struct sigaction act; 139 | memset(&act, 0, sizeof(act)); 140 | act.sa_handler = sighandler; 141 | if (sigaction(sig, &act, NULL) == -1) 142 | err(1, "%s on %s failed", "sigaction", signame); 143 | } 144 | 145 | void close_all() 146 | { 147 | DIR *fds = opendir("/proc/self/fd"); 148 | if (fds == NULL) 149 | err(1, "%s on %s failed", "opendir", "/proc/self/fd"); 150 | 151 | const int dir_fd = dirfd(fds); 152 | if (dir_fd == -1) 153 | err(1, "%s on %s failed", "dirfd", "dir opened on /proc/self/fd"); 154 | 155 | errno = 0; 156 | for (struct dirent *ent; (ent = readdir(fds)); errno = 0) { 157 | if (ent->d_name[0] == '.') 158 | continue; 159 | 160 | errno = 0; 161 | char *endptr; 162 | int fd = strtoul(ent->d_name, &endptr, 10); 163 | if (errno != 0 || *endptr != '\0') 164 | err(1, "%s on %s failed", "Assumption", "/proc/self/fd"); 165 | 166 | if (fd > 2 && fd != dir_fd) 167 | close(fd); 168 | } 169 | if (errno != 0) 170 | err(1, "%s on %s failed", "readdir", "/proc/self/fd"); 171 | 172 | closedir(fds); 173 | } 174 | 175 | int openat_checked(const char *dir, int dirfd, const char *path, int flags) 176 | { 177 | flags |= O_CLOEXEC; 178 | 179 | int fd; 180 | do { 181 | fd = openat(dirfd, path, flags); 182 | } while (fd == -1 && errno == EINTR); 183 | 184 | if (fd == -1) 185 | err(1, "openat %s%s with %d failed", dir, path, flags); 186 | 187 | return fd; 188 | } 189 | 190 | void set_fd_non_blocking(int fd) 191 | { 192 | int flags = fcntl(fd, F_GETFL); 193 | if (flags < 0) 194 | err(1, "%s on %d failed", "fcntl(F_GETFL)", fd); 195 | 196 | flags |= O_NONBLOCK; 197 | 198 | if (fcntl(fd, F_SETFL, flags) < 0) 199 | err(1, "%s on %d failed", "Using fcntl to add O_NONBLOCK", fd); 200 | } 201 | 202 | ssize_t read_autorestart(int fd, void *buf, size_t count) 203 | { 204 | ssize_t ret; 205 | do { 206 | ret = read(fd, buf, count); 207 | } while (ret == -1 && errno == EINTR); 208 | 209 | return ret; 210 | } 211 | ssize_t write_autorestart(int fd, const void *buf, size_t count) 212 | { 213 | ssize_t ret; 214 | do { 215 | ret = write(fd, buf, count); 216 | } while (ret == -1 && errno == EINTR); 217 | 218 | return ret; 219 | } 220 | 221 | ssize_t readall(int fd, void *buffer, size_t len) 222 | { 223 | size_t bytes = 0; 224 | for (ssize_t ret; bytes < len; bytes += ret) { 225 | ret = read_autorestart(fd, (char*) buffer + bytes, min_unsigned(len - bytes, SSIZE_MAX)); 226 | if (ret == 0) 227 | break; 228 | if (ret == -1) { 229 | if (errno == EAGAIN || errno == EWOULDBLOCK) 230 | break; 231 | else 232 | return -1; 233 | } 234 | } 235 | 236 | return bytes; 237 | } 238 | ssize_t asreadall(int fd, char **buffer, size_t *len) 239 | { 240 | size_t bytes = 0; 241 | for (ssize_t ret; ; bytes += ret) { 242 | if (bytes == *len) { 243 | *len += 100; 244 | reallocarray_checked((void**) buffer, *len, sizeof(char)); 245 | } 246 | 247 | ret = read_autorestart(fd, *buffer + bytes, min_unsigned(*len - bytes, SSIZE_MAX)); 248 | if (ret == 0) 249 | break; 250 | if (ret == -1) 251 | return -1; 252 | } 253 | 254 | if (bytes == *len) { 255 | *len += 1; 256 | reallocarray_checked((void**) buffer, *len, sizeof(char)); 257 | } 258 | 259 | (*buffer)[*len - 1] = '\0'; 260 | 261 | return bytes; 262 | } 263 | 264 | const char* readall_as_uintmax(int fd, uintmax_t *val) 265 | { 266 | /* 267 | * 20 bytes is enough for storing decimal string as large as (uint64_t) -1 268 | */ 269 | char line[21]; 270 | ssize_t sz = readall(fd, line, sizeof(line)); 271 | if (sz == -1) 272 | return "read"; 273 | if (sz == 0 || sz == sizeof(line) || line[sz - 1] != '\n') { 274 | errno = 0; 275 | return "readall_as_uintmax assumption"; 276 | } 277 | line[sz - 1] = '\0'; 278 | 279 | errno = 0; 280 | char *endptr; 281 | uintmax_t ret = strtoumax(line, &endptr, 10); 282 | if (*endptr != '\0') { 283 | errno = 0; 284 | return "readall_as_uintmax assumption"; 285 | } 286 | if (errno == ERANGE) 287 | return "strtoumax"; 288 | 289 | *val = ret; 290 | return NULL; 291 | } 292 | 293 | int isdir(const char *dir, int dirfd, const char *path) 294 | { 295 | struct stat dir_stat_v; 296 | if (fstatat(dirfd, path, &dir_stat_v, 0) < 0) 297 | err(1, "%s failed on %s%s", "stat", dir, path); 298 | 299 | return S_ISDIR(dir_stat_v.st_mode); 300 | } 301 | 302 | static void *bt_buffer[20]; 303 | void stack_bt() 304 | { 305 | fputs("\n\n", stderr); 306 | 307 | int sz = backtrace(bt_buffer, sizeof(bt_buffer) / sizeof(void*)); 308 | backtrace_symbols_fd(bt_buffer, sz, 2); 309 | } 310 | 311 | void visit_all_subdirs(const char *path, subdir_visiter visiter, ...) 312 | { 313 | DIR *dir = opendir(path); 314 | if (!dir) 315 | err(1, "%s on %s failed", "opendir", path); 316 | 317 | va_list ap; 318 | va_start(ap, visiter); 319 | 320 | const int path_fd = dirfd(dir); 321 | 322 | errno = 0; 323 | for (struct dirent *ent; (ent = readdir(dir)); errno = 0) { 324 | switch (ent->d_type) { 325 | case DT_UNKNOWN: 326 | case DT_LNK: 327 | // Check if it is a dir after resolution 328 | if (!isdir(path, path_fd, ent->d_name)) 329 | break; 330 | //[[fallthrough]]; 331 | 332 | case DT_DIR: 333 | if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) 334 | visiter(path_fd, ent->d_name, ap); 335 | 336 | default: 337 | break; 338 | } 339 | } 340 | if (errno != 0) 341 | err(1, "%s on %s failed", "readdir", path); 342 | 343 | va_end(ap); 344 | closedir(dir); 345 | } 346 | -------------------------------------------------------------------------------- /src/networking.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "utility.h" 14 | #include "formatting/fmt_utility.hpp" 15 | #include "formatting/Conditional.hpp" 16 | #include "mem_size_t.hpp" 17 | #include "networking.hpp" 18 | 19 | #include "formatting/fmt/include/fmt/core.h" 20 | 21 | using swaystatus::Conditional; 22 | using swaystatus::mem_size_t; 23 | using swaystatus::find_end_of_format; 24 | 25 | namespace swaystatus { 26 | void ipv4_addrs::add(const struct sockaddr *src_addr) noexcept 27 | { 28 | if (cnt == array.size()) 29 | return; 30 | 31 | ++cnt; 32 | auto &dest_ipv4_addr = array[cnt - 1]; 33 | auto &src_ipv4_addr = *reinterpret_cast(src_addr); 34 | 35 | dest_ipv4_addr = src_ipv4_addr.sin_addr; 36 | } 37 | void ipv4_addrs::reset() noexcept 38 | { 39 | cnt = 0; 40 | } 41 | auto ipv4_addrs::begin() const noexcept -> const_iterator 42 | { 43 | return array.begin(); 44 | } 45 | auto ipv4_addrs::end() const noexcept -> const_iterator 46 | { 47 | return begin() + cnt; 48 | } 49 | void ipv6_addrs::add(const struct sockaddr *src_addr) noexcept 50 | { 51 | if (cnt == array.size()) 52 | return; 53 | 54 | ++cnt; 55 | auto &dest_ipv6_addr = array[cnt - 1]; 56 | auto &src_ipv6_addr = *reinterpret_cast(src_addr); 57 | 58 | dest_ipv6_addr = src_ipv6_addr.sin6_addr; 59 | } 60 | void ipv6_addrs::reset() noexcept 61 | { 62 | cnt = 0; 63 | } 64 | auto ipv6_addrs::begin() const noexcept -> const_iterator 65 | { 66 | return array.begin(); 67 | } 68 | auto ipv6_addrs::end() const noexcept -> const_iterator 69 | { 70 | return begin() + cnt; 71 | } 72 | 73 | interface_stats Interface::get_empty_stats() noexcept 74 | { 75 | interface_stats stats; 76 | std::memset(&stats, 0, sizeof(stats)); 77 | return stats; 78 | } 79 | 80 | bool Interface::operator == (std::string_view interface_name) const noexcept 81 | { 82 | return name == interface_name; 83 | } 84 | bool Interface::operator != (std::string_view interface_name) const noexcept 85 | { 86 | return !(*this == interface_name); 87 | } 88 | 89 | void Interface::reset() noexcept 90 | { 91 | /* 92 | * name and flags will be overwriten anyway, so it's ok to not reset them. 93 | */ 94 | stat = get_empty_stats(); 95 | ipv4_addrs_v.reset(); 96 | ipv6_addrs_v.reset(); 97 | } 98 | 99 | auto Interfaces::operator [] (std::string_view name) noexcept -> iterator 100 | { 101 | auto it = std::find(interfaces.begin(), interfaces.begin() + cnt, name); 102 | if (it != interfaces.begin() + cnt) 103 | return it; 104 | 105 | if (cnt == interfaces.size()) 106 | return end(); 107 | 108 | ++cnt; 109 | auto &interface = interfaces[cnt - 1]; 110 | interface.name = name; 111 | 112 | return end() - 1; 113 | } 114 | 115 | bool Interfaces::is_empty() const noexcept 116 | { 117 | return cnt == 0; 118 | } 119 | auto Interfaces::size() const noexcept -> std::size_t 120 | { 121 | return cnt; 122 | } 123 | 124 | auto Interfaces::begin() noexcept -> iterator 125 | { 126 | return interfaces.begin(); 127 | } 128 | auto Interfaces::end() noexcept -> iterator 129 | { 130 | return begin() + cnt; 131 | } 132 | 133 | auto Interfaces::begin() const noexcept -> const_iterator 134 | { 135 | return interfaces.begin(); 136 | } 137 | auto Interfaces::end() const noexcept -> const_iterator 138 | { 139 | return begin() + cnt; 140 | } 141 | 142 | auto Interfaces::cbegin() const noexcept -> const_iterator 143 | { 144 | return interfaces.cbegin(); 145 | } 146 | auto Interfaces::cend() const noexcept -> const_iterator 147 | { 148 | return cbegin() + cnt; 149 | } 150 | 151 | void Interfaces::clear() noexcept 152 | { 153 | for (auto &interface: *this) 154 | interface.reset(); 155 | 156 | cnt = 0; 157 | } 158 | void Interfaces::update() 159 | { 160 | clear(); 161 | 162 | struct ifaddrs *ifaddr; 163 | if (getifaddrs(&ifaddr) < 0) 164 | err(1, "%s failed", "getifaddrs"); 165 | 166 | for (auto *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { 167 | // Filter out uninterested entries 168 | if (ifa->ifa_addr == nullptr) 169 | continue; 170 | 171 | auto ifa_flags = ifa->ifa_flags; 172 | if (ifa_flags & IFF_LOOPBACK) 173 | continue; 174 | if (!(ifa_flags & IFF_UP)) 175 | continue; 176 | if (!(ifa_flags & IFF_RUNNING)) 177 | continue; 178 | 179 | auto sa_family = ifa->ifa_addr->sa_family; 180 | if (sa_family != AF_INET && sa_family != AF_INET6 && sa_family != AF_PACKET) 181 | continue; 182 | 183 | // Add new interface 184 | auto *interface = (*this)[ifa->ifa_name]; 185 | if (!interface) // If it is full, then stop getting more 186 | break; 187 | interface->flags = ifa->ifa_flags; 188 | switch (sa_family) { 189 | case AF_INET: 190 | interface->ipv4_addrs_v.add(ifa->ifa_addr); 191 | break; 192 | 193 | case AF_INET6: 194 | interface->ipv6_addrs_v.add(ifa->ifa_addr); 195 | break; 196 | 197 | case AF_PACKET: 198 | interface->stat = *static_cast(ifa->ifa_data); 199 | break; 200 | } 201 | } 202 | 203 | freeifaddrs(ifaddr); 204 | } 205 | } /* namespace swaystatus */ 206 | 207 | using Interfaces_formatter = fmt::formatter; 208 | 209 | auto Interfaces_formatter::parse(format_parse_context &ctx) -> format_parse_context_it 210 | { 211 | auto it = ctx.begin(), end = ctx.end(); 212 | if (it == end) 213 | return it; 214 | 215 | end = find_end_of_format(ctx); 216 | 217 | fmt_str = std::string_view{it, static_cast(end - it)}; 218 | 219 | return end; 220 | } 221 | auto Interfaces_formatter::format(const Interfaces &interfaces, format_context &ctx) -> 222 | format_context_it 223 | { 224 | if (fmt_str.size() != 0) { 225 | auto out = ctx.out(); 226 | 227 | std::size_t i = 0; 228 | for (const auto &interface: interfaces) { 229 | out = format_to( 230 | out, 231 | fmt_str, 232 | fmt::arg("name", interface.name), 233 | #define FMT_FLAGS(name, var) \ 234 | fmt::arg(name, Conditional{static_cast(interface.flags & IFF_##var)}) 235 | FMT_FLAGS("has_broadcast_support", BROADCAST), 236 | FMT_FLAGS("is_pointopoint", POINTOPOINT), 237 | FMT_FLAGS("has_no_arp_support", NOARP), 238 | FMT_FLAGS("is_in_promisc_mode", PROMISC), 239 | FMT_FLAGS("is_in_notrailers_mode", NOTRAILERS), 240 | FMT_FLAGS("is_master", MASTER), 241 | FMT_FLAGS("is_slave", SLAVE), 242 | FMT_FLAGS("has_multicast_support", MULTICAST), 243 | FMT_FLAGS("has_portsel_support", PORTSEL), 244 | FMT_FLAGS("is_automedia_active", AUTOMEDIA), 245 | FMT_FLAGS("is_dhcp", DYNAMIC), 246 | 247 | fmt::arg( 248 | "HAS_UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO", 249 | # if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 250 | Conditional{true} 251 | ), 252 | FMT_FLAGS("is_lower_up", LOWER_UP), 253 | FMT_FLAGS("is_dormant", DORMANT), 254 | FMT_FLAGS("is_echo_device", ECHO), 255 | # endif 256 | Conditional{false} 257 | ), 258 | #undef FMT_FLAGS 259 | #define FMT_STAT(attr) fmt::arg(# attr, interface.stat.attr) 260 | FMT_STAT(rx_packets), 261 | FMT_STAT(tx_packets), 262 | 263 | fmt::arg("rx_bytes", mem_size_t{interface.stat.rx_bytes}), 264 | fmt::arg("tx_bytes", mem_size_t{interface.stat.tx_bytes}), 265 | 266 | FMT_STAT(rx_errors), 267 | FMT_STAT(tx_errors), 268 | FMT_STAT(rx_dropped), 269 | FMT_STAT(tx_dropped), 270 | FMT_STAT(multicast), 271 | FMT_STAT(collisions), 272 | 273 | FMT_STAT(rx_length_errors), 274 | FMT_STAT(rx_over_errors), 275 | FMT_STAT(rx_crc_errors), 276 | FMT_STAT(rx_frame_errors), 277 | FMT_STAT(rx_fifo_errors), 278 | FMT_STAT(rx_missed_errors), 279 | 280 | FMT_STAT(tx_aborted_errors), 281 | FMT_STAT(tx_carrier_errors), 282 | FMT_STAT(tx_fifo_errors), 283 | FMT_STAT(tx_heartbeat_errors), 284 | FMT_STAT(tx_window_errors), 285 | 286 | FMT_STAT(rx_compressed), 287 | FMT_STAT(tx_compressed), 288 | #undef FMT_STAT 289 | fmt::arg("ipv4_addrs", interface.ipv4_addrs_v), 290 | fmt::arg("ipv6_addrs", interface.ipv6_addrs_v) 291 | ); 292 | 293 | if (++i != interfaces.size()) { 294 | *out = ' '; 295 | ++out; 296 | } 297 | } 298 | 299 | return out; 300 | } else 301 | return ctx.out(); 302 | } 303 | 304 | static auto parse_limit(fmt::format_parse_context &ctx, std::size_t *limit) 305 | { 306 | auto it = ctx.begin(), end = ctx.end(); 307 | if (it == end) 308 | return it; 309 | 310 | int base = 10; 311 | if (*it == '0' && (it + 1) != end && *(it + 1) == 'x') 312 | base = 16; 313 | 314 | errno = 0; 315 | char *endptr; 316 | uintmax_t result = strtoumax(it, &endptr, base); 317 | if (errno == ERANGE || result > SIZE_MAX) 318 | FMT_THROW(fmt::format_error("invalid format: Integer too big")); 319 | if (endptr == end || *endptr != '}') 320 | FMT_THROW(fmt::format_error("invalid format: Unterminated '{'")); 321 | 322 | *limit = result; 323 | 324 | return static_cast(endptr); 325 | } 326 | template 327 | static It format_to(It out, int af, const void *addr, std::size_t buffer_sz) 328 | { 329 | char buffer[buffer_sz]; 330 | 331 | FMT_ASSERT( 332 | inet_ntop(af, addr, buffer, buffer_sz) != nullptr, 333 | std::strerror(errno) 334 | ); 335 | 336 | return fmt::format_to(out, "{}", static_cast(buffer)); 337 | } 338 | 339 | using ipv4_addrs_formatter = fmt::formatter; 340 | 341 | auto ipv4_addrs_formatter::parse(format_parse_context &ctx) -> format_parse_context_it 342 | { 343 | return parse_limit(ctx, &limit); 344 | } 345 | auto ipv4_addrs_formatter::format(const ipv4_addrs &addrs, format_context &ctx) -> 346 | format_context_it 347 | { 348 | auto out = ctx.out(); 349 | 350 | std::size_t cnt = 0; 351 | for (auto &addr: addrs) { 352 | out = format_to(out, AF_INET, &addr, INET_ADDRSTRLEN); 353 | 354 | ++cnt; 355 | if (cnt == limit) 356 | break; 357 | if (cnt != addrs.cnt) { 358 | *out = ' '; 359 | ++out; 360 | } 361 | } 362 | 363 | return out; 364 | } 365 | 366 | using ipv6_addrs_formatter = fmt::formatter; 367 | 368 | auto ipv6_addrs_formatter::parse(format_parse_context &ctx) -> format_parse_context_it 369 | { 370 | return parse_limit(ctx, &limit); 371 | } 372 | auto ipv6_addrs_formatter::format(const ipv6_addrs &addrs, format_context &ctx) -> 373 | format_context_it 374 | { 375 | auto out = ctx.out(); 376 | 377 | std::size_t cnt = 0; 378 | for (auto &addr: addrs) { 379 | out = format_to(out, AF_INET6, &addr, INET6_ADDRSTRLEN); 380 | 381 | ++cnt; 382 | if (cnt == limit) 383 | break; 384 | if (cnt != addrs.cnt) { 385 | *out = ' '; 386 | ++out; 387 | } 388 | } 389 | 390 | return out; 391 | } 392 | -------------------------------------------------------------------------------- /src/Callback/python3.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __swaystatus_python3_HPP__ 2 | # define __swaystatus_python3_HPP__ 3 | 4 | # ifdef USE_PYTHON 5 | 6 | # ifdef __cplusplus 7 | extern "C" { 8 | # endif 9 | 10 | /** 11 | * @param path can be NULL 12 | * 13 | * setup_pythonpath should be called before chdir is called. 14 | */ 15 | void setup_pythonpath(const char *path); 16 | 17 | # ifdef __cplusplus 18 | } 19 | 20 | # include 21 | # include 22 | # include 23 | # include 24 | 25 | # include "../utility.h" 26 | 27 | namespace swaystatus::python { 28 | class Interpreter { 29 | protected: 30 | void *p; 31 | 32 | Interpreter(void *p) noexcept; 33 | 34 | public: 35 | /** 36 | * GIL_scoped automatically call release() in dtor. 37 | */ 38 | class GIL_scoped { 39 | Interpreter *interpreter; 40 | 41 | public: 42 | GIL_scoped(Interpreter *interpreter) noexcept; 43 | ~GIL_scoped(); 44 | }; 45 | /** 46 | * Interpreter::acquire() must be called before any method in this header other than 47 | * load_libpython3 can be invoked 48 | * 49 | * acquire can be called on the same object multiple times 50 | * and only the first call will actually acquire the GIL lock. 51 | */ 52 | auto acquire() -> GIL_scoped; 53 | void release(); 54 | }; 55 | 56 | class MainInterpreter: public Interpreter { 57 | using Interpreter::Interpreter; 58 | 59 | public: 60 | /** 61 | * @pre setup_pythonpath has been invoked 62 | * 63 | * load_libpython3() will only initialize libpython on the first call. 64 | */ 65 | static void load_libpython3(); 66 | 67 | static bool has_initialized() noexcept; 68 | 69 | static auto get() noexcept -> Interpreter&; 70 | }; 71 | 72 | class SubInterpreter: public Interpreter { 73 | public: 74 | SubInterpreter(); 75 | 76 | SubInterpreter(const SubInterpreter&) = delete; 77 | SubInterpreter(SubInterpreter&&); 78 | 79 | SubInterpreter& operator = (const SubInterpreter&) = delete; 80 | SubInterpreter& operator = (SubInterpreter&&) = delete; 81 | 82 | /** 83 | * ~SubInterpreter() can only be called when the SubInterpreter is not loaded (after release()). 84 | */ 85 | ~SubInterpreter(); 86 | }; 87 | 88 | class Object { 89 | protected: 90 | void *obj; 91 | 92 | void free() noexcept; 93 | 94 | public: 95 | /** 96 | * @return object denoting 'None'. 97 | */ 98 | static Object get_none() noexcept; 99 | 100 | /** 101 | * @param obj create_new_ref will call Py_INCREF on obj. 102 | * obj can be nullptr. 103 | */ 104 | static Object create_new_ref(void *obj); 105 | /** 106 | * @param obj create_new_ref will call Py_INCREF on obj.get() 107 | */ 108 | static Object create_new_ref(const Object &obj); 109 | 110 | /** 111 | * @param obj Object will not call Py_INCREF on obj 112 | */ 113 | Object(void *obj = nullptr) noexcept; 114 | 115 | Object(const Object&) = delete; 116 | /** 117 | * @param object after move, no function can be called on object except 118 | * the move assignment and destructor 119 | */ 120 | Object(Object &&object) noexcept; 121 | 122 | Object& operator = (const Object&) = delete; 123 | /** 124 | * @param object after move, no function can be called on object except 125 | * the move assignment and destructor 126 | */ 127 | Object& operator = (Object&&) noexcept; 128 | 129 | auto get() noexcept -> void*; 130 | auto get() const noexcept -> const void*; 131 | 132 | /** 133 | * Test that this object actually contains a PyObject 134 | */ 135 | bool has_value() const noexcept; 136 | 137 | bool is_none() const noexcept; 138 | 139 | Object getattr(const char *name); 140 | void setattr(const char *name, Object object); 141 | 142 | ~Object(); 143 | }; 144 | 145 | /** 146 | * None can only serve as a dummy return value for Callable 147 | */ 148 | struct None { 149 | None() = default; 150 | 151 | None(const None&) = default; 152 | None& operator = (const None&) = default; 153 | 154 | ~None() = default; 155 | 156 | None(Object &&obj); 157 | }; 158 | 159 | template 160 | static constexpr const bool is_object_v = std::is_base_of_v>; 161 | 162 | class tuple; 163 | /** 164 | * Cusomtization point for converting C++ type to python type 165 | */ 166 | template 167 | struct Conversion { 168 | using result_type = tuple; 169 | }; 170 | 171 | template 172 | struct Conversion || std::is_same_v >> { 173 | using result_type = T; 174 | }; 175 | 176 | template 177 | using conversion_result_t = typename Conversion::result_type; 178 | 179 | class Compiled: public Object { 180 | public: 181 | /** 182 | * The type of input 183 | */ 184 | enum class Type { 185 | file_like, 186 | single_line, 187 | single_expression, 188 | }; 189 | Compiled(const char *pseudo_filename, const char *code, Type type = Type::file_like); 190 | }; 191 | 192 | class Module: public Object { 193 | public: 194 | Module(const char *module_name); 195 | Module(const char *persudo_module_name, Compiled &compiled); 196 | 197 | auto getname() noexcept -> const char*; 198 | }; 199 | 200 | class Int: public Object { 201 | template 202 | static Int from_integer(T val) 203 | { 204 | if constexpr(std::is_signed_v) 205 | return Int{ssize_t{val}}; 206 | else 207 | return Int{std::size_t{val}}; 208 | } 209 | 210 | public: 211 | template >> 212 | Int(T val): 213 | Int(from_integer(val)) 214 | {} 215 | 216 | Int(ssize_t); 217 | Int(std::size_t); 218 | 219 | Int(Object &&object); 220 | 221 | /** 222 | * @param val (*val) is only changed on success. 223 | * @return true on success, false if overflow 224 | */ 225 | bool to_ssize_t(ssize_t *val) const noexcept; 226 | bool to_size_t(std::size_t *val) const noexcept; 227 | 228 | /** 229 | * WARNING: implicit conversion does not check for overflow 230 | */ 231 | template >> 232 | operator T () const noexcept 233 | { 234 | if constexpr(std::is_signed_v) { 235 | ssize_t val; 236 | to_ssize_t(&val); 237 | return val; 238 | } else { 239 | std::size_t val; 240 | to_size_t(&val); 241 | return val; 242 | } 243 | } 244 | }; 245 | 246 | template 247 | struct Conversion && !is_object_v >> { 248 | using result_type = Int; 249 | }; 250 | 251 | class MemoryView: public Object { 252 | public: 253 | MemoryView(const std::string_view &view); 254 | }; 255 | 256 | template 257 | struct Conversion< 258 | T, 259 | std::enable_if_t< std::is_constructible_v< MemoryView, T > && !is_object_v > 260 | > { 261 | using result_type = MemoryView; 262 | }; 263 | 264 | class str: public Object { 265 | public: 266 | /** 267 | * Default ctor that construct an empty Object 268 | */ 269 | str() = default; 270 | 271 | str(const std::string_view &view); 272 | 273 | str(Object &&object); 274 | 275 | auto get_view() const noexcept -> std::string_view; 276 | operator std::string_view () const noexcept; 277 | }; 278 | 279 | template <> 280 | struct Conversion { 281 | using result_type = str; 282 | }; 283 | 284 | class str_view { 285 | const str string; 286 | const std::string_view view; 287 | 288 | public: 289 | str_view(std::string_view view) noexcept; 290 | str_view(str &&string) noexcept; 291 | 292 | str_view(const str_view&) = delete; 293 | str_view(str_view&&) = delete; 294 | 295 | str_view& operator = (const str_view&) = delete; 296 | str_view& operator = (str_view&&) = delete; 297 | 298 | auto get_view() const noexcept -> std::string_view; 299 | operator std::string_view () const noexcept; 300 | }; 301 | 302 | class tuple: public Object { 303 | protected: 304 | using creator_t = void* (*)(ssize_t n, ...); 305 | 306 | static auto get_creator() -> creator_t; 307 | 308 | static void handle_error(const char *msg); 309 | 310 | template 311 | static void* create_tuple_checked(Args &&...args) 312 | { 313 | auto *creator = get_creator(); 314 | auto *ret = creator( 315 | sizeof ...(args), 316 | conversion_result_t(std::forward(args)).get()... 317 | ); 318 | if (ret == nullptr) 319 | handle_error("Failed to create tuple"); 320 | 321 | return ret; 322 | } 323 | 324 | public: 325 | template 326 | tuple(Args &&...args): 327 | Object{create_tuple_checked(std::forward(args)...)} 328 | {} 329 | 330 | private: 331 | template 332 | tuple(Unpackable &&unpackable, std::index_sequence): 333 | tuple{std::get(std::forward(unpackable))...} 334 | {} 335 | 336 | public: 337 | /** 338 | * @param unpackable can be std::tuple, std::pair, std::array, or any type 339 | * that support std::get and std::tuple_size 340 | */ 341 | template < 342 | class Unpackable, 343 | class = std::void_t(std::declval()))>, 344 | class = std::void_t>::value)> 345 | > 346 | tuple(Unpackable &&unpackable): 347 | tuple{ 348 | std::forward(unpackable), 349 | std::make_index_sequence>::value>{} 350 | } 351 | {} 352 | 353 | template ()) )>> 354 | tuple(T &&obj): 355 | tuple{to_unpackable(std::forward(obj))} 356 | {} 357 | 358 | tuple(Object &&object); 359 | 360 | /** 361 | * @pre has_value() == true 362 | */ 363 | auto size() const noexcept -> std::size_t; 364 | /** 365 | * @pre has_value() == true 366 | * @param i if i > size(), call Py_Err. 367 | */ 368 | auto get_element(std::size_t i) -> Object; 369 | /** 370 | * @pre has_value() == true 371 | * @param i argument isn't checked, it is UB if i > size(). 372 | */ 373 | auto operator [] (std::size_t i) -> Object; 374 | 375 | private: 376 | template 377 | T convert_to_impl(std::index_sequence) 378 | { 379 | return {std::tuple_element_t{(*this)[I]}...}; 380 | } 381 | 382 | public: 383 | template 384 | T convert_to() 385 | { 386 | static_assert(!std::is_reference_v); 387 | static_assert(!std::is_const_v); 388 | static_assert(!std::is_volatile_v); 389 | 390 | if (size() < std::tuple_size::value) 391 | handle_error("Conversion from swaystatus::python::tuple failed"); 392 | 393 | return convert_to_impl(std::make_index_sequence::value>{}); 394 | } 395 | 396 | template 397 | operator T () 398 | { 399 | return convert_to(); 400 | } 401 | }; 402 | 403 | class Callable_base: public Object { 404 | protected: 405 | using caller_t = void* (*)(void*, ...); 406 | 407 | static auto get_caller() -> caller_t; 408 | 409 | void handle_error(); 410 | 411 | public: 412 | /** 413 | * Convert Object to Callable 414 | * 415 | * Call errx if the object is not a callable or its signature does not fit. 416 | */ 417 | explicit Callable_base(Object &&o); 418 | 419 | /** 420 | * @exception If the object throws, then the error is printed to stderr and _exit is called. 421 | */ 422 | template < 423 | class ...Args, 424 | class = std::enable_if_t< 425 | is_all_true({!std::is_const_v...}) && is_all_true({is_object_v...}) 426 | > 427 | > 428 | Object operator () (Args &&...args) 429 | { 430 | auto *ret = (get_caller())(get(), std::forward(args).get()..., NULL); 431 | if (ret == nullptr) 432 | handle_error(); 433 | 434 | return Object{ret}; 435 | } 436 | 437 | Object operator () () 438 | { 439 | auto *ret = (get_caller())(get(), NULL); 440 | if (ret == nullptr) 441 | handle_error(); 442 | 443 | return Object{ret}; 444 | } 445 | }; 446 | 447 | /** 448 | * @param Ret can be python object or C++ type 449 | * @param Args can be python object or C++ type 450 | */ 451 | template 452 | class Callable: public Callable_base { 453 | public: 454 | /** 455 | * Convert Object to Callable 456 | * 457 | * Call errx if the object is not a callable or its signature does not fit. 458 | */ 459 | explicit Callable(Object &&o): 460 | Callable_base{std::move(o)} 461 | {} 462 | 463 | Callable(Callable_base &&o): 464 | Callable_base{std::move(o)} 465 | {} 466 | 467 | Ret operator () (Args ...args) 468 | { 469 | auto &base = static_cast(*this); 470 | 471 | return Ret{ 472 | static_cast>( 473 | base(static_cast>(std::forward(args))...) 474 | ) 475 | }; 476 | } 477 | }; 478 | } /* namespace swaystatus */ 479 | # endif 480 | 481 | # endif /* USE_PYTHON */ 482 | 483 | #endif 484 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swaystatus 2 | 3 | A lightweight yet feature-rich status bar for i3bar or swaybar. 4 | 5 | ![screenshot] 6 | 7 | It is written completely in C/C++ to make it as lightweight as possible and specifically, to avoid creating new processes every second as in bash script. 8 | 9 | It uses libraries like `libasound` and glibc function `getifaddrs` to retrieve volume information as opposed to using `amixer` and `ip addr`. 10 | 11 | For battery, backlight, load, and meminfo, it reads directly from `/sys/class/power_supply`, 12 | `/sys/class/backlight`, `/proc/loadavg` and `/proc/meminfo`. 13 | 14 | ## Runtime Dependency 15 | - `libasound.so.2` 16 | - `libjson-c.so.5` (also used by sway and swaybar) 17 | - `libsensors.so.5` 18 | 19 | ## Build 20 | 21 | Install `clang` (must have lto support), `lld`, `make` and `pkg-config`, then run 22 | 23 | ``` 24 | git clone --recurse-submodules https://github.com/NobodyXu/swaystatus 25 | cd swaystatus/src 26 | make -j $(nproc) 27 | ``` 28 | 29 | You can customize ou build via environment variable: 30 | - `BUILD_DIR`: affects where the built object will be put. Default is `.`. 31 | - `TARGET_DIR`: where the executable will be installed when `make install` is executed. Default is `/usr/local/bin` 32 | - `PYTHON`: whether to include embeded python interpreter support in `swaystatus`, can be `true` or `false`. Default is `true`. 33 | - `DEBUG`: whether to have a debug build or release build. `true` for debug build and `false`for release build. Default is `false`. 34 | - `EXCEPTION`: whether to enable C++ exception. `false` to disable C++ exception. 35 | 36 | To install, run `sudo make install`, which by default will install a single binary `swaystatus` to `/usr/local/bin`. 37 | 38 | ## Usage 39 | 40 | ### swaybar usage: 41 | 42 | ``` 43 | swaystatus: Usage: swaystatus [options] configuration_filename 44 | 45 | --help Show help message and exit 46 | --interval=unsigned_msec Specify update interval in milliseconds, must be an unsigner integer. 47 | By default, the interval is set to 1000 ms. 48 | ``` 49 | 50 | To reload `swaystatus`, send `SIGUSR1` to `swaystatus` process. 51 | 52 | ### Config file format 53 | 54 | { 55 | "order": ["network_interface", "time"], 56 | "_comment": "element order specify the order of which blocks will appear.", 57 | "_comment2": "If a block is not specified in order, it will not appear.", 58 | 59 | "name": { 60 | "format": "Hello, {variable_name}", 61 | "short_format": "hello", 62 | "update_interval": 20, 63 | "color": "##RRGGBBA", 64 | "background: "##RRGGBBA", 65 | "border": "##RRGGBBA", 66 | "border_top": 1, 67 | "border_bottom": 1, 68 | "border_left": 1, 69 | "border_right": 1, 70 | "min_width": 1, 71 | "align": "center", 72 | "separator": true, 73 | "separator_block_width": 9, 74 | "markup": "none", 75 | 76 | "click_event_handler": { 77 | "type": "python", 78 | "module_name": "hello", 79 | "function_name": "handler" 80 | } 81 | }, 82 | "_comment": "Any variable starts with '_' is a comment" 83 | } 84 | 85 | All property present for "name" above are optional. 86 |
For volume, you can also set property "mix_name" and "card". 87 | 88 | The following values are valid name: 89 | 90 | - brightness 91 | - volume 92 | - battery 93 | 94 | The configuration block of battery support `excluded_model` to exclude certain battery devices 95 | from the output of `swaystatus`. 96 | - network_interface 97 | - load 98 | - memory_usage 99 | - time 100 | - sensors 101 | 102 | **Any unrecognized parameters will be ignored.** 103 | 104 | #### "format" 105 | 106 | It is used internally to generate the "full_text" that will be passed to swaybar. 107 | 108 | Formatting for blocks other than time is done using [fmt - Format String Syntax]. 109 |
Formatting for block time is parsed by [`strftime`]. 110 | 111 | In the format string, user is capable of taking advantage of 112 | [format variables](/#Format_Variables) to customize the output. 113 | 114 | #### "short_format" 115 | 116 | It is used to generate "short_text", which is used by `swaybar` when it decided that the 117 | "full_text" is too long. 118 | 119 | #### Click Event Handling Support 120 | 121 | TO enable click event handling for a block, add json object "click_event_handler" 122 | to the block that block. 123 | 124 | The return value of it can be `or`ed from the following values: 125 | - `0` do nothing 126 | - `1` force the module to update 127 | - `2` force the module to reload 128 | 129 | Currently, the handler can be written in python or C/C++. 130 | 131 | ##### Loading python handler 132 | 133 | For loading python handler, add `"type": "python"` to your "click_event_handler", then specify 134 | the "module_name" and "function_name" of the handler. 135 | 136 | `swaystatus` will attempt to load the module from the same directory of your configuration file, 137 | your current working dir, any path you specified with environment variable `PYTHONPATH` and your 138 | system module paths. 139 | 140 | If instead you want to embed the python code into your configuration file, add "code" to your 141 | "click_event_handler" and assign your code as value to it. 142 | 143 | The python function is expected to have signature (Check [here](https://www.mankier.com/7/swaybar-protocol#Click_Events) for more information): 144 | 145 | ``` 146 | (instance: str, click_pos: Tuple[int, int], button: int, event: int, relative_click_pos: Tuple[int, int], blocksize: Tuple[int, int]) -> int 147 | ``` 148 | 149 | Your function is expected to return 0 and any exception thrown in your function must be handled, 150 | otherwise `swaystatus` will print that error and exit. 151 | 152 | ##### Loading C/C++ handler 153 | 154 | You C/C++ code has to be compiled with `-fPIC -shared`. 155 | 156 | For loading python handler, add `"type": "dynlib"` to your "click_event_handler", then specify 157 | the "module_name" and "function_name" of the handler. 158 | 159 | Just like loading python, `swaystatus` will attemp to load the shared library from the same 160 | directory of your configuration file, your current working dir. 161 | 162 | It is loaded using `dlopen`, so `LD_LIBRARY_PATH`, `/etc/ld.so.cache`, `/lib` and `/usr/lib` is 163 | also searched. 164 | 165 | You can also specify the path instead of a name directly in the `module_name`, which is perfectly 166 | valid. 167 | 168 | The C/C++ function is expected to be `export`ed in `"C"` and have signature: 169 | 170 | ```c 171 | #include 172 | 173 | struct Pos { 174 | uint64_t x; 175 | uint64_t y; 176 | }; 177 | typedef struct Pos ClickPos; 178 | typedef struct Pos BlockSize; 179 | 180 | int f( 181 | const char *instance, 182 | const ClickPos *pos, 183 | uint64_t button, 184 | uint64_t event, 185 | const ClickPos *relative_pos, 186 | const BlockSize *size 187 | ) 188 | ``` 189 | 190 | Your function is expected to return 0. 191 | 192 | #### Disable block 193 | 194 | If you want to disable a certain feature, say brightness, 195 | then add the following to your configuration: 196 | 197 | { 198 | "brightness": false, 199 | } 200 | 201 | Or if you are using element "order" for specifing block order, then you can simply 202 | remove the name from "order". 203 | 204 | Note that `{"brightness": false}` overrides "order": 205 |
If block specified in "order" is disabled by setting it to `false`, then the block 206 | will not appear. 207 | 208 | #### `custom` 209 | 210 | Using this block, you can import a python/c module and display whatever you want in this 211 | block. 212 | 213 | You need to provide `update_callback` and `do_print_callback`, which need to have the 214 | same fields as click event handler `type`, `module_name`, `function_name`, 215 | (optional `code` for python). 216 | 217 | Check [`example-config.json`](/example-config.json) for example configuration of this 218 | block. 219 | 220 | #### `update_interval` 221 | 222 | If specified, then the block will update at `update_interval * main_loop_interval ms`, 223 | where `main_loop_interval` is the value passed by cmdline arg `--interval=` or `1000 ms` 224 | by default. 225 | 226 | ##### `update_interval` for `sensors` 227 | 228 | The sensors on computer are so many that they cannot be fitted into one line, so `swaystatus` 229 | instead, print one sensor each time and once `update_interval` is reached, next sensor is shown 230 | at the `swaybar`. 231 | 232 | When all sensors are shown, `swaystatus` will then update the sensors reading from the computer. 233 | 234 | ### Format_Variables 235 | 236 | #### Battery format variables: 237 | 238 | - `has_battery`: check whether battery device exists 239 | - `per_battery_fmt_str`, which is used to print every battery on this system. 240 | 241 | It contains the following variables: 242 | - `name` 243 | - `present` 244 | - `technology` 245 | - `model_name` 246 | - `manufacturer` 247 | - `serial_number` 248 | - `status` 249 | - `cycle_count` 250 | - `voltage_min_design` 251 | - `voltage_now` 252 | - `charge_full_design` 253 | - `charge_full` 254 | - `charge_now` 255 | - `capacity` 256 | - `capacity_level` 257 | - `is_charging` (Check section "Conditional Variable" for usage) 258 | - `is_discharging` 259 | - `is_not_charging` 260 | - `is_full` 261 | 262 | #### Memory Usage variables: 263 | 264 | - `MemTotal` 265 | - `MemFree` 266 | - `MemAvailable` 267 | - `Buffers` 268 | - `Cached` 269 | - `SwapCached` 270 | - `Active` 271 | - `Inactive` 272 | - `Mlocked` 273 | - `SwapTotal` 274 | - `SwapFree` 275 | - `Dirty` 276 | - `Writeback` 277 | - `AnonPages` 278 | - `Mapped` 279 | - `Shmem` 280 | 281 | The unit (supports 'BKMGTPEZY') of the variables printed can be specified. 282 |
For example, '{MemTotal:K}' will print MemTotal in KiloBytes. 283 | 284 | #### Volume variables: 285 | 286 | - `volume` 287 | 288 | #### Load variables: 289 | 290 | - `loadavg_1m` 291 | - `loadavg_5m` 292 | - `loadavg_15m` 293 | - `running_kthreads_cnt` 294 | - `total_kthreads_cnt` 295 | - `last_created_process_pid` 296 | 297 | #### Brightness variables: 298 | 299 | NOTE that these variables are evaluated per backlight_device. 300 | 301 | - `backlight_device` 302 | - `brightness` 303 | - `max_brightness` 304 | - `has_multiple_backlight_devices` (this is a Conditional Variable) 305 | 306 | #### Network Interface variables; 307 | 308 | - `is_connected` 309 | - `is_not_connected` 310 | - `per_interface_fmt_str`: 311 | The specification for this variable will be formatted once for each interface. 312 | 313 | It contains subvariables that can be used inside: 314 | - `HAS_UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO` 315 | - `has_broadcast_support` 316 | - `is_pointopoint` 317 | - `has_no_arp_support` 318 | - `is_in_promisc_mode` 319 | - `is_in_notrailers_mode` 320 | - `is_master` 321 | - `is_slave` 322 | - `has_multicast_support` 323 | - `has_portsel_support` 324 | - `is_automedia_active` 325 | - `is_dhcp` 326 | - `rx_packets` 327 | - `tx_packets` 328 | - `rx_bytes` (Supports unit specification, checks [Memry Usage Variables](#memory-usage-variables) for more info) 329 | - `tx_bytes` (Supports unit specification, checks [Memry Usage Variables](#memory-usage-variables) for more info) 330 | - `rx_errors` 331 | - `tx_errors` 332 | - `rx_dropped` 333 | - `tx_dropped` 334 | - `multicast` 335 | - `collisions` 336 | - `rx_length_errors` 337 | - `rx_over_errors` 338 | - `rx_crc_errors` 339 | - `rx_frame_errors` 340 | - `rx_fifo_errors` 341 | - `rx_missed_errors` 342 | - `tx_aborted_errors` 343 | - `tx_carrier_errors` 344 | - `tx_fifo_errors` 345 | - `tx_heartbeat_errors` 346 | - `tx_window_errors` 347 | - `rx_compressed` 348 | - `tx_compressed` 349 | - `ipv4_addrs` 350 | - `ipv6_addrs` 351 | 352 | Limit of number of ip address can be done via `{ipv4_addrs:1}` and `{ipv6_addrs:1}` 353 | 354 | Optionally if `HAS_UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO`, 355 | the following variables are also defined: 356 | - `is_lower_up` 357 | - `is_dormant` 358 | - `is_echo_device` 359 | 360 | To limit number of ip addresses in output, please use `{ipv4_config:1}`. 361 | 362 | #### Sensors variables; 363 | 364 | - `prefix`: the name of the device 365 | - `path`: the path to the device in `/sys` 366 | - `addr`: the internal address of the device in `libsensors` 367 | - `bus_type`: the type of device 368 | - `bus_nr`: unclear 369 | - `reading_number`: the internal index for the specific sensor reading 370 | - `reading_temp`: the temperature reading from the sensor 371 | 372 | #### Format string for time: 373 | 374 | #### Conditional Variables 375 | 376 | Conditional variables are used to conditionally evaulate part of the format string. 377 | 378 | For example, setting "format" in "battery" to "{is_charging:Charging}" will print "Charging" only 379 | when the battery is charging. 380 | 381 | All variables start with "is" and "has" are conditional variables. 382 | 383 | #### Recursive Conditional Variable: 384 | 385 | In additional to printing strings conditionally, conditional variables can also be used to 386 | print other variables conditionally. 387 | 388 | For example, "{is_charging:{level}%}" will print "98%" when charging, where 389 | "98" is the actual level of battery. 390 | 391 | Check [`example-config.json`] for the example configuration. 392 | 393 | ### Use `swaybar` in `sway` 394 | 395 | ``` 396 | bar { 397 | status_command swaystatus 398 | } 399 | ``` 400 | 401 | [screenshot]: https://raw.githubusercontent.com/NobodyXu/swaystatus/main/screenshot.png 402 | [`strftime`]: https://man7.org/linux/man-pages/man3/strftime.3.html 403 | [fmt - Format String Syntax]: https://fmt.dev/latest/syntax.html 404 | [`example-config.json`]: https://github.com/NobodyXu/swaystatus/blob/main/example-config.json 405 | -------------------------------------------------------------------------------- /src/Callback/python3.cc: -------------------------------------------------------------------------------- 1 | #ifdef USE_PYTHON 2 | 3 | # include 4 | # include 5 | # include 6 | # include 7 | 8 | # include 9 | 10 | # define PY_SSIZE_T_CLEAN 11 | # include 12 | 13 | # include "../utility.h" 14 | # include "python3.hpp" 15 | 16 | /* 17 | * https://docs.python.org/3/extending/embedding.html 18 | * https://docs.python.org/3/c-api/import.html 19 | * https://stackoverflow.com/questions/37605612/pyimport-importmodule-possible-to-load-module-from-memory 20 | * https://stackoverflow.com/questions/51507196/python-ctypes-to-return-an-array-of-function-pointers 21 | * https://stackoverflow.com/questions/61367199/handling-embedded-python-interpreter-calls-with-gil-and-multi-threading 22 | * https://github.com/pybind/pybind11 23 | * https://stackoverflow.com/questions/10625584/embedding-python-in-multithreaded-c-application 24 | * https://docs.python.org/3/c-api/init.html#non-python-created-threads 25 | * https://stackoverflow.com/questions/29595222/multithreading-with-python-and-c-api 26 | */ 27 | 28 | extern "C" { 29 | void setup_pythonpath(const char *path) 30 | { 31 | auto pythonpath = swaystatus::getcwd_checked(); 32 | 33 | if (path) { 34 | pythonpath += ':'; 35 | pythonpath += path; 36 | } 37 | 38 | auto *old_pythonpath = getenv("PYTHONPATH"); 39 | if (old_pythonpath) { 40 | pythonpath += ':'; 41 | pythonpath += old_pythonpath; 42 | } 43 | 44 | setenv_checked("PYTHONPATH", pythonpath.c_str(), 1); 45 | } 46 | } /* extern "C" */ 47 | 48 | struct raw_mem_deleter { 49 | void operator () (void *p) const noexcept 50 | { 51 | PyMem_RawFree(p); 52 | } 53 | }; 54 | 55 | struct wchar_cstr { 56 | std::unique_ptr str; 57 | std::size_t len; 58 | }; 59 | 60 | static auto Py_DecodeLocale_Checked(const char *s) -> wchar_cstr 61 | { 62 | wchar_cstr ret; 63 | 64 | auto *p = Py_DecodeLocale(s, &ret.len); 65 | if (p == nullptr) { 66 | if (ret.len == static_cast(-1)) 67 | errx(1, "%s failed: %s", "Py_DecodeLocale", "Insufficient memory"); 68 | else if (ret.len == static_cast(-2)) 69 | errx(1, "%s failed: %s", 70 | "Py_DecodeLocale", 71 | "Decoding error - probably bug in the C library libpython depends on"); 72 | else 73 | errx(1, "%s failed: %s", "Py_DecodeLocale", "Unknown error"); 74 | } 75 | 76 | ret.str.reset(p); 77 | 78 | /* Rely on copy-elision in C++17 instead of move semantics */ 79 | return ret; 80 | } 81 | 82 | /** 83 | * Assume the thread holds the GIL 84 | */ 85 | static void Py_Err(const char *fmt, ...) 86 | { 87 | if (PyErr_Occurred() == nullptr) 88 | errx(1, "Internal bug: calling Py_Err when no exception is raised"); 89 | PyErr_PrintEx(0); 90 | 91 | std::va_list args; 92 | va_start(args, fmt); 93 | verrx(1, fmt, args); 94 | va_end(args); 95 | } 96 | 97 | namespace swaystatus::python { 98 | Interpreter::Interpreter(void *p) noexcept: 99 | p{p} 100 | {} 101 | 102 | Interpreter::GIL_scoped::GIL_scoped(Interpreter *interpreter) noexcept: 103 | interpreter{interpreter} 104 | {} 105 | Interpreter::GIL_scoped::~GIL_scoped() 106 | { 107 | if (interpreter) 108 | interpreter->release(); 109 | } 110 | auto Interpreter::acquire() -> GIL_scoped 111 | { 112 | if (p == nullptr) 113 | return GIL_scoped{nullptr}; 114 | 115 | PyEval_RestoreThread(static_cast(p)); 116 | p = nullptr; 117 | return GIL_scoped{this}; 118 | } 119 | void Interpreter::release() 120 | { 121 | p = PyEval_SaveThread(); 122 | } 123 | 124 | static void initialize_interpreter() 125 | { 126 | Module sys("sys"); 127 | 128 | sys.setattr("stdin", Object::get_none()); 129 | sys.setattr("stdout", Object::get_none()); 130 | 131 | sys.setattr("__stdin__", Object::get_none()); 132 | sys.setattr("__stdout__", Object::get_none()); 133 | } 134 | void MainInterpreter::load_libpython3() 135 | { 136 | if (Py_IsInitialized()) 137 | return; 138 | 139 | PyConfig cfg; 140 | PyConfig_InitPythonConfig(&cfg); 141 | 142 | auto wstr = Py_DecodeLocale_Checked("python3"); 143 | cfg.program_name = wstr.str.get(); 144 | /* 145 | * Depreciated in version 3.11: Py_SetProgramName(...) 146 | */ 147 | auto empty_wstr = Py_DecodeLocale_Checked(""); 148 | wchar_t* argv[] = {empty_wstr.str.get(), nullptr}; 149 | PyConfig_SetArgv(&cfg, 1, argv); 150 | 151 | /* 152 | * Changed in version 3.7: Py_Initialize() now initializes the GIL. 153 | */ 154 | Py_InitializeEx(0); 155 | initialize_interpreter(); 156 | get().release(); 157 | } 158 | auto MainInterpreter::get() noexcept -> Interpreter& 159 | { 160 | static MainInterpreter interpreter{nullptr}; 161 | 162 | return interpreter; 163 | } 164 | bool MainInterpreter::has_initialized() noexcept 165 | { 166 | return Py_IsInitialized(); 167 | } 168 | 169 | SubInterpreter::SubInterpreter(): 170 | Interpreter{Py_NewInterpreter()} 171 | { 172 | if (p == nullptr) 173 | Py_Err("%s failed", "Py_NewInterpreter"); 174 | 175 | auto *saved = PyEval_SaveThread(); 176 | 177 | { 178 | auto scope = acquire(); 179 | initialize_interpreter(); 180 | } 181 | 182 | PyEval_RestoreThread(saved); 183 | } 184 | SubInterpreter::SubInterpreter(SubInterpreter &&other): 185 | Interpreter{other.p} 186 | { 187 | other.p = nullptr; 188 | } 189 | 190 | SubInterpreter::~SubInterpreter() 191 | { 192 | if (p) { 193 | auto *saved = PyEval_SaveThread(); 194 | 195 | PyEval_RestoreThread(static_cast(p)); 196 | /* 197 | * Py_EndInterpreter needs the sub interpreter to be loaded. 198 | * After this call, the current thread state will be set to nullptr. 199 | */ 200 | Py_EndInterpreter(static_cast(p)); 201 | 202 | PyEval_RestoreThread(saved); 203 | } 204 | } 205 | 206 | static PyObject* getPyObject(Object &o) 207 | { 208 | return static_cast(o.get()); 209 | } 210 | static PyObject* getPyObject(const Object &o) 211 | { 212 | return static_cast(const_cast(o.get())); 213 | } 214 | 215 | Object Object::get_none() noexcept 216 | { 217 | return {Py_None}; 218 | } 219 | Object Object::create_new_ref(void *obj) 220 | { 221 | if (obj) 222 | Py_INCREF(obj); 223 | 224 | return {obj}; 225 | } 226 | Object Object::create_new_ref(const Object &obj) 227 | { 228 | return create_new_ref(obj.obj); 229 | } 230 | 231 | Object::Object(void *obj) noexcept: 232 | obj{obj} 233 | {} 234 | Object::Object(Object &&other) noexcept: 235 | obj{other.obj} 236 | { 237 | other.obj = nullptr; 238 | } 239 | Object& Object::operator = (Object &&other) noexcept 240 | { 241 | free(); 242 | obj = other.obj; 243 | other.obj = nullptr; 244 | 245 | return *this; 246 | } 247 | 248 | auto Object::get() noexcept -> void* 249 | { 250 | return obj; 251 | } 252 | auto Object::get() const noexcept -> const void* 253 | { 254 | return obj; 255 | } 256 | 257 | bool Object::has_value() const noexcept 258 | { 259 | return obj != nullptr; 260 | } 261 | 262 | bool Object::is_none() const noexcept 263 | { 264 | return obj == Py_None; 265 | } 266 | 267 | Object Object::getattr(const char *name) 268 | { 269 | auto *ret = PyObject_GetAttrString(getPyObject(*this), name); 270 | if (ret == nullptr) 271 | errx(1, "Attr %s is not found in object %p", name, get()); 272 | 273 | return {ret}; 274 | } 275 | void Object::setattr(const char *name, Object object) 276 | { 277 | if (PyObject_SetAttrString(getPyObject(*this), name, getPyObject(object)) == -1) 278 | Py_Err("%s failed", "PyObject_SetAttrString"); 279 | } 280 | 281 | void Object::free() noexcept 282 | { 283 | if (obj) 284 | Py_DECREF(getPyObject(*this)); 285 | } 286 | Object::~Object() 287 | { 288 | free(); 289 | } 290 | 291 | None::None(Object &&obj) 292 | { 293 | if (!obj.is_none()) 294 | errx(1, "Error in None: obj is not actually Py_None!"); 295 | } 296 | 297 | static PyObject* compile(const char *code, const char *pseudo_filename, Compiled::Type type) 298 | { 299 | PyCompilerFlags flags{ 300 | .cf_flags = 0, 301 | .cf_feature_version = PY_MINOR_VERSION 302 | }; 303 | int start; 304 | switch (type) { 305 | case Compiled::Type::file_like: 306 | start = Py_file_input; 307 | break; 308 | 309 | case Compiled::Type::single_line: 310 | start = Py_single_input; 311 | break; 312 | 313 | case Compiled::Type::single_expression: 314 | start = Py_eval_input; 315 | break; 316 | } 317 | auto *obj = Py_CompileStringExFlags(code, pseudo_filename, start, &flags, 2); 318 | 319 | if (obj == nullptr) 320 | Py_Err("%s on %s failed", "Py_CompileStringExFlags", code); 321 | 322 | return obj; 323 | } 324 | Compiled::Compiled(const char *pseudo_filename, const char *code, Type type): 325 | Object{compile(code, pseudo_filename, type)} 326 | {} 327 | 328 | Module::Module(const char *module_name): 329 | Object{PyImport_ImportModule(module_name)} 330 | { 331 | if (get() == nullptr) 332 | Py_Err("%s on %s failed", "PyImport_ImportModule", module_name); 333 | } 334 | 335 | Module::Module(const char *persudo_module_name, Compiled &compiled): 336 | Object{PyImport_ExecCodeModule(persudo_module_name, getPyObject(compiled))} 337 | { 338 | if (get() == nullptr) 339 | Py_Err("%s on %s failed", "PyImport_ExecCodeModule", persudo_module_name); 340 | } 341 | 342 | auto Module::getname() noexcept -> const char* 343 | { 344 | return PyModule_GetName(getPyObject(*this)); 345 | } 346 | 347 | Int::Int(ssize_t val): 348 | Object{PyLong_FromSsize_t(val)} 349 | { 350 | if (get() == nullptr) 351 | Py_Err("%s failed: %s", "PyLong_FromSsize_t", "Possibly out of memory"); 352 | } 353 | 354 | Int::Int(std::size_t val): 355 | Object{PyLong_FromSize_t(val)} 356 | { 357 | if (get() == nullptr) 358 | Py_Err("%s failed: %s", "PyLong_FromSize_t", "Possibly out of memory"); 359 | } 360 | 361 | static Object&& check_for_int(Object &o) 362 | { 363 | auto *pyobject = getPyObject(o); 364 | if (pyobject != nullptr && PyLong_Check(pyobject) == 0) 365 | errx(1, "Object is not %s", "integer"); 366 | 367 | return static_cast(o); 368 | } 369 | Int::Int(Object &&object): 370 | Object{check_for_int(object)} 371 | {} 372 | 373 | bool Int::to_ssize_t(ssize_t *val) const noexcept 374 | { 375 | auto result = PyLong_AsSsize_t(getPyObject(*this)); 376 | if (result == -1 && PyErr_Occurred()) 377 | return false; 378 | 379 | *val = result; 380 | return true; 381 | } 382 | bool Int::to_size_t(std::size_t *val) const noexcept 383 | { 384 | auto result = PyLong_AsSize_t(getPyObject(*this)); 385 | if (result == static_cast(-1) && PyErr_Occurred()) 386 | return false; 387 | 388 | *val = result; 389 | return true; 390 | } 391 | 392 | static auto* PyMemoryView_FromMemory_Checked(const std::string_view &view) 393 | { 394 | auto *ret = PyMemoryView_FromMemory(const_cast(view.data()), view.size(), PyBUF_READ); 395 | if (ret == nullptr) 396 | Py_Err("%s failed", "PyMemoryView_FromMemory"); 397 | return ret; 398 | } 399 | MemoryView::MemoryView(const std::string_view &view): 400 | Object{PyMemoryView_FromMemory_Checked(view)} 401 | {} 402 | 403 | static auto* PyUnicode_DecodeUTF8_Checked(const std::string_view &view) 404 | { 405 | auto *ret = PyUnicode_DecodeUTF8(view.data(), view.size(), nullptr); 406 | if (ret == nullptr) 407 | Py_Err("%s failed", "PyUnicode_DecodeUTF8"); 408 | return ret; 409 | } 410 | str::str(const std::string_view &view): 411 | Object{PyUnicode_DecodeUTF8_Checked(view)} 412 | {} 413 | 414 | static Object&& check_for_str(Object &o) 415 | { 416 | auto *pyobject = getPyObject(o); 417 | if (pyobject != nullptr && PyUnicode_Check(pyobject) == 0) 418 | errx(1, "Object is not %s", "string"); 419 | 420 | return static_cast(o); 421 | } 422 | str::str(Object &&object): 423 | Object{check_for_str(object)} 424 | {} 425 | 426 | auto str::get_view() const noexcept -> std::string_view 427 | { 428 | Py_ssize_t size; 429 | auto *s = PyUnicode_AsUTF8AndSize(getPyObject(*this), &size); 430 | if (s == nullptr) 431 | Py_Err("%s failed", "PyUnicode_AsUTF8AndSize"); 432 | 433 | return {s, static_cast(size)}; 434 | } 435 | str::operator std::string_view () const noexcept 436 | { 437 | return get_view(); 438 | } 439 | 440 | str_view::str_view(std::string_view view) noexcept: 441 | view{view} 442 | {} 443 | str_view::str_view(str &&string_arg) noexcept: 444 | string{std::move(string_arg)}, 445 | view{string} 446 | {} 447 | 448 | auto str_view::get_view() const noexcept -> std::string_view 449 | { 450 | return view; 451 | } 452 | str_view::operator std::string_view () const noexcept 453 | { 454 | return get_view(); 455 | } 456 | 457 | auto tuple::get_creator() -> creator_t 458 | { 459 | static_assert(std::is_same_v); 460 | return reinterpret_cast(PyTuple_Pack); 461 | } 462 | void tuple::handle_error(const char *msg) 463 | { 464 | Py_Err(msg); 465 | } 466 | 467 | static Object&& check_for_tuple(Object &o) 468 | { 469 | auto *pyobject = getPyObject(o); 470 | if (pyobject != nullptr && PyTuple_Check(pyobject) == 0) 471 | errx(1, "Object is not %s", "tuple"); 472 | 473 | return static_cast(o); 474 | } 475 | tuple::tuple(Object &&object): 476 | Object{check_for_tuple(object)} 477 | {} 478 | 479 | auto tuple::size() const noexcept -> std::size_t 480 | { 481 | return static_cast(PyTuple_GET_SIZE(getPyObject(*this))); 482 | } 483 | auto tuple::get_element(std::size_t i) -> Object 484 | { 485 | auto *result = PyTuple_GetItem(getPyObject(*this), i); 486 | if (result == nullptr) 487 | Py_Err("Out of bound access to tuple"); 488 | 489 | return Object::create_new_ref(result); 490 | } 491 | auto tuple::operator [] (std::size_t i) -> Object 492 | { 493 | return Object::create_new_ref(PyTuple_GET_ITEM(getPyObject(*this), i)); 494 | } 495 | 496 | static Object&& check_for_callable(Object &o) 497 | { 498 | auto *pyobject = getPyObject(o); 499 | if (pyobject != nullptr && PyCallable_Check(pyobject) == 0) 500 | errx(1, "object is not %s", "callable"); 501 | 502 | return static_cast(o); 503 | } 504 | Callable_base::Callable_base(Object &&o): 505 | Object{check_for_callable(o)} 506 | {} 507 | 508 | auto Callable_base::get_caller() -> caller_t 509 | { 510 | return reinterpret_cast(PyObject_CallFunctionObjArgs); 511 | } 512 | 513 | void Callable_base::handle_error() 514 | { 515 | Py_Err("Calling object %p failed", get()); 516 | } 517 | } /* namespace swaystatus::python */ 518 | 519 | #endif /* USE_PYTHON */ 520 | --------------------------------------------------------------------------------