diff --git a/build.sh b/build.sh index 35ff3378..b9b3ac45 100755 --- a/build.sh +++ b/build.sh @@ -28,6 +28,7 @@ function main mkdir ./build || msg_err "Failed to create build dir" cd ./build || msg_err "Failed to enter build dir" + local build_ipc_msg="ON" local enable_alsa="ON" local enable_i3="ON" local enable_network="ON" @@ -46,6 +47,8 @@ function main [[ "${p^^}" != "Y" ]] && enable_mpd="OFF" read -r -p "$(msg "Include support for \"internal/github\" (requires libcurl) ---------- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && enable_curl="OFF" + read -r -p "$(msg "Build \"polybar-msg\" used to send ipc messages --------------------- [Y/n]: ")" -n 1 p && echo + [[ "${p^^}" != "Y" ]] && build_ipc_msg="OFF" local cxx="c++" local cc="cc" @@ -69,6 +72,7 @@ function main -DENABLE_MPD:BOOL="${enable_mpd}" \ -DENABLE_NETWORK:BOOL="${enable_network}" \ -DENABLE_CURL:BOOL="${enable_curl}" \ + -DBUILD_IPC_MSG:BOOL="${build_ipc_msg}" \ .. || msg_err "Failed to generate build... read output to get a hint of what went wrong" msg "Building project" diff --git a/cmake/build/options.cmake b/cmake/build/options.cmake index 8a2c457b..d7a65b6a 100644 --- a/cmake/build/options.cmake +++ b/cmake/build/options.cmake @@ -48,6 +48,7 @@ endif() option(CXXLIB_CLANG "Link against libc++" OFF) option(CXXLIB_GCC "Link against stdlibc++" OFF) +option(BUILD_IPC_MSG "Build ipc messager" ON) option(BUILD_TESTS "Build testsuite" OFF) option(DEBUG_LOGGER "Enable extra debug logging" OFF) option(VERBOSE_TRACELOG "Enable verbose trace logs" OFF) diff --git a/cmake/build/summary.cmake b/cmake/build/summary.cmake index d7bf7cc7..ebca0682 100644 --- a/cmake/build/summary.cmake +++ b/cmake/build/summary.cmake @@ -53,6 +53,7 @@ else() endif() message(STATUS "--------------------------") +colored_option(STATUS " Build polybar-msg ${BUILD_IPC_MSG}" BUILD_IPC_MSG "32;1" "37;2") colored_option(STATUS " Build testsuite ${BUILD_TESTS}" BUILD_TESTS "32;1" "37;2") colored_option(STATUS " Debug logging ${DEBUG_LOGGER}" DEBUG_LOGGER "32;1" "37;2") colored_option(STATUS " Verbose tracing ${VERBOSE_TRACELOG}" VERBOSE_TRACELOG "32;1" "37;2") diff --git a/include/utils/file.hpp b/include/utils/file.hpp index 4318cf5b..dafbff41 100644 --- a/include/utils/file.hpp +++ b/include/utils/file.hpp @@ -102,6 +102,7 @@ namespace file_util { string pick(const vector& filenames); string contents(const string& filename); bool is_fifo(const string& filename); + vector glob(const string& pattern); template decltype(auto) make_file_descriptor(Args&&... args) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 15a02a4a..e285dc34 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ # file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR}/src *.c[p]*) +list(REMOVE_ITEM SOURCES ipc.cpp) # Locate dependencies {{{ @@ -142,14 +143,18 @@ target_compile_definitions(${PROJECT_NAME} PUBLIC ${XCB_DEFINITIONS}) # }}} -# Export target details {{{ +# Create executable target: ipc messager {{{ + +if(BUILD_IPC_MSG) + make_executable(${PROJECT_NAME}-msg SOURCES ipc.cpp utils/file.cpp) +endif() + +# }}} set(APP_BINARY ${PROJECT_SOURCE_DIR}/bin/${PROJECT_NAME} PARENT_SCOPE) set(APP_LIBRARIES ${APP_LIBRARIES} PARENT_SCOPE) set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} PARENT_SCOPE) -# }}} - execute_process(COMMAND git describe --tags --dirty=-git WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE APP_VERSION diff --git a/src/ipc.cpp b/src/ipc.cpp new file mode 100644 index 00000000..adf8546c --- /dev/null +++ b/src/ipc.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include + +#include "common.hpp" +#include "utils/file.hpp" + +using namespace polybar; + +void log(const string& msg) { + std::cerr << "polybar-msg: " << msg << std::endl; +} + +void log(int exit_code, const string& msg) { + std::cerr << "polybar-msg: " << msg << std::endl; + exit(exit_code); +} + +void usage(const string& parameters) { + std::cerr << "Usage: polybar-msg [-p pid] " << parameters << std::endl; + exit(127); +} + +bool validate_type(const string& type) { + if (type == "action") { + return true; + } else if (type == "cmd") { + return true; + } else if (type == "hook") { + return true; + } else { + return false; + } +} + +int main(int argc, char** argv) { + const int E_GENERIC{1}; + const int E_NO_CHANNELS{2}; + const int E_MESSAGE_TYPE{3}; + const int E_INVALID_PID{4}; + const int E_INVALID_CHANNEL{5}; + const int E_WRITE{6}; + + vector args{argv + 1, argv + argc}; + string::size_type p; + int pid{0}; + + // If -p is passed, check if the process is running and that + // a valid channel pipe is available + if (args.size() >= 2 && args[0].compare(0, 2, "-p") == 0) { + if (!file_util::exists("/proc/" + args[1])) { + log(E_INVALID_PID, "No process with pid " + args[1]); + } else if (!file_util::is_fifo("/tmp/polybar_mqueue." + args[1])) { + log(E_INVALID_CHANNEL, "No channel available for pid " + args[1]); + } + + pid = std::atoi(args[1].c_str()); + args.erase(args.begin()); + args.erase(args.begin()); + } + + // Validate args + if (args.size() < 2) { + usage(" [...]"); + } else if (!validate_type(args[0])) { + log(E_MESSAGE_TYPE, "\"" + args[0] + "\" is not a valid type."); + } + + string ipc_type{args[0]}; + args.erase(args.begin()); + string ipc_payload{args[0]}; + args.erase(args.begin()); + + // Check hook specific args + if (ipc_type == "hook") { + if (args.size() != 1) { + usage("hook "); + } else if ((p = ipc_payload.find("module/")) != 0) { + ipc_payload = "module/" + ipc_payload + args[0]; + args.erase(args.begin()); + } else { + ipc_payload += args[0]; + args.erase(args.begin()); + } + } + + // Get availble channel pipes + auto channels = file_util::glob("/tmp/polybar_mqueue.*"); + if (channels.empty()) { + log(E_NO_CHANNELS, "There are no active ipc channels"); + } + + // Write the message to each channel in the list and remove stale + // channel pipes that may be left lingering if the owning process got + // SIGKILLED or crashed + for (auto&& channel : channels) { + string handle{channel}; + int handle_pid{0}; + + if ((p = handle.rfind('.')) != string::npos) { + handle_pid = std::atoi(handle.substr(p + 1).c_str()); + } + + if (!file_util::exists("/proc/" + to_string(handle_pid))) { + if (unlink(handle.c_str()) == -1) { + log(E_GENERIC, "Could not remove stale ipc channel: " + string{std::strerror(errno)}); + } else { + log("Removed stale ipc channel: " + handle); + } + } else if (!pid || pid == handle_pid) { + string payload{ipc_type + ":" + ipc_payload}; + + try { + fd_stream out(handle, O_WRONLY); + out << payload << '\n'; + log("Successfully wrote \"" + payload + "\" to \"" + handle + "\""); + } catch (const std::exception& err) { + log(E_WRITE, "Failed to write \"" + payload + "\" to \"" + handle + "\" (err: " + err.what() + ")"); + } + } + } + + return 0; +} diff --git a/src/utils/file.cpp b/src/utils/file.cpp index b5b82088..8ecc0c1a 100644 --- a/src/utils/file.cpp +++ b/src/utils/file.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -201,6 +202,23 @@ namespace file_util { struct stat buffer {}; return stat(filename.c_str(), &buffer) == 0 && S_ISFIFO(buffer.st_mode); } + + /** + * Get glob results using given pattern + */ + vector glob(const string& pattern) { + glob_t result; + vector ret; + + if (glob(pattern.c_str(), GLOB_TILDE, nullptr, &result) == 0) { + for (size_t i = 0_z; i < result.gl_pathc; ++i) { + ret.emplace_back(result.gl_pathv[i]); + } + globfree(&result); + } + + return ret; + } } POLYBAR_NS_END