diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d54972d..ce8492be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ message(STATUS "Configuring Hyprland!") include_directories(.) add_compile_options(-std=c++20 -DWLR_USE_UNSTABLE ) -add_compile_options(-Wall -Wextra -Wno-unused-parameter) +add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value) find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) diff --git a/example/hyprland.conf b/example/hyprland.conf new file mode 100644 index 00000000..72de4d5e --- /dev/null +++ b/example/hyprland.conf @@ -0,0 +1,11 @@ +# This is an example Hyprland config file. +# Syntax is the same as in Hypr, but settings might differ. +# +# Refer to the wiki for more information. + +general { + max_fps=240 + gaps_in=5 + gaps_out=20 + border_size=1 +} \ No newline at end of file diff --git a/src/Compositor.cpp b/src/Compositor.cpp index cc6f6d41..552b58bd 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -40,16 +40,16 @@ CCompositor::CCompositor() { m_sWLRXDGActivation - wlr_xdg_activation_v1_create(m_sWLDisplay); m_sWLROutputLayout = wlr_output_layout_create(); - wl_signal_add(&m_sWLRXDGActivation->events.request_activate, &Events::listener_activate); - wl_signal_add(&m_sWLROutputLayout->events.change, &Events::listener_change); - wl_signal_add(&m_sWLRBackend->events.new_output, &Events::listener_newOutput); + wl_signal_add(&m_sWLRXDGActivation->events.request_activate, &Events::listen_activate); + wl_signal_add(&m_sWLROutputLayout->events.change, &Events::listen_change); + wl_signal_add(&m_sWLRBackend->events.new_output, &Events::listen_newOutput); m_sWLRIdle = wlr_idle_create(m_sWLDisplay); m_sWLRLayerShell = wlr_layer_shell_v1_create(m_sWLDisplay); m_sWLRXDGShell = wlr_xdg_shell_create(m_sWLDisplay); - wl_signal_add(&m_sWLRLayerShell->events.new_surface, &Events::listener_newLayerSurface); - wl_signal_add(&m_sWLRXDGShell->events.new_surface, &Events::listener_newXDGSurface); + wl_signal_add(&m_sWLRLayerShell->events.new_surface, &Events::listen_newLayerSurface); + wl_signal_add(&m_sWLRXDGShell->events.new_surface, &Events::listen_newXDGSurface); wlr_server_decoration_manager_set_default_mode(wlr_server_decoration_manager_create(m_sWLDisplay), WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); wlr_xdg_decoration_manager_v1_create(m_sWLDisplay); @@ -67,22 +67,26 @@ CCompositor::CCompositor() { m_sWLRPresentation = wlr_presentation_create(m_sWLDisplay, m_sWLRBackend); - wl_signal_add(&m_sWLRCursor->events.motion, &Events::listener_mouseMove); - wl_signal_add(&m_sWLRCursor->events.motion_absolute, &Events::listener_mouseMoveAbsolute); - wl_signal_add(&m_sWLRCursor->events.button, &Events::listener_mouseButton); - wl_signal_add(&m_sWLRCursor->events.axis, &Events::listener_mouseAxis); - wl_signal_add(&m_sWLRCursor->events.frame, &Events::listener_mouseFrame); - wl_signal_add(&m_sWLRBackend->events.new_input, &Events::listener_newInput); - wl_signal_add(&m_sWLRVKeyboardMgr->events.new_virtual_keyboard, &Events::listener_newKeyboard); - wl_signal_add(&m_sWLRSeat->events.request_set_cursor, &Events::listener_requestMouse); - wl_signal_add(&m_sWLRSeat->events.request_set_selection, &Events::listener_requestSetSel); - wl_signal_add(&m_sWLRSeat->events.request_set_primary_selection, &Events::listener_requestSetPrimarySel); - wl_signal_add(&m_sWLROutputMgr->events.apply, &Events::listener_outputMgrApply); - wl_signal_add(&m_sWLROutputMgr->events.test, &Events::listener_outputMgrTest); + wl_signal_add(&m_sWLRCursor->events.motion, &Events::listen_mouseMove); + wl_signal_add(&m_sWLRCursor->events.motion_absolute, &Events::listen_mouseMoveAbsolute); + wl_signal_add(&m_sWLRCursor->events.button, &Events::listen_mouseButton); + wl_signal_add(&m_sWLRCursor->events.axis, &Events::listen_mouseAxis); + wl_signal_add(&m_sWLRCursor->events.frame, &Events::listen_mouseFrame); + wl_signal_add(&m_sWLRBackend->events.new_input, &Events::listen_newInput); + wl_signal_add(&m_sWLRVKeyboardMgr->events.new_virtual_keyboard, &Events::listen_newKeyboard); + wl_signal_add(&m_sWLRSeat->events.request_set_cursor, &Events::listen_requestMouse); + wl_signal_add(&m_sWLRSeat->events.request_set_selection, &Events::listen_requestSetSel); + wl_signal_add(&m_sWLRSeat->events.request_set_primary_selection, &Events::listen_requestSetPrimarySel); + wl_signal_add(&m_sWLROutputMgr->events.apply, &Events::listen_outputMgrApply); + wl_signal_add(&m_sWLROutputMgr->events.test, &Events::listen_outputMgrTest); // TODO: XWayland } +CCompositor::~CCompositor() { + +} + void CCompositor::startCompositor() { m_szWLDisplaySocket = wl_display_add_socket_auto(m_sWLDisplay); @@ -95,6 +99,7 @@ void CCompositor::startCompositor() { signal(SIGPIPE, SIG_IGN); + Debug::log(LOG, "Running on WAYLAND_DISPLAY: %s", m_szWLDisplaySocket); if (!wlr_backend_start(m_sWLRBackend)) { Debug::log(CRIT, "Backend did not start!"); @@ -103,6 +108,15 @@ void CCompositor::startCompositor() { wlr_xcursor_manager_set_cursor_image(m_sWLRXCursorMgr, "left_ptr", m_sWLRCursor); + Debug::log(LOG, "Creating the config manager!"); + g_pConfigManager = std::make_unique(); + + Debug::log(LOG, "Creating the ManagerThread!"); + g_pManagerThread = std::make_unique(); + + Debug::log(LOG, "Creating the InputManager!"); + g_pInputManager = std::make_unique(); + // This blocks until we are done. Debug::log(LOG, "Hyprland is ready, running the event loop!"); wl_display_run(m_sWLDisplay); diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 2e724804..b2ec10a2 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -5,6 +5,9 @@ #include "defines.hpp" #include "debug/Log.hpp" #include "events/Events.hpp" +#include "config/ConfigManager.hpp" +#include "ManagerThread.hpp" +#include "input/InputManager.hpp" class CCompositor { public: diff --git a/src/ManagerThread.cpp b/src/ManagerThread.cpp new file mode 100644 index 00000000..8ddb3b53 --- /dev/null +++ b/src/ManagerThread.cpp @@ -0,0 +1,22 @@ +#include "ManagerThread.hpp" + +CManagerThread::CManagerThread() { + m_tMainThread = new std::thread([=]() { + // Call the handle method. + this->handle(); + }); + + m_tMainThread->detach(); // detach and continue. +} + +CManagerThread::~CManagerThread() { + // +} + +void CManagerThread::handle() { + while (3.1415f) { + g_pConfigManager->tick(); + + std::this_thread::sleep_for(std::chrono::microseconds(1000000 / g_pConfigManager->getInt("max_fps"))); + } +} \ No newline at end of file diff --git a/src/ManagerThread.hpp b/src/ManagerThread.hpp new file mode 100644 index 00000000..80856fe4 --- /dev/null +++ b/src/ManagerThread.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "defines.hpp" +#include +#include "Compositor.hpp" + +class CManagerThread { +public: + CManagerThread(); + ~CManagerThread(); + +private: + + void handle(); + + std::thread* m_tMainThread; +}; + +inline std::unique_ptr g_pManagerThread; \ No newline at end of file diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp new file mode 100644 index 00000000..eccad341 --- /dev/null +++ b/src/config/ConfigManager.cpp @@ -0,0 +1,201 @@ +#include "ConfigManager.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +CConfigManager::CConfigManager() { + configValues["general:max_fps"].intValue = 240; + + configValues["general:border_size"].intValue = 1; + configValues["general:gaps_in"].intValue = 5; + configValues["general:gaps_out"].intValue = 20; + + loadConfigLoadVars(); + + isFirstLaunch = false; +} + +void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) { + if (configValues.find(COMMAND) == configValues.end()) { + parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">: No such field."; + return; + } + + auto& CONFIGENTRY = configValues.at(COMMAND); + if (CONFIGENTRY.intValue != -1) { + try { + if (VALUE.find("0x") == 0) { + // Values with 0x are hex + const auto VALUEWITHOUTHEX = VALUE.substr(2); + CONFIGENTRY.intValue = stol(VALUEWITHOUTHEX, nullptr, 16); + } else + CONFIGENTRY.intValue = stol(VALUE); + } catch (...) { + Debug::log(WARN, "Error reading value of %s", COMMAND.c_str()); + parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">."; + } + } else if (CONFIGENTRY.floatValue != -1) { + try { + CONFIGENTRY.floatValue = stof(VALUE); + } catch (...) { + Debug::log(WARN, "Error reading value of %s", COMMAND.c_str()); + parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">."; + } + } else if (CONFIGENTRY.strValue != "") { + try { + CONFIGENTRY.strValue = VALUE; + } catch (...) { + Debug::log(WARN, "Error reading value of %s", COMMAND.c_str()); + parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">."; + } + } +} + +void CConfigManager::handleRawExec(const std::string& command, const std::string& args) { + // Exec in the background dont wait for it. + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr); + + _exit(0); + } +} + +void CConfigManager::parseLine(std::string& line) { + // first check if its not a comment + const auto COMMENTSTART = line.find_first_of('#'); + if (COMMENTSTART == 0) + return; + + // now, cut the comment off + if (COMMENTSTART != std::string::npos) + line = line.substr(0, COMMENTSTART); + + // remove shit at the beginning + while (line[0] == ' ' || line[0] == '\t') { + line = line.substr(1); + } + + if (line.find(" {") != std::string::npos) { + auto cat = line.substr(0, line.find(" {")); + transform(cat.begin(), cat.end(), cat.begin(), ::tolower); + currentCategory = cat; + return; + } + + if (line.find("}") != std::string::npos && currentCategory != "") { + currentCategory = ""; + return; + } + + // And parse + // check if command + const auto EQUALSPLACE = line.find_first_of('='); + + if (EQUALSPLACE == std::string::npos) + return; + + const auto COMMAND = line.substr(0, EQUALSPLACE); + const auto VALUE = line.substr(EQUALSPLACE + 1); + + if (COMMAND == "exec") { + handleRawExec(COMMAND, VALUE); + return; + } else if (COMMAND == "exec-once") { + if (isFirstLaunch) { + handleRawExec(COMMAND, VALUE); + return; + } + } + + configSetValueSafe(currentCategory + (currentCategory == "" ? "" : ":") + COMMAND, VALUE); +} + +void CConfigManager::loadConfigLoadVars() { + Debug::log(LOG, "Reloading the config!"); + parseError = ""; // reset the error + currentCategory = ""; // reset the category + + const char* const ENVHOME = getenv("HOME"); + const std::string CONFIGPATH = ENVHOME + (ISDEBUG ? (std::string) "/.config/hypr/hyprlandd.conf" : (std::string) "/.config/hypr/hyprland.conf"); + + std::ifstream ifs; + ifs.open(CONFIGPATH.c_str()); + + if (!ifs.good()) { + Debug::log(WARN, "Config reading error. (No file?)"); + parseError = "The config could not be read. (No file?)"; + + ifs.close(); + return; + } + + std::string line = ""; + int linenum = 1; + if (ifs.is_open()) { + while (std::getline(ifs, line)) { + // Read line by line. + try { + parseLine(line); + } catch (...) { + Debug::log(ERR, "Error reading line from config. Line:"); + Debug::log(NONE, "%s", line.c_str()); + + parseError = "Config error at line " + std::to_string(linenum) + ": Line parsing error."; + } + + if (parseError != "" && parseError.find("Config error at line") != 0) { + parseError = "Config error at line " + std::to_string(linenum) + ": " + parseError; + } + + ++linenum; + } + + ifs.close(); + } +} + +void CConfigManager::tick() { + const char* const ENVHOME = getenv("HOME"); + + const std::string CONFIGPATH = ENVHOME + (ISDEBUG ? (std::string) "/.config/hypr/hyprd.conf" : (std::string) "/.config/hypr/hypr.conf"); + + struct stat fileStat; + int err = stat(CONFIGPATH.c_str(), &fileStat); + if (err != 0) { + Debug::log(WARN, "Error at ticking config, error %i", errno); + } + + // check if we need to reload cfg + if (fileStat.st_mtime != lastModifyTime) { + lastModifyTime = fileStat.st_mtime; + + loadConfigLoadVars(); + } +} + +std::mutex configmtx; +SConfigValue CConfigManager::getConfigValueSafe(std::string val) { + std::lock_guard lg(configmtx); + + SConfigValue copy = configValues[val]; + + return copy; +} + +int CConfigManager::getInt(std::string v) { + return getConfigValueSafe(v).intValue; +} + +float CConfigManager::getFloat(std::string v) { + return getConfigValueSafe(v).floatValue; +} + +std::string CConfigManager::getString(std::string v) { + return getConfigValueSafe(v).strValue; +} diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp new file mode 100644 index 00000000..5e58173a --- /dev/null +++ b/src/config/ConfigManager.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include "../debug/Log.hpp" +#include +#include "../defines.hpp" +#include + +struct SConfigValue { + int64_t intValue = -1; + float floatValue = -1; + std::string strValue = ""; +}; + +class CConfigManager { +public: + CConfigManager(); + + void tick(); + + int getInt(std::string); + float getFloat(std::string); + std::string getString(std::string); + +private: + std::unordered_map configValues; + time_t lastModifyTime = 0; // for reloading the config if changed + + std::string currentCategory = ""; // For storing the category of the current item + + std::string parseError = ""; // For storing a parse error to display later + + bool isFirstLaunch = true; // For exec-once + + // internal methods + void loadConfigLoadVars(); + SConfigValue getConfigValueSafe(std::string); + void parseLine(std::string&); + void configSetValueSafe(const std::string&, const std::string&); + void handleRawExec(const std::string&, const std::string&); +}; + +inline std::unique_ptr g_pConfigManager; \ No newline at end of file diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp index 65ba67c4..0acecb04 100644 --- a/src/debug/Log.cpp +++ b/src/debug/Log.cpp @@ -2,6 +2,7 @@ #include "../defines.hpp" #include +#include void Debug::log(LogLevel level, const char* fmt, ...) { va_list args; @@ -37,5 +38,8 @@ void Debug::log(LogLevel level, const char* fmt, ...) { ofs.close(); + // log it to the stdout too. + std::cout << buf << "\n"; + va_end(args); } diff --git a/src/defines.hpp b/src/defines.hpp index ecf90ae8..33019666 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -8,4 +8,4 @@ #define RIP(format, ... ) { fprintf(stderr, format "\n", ##__VA_ARGS__); exit(EXIT_FAILURE); } -#define LISTENER(name) inline wl_listener listener_##name = {.notify = ##name}; \ No newline at end of file +#define LISTENER(name) void listener_##name(wl_listener*, void*); inline wl_listener listen_##name = { .notify = listener_##name }; \ No newline at end of file diff --git a/src/events/Events.cpp b/src/events/Events.cpp index 6df4c20b..a5b093f0 100644 --- a/src/events/Events.cpp +++ b/src/events/Events.cpp @@ -1,5 +1,70 @@ #include "Events.hpp" +#include "../input/InputManager.hpp" void Events::listener_activate(wl_listener* listener, void* data) { +} + +void Events::listener_change(wl_listener* listener, void* data) { + +} + +void Events::listener_mouseAxis(wl_listener* listener, void* data) { + +} + +void Events::listener_mouseButton(wl_listener* listener, void* data) { + +} + +void Events::listener_mouseFrame(wl_listener* listener, void* data) { + +} + +void Events::listener_mouseMove(wl_listener* listener, void* data) { + g_pInputManager->onMouseMoved((wlr_event_pointer_motion*)data); +} + +void Events::listener_mouseMoveAbsolute(wl_listener* listener, void* data) { + +} + +void Events::listener_newInput(wl_listener* listener, void* data) { + +} + +void Events::listener_newKeyboard(wl_listener* listener, void* data) { + +} + +void Events::listener_newLayerSurface(wl_listener* listener, void* data) { + +} + +void Events::listener_newOutput(wl_listener* listener, void* data) { + +} + +void Events::listener_newXDGSurface(wl_listener* listener, void* data) { + +} + +void Events::listener_outputMgrApply(wl_listener* listener, void* data) { + +} + +void Events::listener_outputMgrTest(wl_listener* listener, void* data) { + +} + +void Events::listener_requestMouse(wl_listener* listener, void* data) { + +} + +void Events::listener_requestSetPrimarySel(wl_listener* listener, void* data) { + +} + +void Events::listener_requestSetSel(wl_listener* listener, void* data) { + } \ No newline at end of file diff --git a/src/helpers/Vector2D.cpp b/src/helpers/Vector2D.cpp index 33b12e7c..acaace13 100644 --- a/src/helpers/Vector2D.cpp +++ b/src/helpers/Vector2D.cpp @@ -16,4 +16,8 @@ double Vector2D::normalize() { y /= max; return max; +} + +Vector2D Vector2D::floor() { + return Vector2D((int)x, (int)y); } \ No newline at end of file diff --git a/src/helpers/Vector2D.hpp b/src/helpers/Vector2D.hpp index 9bc427e8..20bc032e 100644 --- a/src/helpers/Vector2D.hpp +++ b/src/helpers/Vector2D.hpp @@ -26,4 +26,15 @@ class Vector2D { Vector2D operator/(float a) { return Vector2D(this->x / a, this->y / a); } + + bool operator==(Vector2D& a) { + return a.x == x && a.y == y; + } + + bool operator!=(Vector2D& a) { + return a.x != x || a.y != y; + } + + + Vector2D floor(); }; \ No newline at end of file diff --git a/src/includes.hpp b/src/includes.hpp index 352286d4..a1b51b04 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -1,5 +1,11 @@ #pragma once +// because C/C++ VS Code intellisense is stupid with includes, we will suppress them here. +// This suppresses all "include file not found" errors. +#ifdef __INTELLISENSE__ +#pragma diag_suppress 1696 +#endif + #include #include #include @@ -11,6 +17,8 @@ #include #include #include +#include +#include #if true diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp new file mode 100644 index 00000000..3140e22d --- /dev/null +++ b/src/input/InputManager.cpp @@ -0,0 +1,17 @@ +#include "InputManager.hpp" +#include "../Compositor.hpp" + +void CInputManager::onMouseMoved(wlr_event_pointer_motion* e) { + // TODO: sensitivity + + float sensitivity = 0.25f; + + m_vMouseCoords = m_vMouseCoords + Vector2D(e->delta_x * sensitivity, e->delta_y * sensitivity); + + if (m_vMouseCoords.floor() != m_vWLRMouseCoords) { + Vector2D delta = m_vMouseCoords - m_vWLRMouseCoords; + m_vWLRMouseCoords = m_vMouseCoords.floor(); + + wlr_cursor_move(g_pCompositor->m_sWLRCursor, e->device, delta.floor().x, delta.floor().y); + } +} \ No newline at end of file diff --git a/src/input/InputManager.hpp b/src/input/InputManager.hpp new file mode 100644 index 00000000..38ec419a --- /dev/null +++ b/src/input/InputManager.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "../defines.hpp" + +class CInputManager { +public: + + void onMouseMoved(wlr_event_pointer_motion*); + void onMouseButton(int); + +private: + Vector2D m_vMouseCoords = Vector2D(0,0); + Vector2D m_vWLRMouseCoords = Vector2D(0,0); + +}; + +inline std::unique_ptr g_pInputManager; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index da0437e8..feaee667 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include "defines.hpp" #include "debug/Log.hpp" #include "Compositor.hpp" +#include "config/ConfigManager.hpp" int main(int argc, char** argv) {