Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]

### Fixed
- **Button Highlighting Bug - Remove Broken Static Cache (2026-01-10)**
- Fixed buttons incorrectly showing highlighted/yellow state
- **Root Cause**: The static tile cache optimization in `Layer::Rectangle()` was fundamentally broken. Static variables persisted across all calls, causing stale cache values to be used when rendering buttons with different textures.
- **Solution**: Removed the static cache optimization entirely. Each `Rectangle()` call now properly sets the tile and origin, ensuring correct texture rendering.
- **Files modified**: `term/layer.cc`

- **Button Highlighting Bug with Lazy Texture Loading (2026-01-09)**
- Fixed buttons incorrectly showing highlighted state when they shouldn't be
- **Root Cause**: The lazy texture loading introduced in the memory optimization commit conflicted with the static tile cache in `Layer::Rectangle()`. When textures were loaded on-demand, the cache could cause incorrect texture rendering.
- **Solution**: Added `PreloadAllTextures()` function that loads all textures at startup, ensuring consistent Pixmap values before any rendering occurs.
- **Files modified**: `term/term_view.cc`, `term/term_view.hh`

### Added
- **Performance: Object Pool System for Memory Efficiency (2026-01-09)**
- Created `src/core/object_pool.hh` with thread-safe object pooling templates
- `ObjectPool<T>`: Reusable object pool with configurable max size (default 64)
* Thread-safe acquire/release with mutex protection
* Pre-allocation support via `reserve()` method
* Automatic cleanup of pooled objects on destruction
- `PooledObject<T>`: RAII wrapper for automatic return to pool
- `BufferPool<Size>`: Specialized pool for fixed-size char buffers
- Reduces allocation overhead and memory fragmentation on resource-constrained devices
- **Files added**: `src/core/object_pool.hh`
- **Target**: Raspberry Pi CM5 with 2GB RAM optimization

### Improved
- **Performance: Settings Pointer Caching (2026-01-09)**
- Cached `term->GetSettings()` calls at function start in hot paths
- `zone/dialog_zone.cc`: `CreditCardDialog::Init()` - cached 4 consecutive GetSettings() calls
- `zone/payout_zone.cc`: `EndDayZone::Render()` - cached settings pointer for multiple accesses
- Reduces function call overhead on resource-constrained devices
- **Files modified**: `zone/dialog_zone.cc`, `zone/payout_zone.cc`

### Added
- **Testing: Comprehensive Test Suite Expansion (2026-01-07)**
- Added 26 new test cases covering time/date operations and error handling
Expand Down
1 change: 1 addition & 0 deletions loader/loader_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ int SetupConnection(const std::string &socket_file)
else if (bind(dev, reinterpret_cast<struct sockaddr*>(&server_adr), SUN_LEN(&server_adr)) < 0)
{
logmsg(LOG_ERR, "Failed to bind socket '%s'", SOCKET_FILE.c_str());
close(dev); // Fix socket leak on bind failure
}
else
{
Expand Down
140 changes: 140 additions & 0 deletions main/hardware/printer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "src/utils/vt_logger.hh"
#include "safe_string_utils.hh"
#include "src/utils/cpp23_utils.hh"
#include "src/core/thread_pool.hh"

#include <errno.h>
#include <iostream>
Expand Down Expand Up @@ -287,6 +288,137 @@ int Printer::Close()
return 0;
}

/****
* CloseAsync: Non-blocking version of Close() for use when UI responsiveness
* is critical. Spawns the print job to a background thread.
* Note: Only works for socket and LPD printing where the temp file can be
* safely copied and processed asynchronously.
****/
void Printer::CloseAsync()
{
FnTrace("Printer::CloseAsync()");

// Close the temp file handle first
if (temp_fd > 0)
close(temp_fd);
temp_fd = -1;

// For parallel printing, we still use synchronous (it already forks)
// For file/email, use sync since they're local operations
if (target_type == TARGET_PARALLEL || target_type == TARGET_FILE ||
target_type == TARGET_EMAIL)
{
// Fall back to sync for these
switch (target_type)
{
case TARGET_PARALLEL:
ParallelPrint();
break;
case TARGET_FILE:
FilePrint();
break;
case TARGET_EMAIL:
EmailPrint();
break;
default:
break;
}
unlink(temp_name.Value());
temp_name.Set("");
return;
}

// For socket and LPD, run async
// Capture temp file name for async operation
std::string temp_file = temp_name.Value();
std::string target_str = target.Value();
int port = port_no;
int type = target_type;

temp_name.Set(""); // Clear so printer can be reused

// Queue the print job to the thread pool
vt::ThreadPool::instance().enqueue_detached(
[temp_file, target_str, port, type]() {
vt::Logger::debug("Async print starting: {} -> {}:{}", temp_file, target_str, port);

if (type == TARGET_SOCKET)
{
// Socket printing logic (duplicated to avoid this pointer issues)
ssize_t bytesread;
ssize_t byteswritten = 1;
char buffer[4096];
struct hostent *he;
struct sockaddr_in their_addr;

he = gethostbyname(target_str.c_str());
if (he == nullptr)
{
vt::Logger::error("Async SocketPrint: Failed to resolve host '{}'", target_str);
unlink(temp_file.c_str());
return;
}

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
vt::Logger::error("Async SocketPrint: Failed to create socket");
unlink(temp_file.c_str());
return;
}

their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(static_cast<uint16_t>(port));
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
memset(&(their_addr.sin_zero), '\0', 8);

if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
{
vt::Logger::error("Async SocketPrint: Failed to connect to {}:{}", target_str, port);
close(sockfd);
unlink(temp_file.c_str());
return;
}

int fd = open(temp_file.c_str(), O_RDONLY, 0);
if (fd <= 0)
{
vt::Logger::error("Async SocketPrint: Failed to open temp file '{}'", temp_file);
close(sockfd);
unlink(temp_file.c_str());
return;
}

bytesread = read(fd, buffer, sizeof(buffer));
while (bytesread > 0 && byteswritten > 0)
{
byteswritten = write(sockfd, buffer, static_cast<size_t>(bytesread));
bytesread = read(fd, buffer, sizeof(buffer));
}

close(fd);
close(sockfd);
vt::Logger::info("Async SocketPrint: Successfully printed to {}:{}", target_str, port);
}
else if (type == TARGET_LPD)
{
// LPD printing
char cmd[512];
snprintf(cmd, sizeof(cmd), "cat %s | /usr/bin/lpr -P%s",
temp_file.c_str(), target_str.c_str());
int result = system(cmd);
if (result != 0)
vt::Logger::error("Async LPDPrint: Command failed with code {}", result);
else
vt::Logger::info("Async LPDPrint: Successfully sent to {}", target_str);
}

// Clean up temp file
unlink(temp_file.c_str());
}
);
}

/****
* ParallelPrint: I've been experiencing severe slowdowns with parallel printing.
* Apparently, we have to wait until the whole job is done. So I'm spawning
Expand Down Expand Up @@ -413,6 +545,13 @@ int Printer::SocketPrint()
return 1;
}

// Set socket timeouts (5 seconds for printer connections)
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

their_addr.sin_family = AF_INET; // host byte order
their_addr.sin_port = htons(port_no); // short, network byte order
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
Expand All @@ -423,6 +562,7 @@ int Printer::SocketPrint()
vt::Logger::error("SocketPrint: Failed to connect to {}:{}", target.Value(), port_no);
perror("connect");
fprintf(stderr, "Is vt_print running?\n");
close(sockfd);
return 1;
}

Expand Down
1 change: 1 addition & 0 deletions main/hardware/printer.hh
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public:
virtual int SetTitle(const std::string &title);
virtual int Open();
virtual int Close();
virtual void CloseAsync(); // Non-blocking version - fire and forget
virtual int ParallelPrint();
virtual int LPDPrint();
virtual int SocketPrint(); // print to TCP socket
Expand Down
74 changes: 74 additions & 0 deletions src/core/data_file.hh
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,50 @@ public:
KeyValueInputFile() = default;
explicit KeyValueInputFile(int fd);
explicit KeyValueInputFile(const std::string &filename);

// RAII destructor - ensures file is closed
~KeyValueInputFile() { Close(); }

// Delete copy operations (prevent double-close)
KeyValueInputFile(const KeyValueInputFile&) = delete;
KeyValueInputFile& operator=(const KeyValueInputFile&) = delete;

// Move operations
KeyValueInputFile(KeyValueInputFile&& other) noexcept
: filedes(other.filedes)
, bytesread(other.bytesread)
, keyidx(other.keyidx)
, validx(other.validx)
, buffidx(other.buffidx)
, comment(other.comment)
, getvalue(other.getvalue)
, delimiter(other.delimiter)
, buffer(std::move(other.buffer))
, inputfile(std::move(other.inputfile))
{
other.filedes = -1;
}

KeyValueInputFile& operator=(KeyValueInputFile&& other) noexcept
{
if (this != &other)
{
Close();
filedes = other.filedes;
bytesread = other.bytesread;
keyidx = other.keyidx;
validx = other.validx;
buffidx = other.buffidx;
comment = other.comment;
getvalue = other.getvalue;
delimiter = other.delimiter;
buffer = std::move(other.buffer);
inputfile = std::move(other.inputfile);
other.filedes = -1;
}
return *this;
}

bool Open();
bool Open(const std::string &filename);
[[nodiscard]] bool IsOpen() const noexcept;
Expand All @@ -179,6 +223,36 @@ public:
KeyValueOutputFile() = default;
explicit KeyValueOutputFile(int fd);
explicit KeyValueOutputFile(const std::string &filename);

// RAII destructor - ensures file is closed
~KeyValueOutputFile() { Close(); }

// Delete copy operations (prevent double-close)
KeyValueOutputFile(const KeyValueOutputFile&) = delete;
KeyValueOutputFile& operator=(const KeyValueOutputFile&) = delete;

// Move operations
KeyValueOutputFile(KeyValueOutputFile&& other) noexcept
: filedes(other.filedes)
, delimiter(other.delimiter)
, outputfile(std::move(other.outputfile))
{
other.filedes = -1;
}

KeyValueOutputFile& operator=(KeyValueOutputFile&& other) noexcept
{
if (this != &other)
{
Close();
filedes = other.filedes;
delimiter = other.delimiter;
outputfile = std::move(other.outputfile);
other.filedes = -1;
}
return *this;
}

int Open();
int Open(const std::string &filename);
[[nodiscard]] bool IsOpen() const noexcept;
Expand Down
Loading