diff --git a/Makefile b/Makefile index a33f4cb71076..493a67848d7d 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 951c6a7fe6c9..50baac01c55b 100644 --- a/hyprctl/Strings.hpp +++ b/hyprctl/Strings.hpp @@ -38,7 +38,8 @@ const std::string_view USAGE = R"#(usage: hyprctl [flags] [args...|--h 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 @@ -111,7 +112,7 @@ create : remove : Removes virtual output. Pass the output's name, as found in 'hyprctl monitors' - + flags: See 'hyprctl --help')#"; @@ -155,4 +156,4 @@ const std::string_view SWITCHXKBLAYOUT_HELP = R"#(usage: [flags] switchxkblayout starting from 0 flags: - See 'hyprctl --help')#"; \ No newline at end of file + See 'hyprctl --help')#"; diff --git a/hyprctl/main.cpp b/hyprctl/main.cpp index bdf354e76363..972e3d5d51c7 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 "Strings.hpp" @@ -90,13 +91,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; + char buffer[BUFFER_SIZE] = {0}; + int sizeWritten = 0; + std::cout << "[hyprctl] reading from socket following up log:" << std::endl; + while (!sigintReceived) { + sizeWritten = read(socket, buffer, 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, sizeWritten); + memset(buffer, 0, BUFFER_SIZE); + } + + 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) { - std::cout << "Not enough arguments, expected at least " << minArgs; + std::cout << "Not enough arguments in '" << arg << "', expected at least " << minArgs; return -1; } @@ -131,6 +172,9 @@ int request(std::string arg, int minArgs = 0) { return 4; } + if (needRoll) + return rollingRead(SERVERSOCKET); + std::string reply = ""; char buffer[8192] = {0}; @@ -280,6 +324,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) { @@ -299,6 +344,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") { @@ -356,6 +404,11 @@ int main(int argc, char** argv) { return 0; } + if (needRoll && !fullRequest.contains("/rollinglog")) { + std::cout << "only 'rollinglog' command supports '--follow' option" << std::endl; + return 1; + } + if (overrideInstance.contains("_")) instanceSignature = overrideInstance; else if (!overrideInstance.empty()) { @@ -415,6 +468,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 bbe5019aee36..e1c015843e4a 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -24,6 +24,7 @@ #include "../devices/IKeyboard.hpp" #include "../devices/ITouch.hpp" #include "../devices/Tablet.hpp" +#include "debug/RollingLogFollow.hpp" #include "config/ConfigManager.hpp" static void trimTrailingComma(std::string& str) { @@ -1727,6 +1728,45 @@ 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) { + 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)) { + sleep(1); + 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 himself + break; + + usleep(100000); + } + close(conn); + Debug::RollingLogFollow::Get().StopFor(conn); + }).detach(); +} + +bool isFollowUpRollingLogRequest(const std::string& request) { + return request.contains("rollinglog") && request.find_first_of('f') != std::string::npos; +} + int hyprCtlFDTick(int fd, uint32_t mask, void* data) { if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) return 0; @@ -1770,9 +1810,16 @@ 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 8b82c85209cc..7547204a7cce 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 000000000000..2c951806ac7c --- /dev/null +++ b/src/debug/RollingLogFollow.hpp @@ -0,0 +1,63 @@ +#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 "RollingLogFollow, got " + std::to_string(socketToRollingLogFollowQueue.size()) + " connections"; + } + + 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] = "[LOG] Following log to socket: " + std::to_string(socket) + " started" + "\n"; + running = true; + } + static RollingLogFollow& Get() { + static RollingLogFollow instance; + static std::mutex gm; + std::lock_guard lock(gm); + return instance; + }; + }; +}