From 83f6dc2381012f70a7d9897b178031aad39546af Mon Sep 17 00:00:00 2001 From: decryller Date: Fri, 11 Aug 2023 00:16:14 -0400 Subject: [PATCH] v1.1 release --- LICENSE | 21 ++ README.md | 17 + alma.cpp | 86 +++++ alma.hpp | 126 ++++++++ example.cpp | 323 +++++++++++++++++++ rvmt/LICENSE | 21 ++ rvmt/rvmt.cpp | 865 ++++++++++++++++++++++++++++++++++++++++++++++++++ rvmt/rvmt.hpp | 205 ++++++++++++ 8 files changed, 1664 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 alma.cpp create mode 100644 alma.hpp create mode 100644 example.cpp create mode 100644 rvmt/LICENSE create mode 100644 rvmt/rvmt.cpp create mode 100644 rvmt/rvmt.hpp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8700405 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Decryller (decryller@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..938f5ad --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +### alma +a linux memory analyzer.\ +alma provides: +* Memory reading and writing functions. +* A wildcard-supporting pattern scanning function. +* Easy memory page retrieval based on access permissions. + +Requires root permissions and `/proc` to be mounted.\ +The `rvmt` folder and its contents are necessary just for the example. +### Running the example +``` +g++ -lX11 rvmt/rvmt.cpp alma.cpp example.cpp -o alma && sudo ./alma +``` +Latest version: alma v1.1 +### Contact +E-mail: decryller@gmail.com\ +Discord: decryller diff --git a/alma.cpp b/alma.cpp new file mode 100644 index 0000000..f48d425 --- /dev/null +++ b/alma.cpp @@ -0,0 +1,86 @@ +// alma v1.1 +#include "alma.hpp" +#include +#include + +char memPath[64]; +char mapsPath[64]; + +unsigned int currentPID; + +std::vector alma::memRead(const memAddr address, const unsigned int count) { + + std::ifstream mem(memPath, std::ios::in | std::ios::binary); + std::vector retVec(count); + + mem.seekg(address); + mem.read(reinterpret_cast(&retVec[0]), count); + return retVec; +} + +void alma::memWrite(const memAddr address, const std::vector values) { + std::ofstream mem(memPath, std::ios::out | std::ios::binary); + + mem.seekp(address); + mem.write(reinterpret_cast(&values[0]), values.size()); +} + +std::vector alma::getMemoryPages(const int targetPerms, const int excludedPerms) { + std::vector retVec; + std::ifstream maps_file(mapsPath); + std::string line; + + // Iterate through each line + while (std::getline(maps_file, line)) { + std::istringstream sstream(line); + + memAddr pageBegin, pageEnd; + char dash; + char pagePerms[4]; + + // Parse the line + sstream >> std::hex >> pageBegin >> dash >> pageEnd >> pagePerms; + + memoryPage page { + memoryPermission_NONE, + pageBegin, + pageEnd + }; + + if (pagePerms[0] == 'r') page.permissions |= memoryPermission_READ; + if (pagePerms[1] == 'w') page.permissions |= memoryPermission_WRITE; + if (pagePerms[2] == 'x') page.permissions |= memoryPermission_EXECUTE; + if (pagePerms[3] == 's') page.permissions |= memoryPermission_SHARED; + if (pagePerms[3] == 'p') page.permissions |= memoryPermission_PRIVATE; + + // Check for excluded permissions + if (page.permissions & excludedPerms) + continue; + + // If target permissions are not specified, push back every page. + if (targetPerms == memoryPermission_NONE) + retVec.push_back(page); + + // If target permissions specified, only push back pages that meet the filter + else if (targetPerms & page.permissions) + retVec.push_back(page); + } + + return retVec; +} + +bool alma::openProcess(const unsigned int pid) { + snprintf(memPath, 64, "/proc/%i/mem", pid); + snprintf(mapsPath, 64, "/proc/%i/maps", pid); + + std::ifstream existsCheck(memPath, std::ios::in | std::ios::binary); + + if (existsCheck.is_open()) + currentPID = pid; + + return currentPID == pid; +} + +unsigned int alma::getCurrentlyOpenPID() { + return currentPID; +} \ No newline at end of file diff --git a/alma.hpp b/alma.hpp new file mode 100644 index 0000000..e92d450 --- /dev/null +++ b/alma.hpp @@ -0,0 +1,126 @@ +// alma v1.1 +#ifndef ALMA_HPP +#define ALMA_HPP +#include +typedef unsigned long long memAddr; + +enum memoryPermission_ { + memoryPermission_NONE = 0, + memoryPermission_READ = 1 << 0, + memoryPermission_WRITE = 1 << 1, + memoryPermission_EXECUTE = 1 << 2, + memoryPermission_PRIVATE = 1 << 3, + memoryPermission_SHARED = 1 << 4, +}; + +struct memoryPage { + int permissions = memoryPermission_NONE; + memAddr begin = 0; + memAddr end = 0; +}; + +namespace alma { + // Read bytes at a specific address. + // Returns values residing at the specified address. + extern std::vector memRead(const memAddr address, const unsigned int count); + + // Write bytes to a specific address. + extern void memWrite(const memAddr address, const std::vector values); + + // Get memory pages + // Can return an empty vector. + extern std::vector getMemoryPages(const int targetPerms, const int excludedPerms); + + // Sets up "memPath" and "mapsPath" + // Returns true if pid's mem file is accessible. + extern bool openProcess(const unsigned int pid); + + // Scan between a given range for a specific byte pattern. + // Use 0x420 for wildcards. + // If no matches were found, {0xBAD} will be returned. + template + std::vector patternScan(const memAddr minLimit, const memAddr maxLimit, std::vector pattern, const unsigned int alignment = 4, const unsigned int maxOcurrences = 0) { + std::vector retVec{0xBAD}; + int chunkSize = 0x100000; // Optimal chunk size. Not much difference when increased / decreased away from this. + + const int patternSize = pattern.size(); // Use a variable instead of calling the function. + for (memAddr currentAddress = minLimit; currentAddress < maxLimit; currentAddress += chunkSize) { + + // Prevent going out of maxLimit bounds. + if (currentAddress + chunkSize >= maxLimit) + chunkSize = maxLimit - currentAddress; + + const std::vector chars = memRead(currentAddress, chunkSize); + + for (int x = 0; x < chunkSize - patternSize; x += alignment) // Chunk reading loop + for (int y = 0; y < patternSize; y++) { // Pattern matching loop + // Skip wildcards + if (pattern[y] == 0x420) + continue; + + if (chars[x + y] != pattern[y]) + break; + + // Successful match + if (y == patternSize - 1) { + if (retVec[0] == 0xBAD) // Replace error element + retVec[0] = currentAddress + x; + + else + retVec.push_back(currentAddress + x); + + if (retVec.size() == maxOcurrences) + return retVec; + } + } + } + return retVec; + } + // Scan all pages that have a permission included in targetPerms. + // Ignores all pages that have a permission included in excludedPerms. + // Use 0x420 for wildcards. + // If no matches were found, {0xBAD} will be returned. + template + std::vector patternScanPerms(const int targetPerms, const int excludedPerms, std::vector pattern, const unsigned int alignment = 4, const unsigned int maxOcurrences = 0) { + std::vector retVec{0xBAD}; + for (const memoryPage page : getMemoryPages(targetPerms, excludedPerms)) { + const auto result = patternScan(page.begin, page.end, pattern, alignment, maxOcurrences); + + if (result[0] == 0xBAD) + continue; + + if (retVec[0] == 0xBAD) + retVec = result; // Replace error element. + else + retVec.insert(retVec.end(), result.begin(), result.end()); + } + return retVec; + } + + // Get PID of currently open process. + // Returns 0 if none is open. + extern unsigned int getCurrentlyOpenPID(); + + // Use this only if you know what you're doing. + template + varType hexToVar(std::vector bytes) { + varType rvalue; + + for (int i = 0; i < bytes.size(); i++) + ((char*)&rvalue)[i] = bytes[i]; + + return rvalue; + }; + + // Use this only if you know what you're doing. + template + std::vector varToHex(varType &var, unsigned int varMemSize) { + std::vector rvalue(varMemSize); + + for (int i = 0; i < varMemSize; i++) + rvalue[i] = ((char*)&var)[i]; + + return rvalue; + }; +} +#endif \ No newline at end of file diff --git a/example.cpp b/example.cpp new file mode 100644 index 0000000..dc1e34a --- /dev/null +++ b/example.cpp @@ -0,0 +1,323 @@ +#include "alma.hpp" +#include "rvmt/rvmt.hpp" +#include +#include +#include + +int main() { + RVMT::Start(); + bool quit = false; + + enum GUIPage { + GUIPage_LANDING = 0, + GUIPage_SCANNINGRESULTS = 1, + GUIPage_SCANNINGSETTINGS = 2, + GUIPage_MEMORYREAD = 3, + GUIPage_MEMORYWRITE = 4 + }; + + GUIPage GUIPage_CURRENT = GUIPage_LANDING; + + bool memInspectionRequested = false; + + std::thread autoUpdateInspection([&quit, &memInspectionRequested, &GUIPage_CURRENT](){ + while (!quit) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (GUIPage_CURRENT == GUIPage_MEMORYREAD) { + RVMT::renderRequests.push_back(1); + memInspectionRequested = true; + } + } + }); + + while (!quit) { + + while (RVMT::renderRequests.size() == 0) + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + + // Consume render request. + RVMT::renderRequests.erase(RVMT::renderRequests.begin()); + + const unsigned short rowCount = RVMT::internal::rowCount; + const unsigned short colCount = RVMT::internal::colCount; + + RVMT::SetCursorX(NewCursorPos_ABSOLUTE, 1); + RVMT::SetCursorY(NewCursorPos_ABSOLUTE, 1); + + switch (GUIPage_CURRENT) { + // These variables are shared between GUI Pages. + static int includedPermissions = + memoryPermission_READ | + memoryPermission_WRITE | + memoryPermission_PRIVATE | + memoryPermission_SHARED; + + static int excludedPermissions = memoryPermission_EXECUTE; + + static char scanAlignmentInput[5] = "4"; + static char maxOccurrencesInput[5] = "0"; + static char memoryReadAddrInput[33]; + + case GUIPage_LANDING: + RVMT::Text("Waiting for a process to be opened"); + break; + + case GUIPage_SCANNINGRESULTS: + static char patternInput[129]; + static std::vector scanResults; + + RVMT::Text("Pattern scanning "); + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::PushPropertyForNextItem(WidgetProp_InputText_IdleText, "Input (Use ?? for wildcards)"); + RVMT::InputText("scanPatternInput field", patternInput, 128, 32); + + RVMT::SameLine(); + if (RVMT::Button("Search")) { + + std::vector pattern; + std::istringstream iss(patternInput); + std::string parsedByte; + + while (std::getline(iss, parsedByte, ' ')) + pattern.push_back ( + parsedByte.find('?') == std::string::npos + ? std::stoi(parsedByte, nullptr, 16) + : 0x420 + ); + + scanResults.clear(); + const int targetOccurrences = std::stoi(maxOccurrencesInput); + + for (auto &page : alma::getMemoryPages(includedPermissions, excludedPermissions)) { + const unsigned int maxOccurrences = targetOccurrences == 0 ? 0 : targetOccurrences - scanResults.size(); + const auto result = alma::patternScan(page.begin, page.end, pattern, std::stoi(scanAlignmentInput), maxOccurrences); + + if (result[0] != 0xBAD) { + if (targetOccurrences == 0 || scanResults.size() + result.size() <= targetOccurrences) + scanResults.insert(scanResults.end(), result.begin(), result.end()); + + else { // Target occurrences have been reached. + scanResults.insert(scanResults.end(), result.begin(), result.begin() + scanResults.size() - targetOccurrences); + break; + } + } + } + } + + if (scanResults.size() > 0) { + RVMT::SetCursorY(NewCursorPos_ADD, 1); + RVMT::Text("Click an address to check its memory"); + + unsigned int addressColumns = 1; + for (int i = 0; i < scanResults.size(); i++) { + RVMT::PushPropertyForNextItem(WidgetProp_Button_TextOnly); + + if (RVMT::Button("%llX", scanResults[i])) { + snprintf(memoryReadAddrInput, 32, "%llX", scanResults[i]); + memInspectionRequested = true; + GUIPage_CURRENT = GUIPage_MEMORYREAD; + } + + if (i % (rowCount - 7) == rowCount - 8) { + RVMT::SetCursorX(NewCursorPos_ADD, 13); + RVMT::SetCursorY(NewCursorPos_ABSOLUTE, 4); + } + } + } + + RVMT::SetCursorX(NewCursorPos_ABSOLUTE, colCount - 13); + RVMT::SetCursorY(NewCursorPos_ABSOLUTE, 0); + if (RVMT::Button(" Settings ")) + GUIPage_CURRENT = GUIPage_SCANNINGSETTINGS; + + break; + + case GUIPage_SCANNINGSETTINGS: + // These bools just change the appearance of the checkboxes. + static bool includePermission[4] {1,0,1,1}; + + RVMT::Text("Alignment: "); + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::InputText("alignment input", scanAlignmentInput, 4, 4); + + RVMT::SetCursorY(NewCursorPos_ADD, 2); + RVMT::Text("Max ocurrences: "); + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::InputText("maxOcurrencesInput field", maxOccurrencesInput, 4, 4); + + RVMT::SetCursorY(NewCursorPos_ADD, 1); + RVMT::Text("Permission filtering: "); + + RVMT::Text("memoryPermission_WRITE "); + RVMT::SameLine(); + if (RVMT::Checkbox("[Include]", "[Exclude]", &includePermission[0])) + includedPermissions ^= memoryPermission_WRITE, + excludedPermissions ^= memoryPermission_WRITE; + + RVMT::Text("memoryPermission_EXECUTE "); + RVMT::SameLine(); + if (RVMT::Checkbox("[Include]", "[Exclude]", &includePermission[1])) + includedPermissions ^= memoryPermission_EXECUTE, + excludedPermissions ^= memoryPermission_EXECUTE; + + RVMT::Text("memoryPermission_PRIVATE "); + RVMT::SameLine(); + if (RVMT::Checkbox("[Include]", "[Exclude]", &includePermission[2])) + includedPermissions ^= memoryPermission_PRIVATE, + excludedPermissions ^= memoryPermission_PRIVATE; + + RVMT::Text("memoryPermission_SHARED "); + RVMT::SameLine(); + if (RVMT::Checkbox("[Include]", "[Exclude]", &includePermission[3])) + includedPermissions ^= memoryPermission_SHARED, + excludedPermissions ^= memoryPermission_SHARED; + + RVMT::SetCursorX(NewCursorPos_ABSOLUTE, colCount - 12); + RVMT::SetCursorY(NewCursorPos_ABSOLUTE, 0); + if (RVMT::Button(" Results ")) + GUIPage_CURRENT = GUIPage_SCANNINGRESULTS; + + break; + + case GUIPage_MEMORYREAD: + static char readCountInput[6] = "512"; + static char addrPerRowInput[6] = "16"; + static unsigned int addressesPerRow = 16; + + static std::vector readingResults; + + RVMT::PushPropertyForNextItem(WidgetProp_InputText_CustomCharset, "abcdefABCDEF1234567890"); + RVMT::InputText("memoryReadAddrInput field ", memoryReadAddrInput, 32, 16); + + RVMT::SameLine(); + + if (RVMT::Button("Inspect") || memInspectionRequested && memoryReadAddrInput[0] != 0 && readCountInput[0] != 0) { + readingResults = alma::memRead(std::strtoull(memoryReadAddrInput, 0, 16), std::stoi(readCountInput)); + addressesPerRow = std::stoi(addrPerRowInput); + memInspectionRequested = false; + } + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_ADD, 1); + RVMT::Text(" Addresses to scan: "); + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::PushPropertyForNextItem(WidgetProp_InputText_CustomCharset, "abcdefABCDEF1234567890"); + RVMT::InputText("readCountInput field", readCountInput, 5, 5); + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_ADD, 1); + RVMT::Text(" Addresses per row: "); + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::PushPropertyForNextItem(WidgetProp_InputText_CustomCharset, "1234567890"); + RVMT::InputText("addrPerRowInput field", addrPerRowInput, 5, 5); + + RVMT::SameLine(); + if (RVMT::Button("+128")) { + snprintf(memoryReadAddrInput, 32, "%llX", std::strtoull(memoryReadAddrInput, 0, 16) + 128); + memInspectionRequested = true; + } + + RVMT::SameLine(); + if (RVMT::Button("+256")) { + snprintf(memoryReadAddrInput, 32, "%llX", std::strtoull(memoryReadAddrInput, 0, 16) + 256); + memInspectionRequested = true; + } + + for (int i = 0; i < readingResults.size(); i++) { + + if (i % addressesPerRow == 0) { + if (i / addressesPerRow == rowCount - 7) + break; + RVMT::Text("+%04lX | ", i); + RVMT::SameLine(); + } + + RVMT::Text("%02X ", readingResults[i]); + + if (i % addressesPerRow != addressesPerRow - 1) + RVMT::SameLine(); + } + break; + + case GUIPage_MEMORYWRITE: + static char addressInput[33] = "\0"; + static char pattternInput[129] = "\0"; + + RVMT::Text("Address to write to: "); + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::InputText("memory write address input", addressInput, 32, 16); + + RVMT::SetCursorY(NewCursorPos_ADD, 2); + RVMT::Text("Bytes to write: "); + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::InputText("memory write pattern input", pattternInput, 128, 32); + + RVMT::SetCursorY(NewCursorPos_ADD, 2); + if (RVMT::Button("Write")) { + std::vector pattern; + std::istringstream iss(pattternInput); + std::string parsedByte; + + while (std::getline(iss, parsedByte, ' ')) + pattern.push_back(std::stoi(parsedByte, nullptr, 16)); + + alma::memWrite(std::strtoull(addressInput, 0, 16), pattern); + } + break; + default: + RVMT::Text("unknown gui page..."); + break; + } + + // === Bottom bar + static std::string pidInput(16, 0); + + RVMT::SetCursorX(NewCursorPos_ABSOLUTE, 1); + RVMT::SetCursorY(NewCursorPos_ABSOLUTE, rowCount - 2); + + RVMT::DrawBox(0, rowCount - 4, colCount - 2, 4); + RVMT::Text("PID: "); + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + RVMT::PushPropertyForNextItem(WidgetProp_InputText_CustomCharset, "1234567890"); + RVMT::InputText("pid input", &pidInput[0], 16, 6); + + RVMT::SameLine(); + if (RVMT::Button("Open")) + if (alma::openProcess(std::stoi(pidInput))) + GUIPage_CURRENT = GUIPage_SCANNINGRESULTS; + + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_ADD, 1); + RVMT::Text(" Current process' PID: %i ", alma::getCurrentlyOpenPID()); + + if (alma::getCurrentlyOpenPID() > 0) { + RVMT::SameLine(); RVMT::SetCursorY(NewCursorPos_SUBTRACT, 1); + if (RVMT::Button("Pattern scanning")) + GUIPage_CURRENT = GUIPage_SCANNINGRESULTS; + + RVMT::SameLine(); + if (RVMT::Button("Memory reading")) + GUIPage_CURRENT = GUIPage_MEMORYREAD; + + RVMT::SameLine(); + if (RVMT::Button("Memory writing")) + GUIPage_CURRENT = GUIPage_MEMORYWRITE; + } + + RVMT::SetCursorY(NewCursorPos_ABSOLUTE, rowCount - 3); + RVMT::SetCursorX(NewCursorPos_ABSOLUTE, colCount - 9); + + if (RVMT::Button(" Quit ")) + quit = true; + RVMT::Render(); + } + + autoUpdateInspection.join(); + std::wcout << "\nExited main loop."; std::wcout.flush(); + RVMT::Stop(); + + return 0; +} \ No newline at end of file diff --git a/rvmt/LICENSE b/rvmt/LICENSE new file mode 100644 index 0000000..8700405 --- /dev/null +++ b/rvmt/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Decryller (decryller@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rvmt/rvmt.cpp b/rvmt/rvmt.cpp new file mode 100644 index 0000000..f54cba6 --- /dev/null +++ b/rvmt/rvmt.cpp @@ -0,0 +1,865 @@ +#include "rvmt.hpp" + +#include +#include +#include +#include +#include +#include +#include + +bool strEquals(const char* first, const char* second) { + for (int i = 0; i < 4096; i++) { + if (first[i] == 0 && second[i] == 0) + return true; + + if (first[i] != second[i]) + return false; + } + return false; +} + +int strLength(const char* str) { + if (str[0] == 0) + return 0; + + for (int i = 0; i < 4096; i++) + if (str[i] == 0) + return i; + + return 4096; +} + +unsigned int strCount(const char* str, char ch) { + unsigned int rvalue = 0; + + for (int i = 0; i < 4096; i++) { + if (str[i] == 0) + break; + + if (str[i] == ch) + rvalue++; + } + return rvalue; +} + +bool strContains(const char* str, char ch) { + + for (int i = 0; i < 4096; i++) { + if (str[i] == 0) + return false; + + if (str[i] == ch) + return true; + } + return false; +} + +// Start extern variables. + std::vector RVMT::internal::drawList{0}; + std::vector RVMT::internal::canvas{L""}; + std::wostringstream RVMT::internal::preScreen{L""}; + + unsigned short RVMT::internal::rowCount = 0; + unsigned short RVMT::internal::colCount = 0; + + int RVMT::internal::NEWINPUT_MOUSEWINX = false; + int RVMT::internal::NEWINPUT_MOUSEWINY = false; + int RVMT::internal::NEWINPUT_MOUSEROW = false; + int RVMT::internal::NEWINPUT_MOUSECOL = false; + bool RVMT::internal::PREVINPUT_MOUSE1HELD = false; + bool RVMT::internal::NEWINPUT_MOUSE1HELD = false; + + int RVMT::internal::NEWINPUT_TERMX = false; + int RVMT::internal::NEWINPUT_TERMY = false; + unsigned int RVMT::internal::NEWINPUT_TERMWIDTH = false; + unsigned int RVMT::internal::PREVINPUT_TERMWIDTH = false; + unsigned int RVMT::internal::NEWINPUT_TERMHEIGHT = false; + unsigned int RVMT::internal::PREVINPUT_TERMHEIGHT = false; + + int RVMT::internal::NEWINPUT_CELLWIDTH = false; + + bool RVMT::internal::sameLineCalled = false; + bool RVMT::internal::sameLinedPreviousItem = false; + + int RVMT::internal::sameLineX = 0; + int RVMT::internal::sameLineXRevert = 0; + int RVMT::internal::sameLineY = 0; + int RVMT::internal::sameLineYRevert = 0; + + Display* RVMT::internal::rootDisplay = nullptr; + Window RVMT::internal::rootWindow = 0; + Window RVMT::internal::termX11Win = 0; + + RVMT::internal::ItemType_ RVMT::internal::activeItemType = ItemType_None; + const char* RVMT::internal::activeItemID = "none"; + + bool RVMT::internal::startCalled = false; + bool RVMT::internal::stopCalled = false; + + std::vector RVMT::internal::KEYPRESSES; + + int RVMT::internal::cursorX = 0; + int RVMT::internal::cursorY = 0; + + std::vector RVMT::renderRequests{1}; // Start with a single render request + + int BoxStyle_Current = BoxStyle_Round; + +using namespace RVMT; +using namespace RVMT::internal; + +struct CustomProperty { + WidgetProp property = WidgetProp_NULL_RVMT_WIDGET_PROPERTY; + int intVal = 0; + const char* strVal = "none"; +}; + +CustomProperty _DefaultCustomProperty; + +std::vector pushedProperties; + +int _NULLINT = 0; +unsigned int _NULLUINT = 0; +Window _NULLX11WINDOW = 0; + +bool manuallyModdedCurrentCursor = false; + +void requestNewRender() { + renderRequests.push_back(1); +}; + +void resetActiveItem() { + activeItemType = ItemType_None; + activeItemID = "none"; +} + +void preWidgetDrawCursorHandling() { + if (sameLineCalled) + cursorX = sameLineX, + cursorY = sameLineY, + sameLineCalled = false, + sameLinedPreviousItem = true; + + else if (!sameLineCalled && sameLinedPreviousItem) + cursorX = sameLineXRevert, + cursorY = sameLineYRevert, + sameLinedPreviousItem = false; +} + +// !=== Widgets ===! +// === RVMT::Text +// === RVMT::Checkbox +// === RVMT::Button +// === RVMT::Slider +// === RVMT::InputText + +void RVMT::Text(const char* val, ...) { + preWidgetDrawCursorHandling(); + + char buffer[1024]; + + va_list args; + va_start(args, val); + const int textLength = vsnprintf(buffer, 1024, val, args); + va_end(args); + + DrawText(cursorX, cursorY, buffer); + + sameLineX = cursorX + textLength; + sameLineY = cursorY; + + cursorY += 1 + strCount(val, 10); // Count newlines +} + +bool RVMT::Checkbox(const char* trueText, const char* falseText, bool* val) { + preWidgetDrawCursorHandling(); + + const int startX = cursorX; + const int startY = cursorY; + + // Print text + const char* ptr = *val ? &trueText[0] : &falseText[0]; + unsigned short textWidth = 0; + for (unsigned short i = 0; i < 32767; i++) { + if (ptr[i] == 0) { + textWidth = i; + break; + } + drawList.push_back({cursorX++, cursorY, ptr[i]}); + } + + // Handle cursor and SameLine. + sameLineX = cursorX; + sameLineY = cursorY; + + cursorX = startX; + cursorY++; + + // Handle return value + if (!PREVINPUT_MOUSE1HELD && NEWINPUT_MOUSE1HELD && + NEWINPUT_MOUSECOL >= startX && NEWINPUT_MOUSECOL < startX + textWidth && + NEWINPUT_MOUSEROW == startY) { + + resetActiveItem(); + *val = !*val; + activeItemType = ItemType_Checkbox; + return true; + } + + return false; +} + +bool RVMT::Button(const char* str, ...) { + + bool drawTextOnly = false; + for (auto &prop : pushedProperties) + if (prop.property == WidgetProp_Button_TextOnly) + drawTextOnly = true; + + preWidgetDrawCursorHandling(); + + // Parse argument list + char buffer[1024]; + va_list args; + va_start(args, str); + const auto textLength = vsnprintf(buffer, sizeof(buffer), str, args); + va_end(args); + + const int startX = cursorX; + const int startY = cursorY; + const unsigned int buttonHeight = drawTextOnly ? 1 : 3; + const unsigned int buttonWidth = drawTextOnly ? textLength : textLength + 2; + + if (!drawTextOnly) { + DrawBox(cursorX, cursorY, textLength, 1); + + // Prepare cursor for text to be drawn at the box's middle + cursorX++; + cursorY++; + } + + DrawText(cursorX, cursorY, buffer); + + cursorX = startX; + cursorY = startY + buttonHeight; + + sameLineX = startX + buttonWidth; + sameLineY = startY; + + pushedProperties.clear(); + // Handle return value + if (!PREVINPUT_MOUSE1HELD && NEWINPUT_MOUSE1HELD && + NEWINPUT_MOUSECOL >= startX && NEWINPUT_MOUSECOL <= startX + buttonWidth && + NEWINPUT_MOUSEROW >= startY && NEWINPUT_MOUSEROW < startY + buttonHeight) { + + resetActiveItem(); + activeItemType = ItemType_Button; + return true; + } + return false; +} + +bool RVMT::Slider(const char* sliderID, int length, float minVal, float maxVal, float* var) { + + if (length < 1) + length = 1; + + preWidgetDrawCursorHandling(); + + + const int x = cursorX; + const int y = cursorY; + + const int startXPX = (x + 1) * NEWINPUT_CELLWIDTH; + const int endXPX = (x + length + 1) * NEWINPUT_CELLWIDTH; + + bool rvalue = false; // Idle. + + // Begin interaction + if (activeItemType != ItemType_Slider && + NEWINPUT_MOUSE1HELD && + NEWINPUT_MOUSEWINX > startXPX && NEWINPUT_MOUSEWINX < endXPX && + NEWINPUT_MOUSEROW == y) { + + resetActiveItem(); + rvalue = true; // Clicked + activeItemType = ItemType_Slider; + activeItemID = sliderID; + } + + // Continue interaction + if (activeItemType == ItemType_Slider && + strEquals(activeItemID, sliderID)) { + + if (NEWINPUT_MOUSEWINX >= endXPX) // Clamp to maxval + *var = maxVal; + + else if (NEWINPUT_MOUSEWINX <= startXPX) // Clamp to minval + *var = minVal; + + else + *var = minVal + (((maxVal - minVal) / (endXPX - startXPX)) * (NEWINPUT_MOUSEWINX - startXPX)); + } + + // Prepare slider output + std::string sliderStr(length, '-'); + + const float sliderTickVal = (maxVal - minVal) / length; + for (int i = 0; sliderTickVal * (i + 1) <= *var - minVal; i++) + sliderStr[i] = '='; + + // Push slider content + drawList.push_back({x, y, '['}); + + for (int i = 1; i <= length; i++) + drawList.push_back({x+i, y, sliderStr[i-1]}); + + drawList.push_back({x+length+1, y, ']'}); + + // Handle SameLine and cursor. + sameLineX = cursorX + length + 2; + sameLineY = cursorY; + + cursorX = x; + cursorY++; + + return rvalue; +} + +bool RVMT::InputText(const char* fieldID, char* val, unsigned int maxStrSize, int width) { + + const char* customCharset = nullptr; + const char* idlingText = "..."; + bool censorOutput = false; + + for (auto &prop : pushedProperties) + switch (prop.property) { + case WidgetProp_InputText_CustomCharset: + customCharset = prop.strVal; + break; + + case WidgetProp_InputText_IdleText: + idlingText = prop.strVal; + break; + + case WidgetProp_InputText_Censor: + censorOutput = true; + break; + + default: + break; + } + + preWidgetDrawCursorHandling(); + + const int startX = cursorX; + const int startY = cursorY; + bool rvalue = false; + + if (!PREVINPUT_MOUSE1HELD && NEWINPUT_MOUSE1HELD && + NEWINPUT_MOUSECOL >= startX && NEWINPUT_MOUSECOL <= startX + width + 1 && + NEWINPUT_MOUSEROW >= startY && NEWINPUT_MOUSEROW < startY + 3) { + + resetActiveItem(); + rvalue = true; // clicked + activeItemType = ItemType_InputText; + activeItemID = fieldID; + } + + const bool thisFieldIsActive = ( + activeItemType == ItemType_InputText && + strEquals(activeItemID, fieldID) + ); + + int inputLength = strLength(val); + + if (thisFieldIsActive) + for (auto& keypress : KEYPRESSES) + if (strEquals(keypress.field, activeItemID)) { + const char KEY = keypress.key; + + KEYPRESSES.erase(KEYPRESSES.begin()); + + // Escape / BEL + if (KEY == 27 || KEY == 7) { + activeItemID = "none"; + activeItemType = ItemType_None; + break; + } + + // Delete / Backspace + if (KEY == 127 || KEY == 8) { + if (inputLength == 0) // Empty field + continue; + + val[inputLength - 1] = 0; + } + + if (customCharset != nullptr && + !strContains(customCharset, KEY)) + continue; + + + else if (inputLength < maxStrSize) { + val[inputLength] = + censorOutput + ? '*' + : KEY; + val[++inputLength] = 0; + } + } + + DrawBox(cursorX, cursorY, width, 1); + cursorX++; + cursorY++; + + // Unfocused and no text written. + if (!thisFieldIsActive && + inputLength == 0) + DrawText(cursorX, cursorY, idlingText); + + else if (inputLength > width) + DrawText(cursorX, cursorY, &val[inputLength - width]); + + else + DrawText(cursorX, cursorY, val); + + // Handle cursor and SameLine + sameLineX = cursorX + width + 1; + sameLineY = cursorY - 1; + + cursorY += 2; + cursorX--; + + pushedProperties.clear(); + return 0; +} + +// !=== Drawing ===! +// === RVMT::DrawText +// === RVMT::DrawBox +// === RVMT::DrawHSeparator +// === RVMT::DrawVSeparator +// === RVMT::SetCursorX +// === RVMT::SetCursorY +// === RVMT::SameLine + +void RVMT::DrawText(int x, int y, const char* val) { + const int startX = x; + + for (int z = 0; z < 1024; z++) { + if (val[z] == 0) { + break; + } + + if (val[z] == 10) { // Newline + x = startX; + y++; + continue; + } + + drawList.push_back({x++, y, val[z]}); + } +} + +void RVMT::DrawBox(int x, int y, int width, int height) { + // Note that this function does not change the cursor. + // Rather doing this than doing "+1" every time these are called. + height++; + width++; + + // Set box style according to BoxStyle_Current + short TLC, TRC, vBorders; + short BLC, BRC; + short hBorders; + + switch (BoxStyle_Current) { + case BoxStyle_Simple: + TLC = 9484, TRC = 9488, vBorders = 9474, + BLC = 9492, BRC = 9496, + hBorders = 9472; + break; + + case BoxStyle_Bold: + TLC = 9487, TRC = 9491, vBorders = 9475, + BLC = 9495, BRC = 9499, + hBorders = 9473; + break; + + case BoxStyle_DoubleLine: + TLC = 9556, TRC = 9559, vBorders = 9553, + BLC = 9562, BRC = 9565, + hBorders = 9552; + break; + + case BoxStyle_Round: + TLC = 9581, TRC = 9582, vBorders = 9474, + BLC = 9584, BRC = 9583, + hBorders = 9472; + break; + } + + // Push corners + drawList.push_back({x, y, TLC}); + drawList.push_back({x + width, y, TRC}); + + drawList.push_back({x, y + height, BLC}); + drawList.push_back({x + width, y + height, BRC}); + + // Push borders + for (unsigned short i = 1; i < width ; i++) // Top + drawList.push_back({x + i, y, hBorders}); + + for (unsigned short i = 1; i < width; i++) // Bottom + drawList.push_back({x + i, y + height, hBorders}); + + for (unsigned short i = 1; i < height; i++) // Left + drawList.push_back({x, y + i, vBorders}); + + for (unsigned short i = 1; i < height; i++) // Right + drawList.push_back({x + width, y + i, vBorders}); + +} + +void RVMT::DrawHSeparator(int x, int y, int width) { + wchar_t leftLimit, middle, rightLimit; + + switch (BoxStyle_Current) { + case BoxStyle_Simple: + leftLimit = 9500, middle = 9472, rightLimit = 9508; + break; + + case BoxStyle_Bold: + leftLimit = 9507, middle = 9473, rightLimit = 9515; + break; + + case BoxStyle_DoubleLine: + leftLimit = 9568, middle = 9552, rightLimit = 9571; + break; + + case BoxStyle_Round: // Same as simple + leftLimit = 9500, middle = 9472, rightLimit = 9508; + break; + } + + drawList.push_back({x, y, leftLimit}); + + for (int i = 1; i <= width; i++) + drawList.push_back({x+i, y, middle}); + + drawList.push_back({x + width + 1, y, rightLimit}); + +} + +void RVMT::DrawVSeparator(int x, int y, int height) { + wchar_t topLimit, middle, bottomLimit; + + switch (BoxStyle_Current) { + case BoxStyle_Simple: + topLimit = 9516, middle = 9474, bottomLimit = 9524; + break; + + case BoxStyle_Bold: + topLimit = 9523, middle = 9475, bottomLimit = 9531; + break; + + case BoxStyle_DoubleLine: + topLimit = 9574, middle = 9553, bottomLimit = 9577; + break; + + case BoxStyle_Round: // Same as simple + topLimit = 9516, middle = 9474, bottomLimit = 9524; + break; + } + + drawList.push_back({x, y, topLimit}); + + for (int i = 1; i <= height; i++) + drawList.push_back({x, y + i, middle}); + + drawList.push_back({x, y + height + 1, bottomLimit}); +} + +void RVMT::SetCursorX(NewCursorPos mode, int value) { + InternalSetCursor('x', mode, value); +} + +void RVMT::SetCursorY(NewCursorPos mode, int value) { + InternalSetCursor('y', mode, value); +} + +void RVMT::SameLine() { + sameLineCalled = true; + + if (!sameLinedPreviousItem) + sameLineXRevert = cursorX, + sameLineYRevert = cursorY; + + cursorX = sameLineX; + cursorY = sameLineY; +} + +void RVMT::PushPropertyForNextItem(WidgetProp property, int value) { + CustomProperty newProp; + + newProp.property = property; + newProp.intVal = value; + + pushedProperties.push_back(newProp); +}; + +void RVMT::PushPropertyForNextItem(WidgetProp property, const char* value) { + CustomProperty newProp; + + newProp.property = property; + newProp.strVal = value; + + pushedProperties.push_back(newProp); +}; + +void RVMT::PushPropertyForNextItem(WidgetProp property) { + CustomProperty newProp; + + newProp.property = property; + + pushedProperties.push_back(newProp); +}; + + +// !=== Internal ===! +// === Input threads +// === RVMT::internal::InternalSetCursor +// === RVMT::Render +// === RVMT::Start +// === RVMT::Stop + +struct termios _termios; +std::thread mouseInputThread; +std::thread kbInputsThread; + +void mouseInputThreadFunc() { + + while (!stopCalled) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // In some cases, such as in i3-wm: + // XGetInputFocus somehow does not return the same as XQueryPointer. + // XGetInputFocus returns the "child" + // XQueryPointer returns the "parent" + + Window currentWindow; + XQueryPointer(rootDisplay, rootWindow, &_NULLX11WINDOW, ¤tWindow, + &_NULLINT, &_NULLINT, &_NULLINT, &_NULLINT, &_NULLUINT); + + if (currentWindow != termX11Win && + activeItemType != ItemType_Slider) + continue; + + // === Get borders thickness. + // There are two ways of getting it. + // 1) Using the XGetGeometry function that literally retrieves the borders' size. + unsigned int topBorder; + unsigned int leftBorder; + + PREVINPUT_TERMWIDTH = NEWINPUT_TERMWIDTH; + PREVINPUT_TERMHEIGHT = NEWINPUT_TERMHEIGHT; + + XGetGeometry(rootDisplay, termX11Win, &_NULLX11WINDOW, &NEWINPUT_TERMX, &NEWINPUT_TERMY, &NEWINPUT_TERMWIDTH, &NEWINPUT_TERMHEIGHT, &leftBorder, &topBorder); + + // 2) Getting the "inner window's" x and y. Used only if the first one doesn't work. + if (topBorder == 0 && leftBorder == 0) { + unsigned long innerWindow; + XGetInputFocus(rootDisplay, &innerWindow, &_NULLINT); + + XWindowAttributes windowAttributes; + XGetWindowAttributes(rootDisplay, innerWindow, &windowAttributes); + + if (windowAttributes.y >= 0) + topBorder = windowAttributes.y; + + if (windowAttributes.x >= 0) + leftBorder = windowAttributes.x; + } + + NEWINPUT_TERMWIDTH -= (leftBorder*2); + NEWINPUT_TERMHEIGHT -= topBorder; + + // === Get mouse values. + unsigned int mouseMask; + int mouseXPos; + int mouseYPos; + XQueryPointer(rootDisplay, rootWindow, &_NULLX11WINDOW, &_NULLX11WINDOW, + &_NULLINT, &_NULLINT, &mouseXPos, &mouseYPos, &mouseMask); + + NEWINPUT_MOUSEWINX = mouseXPos - NEWINPUT_TERMX - leftBorder; + NEWINPUT_MOUSEWINY = mouseYPos - NEWINPUT_TERMY - topBorder; + + PREVINPUT_MOUSE1HELD = NEWINPUT_MOUSE1HELD; + NEWINPUT_MOUSE1HELD = mouseMask & Button1Mask; + + // "> 0"'s here to prevent floating point exceptions. + if (colCount > 0 && rowCount > 0) { + NEWINPUT_CELLWIDTH = ((float)NEWINPUT_TERMWIDTH / (float)colCount); + + if (NEWINPUT_TERMWIDTH > 0) + NEWINPUT_MOUSECOL = std::floor((float)NEWINPUT_MOUSEWINX / ((float)NEWINPUT_TERMWIDTH / (float)colCount)); + + if (NEWINPUT_TERMHEIGHT > 0) + NEWINPUT_MOUSEROW = std::floor((float)NEWINPUT_MOUSEWINY / ((float)NEWINPUT_TERMHEIGHT / (float)rowCount)); + } + + if (NEWINPUT_MOUSE1HELD || + (PREVINPUT_MOUSE1HELD && !NEWINPUT_MOUSE1HELD) || + PREVINPUT_TERMWIDTH != NEWINPUT_TERMWIDTH || + PREVINPUT_TERMHEIGHT != NEWINPUT_TERMHEIGHT) + renderRequests.push_back(1); + } +} + +void kbInputsThreadFunc() { + while (!stopCalled) { + const char LATEST_KEYPRESS = std::cin.get(); + + if (activeItemType == ItemType_InputText) { + KEYPRESSES.push_back({ + LATEST_KEYPRESS, + activeItemID + }); + renderRequests.push_back(1); + } + } +} + +void RVMT::internal::InternalSetCursor(char axis, NewCursorPos mode, int value) { + int *cursor; + + if (axis == 'x') { + if (sameLineCalled) + cursor = &sameLineX; + + else if (!sameLineCalled && sameLinedPreviousItem) + cursor = &sameLineXRevert; + + else + cursor = &cursorX; + } + + if (axis == 'y') { + if (sameLineCalled) + cursor = &sameLineY; + + else if (!sameLineCalled && sameLinedPreviousItem) + cursor = &sameLineYRevert; + + else + cursor = &cursorY; + } + + switch (mode) { + case NewCursorPos_ADD: + *cursor += value; + break; + + case NewCursorPos_SUBTRACT: + *cursor -= value; + break; + + case NewCursorPos_ABSOLUTE: + *cursor = value; + break; + } +} + +void RVMT::Render() { + + // Reset active item if idling. + if (!NEWINPUT_MOUSE1HELD && + activeItemType != ItemType_None && + activeItemType != ItemType_InputText) { + + activeItemType = ItemType_None; + activeItemID = "none"; + renderRequests.push_back(1); + } + + struct winsize terminalSize; + ioctl(1, TIOCGWINSZ, &terminalSize); + + rowCount = terminalSize.ws_row; + colCount = terminalSize.ws_col; + + // Populate canvas. + for (int y = 0; y < rowCount; y++) + canvas.push_back(std::wstring(colCount, ' ')); + + // Push drawlist's elements into the canvas. + for (auto &elem : drawList) { + if (elem.y >= canvas.size()) + continue; + + if (elem.x >= canvas[elem.y].length()) + continue; + canvas[elem.y][elem.x] = elem.ch; + } + drawList.clear(); + + // Store into prescreen to print everything at once. + for (int i = 0; i < canvas.size(); i++) + preScreen << canvas[i]; + canvas.clear(); + + // Clear screen + std::wcout << "\033[H\033[J"; + + std::wcout << preScreen.str(); std::wcout.flush(); + preScreen.str(L""); + + cursorX = 0; + cursorY = 0; +} + +void RVMT::Start() { + // Set locale to print unicodes correctly. + std::locale::global(std::locale("")); + + // Clear non-initialized vars from any possible trash data + drawList.clear(); + canvas.clear(); + preScreen.str(L""); + + rootDisplay = XOpenDisplay(NULL); + rootWindow = DefaultRootWindow(rootDisplay); + + XQueryPointer( + rootDisplay, rootWindow, &_NULLX11WINDOW, &termX11Win, + &_NULLINT, &_NULLINT, &_NULLINT, &_NULLINT, &_NULLUINT); + + + tcgetattr(0, &_termios); + // Turn off canonical mode and input echoing for keyboard inputs. + _termios.c_lflag = _termios.c_lflag ^ ICANON; + _termios.c_lflag = _termios.c_lflag ^ ECHO; + tcsetattr(0, 0, &_termios); + + // Start input threads. + mouseInputThread = std::thread(&mouseInputThreadFunc); + kbInputsThread = std::thread(&kbInputsThreadFunc); + + startCalled = true; +} + +void RVMT::Stop() { + stopCalled = true; + + // Wait for input threads to finish. + std::wcout << "\nPress any key to exit...\n"; + mouseInputThread.join(); + kbInputsThread.join(); + + // Restore terminal to the normal state. + _termios.c_lflag = _termios.c_lflag ^ ICANON; + _termios.c_lflag = _termios.c_lflag ^ ECHO; + tcsetattr(0, 0, &_termios); + + XCloseDisplay(rootDisplay); +} \ No newline at end of file diff --git a/rvmt/rvmt.hpp b/rvmt/rvmt.hpp new file mode 100644 index 0000000..53db18a --- /dev/null +++ b/rvmt/rvmt.hpp @@ -0,0 +1,205 @@ +#ifndef RVMT_HPP +#define RVMT_HPP + +#include +#include +#include + +enum BoxStyle { + BoxStyle_Simple, + BoxStyle_Bold, + BoxStyle_DoubleLine, + BoxStyle_Round +}; + +enum NewCursorPos { + NewCursorPos_ADD = 0, + NewCursorPos_SUBTRACT = 1, + NewCursorPos_ABSOLUTE = 2 +}; + +enum WidgetProp { + WidgetProp_NULL_RVMT_WIDGET_PROPERTY = 0, + WidgetProp_InputText_CustomCharset = 1, + WidgetProp_InputText_IdleText = 2, + WidgetProp_InputText_Censor = 3, + WidgetProp_Button_TextOnly = 4 +}; + +extern int BoxStyle_Current; + +namespace RVMT { + + // All of these variables could perfectly be inside the cpp, but I'd rather put them in an additional + // namespace to make debug easier. + namespace internal { + + enum ItemType_ { + ItemType_None = 0, + ItemType_Slider = 1, + ItemType_Button = 2, + ItemType_Checkbox = 3, + ItemType_InputText = 4 + }; + + struct drawableElement { + int x = 0; + int y = 0; + wchar_t ch = 0; + }; + + struct keyPress { + char key; + const char* field; + + keyPress operator=(keyPress from) const { + return {from.key, from.field}; + } + }; + + // I need to tidy this up. + extern std::vector drawList; // Vector of "drawableElement" struct. + extern std::vector canvas; // Map to which drawlist's content will go to + extern std::wostringstream preScreen; // Temporal buffer used to print the canvas all at once. + + extern unsigned short rowCount; // Terminal's row count (Y Axis). + extern unsigned short colCount; // Terminal's column count (X Axis). + + extern int NEWINPUT_MOUSEWINX; // Mouse X Position inside the window. + extern int NEWINPUT_MOUSEWINY; // Mouse Y Position inside the window. + + extern int NEWINPUT_MOUSEROW; // Row the mouse is at. + extern int NEWINPUT_MOUSECOL; // Column the mouse is at. + + extern bool PREVINPUT_MOUSE1HELD; // Past state of NEWINPUT_MOUSE1HELD. + extern bool NEWINPUT_MOUSE1HELD; // Is Mouse button 1 being pressed? + + extern int NEWINPUT_TERMX; // Terminal's window X Position. + extern int NEWINPUT_TERMY; // Terminal's window Y Position. + + extern unsigned int NEWINPUT_TERMWIDTH; // Terminal's window width. + extern unsigned int NEWINPUT_TERMHEIGHT; // Terminal's window height. + + extern unsigned int PREVINPUT_TERMWIDTH; // Terminal's previous window width. + extern unsigned int PREVINPUT_TERMHEIGHT; // Terminal's previous window height. + + extern int NEWINPUT_CELLWIDTH; // Terminal's Cell width. (Not perfectly accurate) + + extern Display* rootDisplay; // XOpenDisplay(NULL) | Cleared by Stop(). + extern Window rootWindow; // DefaultRootWindow(rootDisplay) + + extern Window termX11Win; // Terminal's X11 Window ID + + extern bool sameLineCalled; + extern bool sameLinedPreviousItem; + extern int sameLineX; + extern int sameLineY; + + extern int sameLineXRevert; + extern int sameLineYRevert; + + extern ItemType_ activeItemType; + extern const char* activeItemID; + + extern bool startCalled; + extern bool stopCalled; // Used to notify threads to exit their main loops. + + extern std::vector KEYPRESSES; + + extern int cursorX; + extern int cursorY; + + // Set cursor's position. + void InternalSetCursor(char axis, NewCursorPos mode, int value); + } + + extern std::vector renderRequests; + + // !=== Widgets ===! + // === Texts + // === Checkbox + // === Button + // === Slider + // === InputText + + // Prints text. + void Text(const char* val, ...); + + // Print a checkbox. + // When val is false, it will print falseText. + // When val is true, it will print trueText. + // Returns true if the text is clicked. + bool Checkbox(const char* trueText, const char* falseText, bool* val); + + // Print a button. + // Uses "BoxStyle_Current" for its style. + // Returns true if the box is clicked. + bool Button(const char* text, ...); + + // Print a draggable slider. + // Returns true if it's clicked + bool Slider(const char* sliderID, int length, float minVal, float maxVal, float* var); + + // Print a text input field. + // Returns true if it's clicked + bool InputText(const char* fieldID, char* val, unsigned int maxStrSize, int width); + + // !=== Drawing ===! + // === DrawText + // === DrawBox + // === DrawHSeparator + // === DrawVSeparator + // === SetCursorX + // === SetCursorY + // === SameLine + + // Draws text without modifying the cursor. + // Returns final text length + void DrawText(int x, int y, const char* val); + + // Draw a box. + // Does not modify the cursor. + // Gets style from BoxStyle_Current. + void DrawBox(int x, int y, int width, int height); + + // Draw a horizontal separator. + // Does not modify the cursor. + // Gets style from BoxStyle_Current. + void DrawHSeparator(int x, int y, int width); + + // Draw a vertical separator. + // Does not modify the cursor. + // Gets style from BoxStyle_Current. + void DrawVSeparator(int x, int y, int height); + + // Moves X Cursor. + void SetCursorX(NewCursorPos mode, int value); + + // Moves Y Cursor. + void SetCursorY(NewCursorPos mode, int value); + + // Move cursor to the previous element's right. + void SameLine(); + + // !=== Misc ===! + void PushPropertyForNextItem(WidgetProp property); + void PushPropertyForNextItem(WidgetProp property, int value); + void PushPropertyForNextItem(WidgetProp property, const char* value); + + // !=== Internal ===! + // === Render + // === Start + // === Stop + // === InternalSetCursor is located at internal namespace + + // Render drawlist's contents. + void Render(); + + // Start. + // RVMT will treat the window that is focused when this function gets called as the main window. + void Start(); + + // Stop. + void Stop(); +} +#endif \ No newline at end of file