WinComm/forms/formsrv.c
Scott Duensing e25428bb8b Apply K&R brace style and unwrap function signatures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:22:02 -06:00

298 lines
7.8 KiB
C

// formsrv.c - Remote forms server library implementation
#define _POSIX_C_SOURCE 200809L
#include "formsrv.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define MAX_MSG_LEN 4096
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
struct FormServerS {
FormTransportT *transport;
EventCallbackT eventCallback;
void *eventUserData;
int32_t nextFormId;
char msgBuf[MAX_MSG_LEN];
};
// ---------------------------------------------------------------------------
// Prototypes
// ---------------------------------------------------------------------------
static bool parseToken(const char **p, char *buf, int32_t bufSize);
static void rewriteFormId(char *line, int32_t lineSize, int32_t formId);
static void sendCommand(FormServerT *server, const char *fmt, ...);
static bool skipSpaces(const char **p);
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
static bool parseToken(const char **p, char *buf, int32_t bufSize) {
skipSpaces(p);
if (**p == '\0') {
return false;
}
int32_t i = 0;
if (**p == '"') {
// Quoted string — read until closing quote
(*p)++;
while (**p != '\0' && **p != '"') {
if (**p == '\\' && *(*p + 1) != '\0') {
(*p)++;
char c = **p;
switch (c) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case '"': c = '"'; break;
case '\\': c = '\\'; break;
default: break;
}
if (i < bufSize - 1) {
buf[i++] = c;
}
} else {
if (i < bufSize - 1) {
buf[i++] = **p;
}
}
(*p)++;
}
if (**p == '"') {
(*p)++;
}
} else {
// Bare token — read until whitespace
while (**p != '\0' && **p != ' ' && **p != '\t') {
if (i < bufSize - 1) {
buf[i++] = **p;
}
(*p)++;
}
}
buf[i] = '\0';
return i > 0;
}
static void rewriteFormId(char *line, int32_t lineSize, int32_t formId) {
// Protocol lines have the form: COMMAND <formId> <rest...>
// Skip command prefix (non-space chars)
char *p = line;
while (*p && *p != ' ') {
p++;
}
// Skip spaces
while (*p == ' ') {
p++;
}
// p now points at the placeholder ID token ("0")
// Find the end of this token
char *idStart = p;
while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') {
p++;
}
char *idEnd = p;
// Format the new ID
char idBuf[16];
int32_t idLen = snprintf(idBuf, sizeof(idBuf), "%d", formId);
int32_t oldLen = (int32_t)(idEnd - idStart);
int32_t shift = idLen - oldLen;
// Check that the rewritten line still fits
int32_t curLen = (int32_t)strlen(line);
if (curLen + shift >= lineSize - 1) {
return;
}
// Shift the remainder of the line
if (shift != 0) {
memmove(idStart + idLen, idEnd, strlen(idEnd) + 1);
}
// Write the new ID (no NUL — the memmove already placed the rest)
memcpy(idStart, idBuf, idLen);
}
static void sendCommand(FormServerT *server, const char *fmt, ...) {
char buf[MAX_MSG_LEN];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
server->transport->writeMessage(buf, server->transport->ctx);
}
static bool skipSpaces(const char **p) {
while (**p == ' ' || **p == '\t') {
(*p)++;
}
return **p != '\0';
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
void formServerBindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, const char *eventName) {
sendCommand(server, "EVENT.BIND %d %d %s", formId, ctrlId, eventName);
}
FormServerT *formServerCreate(FormTransportT *transport) {
FormServerT *server = (FormServerT *)calloc(1, sizeof(FormServerT));
if (server == NULL) {
return NULL;
}
server->transport = transport;
server->nextFormId = 1;
return server;
}
void formServerDestroy(FormServerT *server) {
if (server == NULL) {
return;
}
free(server);
}
void formServerDestroyForm(FormServerT *server, int32_t formId) {
sendCommand(server, "FORM.DESTROY %d", formId);
}
void formServerHideForm(FormServerT *server, int32_t formId) {
sendCommand(server, "FORM.HIDE %d", formId);
}
bool formServerPollEvent(FormServerT *server) {
int bytesRead = server->transport->readMessage(
server->msgBuf, MAX_MSG_LEN - 1, server->transport->ctx);
if (bytesRead <= 0) {
return false;
}
server->msgBuf[bytesRead] = '\0';
// Parse: EVENT <formId> <ctrlId> <eventName> [<data>]
const char *p = server->msgBuf;
char token[256];
if (!parseToken(&p, token, sizeof(token))) {
return false;
}
if (strcmp(token, "EVENT") != 0) {
return false;
}
// formId
if (!parseToken(&p, token, sizeof(token))) {
return false;
}
int32_t formId = (int32_t)atoi(token);
// ctrlId
if (!parseToken(&p, token, sizeof(token))) {
return false;
}
int32_t ctrlId = (int32_t)atoi(token);
// eventName
char eventName[64];
if (!parseToken(&p, eventName, sizeof(eventName))) {
return false;
}
// Remaining data (rest of line after skipping spaces)
skipSpaces(&p);
const char *data = p;
if (server->eventCallback != NULL) {
server->eventCallback(formId, ctrlId, eventName, data,
server->eventUserData);
}
return true;
}
int32_t formServerSendForm(FormServerT *server, const char *path) {
FILE *f = fopen(path, "r");
if (f == NULL) {
fprintf(stderr, "formsrv: cannot open '%s': %s\n", path, strerror(errno));
return -1;
}
int32_t formId = server->nextFormId++;
char lineBuf[MAX_MSG_LEN];
while (fgets(lineBuf, sizeof(lineBuf), f) != NULL) {
// Strip trailing newline
int32_t len = (int32_t)strlen(lineBuf);
while (len > 0 && (lineBuf[len - 1] == '\n' || lineBuf[len - 1] == '\r')) {
lineBuf[--len] = '\0';
}
// Skip empty lines
if (len == 0) {
continue;
}
rewriteFormId(lineBuf, sizeof(lineBuf), formId);
server->transport->writeMessage(lineBuf, server->transport->ctx);
}
fclose(f);
return formId;
}
void formServerSetEventCallback(FormServerT *server, EventCallbackT cb, void *userData) {
server->eventCallback = cb;
server->eventUserData = userData;
}
void formServerSetProp(FormServerT *server, int32_t formId, int32_t ctrlId, const char *prop, const char *value) {
sendCommand(server, "CTRL.SET %d %d %s=%s", formId, ctrlId, prop, value);
}
void formServerShowForm(FormServerT *server, int32_t formId) {
sendCommand(server, "FORM.SHOW %d", formId);
}
void formServerUnbindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, const char *eventName) {
sendCommand(server, "EVENT.UNBIND %d %d %s", formId, ctrlId, eventName);
}