diff --git a/Makefile b/Makefile index a33f4cb7..493a6784 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ uninstall: pluginenv: @echo -en "$(MAKE) pluginenv has been deprecated.\nPlease run $(MAKE) all && sudo $(MAKE) installheaders\n" @exit 1 - + installheaders: @if [ ! -f ./src/version.h ]; then echo -en "You need to run $(MAKE) all first.\n" && exit 1; fi diff --git a/hyprctl/Strings.hpp b/hyprctl/Strings.hpp index 76e87ecb..17725e77 100644 --- a/hyprctl/Strings.hpp +++ b/hyprctl/Strings.hpp @@ -38,7 +38,8 @@ commands: plugin ... → Issue a plugin request reload [config-only] → Issue a reload to force reload the config. Pass 'config-only' to disable monitor reload - rollinglog → Prints tail of the log + rollinglog → Prints tail of the log. Also supports -f/--follow + option setcursor → Sets the cursor theme and reloads the cursor manager seterror → Sets the hyprctl error string. Color has @@ -112,7 +113,7 @@ create : remove : Removes virtual output. Pass the output's name, as found in 'hyprctl monitors' - + flags: See 'hyprctl --help')#"; diff --git a/hyprctl/main.cpp b/hyprctl/main.cpp index 8fb9194c..f5de041b 100644 --- a/hyprctl/main.cpp +++ b/hyprctl/main.cpp @@ -1,9 +1,9 @@ -#include +#include #include #include -#include -#include -#include +#include +#include +#include #include #include #include @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include @@ -22,8 +22,9 @@ #include #include #include -#include +#include #include +#include #include using namespace Hyprutils::String; @@ -100,13 +101,53 @@ std::vector instances() { return result; } -int request(std::string arg, int minArgs = 0) { +static volatile bool sigintReceived = false; +void intHandler(int sig) { + sigintReceived = true; + std::cout << "[hyprctl] SIGINT received, closing connection" << std::endl; +} + +int rollingRead(const int socket) { + sigintReceived = false; + signal(SIGINT, intHandler); + + constexpr size_t BUFFER_SIZE = 8192; + std::array buffer = {0}; + int sizeWritten = 0; + std::cout << "[hyprctl] reading from socket following up log:" << std::endl; + while (!sigintReceived) { + sizeWritten = read(socket, buffer.data(), BUFFER_SIZE); + if (sizeWritten < 0 && errno != EAGAIN) { + if (errno != EINTR) + std::cout << "Couldn't read (5) " << strerror(errno) << ":" << errno << std::endl; + close(socket); + return 5; + } + + if (sizeWritten == 0) + break; + + if (sizeWritten > 0) { + std::cout << std::string(buffer.data(), sizeWritten); + buffer.fill('\0'); + } + + usleep(100000); + } + close(socket); + return 0; +} + +int request(std::string arg, int minArgs = 0, bool needRoll = false) { const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0); + auto t = timeval{.tv_sec = 0, .tv_usec = 100000}; + setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)); + const auto ARGS = std::count(arg.begin(), arg.end(), ' '); if (ARGS < minArgs) { - log("Not enough arguments, expected at least " + minArgs); + log(std::format("Not enough arguments in '{}', expected at least {}", arg, minArgs)); return -1; } @@ -141,6 +182,9 @@ int request(std::string arg, int minArgs = 0) { return 4; } + if (needRoll) + return rollingRead(SERVERSOCKET); + std::string reply = ""; char buffer[8192] = {0}; @@ -284,6 +328,7 @@ int main(int argc, char** argv) { std::string fullArgs = ""; const auto ARGS = splitArgs(argc, argv); bool json = false; + bool needRoll = false; std::string overrideInstance = ""; for (std::size_t i = 0; i < ARGS.size(); ++i) { @@ -303,6 +348,9 @@ int main(int argc, char** argv) { fullArgs += "a"; } else if ((ARGS[i] == "-c" || ARGS[i] == "--config") && !fullArgs.contains("c")) { fullArgs += "c"; + } else if ((ARGS[i] == "-f" || ARGS[i] == "--follow") && !fullArgs.contains("f")) { + fullArgs += "f"; + needRoll = true; } else if (ARGS[i] == "--batch") { fullRequest = "--batch "; } else if (ARGS[i] == "--instance" || ARGS[i] == "-i") { @@ -362,6 +410,11 @@ int main(int argc, char** argv) { return 0; } + if (needRoll && !fullRequest.contains("/rollinglog")) { + log("only 'rollinglog' command supports '--follow' option"); + return 1; + } + if (overrideInstance.contains("_")) instanceSignature = overrideInstance; else if (!overrideInstance.empty()) { @@ -421,6 +474,8 @@ int main(int argc, char** argv) { exitStatus = request(fullRequest, 1); else if (fullRequest.contains("/--help")) std::cout << USAGE << std::endl; + else if (fullRequest.contains("/rollinglog") && needRoll) + exitStatus = request(fullRequest, 0, true); else { exitStatus = request(fullRequest); } diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index a7e714bd..70b886f2 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -28,6 +28,7 @@ using namespace Hyprutils::String; #include "../devices/IKeyboard.hpp" #include "../devices/ITouch.hpp" #include "../devices/Tablet.hpp" +#include "debug/RollingLogFollow.hpp" #include "config/ConfigManager.hpp" #include "helpers/MiscFunctions.hpp" @@ -1732,6 +1733,46 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) { return getReply(input); } +bool successWrite(int fd, const std::string& data, bool needLog = true) { + if (write(fd, data.c_str(), data.length()) > 0) + return true; + + if (errno == EAGAIN) + return true; + + if (needLog) + Debug::log(ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno))); + + return false; +} + +void runWritingDebugLogThread(const int conn) { + using namespace std::chrono_literals; + Debug::log(LOG, "In followlog thread, got connection, start writing: {}", conn); + //will be finished, when reading side close connection + std::thread([conn]() { + while (Debug::RollingLogFollow::Get().IsRunning()) { + if (Debug::RollingLogFollow::Get().isEmpty(conn)) { + std::this_thread::sleep_for(1000ms); + continue; + } + + auto line = Debug::RollingLogFollow::Get().GetLog(conn); + if (!successWrite(conn, line)) + // We cannot write, when connection is closed. So thread will successfully exit by itself + break; + + std::this_thread::sleep_for(100ms); + } + close(conn); + Debug::RollingLogFollow::Get().StopFor(conn); + }).detach(); +} + +bool isFollowUpRollingLogRequest(const std::string& request) { + return request.contains("rollinglog") && request.contains("f"); +} + int hyprCtlFDTick(int fd, uint32_t mask, void* data) { if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) return 0; @@ -1775,9 +1816,15 @@ int hyprCtlFDTick(int fd, uint32_t mask, void* data) { reply = "Err: " + std::string(e.what()); } - write(ACCEPTEDCONNECTION, reply.c_str(), reply.length()); + successWrite(ACCEPTEDCONNECTION, reply); - close(ACCEPTEDCONNECTION); + if (isFollowUpRollingLogRequest(request)) { + Debug::log(LOG, "Followup rollinglog request received. Starting thread to write to socket."); + Debug::RollingLogFollow::Get().StartFor(ACCEPTEDCONNECTION); + runWritingDebugLogThread(ACCEPTEDCONNECTION); + Debug::log(LOG, Debug::RollingLogFollow::Get().DebugInfo()); + } else + close(ACCEPTEDCONNECTION); if (g_pConfigManager->m_bWantsMonitorReload) g_pConfigManager->ensureMonitorStatus(); diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp index 8b82c852..7547204a 100644 --- a/src/debug/Log.cpp +++ b/src/debug/Log.cpp @@ -1,6 +1,7 @@ #include "Log.hpp" #include "../defines.hpp" #include "../Compositor.hpp" +#include "RollingLogFollow.hpp" #include #include @@ -73,6 +74,9 @@ void Debug::log(LogLevel level, std::string str) { if (rollingLog.size() > ROLLING_LOG_SIZE) rollingLog = rollingLog.substr(rollingLog.size() - ROLLING_LOG_SIZE); + if (RollingLogFollow::Get().IsRunning()) + RollingLogFollow::Get().AddLog(str); + if (!disableLogs || !**disableLogs) { // log to a file std::ofstream ofs; diff --git a/src/debug/RollingLogFollow.hpp b/src/debug/RollingLogFollow.hpp new file mode 100644 index 00000000..5ff018af --- /dev/null +++ b/src/debug/RollingLogFollow.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +namespace Debug { + struct RollingLogFollow { + std::unordered_map socketToRollingLogFollowQueue; + std::shared_mutex m; + bool running = false; + static constexpr size_t ROLLING_LOG_FOLLOW_TOO_BIG = 8192; + + // Returns true if the queue is empty for the given socket + bool isEmpty(int socket) { + std::shared_lock r(m); + return socketToRollingLogFollowQueue[socket].empty(); + } + + std::string DebugInfo() { + std::shared_lock r(m); + return std::format("RollingLogFollow, got {} connections", socketToRollingLogFollowQueue.size()); + } + + std::string GetLog(int socket) { + std::unique_lock w(m); + + const std::string ret = socketToRollingLogFollowQueue[socket]; + socketToRollingLogFollowQueue[socket] = ""; + + return ret; + }; + + void AddLog(std::string log) { + std::unique_lock w(m); + running = true; + std::vector to_erase; + for (const auto& p : socketToRollingLogFollowQueue) + socketToRollingLogFollowQueue[p.first] += log + "\n"; + } + + bool IsRunning() { + std::shared_lock r(m); + return running; + } + + void StopFor(int socket) { + std::unique_lock w(m); + socketToRollingLogFollowQueue.erase(socket); + if (socketToRollingLogFollowQueue.empty()) + running = false; + } + + void StartFor(int socket) { + std::unique_lock w(m); + socketToRollingLogFollowQueue[socket] = std::format("[LOG] Following log to socket: {} started\n", socket); + running = true; + } + + static RollingLogFollow& Get() { + static RollingLogFollow instance; + static std::mutex gm; + std::lock_guard lock(gm); + return instance; + }; + }; +}