Compare commits
6 Commits
80f64a4256
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| aea76b9d3e | |||
| 28f4c1ac94 | |||
| 8f895f0757 | |||
| bdade6bc27 | |||
| 5d47f08354 | |||
| 47008a0139 |
@@ -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`
|
||||||
@@ -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)
|
||||||
|
|
||||||
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).
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"terminal.integrated.defaultProfile.windows": "MSYS2 UCRT",
|
"terminal.integrated.defaultProfile.windows": "MSYS2 UCRT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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/
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user