Compare commits

..

2 commits

Author SHA1 Message Date
a4727754e3 First alpha release built for testing. 2026-03-19 12:59:31 -05:00
b1d74d7e3c Start of preferences system. 2026-03-19 01:12:15 -05:00
22 changed files with 747 additions and 30 deletions

2
.gitattributes vendored
View file

@ -1,2 +1,4 @@
*.bmp filter=lfs diff=lfs merge=lfs -text
*.BMP filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.ZIP filter=lfs diff=lfs merge=lfs -text

View file

@ -23,5 +23,5 @@ clean:
$(MAKE) -C tasks clean
$(MAKE) -C dvxshell clean
$(MAKE) -C apps clean
-rmdir obj/dvx/widgets obj/dvx/platform obj/dvx obj/tasks obj/dvxshell obj/apps obj 2>/dev/null
-rmdir bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin lib 2>/dev/null
-rmdir obj/dvx/widgets obj/dvx/platform obj/dvx/thirdparty obj/dvx obj/tasks obj/dvxshell obj/apps obj 2>/dev/null
-rmdir bin/config bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin lib 2>/dev/null

20
dvx.ini Normal file
View file

@ -0,0 +1,20 @@
; DVX Configuration
;
; Video mode selection. The system picks the closest available VESA mode.
; Common resolutions: 640x480, 800x600, 1024x768
; Supported color depths: 8, 15, 16, 24, 32
[video]
width = 640
height = 480
bpp = 16
; Mouse settings.
; wheel: normal or reversed
; doubleclick: double-click speed in milliseconds (200-900, default 500)
; acceleration: off, low, medium, high (default medium)
[mouse]
wheel = normal
doubleclick = 500
acceleration = medium

View file

@ -10,12 +10,15 @@ CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586
OBJDIR = ../obj/dvx
WOBJDIR = ../obj/dvx/widgets
POBJDIR = ../obj/dvx/platform
TOBJDIR = ../obj/dvx/thirdparty
LIBDIR = ../lib
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c dvxPrefs.c
PSRCS = platform/dvxPlatformDos.c
TSRCS = thirdparty/ini/src/ini.c
WSRCS = widgets/widgetAnsiTerm.c \
widgets/widgetClass.c \
widgets/widgetCore.c \
@ -51,14 +54,15 @@ WSRCS = widgets/widgetAnsiTerm.c \
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
POBJS = $(patsubst platform/%.c,$(POBJDIR)/%.o,$(PSRCS))
WOBJS = $(patsubst widgets/%.c,$(WOBJDIR)/%.o,$(WSRCS))
TOBJS = $(TOBJDIR)/ini.o
TARGET = $(LIBDIR)/libdvx.a
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(POBJS) $(WOBJS) | $(LIBDIR)
$(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS)
$(TARGET): $(OBJS) $(POBJS) $(WOBJS) $(TOBJS) | $(LIBDIR)
$(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS) $(TOBJS)
$(RANLIB) $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
@ -70,6 +74,9 @@ $(POBJDIR)/%.o: platform/%.c | $(POBJDIR)
$(WOBJDIR)/%.o: widgets/%.c | $(WOBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(TOBJDIR)/ini.o: thirdparty/ini/src/ini.c | $(TOBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
@ -79,6 +86,9 @@ $(POBJDIR):
$(WOBJDIR):
mkdir -p $(WOBJDIR)
$(TOBJDIR):
mkdir -p $(TOBJDIR)
$(LIBDIR):
mkdir -p $(LIBDIR)
@ -92,9 +102,14 @@ $(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h widgets/widgetInternal.h dvxTypes.h dvxDraw.h
$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h thirdparty/ini/src/ini.h
# Platform file dependencies
$(POBJDIR)/dvxPlatformDos.o: platform/dvxPlatformDos.c platform/dvxPlatform.h dvxTypes.h dvxPalette.h
# Thirdparty file dependencies
$(TOBJDIR)/ini.o: thirdparty/ini/src/ini.c thirdparty/ini/src/ini.h
# Widget file dependencies
WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
$(WOBJDIR)/widgetClass.o: widgets/widgetClass.c $(WIDGET_DEPS)
@ -130,4 +145,4 @@ $(WOBJDIR)/widgetScrollbar.o: widgets/widgetScrollbar.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetTreeView.o: widgets/widgetTreeView.c $(WIDGET_DEPS)
clean:
rm -f $(OBJS) $(POBJS) $(WOBJS) $(TARGET)
rm -f $(OBJS) $(POBJS) $(WOBJS) $(TOBJS) $(TARGET)

View file

@ -1114,7 +1114,7 @@ static void dispatchEvents(AppContextT *ctx) {
if (sb) {
int32_t oldValue = sb->value;
sb->value += ctx->mouseWheel * MOUSE_WHEEL_STEP;
sb->value += ctx->mouseWheel * ctx->wheelDirection * MOUSE_WHEEL_STEP;
if (sb->value < sb->min) {
sb->value = sb->min;
@ -1603,6 +1603,9 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
ctx->lastCloseClickTime = 0;
ctx->lastTitleClickId = -1;
ctx->lastTitleClickTime = 0;
ctx->wheelDirection = 1;
ctx->dblClickTicks = DBLCLICK_THRESHOLD;
sDblClickTicks = DBLCLICK_THRESHOLD;
// Pre-compute fixed-point 16.16 reciprocal of character height so
// popup menu item index calculation can use multiply+shift instead
@ -1884,6 +1887,21 @@ int32_t dvxScreenshot(AppContextT *ctx, const char *path) {
}
// ============================================================
// dvxSetMouseConfig
// ============================================================
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold) {
ctx->wheelDirection = (wheelDir < 0) ? -1 : 1;
ctx->dblClickTicks = (clock_t)dblClickMs * CLOCKS_PER_SEC / 1000;
sDblClickTicks = ctx->dblClickTicks;
if (accelThreshold > 0) {
platformMouseSetAccel(accelThreshold);
}
}
// ============================================================
// dvxShutdown
// ============================================================
@ -2239,7 +2257,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
clock_t now = clock();
if (ctx->lastIconClickId == iconWin->id &&
(now - ctx->lastIconClickTime) < DBLCLICK_THRESHOLD) {
(now - ctx->lastIconClickTime) < ctx->dblClickTicks) {
// Double-click: restore minimized window
// Dirty the entire icon strip area
dirtyListAdd(&ctx->dirty, 0,
@ -2288,7 +2306,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
if (win->resizable &&
ctx->lastTitleClickId == win->id &&
(now - ctx->lastTitleClickTime) < DBLCLICK_THRESHOLD) {
(now - ctx->lastTitleClickTime) < ctx->dblClickTicks) {
// Double-click: toggle maximize/restore
ctx->lastTitleClickId = -1;
@ -2310,7 +2328,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
clock_t now = clock();
if (ctx->lastCloseClickId == win->id &&
(now - ctx->lastCloseClickTime) < DBLCLICK_THRESHOLD) {
(now - ctx->lastCloseClickTime) < ctx->dblClickTicks) {
ctx->lastCloseClickId = -1;
closeSysMenu(ctx);

View file

@ -92,6 +92,9 @@ typedef struct AppContextT {
// terminal/text widgets) becomes a multiply+shift instead of an
// integer divide, which is very slow on 486 (40+ cycles per DIV).
uint32_t charHeightRecip; // fixed-point 16.16 reciprocal of font.charHeight
// Mouse configuration (loaded from preferences)
int32_t wheelDirection; // 1 = normal, -1 = reversed
clock_t dblClickTicks; // double-click speed in clock() ticks
} AppContextT;
// Initialize the entire GUI stack: video mode, input devices, font,
@ -99,6 +102,11 @@ typedef struct AppContextT {
// entry point for starting a DVX application. Returns 0 on success.
int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
// Configure mouse behaviour. wheelDir: 1 = normal, -1 = reversed.
// dblClickMs: double-click speed in milliseconds (e.g. 500).
// accelThreshold: double-speed threshold in mickeys/sec (0 = don't change).
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold);
// Tear down the GUI stack in reverse order: destroy all windows, restore
// text mode, release input devices. Safe to call after a failed dvxInit().
void dvxShutdown(AppContextT *ctx);

101
dvx/dvxPrefs.c Normal file
View file

@ -0,0 +1,101 @@
// dvxPrefs.c — INI-based preferences system
//
// Thin wrapper around rxi/ini that adds typed accessors with defaults.
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "dvxPrefs.h"
#include "thirdparty/ini/src/ini.h"
static ini_t *sIni = NULL;
// ============================================================
// prefsFree
// ============================================================
void prefsFree(void) {
if (sIni) {
ini_free(sIni);
sIni = NULL;
}
}
// ============================================================
// prefsGetBool
// ============================================================
bool prefsGetBool(const char *section, const char *key, bool defaultVal) {
const char *val = prefsGetString(section, key, NULL);
if (!val) {
return defaultVal;
}
// Case-insensitive first character check covers true/yes/1 and false/no/0
char c = (char)tolower((unsigned char)val[0]);
if (c == 't' || c == 'y' || c == '1') {
return true;
}
if (c == 'f' || c == 'n' || c == '0') {
return false;
}
return defaultVal;
}
// ============================================================
// prefsGetInt
// ============================================================
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal) {
const char *val = prefsGetString(section, key, NULL);
if (!val) {
return defaultVal;
}
char *end = NULL;
long n = strtol(val, &end, 10);
if (end == val) {
return defaultVal;
}
return (int32_t)n;
}
// ============================================================
// prefsGetString
// ============================================================
const char *prefsGetString(const char *section, const char *key, const char *defaultVal) {
if (!sIni) {
return defaultVal;
}
const char *val = ini_get(sIni, section, key);
return val ? val : defaultVal;
}
// ============================================================
// prefsLoad
// ============================================================
bool prefsLoad(const char *filename) {
prefsFree();
sIni = ini_load(filename);
return sIni != NULL;
}

37
dvx/dvxPrefs.h Normal file
View file

@ -0,0 +1,37 @@
// dvxPrefs.h — INI-based preferences system
//
// Loads a configuration file at startup and provides typed accessors
// with caller-supplied defaults. The INI file is read-only at runtime;
// values are queried by section + key. If the file is missing or a key
// is absent, the default is returned silently.
//
// The underlying parser is rxi/ini (thirdparty/ini/src).
#ifndef DVX_PREFS_H
#define DVX_PREFS_H
#include <stdbool.h>
#include <stdint.h>
// Load an INI file into memory. Returns true on success, false if the
// file could not be opened (all getters will return their defaults).
// Only one file may be loaded at a time; calling again frees the previous.
bool prefsLoad(const char *filename);
// Release all memory held by the loaded INI file.
void prefsFree(void);
// Retrieve a string value. Returns defaultVal if the section/key pair
// is not present. The returned pointer is valid until prefsFree().
const char *prefsGetString(const char *section, const char *key, const char *defaultVal);
// Retrieve an integer value. Returns defaultVal if the section/key pair
// is not present or cannot be parsed.
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal);
// Retrieve a boolean value. Recognises "true", "yes", "1" as true and
// "false", "no", "0" as false (case-insensitive). Returns defaultVal
// for anything else or if the key is missing.
bool prefsGetBool(const char *section, const char *key, bool defaultVal);
#endif

View file

@ -139,6 +139,11 @@ bool platformMouseWheelInit(void);
// driver between polls).
int32_t platformMouseWheelPoll(void);
// Set the double-speed threshold in mickeys/second. When the mouse
// moves faster than this, cursor movement is doubled by the driver.
// A very high value (e.g. 10000) effectively disables acceleration.
void platformMouseSetAccel(int32_t threshold);
// Move the mouse cursor to an absolute screen position. Uses INT 33h
// function 04h on DOS, SDL_WarpMouseInWindow on Linux. Used to clamp
// the cursor to window edges during resize operations.
@ -195,4 +200,14 @@ const char *platformGetSystemInfo(const DisplayT *display);
// describing why it's invalid. Used by the file dialog's save-as validation.
const char *platformValidateFilename(const char *name);
// Change the working directory, including drive letter on DOS. Standard
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
// first when the path contains a drive prefix (e.g. "A:\DVX").
void platformChdir(const char *path);
// Return a pointer to the last directory separator in path, or NULL if
// none is found. On DOS this checks both '/' and '\\' since DJGPP
// accepts either. On other platforms only '/' is recognised.
char *platformPathDirEnd(const char *path);
#endif // DVX_PLATFORM_H

View file

@ -29,12 +29,14 @@
#include "dvxPlatform.h"
#include "../dvxPalette.h"
#include <ctype.h>
#include <dir.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <unistd.h>
// DJGPP-specific headers — this is the ONLY file that includes these
#include <dpmi.h>
@ -475,6 +477,24 @@ char platformAltScanToChar(int32_t scancode) {
}
// ============================================================
// platformChdir
// ============================================================
//
// Changes the working directory, switching the active DOS drive first
// when the path contains a drive letter (e.g. "A:\DVX"). Standard
// chdir() under DJGPP only changes the directory on the current drive.
void platformChdir(const char *path) {
if (path[0] && path[1] == ':') {
int drive = toupper((unsigned char)path[0]) - 'A';
setdisk(drive);
}
chdir(path);
}
// ============================================================
// platformFlushRect
// ============================================================
@ -1130,6 +1150,25 @@ void platformMouseInit(int32_t screenW, int32_t screenH) {
}
// ============================================================
// platformMouseSetAccel
// ============================================================
//
// Sets the double-speed threshold via INT 33h function 13h. When the
// mouse moves faster than this many mickeys per second, cursor movement
// is doubled. A very high value (e.g. 10000) effectively disables
// acceleration. Typical driver defaults are around 64 mickeys/sec.
void platformMouseSetAccel(int32_t threshold) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0013;
r.x.dx = threshold;
__dpmi_int(0x33, &r);
}
// ============================================================
// platformMousePoll
// ============================================================
@ -1218,6 +1257,25 @@ void platformMouseWarp(int32_t x, int32_t y) {
}
// ============================================================
// platformPathDirEnd
// ============================================================
//
// Returns a pointer to the last directory separator in path. DOS
// accepts both '/' and '\\', so we check for whichever appears last.
char *platformPathDirEnd(const char *path) {
char *fwd = strrchr(path, '/');
char *back = strrchr(path, '\\');
if (back > fwd) {
return back;
}
return fwd;
}
// ============================================================
// platformSpanCopy8
// ============================================================

20
dvx/thirdparty/ini/LICENSE vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2016 rxi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

63
dvx/thirdparty/ini/README.md vendored Normal file
View file

@ -0,0 +1,63 @@
# ini
A *tiny* ANSI C library for loading .ini config files
## Usage
The files **[ini.c](src/ini.c?raw=1)** and **[ini.h](src/ini.h?raw=1)** should
be dropped into an existing project.
The library has support for sections, comment lines and quoted string values
(with escapes). Unquoted values and keys are trimmed of whitespace when loaded.
```ini
; last modified 1 April 2001 by John Doe
[owner]
name = John Doe
organization = Acme Widgets Inc.
[database]
; use IP address in case network name resolution is not working
server = 192.0.2.62
port = 143
file = "payroll.dat"
```
An ini file can be loaded into memory by using the `ini_load()` function.
`NULL` is returned if the file cannot be loaded.
```c
ini_t *config = ini_load("config.ini");
```
The library provides two functions for retrieving values: the first is
`ini_get()`. Given a section and a key the corresponding value is returned if
it exists. If the `section` argument is `NULL` then all sections are searched.
```c
const char *name = ini_get(config, "owner", "name");
if (name) {
printf("name: %s\n", name);
}
```
The second, `ini_sget()`, takes the same arguments as `ini_get()` with the
addition of a scanf format string and a pointer for where to store the value.
```c
const char *server = "default";
int port = 80;
ini_sget(config, "database", "server", NULL, &server);
ini_sget(config, "database", "port", "%d", &port);
printf("server: %s:%d\n", server, port);
```
The `ini_free()` function is used to free the memory used by the `ini_t*`
object when we are done with it. Calling this function invalidates all string
pointers returned by the library.
```c
ini_free(config);
```
## License
This library is free software; you can redistribute it and/or modify it under
the terms of the MIT license. See [LICENSE](LICENSE) for details.

274
dvx/thirdparty/ini/src/ini.c vendored Normal file
View file

@ -0,0 +1,274 @@
/**
* Copyright (c) 2016 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "ini.h"
struct ini_t {
char *data;
char *end;
};
/* Case insensitive string compare */
static int strcmpci(const char *a, const char *b) {
for (;;) {
int d = tolower(*a) - tolower(*b);
if (d != 0 || !*a) {
return d;
}
a++, b++;
}
}
/* Returns the next string in the split data */
static char* next(ini_t *ini, char *p) {
p += strlen(p);
while (p < ini->end && *p == '\0') {
p++;
}
return p;
}
static void trim_back(ini_t *ini, char *p) {
while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
*p-- = '\0';
}
}
static char* discard_line(ini_t *ini, char *p) {
while (p < ini->end && *p != '\n') {
*p++ = '\0';
}
return p;
}
static char *unescape_quoted_value(ini_t *ini, char *p) {
/* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
* as escape sequences are always larger than their resultant data */
char *q = p;
p++;
while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
if (*p == '\\') {
/* Handle escaped char */
p++;
switch (*p) {
default : *q = *p; break;
case 'r' : *q = '\r'; break;
case 'n' : *q = '\n'; break;
case 't' : *q = '\t'; break;
case '\r' :
case '\n' :
case '\0' : goto end;
}
} else {
/* Handle normal char */
*q = *p;
}
q++, p++;
}
end:
return q;
}
/* Splits data in place into strings containing section-headers, keys and
* values using one or more '\0' as a delimiter. Unescapes quoted values */
static void split_data(ini_t *ini) {
char *value_start, *line_start;
char *p = ini->data;
while (p < ini->end) {
switch (*p) {
case '\r':
case '\n':
case '\t':
case ' ':
*p = '\0';
/* Fall through */
case '\0':
p++;
break;
case '[':
p += strcspn(p, "]\n");
*p = '\0';
break;
case ';':
p = discard_line(ini, p);
break;
default:
line_start = p;
p += strcspn(p, "=\n");
/* Is line missing a '='? */
if (*p != '=') {
p = discard_line(ini, line_start);
break;
}
trim_back(ini, p - 1);
/* Replace '=' and whitespace after it with '\0' */
do {
*p++ = '\0';
} while (*p == ' ' || *p == '\r' || *p == '\t');
/* Is a value after '=' missing? */
if (*p == '\n' || *p == '\0') {
p = discard_line(ini, line_start);
break;
}
if (*p == '"') {
/* Handle quoted string value */
value_start = p;
p = unescape_quoted_value(ini, p);
/* Was the string empty? */
if (p == value_start) {
p = discard_line(ini, line_start);
break;
}
/* Discard the rest of the line after the string value */
p = discard_line(ini, p);
} else {
/* Handle normal value */
p += strcspn(p, "\n");
trim_back(ini, p - 1);
}
break;
}
}
}
ini_t* ini_load(const char *filename) {
ini_t *ini = NULL;
FILE *fp = NULL;
int n, sz;
/* Init ini struct */
ini = malloc(sizeof(*ini));
if (!ini) {
goto fail;
}
memset(ini, 0, sizeof(*ini));
/* Open file */
fp = fopen(filename, "rb");
if (!fp) {
goto fail;
}
/* Get file size */
fseek(fp, 0, SEEK_END);
sz = ftell(fp);
rewind(fp);
/* Load file content into memory, null terminate, init end var */
ini->data = malloc(sz + 1);
ini->data[sz] = '\0';
ini->end = ini->data + sz;
n = fread(ini->data, 1, sz, fp);
if (n != sz) {
goto fail;
}
/* Prepare data */
split_data(ini);
/* Clean up and return */
fclose(fp);
return ini;
fail:
if (fp) fclose(fp);
if (ini) ini_free(ini);
return NULL;
}
void ini_free(ini_t *ini) {
free(ini->data);
free(ini);
}
const char* ini_get(ini_t *ini, const char *section, const char *key) {
char *current_section = "";
char *val;
char *p = ini->data;
if (*p == '\0') {
p = next(ini, p);
}
while (p < ini->end) {
if (*p == '[') {
/* Handle section */
current_section = p + 1;
} else {
/* Handle key */
val = next(ini, p);
if (!section || !strcmpci(section, current_section)) {
if (!strcmpci(p, key)) {
return val;
}
}
p = val;
}
p = next(ini, p);
}
return NULL;
}
int ini_sget(
ini_t *ini, const char *section, const char *key,
const char *scanfmt, void *dst
) {
const char *val = ini_get(ini, section, key);
if (!val) {
return 0;
}
if (scanfmt) {
sscanf(val, scanfmt, dst);
} else {
*((const char**) dst) = val;
}
return 1;
}

20
dvx/thirdparty/ini/src/ini.h vendored Normal file
View file

@ -0,0 +1,20 @@
/**
* Copyright (c) 2016 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `ini.c` for details.
*/
#ifndef INI_H
#define INI_H
#define INI_VERSION "0.1.1"
typedef struct ini_t ini_t;
ini_t* ini_load(const char *filename);
void ini_free(ini_t *ini);
const char* ini_get(ini_t *ini, const char *section, const char *key);
int ini_sget(ini_t *ini, const char *section, const char *key, const char *scanfmt, void *dst);
#endif

View file

@ -37,6 +37,7 @@
// otherwise dangling pointers would cause crashes. widgetDestroyChildren()
// and wgtDestroy() handle this cleanup.
clock_t sDblClickTicks = 0; // set from ctx->dblClickTicks during first paint
bool sDebugLayout = false;
WidgetT *sFocusedWidget = NULL; // currently focused widget (O(1) access, avoids tree walk)
WidgetT *sOpenPopup = NULL; // dropdown/combobox with open popup list

View file

@ -174,6 +174,7 @@ static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const
// and only one mouse can exist, this is safe.
extern bool sCursorBlinkOn; // text cursor blink phase (toggled by wgtUpdateCursorBlink)
extern clock_t sDblClickTicks; // double-click threshold (set from AppContextT.dblClickTicks)
extern bool sDebugLayout;
extern WidgetT *sClosedPopup; // popup that was just closed (prevents immediate reopen)
extern WidgetT *sFocusedWidget; // currently focused widget across all windows

View file

@ -321,7 +321,7 @@ static int32_t sClickCount = 0;
int32_t multiClickDetect(int32_t vx, int32_t vy) {
clock_t now = clock();
if ((now - sLastClickTime) < DBLCLICK_TICKS &&
if ((now - sLastClickTime) < sDblClickTicks &&
abs(vx - sLastClickX) < 4 && abs(vy - sLastClickY) < 4) {
sClickCount++;
} else {

View file

@ -10,6 +10,7 @@ LDFLAGS = -L../lib -ldvx -ltasks -lm
OBJDIR = ../obj/dvxshell
BINDIR = ../bin
CONFIGDIR = ../bin/config
LIBDIR = ../lib
SRCS = shellMain.c shellApp.c shellExport.c shellInfo.c
@ -18,7 +19,7 @@ TARGET = $(BINDIR)/dvx.exe
.PHONY: all clean libs
all: libs $(TARGET)
all: libs $(TARGET) $(CONFIGDIR)/dvx.ini
libs:
$(MAKE) -C ../dvx
@ -30,6 +31,9 @@ $(TARGET): $(OBJS) $(LIBDIR)/libdvx.a $(LIBDIR)/libtasks.a | $(BINDIR)
cat $(CWSDSTUB) $(BINDIR)/dvx > $@
rm -f $(BINDIR)/dvx
$(CONFIGDIR)/dvx.ini: ../dvx.ini | $(CONFIGDIR)
cp $< $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
@ -39,6 +43,9 @@ $(OBJDIR):
$(BINDIR):
mkdir -p $(BINDIR)
$(CONFIGDIR):
mkdir -p $(CONFIGDIR)
# Dependencies
$(OBJDIR)/shellMain.o: shellMain.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h
$(OBJDIR)/shellApp.o: shellApp.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h
@ -46,3 +53,4 @@ $(OBJDIR)/shellExport.o: shellExport.c shellApp.h shellInfo.h ../dvx/dvxApp.h .
$(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h ../dvx/dvxApp.h ../dvx/platform/dvxPlatform.h
clean:
rm -f $(OBJS) $(TARGET) $(BINDIR)/dvx.map $(BINDIR)/dvx.log
rm -rf $(CONFIGDIR)

View file

@ -5,6 +5,7 @@
#include "shellApp.h"
#include "dvxDialog.h"
#include "platform/dvxPlatform.h"
#include <dlfcn.h>
#include <stdlib.h>
@ -363,21 +364,15 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
app->dxeCtx.shellCtx = ctx;
app->dxeCtx.appId = id;
// Derive app directory from path (everything up to last '/' or '\').
// Derive app directory from path (everything up to the last separator).
// This lets apps load resources relative to their own location rather
// than the shell's working directory. Handles both Unix and DOS path
// separators because DJGPP accepts either.
// than the shell's working directory.
snprintf(app->dxeCtx.appDir, sizeof(app->dxeCtx.appDir), "%s", path);
char *lastSlash = strrchr(app->dxeCtx.appDir, '/');
char *lastBack = strrchr(app->dxeCtx.appDir, '\\');
char *sep = platformPathDirEnd(app->dxeCtx.appDir);
if (lastBack > lastSlash) {
lastSlash = lastBack;
}
if (lastSlash) {
*lastSlash = '\0';
if (sep) {
*sep = '\0';
} else {
app->dxeCtx.appDir[0] = '.';
app->dxeCtx.appDir[1] = '\0';

View file

@ -33,6 +33,7 @@
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxDraw.h"
#include "dvxPrefs.h"
#include "dvxVideo.h"
#include "dvxWm.h"
#include "taskswitch.h"
@ -143,8 +144,14 @@ DXE_EXPORT_TABLE(shellExportTable)
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
// dvxPrefs.h — preferences
DXE_EXPORT(prefsGetBool)
DXE_EXPORT(prefsGetInt)
DXE_EXPORT(prefsGetString)
// dvxApp.h — direct exports
DXE_EXPORT(dvxInit)
DXE_EXPORT(dvxSetMouseConfig)
DXE_EXPORT(dvxShutdown)
DXE_EXPORT(dvxUpdate)
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },

View file

@ -27,6 +27,7 @@
#include "shellApp.h"
#include "shellInfo.h"
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "platform/dvxPlatform.h"
#include <stdarg.h>
@ -220,12 +221,61 @@ void shellRegisterDesktopUpdate(void (*updateFn)(void)) {
// main
// ============================================================
int main(void) {
int main(int argc, char *argv[]) {
(void)argc;
// Change to the directory containing the executable so that relative
// paths (CONFIG/, APPS/, etc.) resolve correctly regardless of where
// the user launched from.
char exeDir[260];
strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
exeDir[sizeof(exeDir) - 1] = '\0';
char *sep = platformPathDirEnd(exeDir);
if (sep) {
*sep = '\0';
platformChdir(exeDir);
}
sLogFile = fopen("dvx.log", "w");
shellLog("DVX Shell starting...");
// Load preferences (missing file or keys silently use defaults)
prefsLoad("CONFIG/DVX.INI");
int32_t videoW = prefsGetInt("video", "width", 640);
int32_t videoH = prefsGetInt("video", "height", 480);
int32_t videoBpp = prefsGetInt("video", "bpp", 32);
shellLog("Preferences: video %ldx%ld %ldbpp", (long)videoW, (long)videoH, (long)videoBpp);
// Initialize GUI
int32_t result = dvxInit(&sCtx, 640, 480, 32);
int32_t result = dvxInit(&sCtx, videoW, videoH, videoBpp);
if (result == 0) {
// Apply mouse preferences
const char *wheelStr = prefsGetString("mouse", "wheel", "normal");
int32_t wheelDir = (strcmp(wheelStr, "reversed") == 0) ? -1 : 1;
int32_t dblClick = prefsGetInt("mouse", "doubleclick", 500);
// Map acceleration name to double-speed threshold (mickeys/sec).
// "off" sets a very high threshold so acceleration never triggers.
const char *accelStr = prefsGetString("mouse", "acceleration", "medium");
int32_t accelVal = 0;
if (strcmp(accelStr, "off") == 0) {
accelVal = 10000;
} else if (strcmp(accelStr, "low") == 0) {
accelVal = 100;
} else if (strcmp(accelStr, "medium") == 0) {
accelVal = 64;
} else if (strcmp(accelStr, "high") == 0) {
accelVal = 32;
}
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal);
shellLog("Preferences: mouse wheel=%s doubleclick=%ldms accel=%s", wheelStr, (long)dblClick, accelStr);
}
if (result != 0) {
shellLog("Failed to initialize DVX GUI (error %ld)", (long)result);
@ -359,6 +409,7 @@ int main(void) {
tsShutdown();
dvxShutdown(&sCtx);
prefsFree();
shellLog("DVX Shell exited.");

BIN
releases/dvx-a1.zip (Stored with Git LFS) Normal file

Binary file not shown.