Replace form store with stream-from-disk and dynamic form IDs
Form IDs were baked into .form files at conversion time, preventing a form from being displayed more than once. dfm2form now writes a placeholder ID (0), and formServerSendForm streams the file directly from disk, assigning a unique ID on the fly via rewriteFormId. This eliminates formServerLoadFile, the in-memory form store, and the -i flag from dfm2form. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
38125a51a1
commit
17394e4f5d
4 changed files with 164 additions and 256 deletions
|
|
@ -46,39 +46,37 @@ part of a Delphi 1.0 project on Windows.
|
||||||
## DFM Converter
|
## DFM Converter
|
||||||
|
|
||||||
`dfm2form` reads Delphi 1.0 binary DFM files (TPF0 format) and outputs
|
`dfm2form` reads Delphi 1.0 binary DFM files (TPF0 format) and outputs
|
||||||
`.form` files containing protocol commands.
|
`.form` files containing protocol commands. The form ID in the output
|
||||||
|
is a placeholder (`0`); the server assigns a dynamic ID when it streams
|
||||||
|
the file to the client.
|
||||||
|
|
||||||
```
|
```
|
||||||
dfm2form [-i <formId>] <input.dfm> [output.form]
|
dfm2form <input.dfm> [output.form]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Options:**
|
|
||||||
|
|
||||||
- `-i <formId>` — Set the form ID (default: 1). Each form needs a
|
|
||||||
unique ID when serving multiple forms.
|
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
|
|
||||||
```
|
```
|
||||||
dfm2form login.dfm # output to stdout
|
dfm2form login.dfm # output to stdout
|
||||||
dfm2form login.dfm login.form # output to file
|
dfm2form login.dfm login.form # output to file
|
||||||
dfm2form -i 2 settings.dfm settings.form
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Output** is a sequence of protocol commands:
|
**Output** is a sequence of protocol commands:
|
||||||
|
|
||||||
```
|
```
|
||||||
FORM.CREATE 1 400 300 "Login"
|
FORM.CREATE 0 400 300 "Login"
|
||||||
CTRL.CREATE 1 1 Label 20 20 100 17 Caption="Username:"
|
CTRL.CREATE 0 1 Label 20 20 100 17 Caption="Username:"
|
||||||
CTRL.CREATE 1 2 Edit 120 18 200 21 Text="" MaxLength=32 TabOrder=0
|
CTRL.CREATE 0 2 Edit 120 18 200 21 Text="" MaxLength=32 TabOrder=0
|
||||||
CTRL.CREATE 1 3 Label 20 52 100 17 Caption="Password:"
|
CTRL.CREATE 0 3 Label 20 52 100 17 Caption="Password:"
|
||||||
CTRL.CREATE 1 4 Edit 120 50 200 21 Text="" MaxLength=32 TabOrder=1
|
CTRL.CREATE 0 4 Edit 120 50 200 21 Text="" MaxLength=32 TabOrder=1
|
||||||
CTRL.CREATE 1 5 Button 245 90 75 25 Caption="OK" TabOrder=2
|
CTRL.CREATE 0 5 Button 245 90 75 25 Caption="OK" TabOrder=2
|
||||||
CTRL.CREATE 1 6 Button 160 90 75 25 Caption="Cancel" TabOrder=3
|
CTRL.CREATE 0 6 Button 160 90 75 25 Caption="Cancel" TabOrder=3
|
||||||
EVENT.BIND 1 5 Enter
|
EVENT.BIND 0 5 Enter
|
||||||
FORM.SHOW 1
|
FORM.SHOW 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The placeholder `0` IDs are replaced at runtime by `formServerSendForm`.
|
||||||
|
|
||||||
The converter maps Delphi class names to protocol control types,
|
The converter maps Delphi class names to protocol control types,
|
||||||
extracts geometry and properties, and emits `EVENT.BIND` for any
|
extracts geometry and properties, and emits `EVENT.BIND` for any
|
||||||
event handler assignments in the DFM that are not auto-wired (see
|
event handler assignments in the DFM that are not auto-wired (see
|
||||||
|
|
@ -86,9 +84,9 @@ Events below). Unknown control classes are skipped with a warning.
|
||||||
|
|
||||||
## Server Library (C)
|
## Server Library (C)
|
||||||
|
|
||||||
The server loads `.form` files and sends their commands to a remote
|
The server streams `.form` files from disk to a remote client through
|
||||||
client through a pluggable transport interface. It also receives
|
a pluggable transport interface, assigning dynamic form IDs. It also
|
||||||
events from the client and dispatches them to a callback.
|
receives events from the client and dispatches them to a callback.
|
||||||
|
|
||||||
### Transport Interface
|
### Transport Interface
|
||||||
|
|
||||||
|
|
@ -117,17 +115,15 @@ typedef struct {
|
||||||
FormServerT *formServerCreate(FormTransportT *transport);
|
FormServerT *formServerCreate(FormTransportT *transport);
|
||||||
void formServerDestroy(FormServerT *server);
|
void formServerDestroy(FormServerT *server);
|
||||||
|
|
||||||
// Load a .form file. Returns the form ID, or -1 on error.
|
// Stream a .form file to the client, assigning a dynamic form ID.
|
||||||
int32_t formServerLoadFile(FormServerT *server, const char *path);
|
// Returns the assigned form ID, or -1 on error.
|
||||||
|
int32_t formServerSendForm(FormServerT *server, const char *path);
|
||||||
// Send all commands for a loaded form to the client.
|
|
||||||
void formServerSendForm(FormServerT *server, int32_t formId);
|
|
||||||
|
|
||||||
// Form visibility
|
// Form visibility
|
||||||
void formServerShowForm(FormServerT *server, int32_t formId);
|
void formServerShowForm(FormServerT *server, int32_t formId);
|
||||||
void formServerHideForm(FormServerT *server, int32_t formId);
|
void formServerHideForm(FormServerT *server, int32_t formId);
|
||||||
|
|
||||||
// Destroy a form (sends FORM.DESTROY and removes from store)
|
// Destroy a form on the client.
|
||||||
void formServerDestroyForm(FormServerT *server, int32_t formId);
|
void formServerDestroyForm(FormServerT *server, int32_t formId);
|
||||||
|
|
||||||
// Update a control property
|
// Update a control property
|
||||||
|
|
@ -188,8 +184,7 @@ int main(void)
|
||||||
|
|
||||||
formServerSetEventCallback(server, onEvent, server);
|
formServerSetEventCallback(server, onEvent, server);
|
||||||
|
|
||||||
int32_t formId = formServerLoadFile(server, "login.form");
|
int32_t formId = formServerSendForm(server, "login.form");
|
||||||
formServerSendForm(server, formId);
|
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
while (running) {
|
while (running) {
|
||||||
|
|
@ -540,8 +535,6 @@ any protocol or application code.
|
||||||
| Limit | Value |
|
| Limit | Value |
|
||||||
|---------------------------|-------|
|
|---------------------------|-------|
|
||||||
| Max message length | 4096 bytes |
|
| Max message length | 4096 bytes |
|
||||||
| Max forms (server) | 64 |
|
|
||||||
| Max lines per .form file | 1024 |
|
|
||||||
| Max controls per form | 256 |
|
| Max controls per form | 256 |
|
||||||
| Form ID range | 1-65535 (stored in high word of Tag) |
|
| Form ID range | 1-65535 (stored in high word of Tag) |
|
||||||
| Control ID range | 1-65535 (stored in low word of Tag) |
|
| Control ID range | 1-65535 (stored in low word of Tag) |
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// dfm2form.c - Convert Delphi 1.0 binary DFM (TPF0) to .form protocol text
|
// dfm2form.c - Convert Delphi 1.0 binary DFM (TPF0) to .form protocol text
|
||||||
//
|
//
|
||||||
// Usage: dfm2form [-i <formId>] <input.dfm> [output.form]
|
// Usage: dfm2form <input.dfm> [output.form]
|
||||||
//
|
//
|
||||||
// Reads a binary DFM file, extracts the form and control definitions,
|
// Reads a binary DFM file, extracts the form and control definitions,
|
||||||
// and outputs protocol commands (FORM.CREATE, CTRL.CREATE, EVENT.BIND,
|
// and outputs protocol commands (FORM.CREATE, CTRL.CREATE, EVENT.BIND,
|
||||||
|
|
@ -864,8 +864,7 @@ static void emitForm(FILE *out, int32_t formId, DfmFormT *form)
|
||||||
|
|
||||||
static void usage(const char *progName)
|
static void usage(const char *progName)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Usage: %s [-i <formId>] <input.dfm> [output.form]\n", progName);
|
fprintf(stderr, "Usage: %s <input.dfm> [output.form]\n", progName);
|
||||||
fprintf(stderr, " -i <formId> Set form ID (default: 1)\n");
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -876,23 +875,13 @@ static void usage(const char *progName)
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int32_t formId = 1;
|
const char *inputPath = NULL;
|
||||||
const char *inputPath = NULL;
|
|
||||||
const char *outputPath = NULL;
|
const char *outputPath = NULL;
|
||||||
|
|
||||||
// Parse arguments
|
// Parse arguments
|
||||||
int32_t i = 1;
|
int32_t i = 1;
|
||||||
while (i < argc) {
|
while (i < argc) {
|
||||||
if (strcmp(argv[i], "-i") == 0) {
|
if (argv[i][0] == '-') {
|
||||||
if (i + 1 >= argc) {
|
|
||||||
usage(argv[0]);
|
|
||||||
}
|
|
||||||
formId = atoi(argv[++i]);
|
|
||||||
if (formId <= 0) {
|
|
||||||
fprintf(stderr, "Error: form ID must be positive\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
} else if (argv[i][0] == '-') {
|
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
} else if (inputPath == NULL) {
|
} else if (inputPath == NULL) {
|
||||||
inputPath = argv[i];
|
inputPath = argv[i];
|
||||||
|
|
@ -965,7 +954,7 @@ int main(int argc, char *argv[])
|
||||||
fout = stdout;
|
fout = stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
emitForm(fout, formId, &form);
|
emitForm(fout, 0, &form);
|
||||||
|
|
||||||
if (fout != stdout) {
|
if (fout != stdout) {
|
||||||
fclose(fout);
|
fclose(fout);
|
||||||
|
|
|
||||||
331
forms/formsrv.c
331
forms/formsrv.c
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "formsrv.h"
|
#include "formsrv.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
@ -13,26 +14,17 @@
|
||||||
// Constants
|
// Constants
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
#define MAX_FORMS 64
|
|
||||||
#define MAX_LINES 1024
|
|
||||||
#define MAX_MSG_LEN 4096
|
#define MAX_MSG_LEN 4096
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Types
|
// Types
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t formId;
|
|
||||||
char *lines[MAX_LINES];
|
|
||||||
int32_t lineCount;
|
|
||||||
} FormDataT;
|
|
||||||
|
|
||||||
struct FormServerS {
|
struct FormServerS {
|
||||||
FormTransportT *transport;
|
FormTransportT *transport;
|
||||||
EventCallbackT eventCallback;
|
EventCallbackT eventCallback;
|
||||||
void *eventUserData;
|
void *eventUserData;
|
||||||
FormDataT forms[MAX_FORMS];
|
int32_t nextFormId;
|
||||||
int32_t formCount;
|
|
||||||
char msgBuf[MAX_MSG_LEN];
|
char msgBuf[MAX_MSG_LEN];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -40,66 +32,16 @@ struct FormServerS {
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
static FormDataT *findForm(FormServerT *server, int32_t formId);
|
static bool parseToken(const char **p, char *buf, int32_t bufSize);
|
||||||
static void freeFormData(FormDataT *fd);
|
static void rewriteFormId(char *line, int32_t lineSize, int32_t formId);
|
||||||
static int32_t parseFormId(const char *line);
|
static void sendCommand(FormServerT *server, const char *fmt, ...);
|
||||||
static void sendCommand(FormServerT *server, const char *fmt, ...);
|
static bool skipSpaces(const char **p);
|
||||||
static bool skipSpaces(const char **p);
|
|
||||||
static bool parseToken(const char **p, char *buf, int32_t bufSize);
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Internal helpers
|
// Internal helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
static FormDataT *findForm(FormServerT *server, int32_t formId)
|
|
||||||
{
|
|
||||||
for (int32_t i = 0; i < server->formCount; i++) {
|
|
||||||
if (server->forms[i].formId == formId) {
|
|
||||||
return &server->forms[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void freeFormData(FormDataT *fd)
|
|
||||||
{
|
|
||||||
for (int32_t i = 0; i < fd->lineCount; i++) {
|
|
||||||
free(fd->lines[i]);
|
|
||||||
fd->lines[i] = NULL;
|
|
||||||
}
|
|
||||||
fd->lineCount = 0;
|
|
||||||
fd->formId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int32_t parseFormId(const char *line)
|
|
||||||
{
|
|
||||||
// Skip command prefix (e.g., "FORM.CREATE "), then read first integer
|
|
||||||
const char *p = line;
|
|
||||||
// Skip non-space command name
|
|
||||||
while (*p && *p != ' ') {
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
// Skip space
|
|
||||||
while (*p == ' ') {
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
// Parse integer
|
|
||||||
return (int32_t)atoi(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool skipSpaces(const char **p)
|
|
||||||
{
|
|
||||||
while (**p == ' ' || **p == '\t') {
|
|
||||||
(*p)++;
|
|
||||||
}
|
|
||||||
return **p != '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool parseToken(const char **p, char *buf, int32_t bufSize)
|
static bool parseToken(const char **p, char *buf, int32_t bufSize)
|
||||||
{
|
{
|
||||||
skipSpaces(p);
|
skipSpaces(p);
|
||||||
|
|
@ -152,7 +94,49 @@ static bool parseToken(const char **p, char *buf, int32_t bufSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#include <stdarg.h>
|
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, ...)
|
static void sendCommand(FormServerT *server, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
|
|
@ -167,17 +151,34 @@ static void sendCommand(FormServerT *server, const char *fmt, ...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool skipSpaces(const char **p)
|
||||||
|
{
|
||||||
|
while (**p == ' ' || **p == '\t') {
|
||||||
|
(*p)++;
|
||||||
|
}
|
||||||
|
return **p != '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public API
|
// 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 *formServerCreate(FormTransportT *transport)
|
||||||
{
|
{
|
||||||
FormServerT *server = (FormServerT *)calloc(1, sizeof(FormServerT));
|
FormServerT *server = (FormServerT *)calloc(1, sizeof(FormServerT));
|
||||||
if (server == NULL) {
|
if (server == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
server->transport = transport;
|
server->transport = transport;
|
||||||
|
server->nextFormId = 1;
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,151 +188,19 @@ void formServerDestroy(FormServerT *server)
|
||||||
if (server == NULL) {
|
if (server == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int32_t i = 0; i < server->formCount; i++) {
|
|
||||||
freeFormData(&server->forms[i]);
|
|
||||||
}
|
|
||||||
free(server);
|
free(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int32_t formServerLoadFile(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (server->formCount >= MAX_FORMS) {
|
|
||||||
fprintf(stderr, "formsrv: too many forms (max %d)\n", MAX_FORMS);
|
|
||||||
fclose(f);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormDataT *fd = &server->forms[server->formCount];
|
|
||||||
memset(fd, 0, sizeof(FormDataT));
|
|
||||||
|
|
||||||
char lineBuf[MAX_MSG_LEN];
|
|
||||||
int32_t formId = -1;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fd->lineCount >= MAX_LINES) {
|
|
||||||
fprintf(stderr, "formsrv: too many lines in '%s' (max %d)\n", path, MAX_LINES);
|
|
||||||
freeFormData(fd);
|
|
||||||
fclose(f);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fd->lines[fd->lineCount] = strdup(lineBuf);
|
|
||||||
if (fd->lines[fd->lineCount] == NULL) {
|
|
||||||
fprintf(stderr, "formsrv: out of memory\n");
|
|
||||||
freeFormData(fd);
|
|
||||||
fclose(f);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
fd->lineCount++;
|
|
||||||
|
|
||||||
// Extract form ID from first FORM.CREATE line
|
|
||||||
if (formId == -1 && strncmp(lineBuf, "FORM.CREATE ", 12) == 0) {
|
|
||||||
formId = parseFormId(lineBuf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
if (formId <= 0) {
|
|
||||||
fprintf(stderr, "formsrv: no FORM.CREATE found in '%s'\n", path);
|
|
||||||
freeFormData(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fd->formId = formId;
|
|
||||||
server->formCount++;
|
|
||||||
return formId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void formServerSendForm(FormServerT *server, int32_t formId)
|
|
||||||
{
|
|
||||||
FormDataT *fd = findForm(server, formId);
|
|
||||||
if (fd == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int32_t i = 0; i < fd->lineCount; i++) {
|
|
||||||
server->transport->writeMessage(fd->lines[i], server->transport->ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void formServerShowForm(FormServerT *server, int32_t formId)
|
|
||||||
{
|
|
||||||
sendCommand(server, "FORM.SHOW %d", formId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void formServerHideForm(FormServerT *server, int32_t formId)
|
|
||||||
{
|
|
||||||
sendCommand(server, "FORM.HIDE %d", formId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void formServerDestroyForm(FormServerT *server, int32_t formId)
|
void formServerDestroyForm(FormServerT *server, int32_t formId)
|
||||||
{
|
{
|
||||||
sendCommand(server, "FORM.DESTROY %d", formId);
|
sendCommand(server, "FORM.DESTROY %d", formId);
|
||||||
|
|
||||||
// Remove from form store
|
|
||||||
for (int32_t i = 0; i < server->formCount; i++) {
|
|
||||||
if (server->forms[i].formId == formId) {
|
|
||||||
freeFormData(&server->forms[i]);
|
|
||||||
// Shift remaining forms down
|
|
||||||
for (int32_t j = i; j < server->formCount - 1; j++) {
|
|
||||||
server->forms[j] = server->forms[j + 1];
|
|
||||||
}
|
|
||||||
server->formCount--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void formServerSetProp(FormServerT *server, int32_t formId, int32_t ctrlId,
|
void formServerHideForm(FormServerT *server, int32_t formId)
|
||||||
const char *prop, const char *value)
|
|
||||||
{
|
{
|
||||||
sendCommand(server, "CTRL.SET %d %d %s=%s", formId, ctrlId, prop, value);
|
sendCommand(server, "FORM.HIDE %d", formId);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void formServerBindEvent(FormServerT *server, int32_t formId, int32_t ctrlId,
|
|
||||||
const char *eventName)
|
|
||||||
{
|
|
||||||
sendCommand(server, "EVENT.BIND %d %d %s", formId, ctrlId, eventName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void formServerUnbindEvent(FormServerT *server, int32_t formId, int32_t ctrlId,
|
|
||||||
const char *eventName)
|
|
||||||
{
|
|
||||||
sendCommand(server, "EVENT.UNBIND %d %d %s", formId, ctrlId, eventName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void formServerSetEventCallback(FormServerT *server, EventCallbackT cb,
|
|
||||||
void *userData)
|
|
||||||
{
|
|
||||||
server->eventCallback = cb;
|
|
||||||
server->eventUserData = userData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -348,7 +217,7 @@ bool formServerPollEvent(FormServerT *server)
|
||||||
|
|
||||||
// Parse: EVENT <formId> <ctrlId> <eventName> [<data>]
|
// Parse: EVENT <formId> <ctrlId> <eventName> [<data>]
|
||||||
const char *p = server->msgBuf;
|
const char *p = server->msgBuf;
|
||||||
char token[256];
|
char token[256];
|
||||||
|
|
||||||
if (!parseToken(&p, token, sizeof(token))) {
|
if (!parseToken(&p, token, sizeof(token))) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -386,3 +255,63 @@ bool formServerPollEvent(FormServerT *server)
|
||||||
|
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// formsrv.h - Remote forms server library
|
// formsrv.h - Remote forms server library
|
||||||
//
|
//
|
||||||
// Loads .form files (protocol command sequences) and sends them to a
|
// Streams .form files from disk to a remote client via a transport
|
||||||
// remote client via a transport interface. Receives EVENT messages
|
// interface, assigning dynamic form IDs. Receives EVENT messages from
|
||||||
// from the client and dispatches them to a callback.
|
// the client and dispatches them to a callback.
|
||||||
|
|
||||||
#ifndef FORMSRV_H
|
#ifndef FORMSRV_H
|
||||||
#define FORMSRV_H
|
#define FORMSRV_H
|
||||||
|
|
@ -48,12 +48,9 @@ typedef struct FormServerS FormServerT;
|
||||||
FormServerT *formServerCreate(FormTransportT *transport);
|
FormServerT *formServerCreate(FormTransportT *transport);
|
||||||
void formServerDestroy(FormServerT *server);
|
void formServerDestroy(FormServerT *server);
|
||||||
|
|
||||||
// Load a .form file into the server's form store. Returns the form ID
|
// Stream a .form file to the client, assigning a dynamic form ID.
|
||||||
// parsed from the first FORM.CREATE line, or -1 on error.
|
// Returns the assigned form ID, or -1 on error.
|
||||||
int32_t formServerLoadFile(FormServerT *server, const char *path);
|
int32_t formServerSendForm(FormServerT *server, const char *path);
|
||||||
|
|
||||||
// Send all commands for a loaded form to the client.
|
|
||||||
void formServerSendForm(FormServerT *server, int32_t formId);
|
|
||||||
|
|
||||||
// Send FORM.SHOW / FORM.HIDE / FORM.DESTROY commands.
|
// Send FORM.SHOW / FORM.HIDE / FORM.DESTROY commands.
|
||||||
void formServerShowForm(FormServerT *server, int32_t formId);
|
void formServerShowForm(FormServerT *server, int32_t formId);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue