Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added --follow option to hyprctl rollinglog #6300 #6325

Merged
merged 1 commit into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions hyprctl/Strings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const std::string_view USAGE = R"#(usage: hyprctl [flags] <command> [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 <theme> <size> → Sets the cursor theme and reloads the cursor
manager
seterror <color> <message...> → Sets the hyprctl error string. Color has
Expand Down Expand Up @@ -112,7 +113,7 @@ create <backend>:
remove <name>:
Removes virtual output. Pass the output's name, as found in
'hyprctl monitors'

flags:
See 'hyprctl --help')#";

Expand Down
71 changes: 63 additions & 8 deletions hyprctl/main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include <ctype.h>
#include <cctype>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
Expand All @@ -12,7 +12,7 @@
#include <unistd.h>
#include <ranges>
#include <algorithm>
#include <signal.h>
#include <csignal>
#include <format>

#include <iostream>
Expand All @@ -22,8 +22,9 @@
#include <vector>
#include <deque>
#include <filesystem>
#include <stdarg.h>
#include <cstdarg>
#include <regex>
#include <sys/socket.h>
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;

Expand Down Expand Up @@ -100,13 +101,53 @@ std::vector<SInstanceData> 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<char, BUFFER_SIZE> 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;
}

Expand Down Expand Up @@ -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};

Expand Down Expand Up @@ -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) {
Expand All @@ -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";
vaxerski marked this conversation as resolved.
Show resolved Hide resolved
needRoll = true;
} else if (ARGS[i] == "--batch") {
fullRequest = "--batch ";
} else if (ARGS[i] == "--instance" || ARGS[i] == "-i") {
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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);
}
Expand Down
51 changes: 49 additions & 2 deletions src/debug/HyprCtl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions src/debug/Log.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Log.hpp"
#include "../defines.hpp"
#include "../Compositor.hpp"
#include "RollingLogFollow.hpp"

#include <fstream>
#include <iostream>
Expand Down Expand Up @@ -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;
Expand Down
65 changes: 65 additions & 0 deletions src/debug/RollingLogFollow.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once

#include <shared_mutex>

namespace Debug {
struct RollingLogFollow {
std::unordered_map<int, std::string> 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<std::shared_mutex> r(m);
return socketToRollingLogFollowQueue[socket].empty();
}

std::string DebugInfo() {
std::shared_lock<std::shared_mutex> r(m);
return std::format("RollingLogFollow, got {} connections", socketToRollingLogFollowQueue.size());
}

std::string GetLog(int socket) {
std::unique_lock<std::shared_mutex> w(m);

const std::string ret = socketToRollingLogFollowQueue[socket];
socketToRollingLogFollowQueue[socket] = "";

return ret;
};

void AddLog(std::string log) {
std::unique_lock<std::shared_mutex> w(m);
running = true;
std::vector<int> to_erase;
for (const auto& p : socketToRollingLogFollowQueue)
socketToRollingLogFollowQueue[p.first] += log + "\n";
}

bool IsRunning() {
std::shared_lock<std::shared_mutex> r(m);
return running;
}

void StopFor(int socket) {
std::unique_lock<std::shared_mutex> w(m);
socketToRollingLogFollowQueue.erase(socket);
if (socketToRollingLogFollowQueue.empty())
running = false;
}
vaxerski marked this conversation as resolved.
Show resolved Hide resolved

void StartFor(int socket) {
std::unique_lock<std::shared_mutex> 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<std::mutex> lock(gm);
return instance;
};
};
}
Loading