Start of preferences system.
This commit is contained in:
parent
a3e7292591
commit
b1d74d7e3c
20 changed files with 742 additions and 30 deletions
4
Makefile
4
Makefile
|
|
@ -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
20
dvx.ini
Normal 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
|
||||
23
dvx/Makefile
23
dvx/Makefile
|
|
@ -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)
|
||||
|
|
|
|||
26
dvx/dvxApp.c
26
dvx/dvxApp.c
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
101
dvx/dvxPrefs.c
Normal 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
37
dvx/dvxPrefs.h
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
20
dvx/thirdparty/ini/LICENSE
vendored
Normal 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
63
dvx/thirdparty/ini/README.md
vendored
Normal 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
274
dvx/thirdparty/ini/src/ini.c
vendored
Normal 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
20
dvx/thirdparty/ini/src/ini.h
vendored
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE
|
|||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../dvx -I../tasks
|
||||
LDFLAGS = -L../lib -ldvx -ltasks -lm
|
||||
|
||||
OBJDIR = ../obj/dvxshell
|
||||
BINDIR = ../bin
|
||||
LIBDIR = ../lib
|
||||
OBJDIR = ../obj/dvxshell
|
||||
BINDIR = ../bin
|
||||
CONFIGDIR = ../bin/config
|
||||
LIBDIR = ../lib
|
||||
|
||||
SRCS = shellMain.c shellApp.c shellExport.c shellInfo.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue