Compare commits

..

6 Commits

Author SHA1 Message Date
Kittycannon aea76b9d3e Added serial support 2026-09-07 20:19:13 -06:00
Kittycannon 28f4c1ac94 Added process 2026-09-07 17:13:40 -06:00
Kittycannon 8f895f0757 added process 2026-09-07 15:24:52 -06:00
Kittycannon bdade6bc27 more work on desktoplib 2026-09-07 14:36:45 -06:00
Kittycannon 5d47f08354 Merge branch 'main' of git.sintekanalytics.com:Kittycannon/desktoplib 2026-08-17 21:14:47 -06:00
Kittycannon 47008a0139 process 2026-08-17 21:14:44 -06:00
17 changed files with 1318 additions and 6 deletions
+59
View File
@@ -0,0 +1,59 @@
# AGENTS.md
## Project
**desktoplib** — Cross-platform C++20 library for terminal UIs, process management, and serial (USB) communication. Namespace: `ckitty`.
## Build
```bash
make -f makelib.mak # Build static library (out/libdesktoplib.a)
make # Build test executable (out/out.exe)
make -f makelib.mak remake # Clean and rebuild library
```
Compiler: `g++` with `-std=c++20` (developed with MSYS2/UCRT64 on Windows). Test build defines `DESKTOPLIB_TEST` and links `src/desktoplib/main.cpp`.
## Conventions
- Headers use `#pragma once`
- Source lives under `src/desktoplib/<module>/`
- OS abstraction: `os/common.hpp` declares interface; `os/unix.cpp` and `os/windows.cpp` implement platform-specific behavior
- Terminal methods chain via `Terminal&` return
- ANSI escape sequences are encapsulated in `color`, `backg`, `style`, `pos`, `move`, `key`
- `Terminal` has friend access to `color`/`backg`/`style` private constructors
- UI elements inherit from `ckitty::terminal::UI` and implement `render(Terminal&)`
## Module Layout
```
src/desktoplib/
├── main.cpp # Test harness (compiled when DESKTOPLIB_TEST is defined)
├── os/
│ ├── common.hpp # Cross-platform terminal OS interface
│ ├── unix.cpp # POSIX implementations
│ └── windows.cpp # Windows implementations
├── terminal/
│ ├── Terminal.hpp/cpp # Core terminal output/input engine
│ ├── UI.hpp # Abstract UI base class
│ └── ui/
│ ├── Box.hpp/cpp # Bordered box widget
│ ├── InnerScroll.hpp/cpp # Scrollbar widget
│ ├── MenuMap.hpp # 2D navigation grid
│ └── Content.hpp # Virtual content renderer base
└── proc/
├── Process.hpp # Stub: planned process abstraction
└── Process.cpp
```
## Current State
- **Terminal**: functional (ANSI colors, RGB/HSL, cursor positioning, input, UI widgets)
- **Process**: stub only — `class Process {};`
- **Serial**: not yet started
## Notes
- Do not add comments unless explicitly requested
- Follow existing naming: module directories are lowercase, classes are PascalCase, methods/variables are camelCase
- When extending OS abstraction, update `common.hpp` first, then both `unix.cpp` and `windows.cpp`
+82 -3
View File
@@ -1,6 +1,85 @@
# desktoplib # desktoplib
A library that helps when building desktop apps by adding support for Terminals, Serial interfaces, and other general desktop-only things. [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
Designed as a standalone library, so its scope is intentionally limited. A lightweight, cross-platform C++20 library for building desktop applications that need uniform access to terminal UIs, OS processes, and serial (USB) interfaces.
ckittylib: Not required.
Designed as a standalone static library with an intentionally limited scope. No heavy dependencies — just standard C++20 and platform-specific OS calls.
## Features
- **Terminal UI** — ANSI escape-sequenced output with RGB/HSL colors, cursor positioning, alternative buffers, and input handling
- **UI Widgets** — Built-in `Box`, `InnerScroll`, `MenuMap`, and `Content` for composing terminal layouts
- **Process Management** — Planned uniform process spawn/stream abstraction (stub in place)
- **Serial Communication** — Planned cross-platform USB/serial port interface
- **OS Abstraction Layer** — Shared terminal/input/OS calls behind `common.hpp`, with POSIX and Windows implementations
## Requirements
- C++20 compatible compiler (`g++` recommended)
- Make (GNU Make)
- POSIX or Windows environment
## Building
```bash
# Build the static library
make -f makelib.mak
# Build and run the test executable
make
./out/out.exe
```
Produces `out/libdesktoplib.a` for linking into your own projects.
## Quick Start
```cpp
#include <desktoplib/terminal/Terminal.hpp>
int main() {
using namespace ckitty::terminal;
Terminal term;
term.altbuff(true)
.cursor(false)
.cls()
.fill(color::B_BLACK);
term << pos{ 1, 1 } << backg::BLUE << color::WHITE << style::BOLD;
term.center(40, " Hello, desktoplib ");
term << style::RESET;
term.flush();
term.wait(); // Block until a key is pressed
term.altbuff(false).cursor(true).cls();
return 0;
}
```
## Architecture
```
src/desktoplib/
├── os/ # Platform abstraction (POSIX / Windows)
├── terminal/ # Terminal engine, ANSI codes, input
│ └── ui/ # Widgets: Box, InnerScroll, MenuMap, Content
└── proc/ # Process management (stub)
```
All modules live in the `ckitty` namespace. The terminal layer uses ANSI escape sequences wrapped in typed structs (`color`, `backg`, `style`, `pos`, `move`) and supports method chaining on `Terminal`.
## Roadmap
| Module | Status | Target |
|--------|--------|--------|
| Terminal UI | Stable | — |
| OS Abstraction | Stable (POSIX + Windows) | — |
| Process | Stub | Spawn, stdin/stdout/stderr streams, exit code |
| Serial / USB | Not started | Cross-platform port enumeration + read/write |
## License
MIT — see [LICENSE](LICENSE).
+1 -1
View File
@@ -5,6 +5,6 @@
} }
], ],
"settings": { "settings": {
"terminal.integrated.defaultProfile.windows": "MSYS2 UCRT", "terminal.integrated.defaultProfile.windows": "MSYS2 UCRT"
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ CFLAGS := -std=c++20 -O2 $(CDEFS) \
-Wlogical-op -Wuseless-cast -Wlogical-op -Wuseless-cast
LFLAGS := -std=c++20 -static-libstdc++ -static-libgcc \ LFLAGS := -std=c++20 -static-libstdc++ -static-libgcc \
-Wl,--fatal-warnings -Wl,--warn-common -Wl,--fatal-warnings -Wl,--warn-common
LIB := LIB := -lsetupapi -ldxguid
INC := -I./src/ INC := -I./src/
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
+1
View File
@@ -25,6 +25,7 @@ CFLAGS := -std=c++20 -O2 \
-Wformat=2 -Wimplicit-fallthrough -Wsuggest-override \ -Wformat=2 -Wimplicit-fallthrough -Wsuggest-override \
-Wextra-semi -Wduplicated-cond -Wduplicated-branches \ -Wextra-semi -Wduplicated-cond -Wduplicated-branches \
-Wlogical-op -Wuseless-cast -Wlogical-op -Wuseless-cast
LIB := -lsetupapi -ldxguid
INC := -I./src/ INC := -I./src/
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
BOLD="\033[1m"
RESET="\033[0m"
SUCCESS="\033[38;2;80;220;100m"
FAIL="\033[38;2;255;90;90m"
INFO="\033[38;2;100;200;255m"
DIM="\033[2m"
LOG="./out/out.log"
# Start
printf "${DIM}────────────────────────────────────────${RESET}\n"
printf "${INFO}${BOLD}> Running...${RESET}\n"
printf "${DIM}────────────────────────────────────────${RESET}\n\n"
# Send both stdout and stderr to the console and the log file.
start=$(date +%s.%N)
./out/out "$@" 2>&1 | tee "$LOG"
status=${PIPESTATUS[0]}
end=$(date +%s.%N)
elapsed=$(awk "BEGIN { printf \"%.3f\", $end - $start }")
# Status!
printf "\n${DIM}────────────────────────────────────────${RESET}\n"
if (( status == 0 )); then
printf "${SUCCESS}${BOLD}✓ Success${RESET} (exit %d)\n" "$status"
else
printf "${FAIL}${BOLD}✗ Failed${RESET} (exit %d)\n" "$status"
fi
printf "${INFO}${BOLD}> Time${RESET} ${elapsed}s\n"
printf "${INFO}${BOLD}> Log${RESET} %s\n" "$LOG"
printf "${DIM}────────────────────────────────────────${RESET}"
exit "$status"
+132 -1
View File
@@ -2,12 +2,15 @@
#include <desktoplib/terminal/Terminal.hpp> #include <desktoplib/terminal/Terminal.hpp>
#include <desktoplib/terminal/ui/Box.hpp> #include <desktoplib/terminal/ui/Box.hpp>
#include <desktoplib/proc/Process.hpp>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <thread> #include <thread>
#include <chrono> #include <chrono>
int main() { /*
// Original Terminal test - commented out
int main_terminal() {
using namespace ckitty::terminal; using namespace ckitty::terminal;
Terminal term; Terminal term;
@@ -111,5 +114,133 @@ int main() {
return 0; return 0;
} }
*/
// Process test
int main() {
using namespace ckitty;
std::cout << "=== Process Test ===\n";
// Test 1: Simple command execution
{
std::cout << "\n--- Test 1: echo command ---\n";
Process proc;
#ifdef _WIN32
proc.setExec("cmd.exe");
proc.addParam("/c echo Hello from Process!");
#else
proc.setExec("echo");
proc.addParam("Hello from Process!");
#endif #endif
proc.run();
while (proc.isRunning()) {
std::string_view out = proc.nextOutput();
if (!out.empty()) {
std::cout << "Output: " << out;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "Final output: " << proc.getOutput() << "\n";
std::cout << "Exit code: " << proc.stopCode() << "\n";
}
// Test 2: Command with input
{
std::cout << "\n--- Test 2: cat with input ---\n";
Process proc;
#ifdef _WIN32
proc.setExec("cmd.exe");
proc.addParam("/c findstr /r \"^\"");
#else
proc.setExec("cat");
#endif
proc.run();
std::string testInput = "Line 1\nLine 2\nLine 3\n";
proc.addInput(testInput);
proc.closeInput(); // Close stdin so cat gets EOF
std::this_thread::sleep_for(std::chrono::milliseconds(100));
while (proc.isRunning()) {
std::string_view out = proc.nextOutput();
if (!out.empty()) {
std::cout << "Output: " << out;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "Final output: " << proc.getOutput() << "\n";
std::cout << "Exit code: " << proc.stopCode() << "\n";
}
// Test 3: Non-existent command (should fail)
{
std::cout << "\n--- Test 3: Non-existent command ---\n";
Process proc;
proc.setExec("this_command_does_not_exist_12345");
proc.run();
while (proc.isRunning()) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "Exit code: " << proc.stopCode() << "\n";
}
// Test 4: Long running process with timeout
{
std::cout << "\n--- Test 4: Sleep command ---\n";
Process proc;
#ifdef _WIN32
proc.setExec("cmd.exe");
proc.addParam("/c timeout /t 5");
#else
proc.setExec("sleep");
proc.addParam("5");
#endif
proc.run();
int polls = 0;
while (proc.isRunning() && polls < 10) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
polls++;
}
if (proc.isRunning()) {
std::cout << "Process still running, terminating...\n";
proc.terminate();
}
while (proc.isRunning()) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "failed(): " << (proc.failed() ? "true" : "false") << " (expected false)\n";
std::cout << "Exit code: " << proc.stopCode() << "\n";
}
// Test 5: Non-existent command
{
std::cout << "\n--- Test 5: Non-existent command ---\n";
Process proc;
proc.setExec("this_command_does_not_exist_12345");
proc.run();
std::cout << "failed(): " << (proc.failed() ? "true" : "false") << " (expected true)\n";
while (proc.isRunning()) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << "Exit code: " << proc.stopCode() << " (expected 127 on Unix)\n";
}
std::cout << "\n=== All Process Tests Complete ===\n";
return 0;
}
#endif
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
namespace ckitty {
namespace proc {
namespace os {
struct ProcessInfo;
ProcessInfo* createProcess(const std::string& exec, const std::vector<std::string>& args);
void destroyProcess(ProcessInfo* info);
bool isRunning(ProcessInfo* info);
int getExitCode(ProcessInfo* info);
void writeInput(ProcessInfo* info, std::string_view data);
void closeInput(ProcessInfo* info);
void terminate(ProcessInfo* info);
std::string readOutput(ProcessInfo* info);
std::string readError(ProcessInfo* info);
}
}
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include <cstdint>
namespace ckitty {
namespace serial {
namespace os {
struct SerialInfo;
std::vector<std::string> listPorts();
SerialInfo* openPort(const std::string& port, uint32_t baudRate);
void closePort(SerialInfo* info);
bool isOpen(SerialInfo* info);
size_t write(SerialInfo* info, const uint8_t* data, size_t size);
size_t read(SerialInfo* info, uint8_t* buffer, size_t maxSize);
void setDTR(SerialInfo* info, bool level);
void setRTS(SerialInfo* info, bool level);
bool getCTS(SerialInfo* info);
bool getDSR(SerialInfo* info);
bool getRI(SerialInfo* info);
bool getCD(SerialInfo* info);
}
}
}
+168
View File
@@ -0,0 +1,168 @@
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#include "common_proc.hpp"
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <vector>
#include <string>
#include <cstring>
namespace ckitty {
namespace proc {
namespace os {
struct ProcessInfo {
pid_t pid = -1;
int stdin_fd = -1;
int stdout_fd = -1;
int stderr_fd = -1;
bool running = false;
std::string output;
std::string error;
};
ProcessInfo* createProcess(const std::string& exec, const std::vector<std::string>& args) {
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
if (pipe(stdin_pipe) != 0) return nullptr;
if (pipe(stdout_pipe) != 0) { close(stdin_pipe[0]); close(stdin_pipe[1]); return nullptr; }
if (pipe(stderr_pipe) != 0) { close(stdin_pipe[0]); close(stdin_pipe[1]); close(stdout_pipe[0]); close(stdout_pipe[1]); return nullptr; }
pid_t pid = fork();
if (pid == 0) {
close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(stderr_pipe[0]);
dup2(stdin_pipe[0], STDIN_FILENO);
dup2(stdout_pipe[1], STDOUT_FILENO);
dup2(stderr_pipe[1], STDERR_FILENO);
close(stdin_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[1]);
std::vector<char*> argv;
argv.push_back(const_cast<char*>(exec.c_str()));
for (const auto& arg : args) {
argv.push_back(const_cast<char*>(arg.c_str()));
}
argv.push_back(nullptr);
execvp(argv[0], argv.data());
_exit(127);
}
if (pid < 0) {
close(stdin_pipe[0]); close(stdin_pipe[1]);
close(stdout_pipe[0]); close(stdout_pipe[1]);
close(stderr_pipe[0]); close(stderr_pipe[1]);
return nullptr;
}
close(stdin_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[1]);
int flags = fcntl(stdout_pipe[0], F_GETFL, 0);
fcntl(stdout_pipe[0], F_SETFL, flags | O_NONBLOCK);
flags = fcntl(stderr_pipe[0], F_GETFL, 0);
fcntl(stderr_pipe[0], F_SETFL, flags | O_NONBLOCK);
auto* info = new ProcessInfo();
info->pid = pid;
info->stdin_fd = stdin_pipe[1];
info->stdout_fd = stdout_pipe[0];
info->stderr_fd = stderr_pipe[0];
info->running = true;
return info;
}
void destroyProcess(ProcessInfo* info) {
if (!info) return;
if (info->running) {
kill(info->pid, SIGKILL);
waitpid(info->pid, nullptr, 0);
}
if (info->stdin_fd != -1) close(info->stdin_fd);
if (info->stdout_fd != -1) close(info->stdout_fd);
if (info->stderr_fd != -1) close(info->stderr_fd);
delete info;
}
bool isRunning(ProcessInfo* info) {
if (!info || !info->running) return false;
int status = 0;
pid_t result = waitpid(info->pid, &status, WNOHANG);
if (result == info->pid) {
info->running = false;
char buf[4096];
ssize_t n;
while ((n = read(info->stdout_fd, buf, sizeof(buf))) > 0) {
info->output.append(buf, n);
}
while ((n = read(info->stderr_fd, buf, sizeof(buf))) > 0) {
info->error.append(buf, n);
}
return false;
}
return true;
}
int getExitCode(ProcessInfo* info) {
if (!info) return 0;
if (info->running) return 0;
int status = 0;
waitpid(info->pid, &status, WNOHANG);
if (WIFEXITED(status)) return WEXITSTATUS(status);
return 1;
}
void writeInput(ProcessInfo* info, std::string_view data) {
if (!info || !info->running) return;
write(info->stdin_fd, data.data(), data.size());
}
void closeInput(ProcessInfo* info) {
if (!info) return;
if (info->stdin_fd != -1) {
close(info->stdin_fd);
info->stdin_fd = -1;
}
}
void terminate(ProcessInfo* info) {
if (!info || !info->running) return;
kill(info->pid, SIGKILL);
info->running = false;
}
std::string readOutput(ProcessInfo* info) {
if (!info) return "";
char buf[4096];
ssize_t n = read(info->stdout_fd, buf, sizeof(buf));
if (n > 0) {
info->output.append(buf, n);
return info->output.substr(info->output.size() - n, n);
}
return "";
}
std::string readError(ProcessInfo* info) {
if (!info) return "";
char buf[4096];
ssize_t n = read(info->stderr_fd, buf, sizeof(buf));
if (n > 0) {
info->error.append(buf, n);
return info->error.substr(info->error.size() - n, n);
}
return "";
}
}
}
}
#endif
+165
View File
@@ -0,0 +1,165 @@
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#include "common_serial.hpp"
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <vector>
#include <string>
#include <cstring>
namespace ckitty {
namespace serial {
namespace os {
struct SerialInfo {
int fd = -1;
std::string portName;
};
std::vector<std::string> listPorts() {
std::vector<std::string> ports;
DIR* dir = opendir("/dev");
if (!dir) return ports;
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
std::string name = entry->d_name;
if (name.rfind("ttyS", 0) == 0 ||
name.rfind("ttyUSB", 0) == 0 ||
name.rfind("ttyACM", 0) == 0 ||
name.rfind("rfcomm", 0) == 0) {
ports.push_back("/dev/" + name);
}
}
closedir(dir);
return ports;
}
SerialInfo* openPort(const std::string& port, uint32_t baudRate) {
int fd = open(port.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) return nullptr;
struct termios tty = {};
if (tcgetattr(fd, &tty) != 0) {
close(fd);
return nullptr;
}
cfsetospeed(&tty, baudRate);
cfsetispeed(&tty, baudRate);
tty.c_cflag &= ~PARENB;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
tty.c_cflag &= ~CRTSCTS;
tty.c_cflag |= CREAD | CLOCAL;
tty.c_lflag &= ~ICANON;
tty.c_lflag &= ~ECHO;
tty.c_lflag &= ~ECHOE;
tty.c_lflag &= ~ECHONL;
tty.c_lflag &= ~ISIG;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
tty.c_oflag &= ~OPOST;
tty.c_oflag &= ~ONLCR;
tty.c_cc[VTIME] = 0;
tty.c_cc[VMIN] = 0;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
close(fd);
return nullptr;
}
auto* info = new SerialInfo();
info->fd = fd;
info->portName = port;
return info;
}
void closePort(SerialInfo* info) {
if (!info) return;
if (info->fd != -1) {
close(info->fd);
info->fd = -1;
}
delete info;
}
bool isOpen(SerialInfo* info) {
return info && info->fd != -1;
}
size_t write(SerialInfo* info, const uint8_t* data, size_t size) {
if (!info || !isOpen(info)) return 0;
ssize_t written = ::write(info->fd, data, size);
return written > 0 ? written : 0;
}
size_t read(SerialInfo* info, uint8_t* buffer, size_t maxSize) {
if (!info || !isOpen(info)) return 0;
ssize_t n = ::read(info->fd, buffer, maxSize);
return n > 0 ? n : 0;
}
void setDTR(SerialInfo* info, bool level) {
if (!info || !isOpen(info)) return;
int status;
ioctl(info->fd, TIOCMGET, &status);
if (level) status |= TIOCM_DTR;
else status &= ~TIOCM_DTR;
ioctl(info->fd, TIOCMSET, &status);
}
void setRTS(SerialInfo* info, bool level) {
if (!info || !isOpen(info)) return;
int status;
ioctl(info->fd, TIOCMGET, &status);
if (level) status |= TIOCM_RTS;
else status &= ~TIOCM_RTS;
ioctl(info->fd, TIOCMSET, &status);
}
bool getCTS(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
int status;
ioctl(info->fd, TIOCMGET, &status);
return (status & TIOCM_CTS) != 0;
}
bool getDSR(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
int status;
ioctl(info->fd, TIOCMGET, &status);
return (status & TIOCM_DSR) != 0;
}
bool getRI(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
int status;
ioctl(info->fd, TIOCMGET, &status);
return (status & TIOCM_RI) != 0;
}
bool getCD(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
int status;
ioctl(info->fd, TIOCMGET, &status);
return (status & TIOCM_CD) != 0;
}
}
}
}
#endif
+181
View File
@@ -0,0 +1,181 @@
#ifdef _WIN32
#include "common_proc.hpp"
#include <windows.h>
#include <vector>
#include <string>
#include <sstream>
namespace ckitty {
namespace proc {
namespace os {
struct ProcessInfo {
PROCESS_INFORMATION pi = {};
HANDLE hStdin = nullptr;
HANDLE hStdout = nullptr;
HANDLE hStderr = nullptr;
bool running = false;
std::string output;
std::string error;
};
ProcessInfo* createProcess(const std::string& exec, const std::vector<std::string>& args) {
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = nullptr;
HANDLE hStdinRead = nullptr, hStdinWrite = nullptr;
HANDLE hStdoutRead = nullptr, hStdoutWrite = nullptr;
HANDLE hStderrRead = nullptr, hStderrWrite = nullptr;
if (!CreatePipe(&hStdoutRead, &hStdoutWrite, &sa, 0)) return nullptr;
if (!CreatePipe(&hStderrRead, &hStderrWrite, &sa, 0)) {
CloseHandle(hStdoutRead); CloseHandle(hStdoutWrite);
return nullptr;
}
if (!CreatePipe(&hStdinRead, &hStdinWrite, &sa, 0)) {
CloseHandle(hStdoutRead); CloseHandle(hStdoutWrite);
CloseHandle(hStderrRead); CloseHandle(hStderrWrite);
return nullptr;
}
SetHandleInformation(hStdoutRead, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(hStderrRead, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(hStdinWrite, HANDLE_FLAG_INHERIT, 0);
STARTUPINFOA si;
ZeroMemory(&si, sizeof(STARTUPINFOA));
si.cb = sizeof(STARTUPINFOA);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hStdoutWrite;
si.hStdError = hStderrWrite;
si.hStdInput = hStdinRead;
std::string cmdLine = exec;
for (const auto& arg : args) {
cmdLine += ' ';
cmdLine += arg;
}
PROCESS_INFORMATION pi = {};
BOOL result = CreateProcessA(
nullptr,
&cmdLine[0],
nullptr,
nullptr,
TRUE,
0,
nullptr,
nullptr,
&si,
&pi
);
CloseHandle(hStdoutWrite);
CloseHandle(hStderrWrite);
CloseHandle(hStdinRead);
if (!result) {
CloseHandle(hStdoutRead);
CloseHandle(hStderrRead);
CloseHandle(hStdinWrite);
return nullptr;
}
auto* info = new ProcessInfo();
info->pi = pi;
info->hStdin = hStdinWrite;
info->hStdout = hStdoutRead;
info->hStderr = hStderrRead;
info->running = true;
return info;
}
void destroyProcess(ProcessInfo* info) {
if (!info) return;
if (info->running) {
TerminateProcess(info->pi.hProcess, 1);
WaitForSingleObject(info->pi.hProcess, 1000);
}
if (info->pi.hProcess) CloseHandle(info->pi.hProcess);
if (info->pi.hThread) CloseHandle(info->pi.hThread);
if (info->hStdin) CloseHandle(info->hStdin);
if (info->hStdout) CloseHandle(info->hStdout);
if (info->hStderr) CloseHandle(info->hStderr);
delete info;
}
bool isRunning(ProcessInfo* info) {
if (!info || !info->running) return false;
DWORD result = WaitForSingleObject(info->pi.hProcess, 0);
if (result == WAIT_OBJECT_0) {
info->running = false;
return false;
}
return true;
}
int getExitCode(ProcessInfo* info) {
if (!info) return 0;
if (info->running) return 0;
DWORD code = 0;
GetExitCodeProcess(info->pi.hProcess, &code);
return static_cast<int>(code);
}
void writeInput(ProcessInfo* info, std::string_view data) {
if (!info || !info->running) return;
DWORD written = 0;
WriteFile(info->hStdin, data.data(), static_cast<DWORD>(data.size()), &written, nullptr);
}
void closeInput(ProcessInfo* info) {
if (!info) return;
if (info->hStdin) {
CloseHandle(info->hStdin);
info->hStdin = nullptr;
}
}
void terminate(ProcessInfo* info) {
if (!info || !info->running) return;
TerminateProcess(info->pi.hProcess, 1);
info->running = false;
}
std::string readOutput(ProcessInfo* info) {
if (!info) return "";
char buf[4096];
DWORD read = 0;
DWORD avail = 0;
if (PeekNamedPipe(info->hStdout, nullptr, 0, nullptr, &avail, nullptr) && avail > 0) {
if (ReadFile(info->hStdout, buf, std::min<DWORD>(avail, sizeof(buf)), &read, nullptr) && read > 0) {
info->output.append(buf, read);
return info->output.substr(info->output.size() - read, read);
}
}
return "";
}
std::string readError(ProcessInfo* info) {
if (!info) return "";
char buf[4096];
DWORD read = 0;
DWORD avail = 0;
if (PeekNamedPipe(info->hStderr, nullptr, 0, nullptr, &avail, nullptr) && avail > 0) {
if (ReadFile(info->hStderr, buf, std::min<DWORD>(avail, sizeof(buf)), &read, nullptr) && read > 0) {
info->error.append(buf, read);
return info->error.substr(info->error.size() - read, read);
}
}
return "";
}
}
}
}
#endif
+175
View File
@@ -0,0 +1,175 @@
#ifdef _WIN32
#define INITGUID
#include <windows.h>
#include <setupapi.h>
#include <devguid.h>
#include "common_serial.hpp"
#include <vector>
#include <string>
#include <memory>
namespace ckitty {
namespace serial {
namespace os {
struct SerialInfo {
HANDLE handle = INVALID_HANDLE_VALUE;
std::string portName;
};
std::vector<std::string> listPorts() {
std::vector<std::string> ports;
HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, nullptr, nullptr, DIGCF_PRESENT);
if (devInfo == INVALID_HANDLE_VALUE) return ports;
SP_DEVINFO_DATA devInfoData;
ZeroMemory(&devInfoData, sizeof(SP_DEVINFO_DATA));
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(devInfo, i, &devInfoData); ++i) {
char buffer[256];
DWORD requiredSize = 0;
if (SetupDiGetDeviceRegistryPropertyA(devInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr,
reinterpret_cast<PBYTE>(buffer), sizeof(buffer), &requiredSize)) {
std::string name(buffer);
size_t comPos = name.find("COM");
if (comPos != std::string::npos) {
size_t endPos = name.find_first_of(" )", comPos);
if (endPos == std::string::npos) endPos = name.size();
std::string port = name.substr(comPos, endPos - comPos);
if (!port.empty() && port[0] == 'C') {
ports.push_back(port);
}
}
}
}
SetupDiDestroyDeviceInfoList(devInfo);
return ports;
}
SerialInfo* openPort(const std::string& port, uint32_t baudRate) {
std::string fullPort = "\\\\.\\" + port;
HANDLE h = CreateFileA(fullPort.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) return nullptr;
DCB dcb;
ZeroMemory(&dcb, sizeof(DCB));
dcb.DCBlength = sizeof(DCB);
if (!GetCommState(h, &dcb)) {
CloseHandle(h);
return nullptr;
}
dcb.BaudRate = baudRate;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
if (!SetCommState(h, &dcb)) {
CloseHandle(h);
return nullptr;
}
COMMTIMEOUTS timeouts = {};
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
SetCommTimeouts(h, &timeouts);
auto* info = new SerialInfo();
info->handle = h;
info->portName = port;
return info;
}
void closePort(SerialInfo* info) {
if (!info) return;
if (info->handle != INVALID_HANDLE_VALUE) {
CloseHandle(info->handle);
info->handle = INVALID_HANDLE_VALUE;
}
delete info;
}
bool isOpen(SerialInfo* info) {
return info && info->handle != INVALID_HANDLE_VALUE;
}
size_t write(SerialInfo* info, const uint8_t* data, size_t size) {
if (!info || !isOpen(info)) return 0;
DWORD written = 0;
WriteFile(info->handle, data, static_cast<DWORD>(size), &written, nullptr);
return written;
}
size_t read(SerialInfo* info, uint8_t* buffer, size_t maxSize) {
if (!info || !isOpen(info)) return 0;
DWORD read = 0;
ReadFile(info->handle, buffer, static_cast<DWORD>(maxSize), &read, nullptr);
return read;
}
void setDTR(SerialInfo* info, bool level) {
if (!info || !isOpen(info)) return;
EscapeCommFunction(info->handle, level ? SETDTR : CLRDTR);
}
void setRTS(SerialInfo* info, bool level) {
if (!info || !isOpen(info)) return;
EscapeCommFunction(info->handle, level ? SETRTS : CLRRTS);
}
bool getCTS(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
DWORD modemStatus = 0;
GetCommModemStatus(info->handle, &modemStatus);
return (modemStatus & MS_CTS_ON) != 0;
}
bool getDSR(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
DWORD modemStatus = 0;
GetCommModemStatus(info->handle, &modemStatus);
return (modemStatus & MS_DSR_ON) != 0;
}
bool getRI(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
DWORD modemStatus = 0;
GetCommModemStatus(info->handle, &modemStatus);
return (modemStatus & MS_RING_ON) != 0;
}
bool getCD(SerialInfo* info) {
if (!info || !isOpen(info)) return false;
DWORD modemStatus = 0;
GetCommModemStatus(info->handle, &modemStatus);
return (modemStatus & MS_RLSD_ON) != 0;
}
}
}
}
#endif
+74
View File
@@ -0,0 +1,74 @@
#include "Process.hpp"
#include <sstream>
namespace ckitty {
Process::~Process() {
if (info) {
ckitty::proc::os::destroyProcess(info);
info = nullptr;
}
}
void Process::setExec(std::string_view view) {
exec_ = view;
}
void Process::addParam(std::string_view param) {
args_.push_back(std::string(param));
}
void Process::run() {
if (info) return;
info = ckitty::proc::os::createProcess(exec_, args_);
failed_ = (info == nullptr);
}
bool Process::isRunning() {
if (!info) return false;
return ckitty::proc::os::isRunning(info);
}
int Process::stopCode() {
if (!info) return 0;
return ckitty::proc::os::getExitCode(info);
}
void Process::addInput(std::string_view view) {
if (!info) return;
ckitty::proc::os::writeInput(info, view);
}
void Process::closeInput() {
if (!info) return;
ckitty::proc::os::closeInput(info);
}
void Process::terminate() {
if (!info) return;
ckitty::proc::os::terminate(info);
}
std::string_view Process::getOutput() {
if (!info) return "";
static std::string accumulated;
std::string fresh = ckitty::proc::os::readOutput(info);
if (!fresh.empty()) {
accumulated += fresh;
}
return accumulated;
}
std::string_view Process::nextOutput() {
if (!info) return "";
static std::string fresh;
fresh = ckitty::proc::os::readOutput(info);
return fresh;
}
bool Process::failed() const {
return failed_;
}
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include <desktoplib/os/common_proc.hpp>
namespace ckitty {
class Process {
private:
ckitty::proc::os::ProcessInfo* info = nullptr;
std::string exec_;
std::vector<std::string> args_;
bool failed_ = false;
public:
Process() = default;
~Process();
void setExec(std::string_view view);
void addParam(std::string_view param);
void run();
bool isRunning();
int stopCode();
bool failed() const;
void addInput(std::string_view view);
void closeInput();
void terminate();
std::string_view getOutput();
std::string_view nextOutput();
};
}
+85
View File
@@ -0,0 +1,85 @@
#include "Serial.hpp"
namespace ckitty {
Serial::~Serial() {
if (info) {
ckitty::serial::os::closePort(info);
info = nullptr;
}
}
std::vector<std::string> Serial::listPorts() {
return ckitty::serial::os::listPorts();
}
bool Serial::open(std::string_view port, uint32_t baudRate) {
if (info) return false;
info = ckitty::serial::os::openPort(std::string(port), baudRate);
return info != nullptr;
}
void Serial::close() {
if (info) {
ckitty::serial::os::closePort(info);
info = nullptr;
}
}
bool Serial::isOpen() const {
return info && ckitty::serial::os::isOpen(info);
}
size_t Serial::write(const uint8_t* data, size_t size) {
if (!info || !isOpen()) return 0;
return ckitty::serial::os::write(info, data, size);
}
size_t Serial::write(std::string_view data) {
return write(reinterpret_cast<const uint8_t*>(data.data()), data.size());
}
size_t Serial::read(uint8_t* buffer, size_t maxSize) {
if (!info || !isOpen()) return 0;
return ckitty::serial::os::read(info, buffer, maxSize);
}
std::string Serial::read(size_t maxSize) {
std::string result;
result.resize(maxSize);
size_t n = read(reinterpret_cast<uint8_t*>(result.data()), maxSize);
result.resize(n);
return result;
}
void Serial::setDTR(bool level) {
if (!info || !isOpen()) return;
ckitty::serial::os::setDTR(info, level);
}
void Serial::setRTS(bool level) {
if (!info || !isOpen()) return;
ckitty::serial::os::setRTS(info, level);
}
bool Serial::getCTS() const {
if (!info || !isOpen()) return false;
return ckitty::serial::os::getCTS(info);
}
bool Serial::getDSR() const {
if (!info || !isOpen()) return false;
return ckitty::serial::os::getDSR(info);
}
bool Serial::getRI() const {
if (!info || !isOpen()) return false;
return ckitty::serial::os::getRI(info);
}
bool Serial::getCD() const {
if (!info || !isOpen()) return false;
return ckitty::serial::os::getCD(info);
}
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include <cstdint>
#include <desktoplib/os/common_serial.hpp>
namespace ckitty {
class Serial {
private:
ckitty::serial::os::SerialInfo* info = nullptr;
public:
Serial() = default;
~Serial();
static std::vector<std::string> listPorts();
bool open(std::string_view port, uint32_t baudRate);
void close();
bool isOpen() const;
size_t write(const uint8_t* data, size_t size);
size_t write(std::string_view data);
size_t read(uint8_t* buffer, size_t maxSize);
std::string read(size_t maxSize = 1024);
void setDTR(bool level);
void setRTS(bool level);
bool getCTS() const;
bool getDSR() const;
bool getRI() const;
bool getCD() const;
};
}