Pathname and drive letter enhancements.
This commit is contained in:
parent
eaf965555b
commit
f20cec7333
93 changed files with 1181 additions and 558 deletions
|
|
@ -505,6 +505,7 @@ img { max-width: 100%; }
|
||||||
<li><a href="#lang.runtime">FileOpsMakePath</a></li>
|
<li><a href="#lang.runtime">FileOpsMakePath</a></li>
|
||||||
<li><a href="#lang.runtime">FileOpsPathIsAbsolute</a></li>
|
<li><a href="#lang.runtime">FileOpsPathIsAbsolute</a></li>
|
||||||
<li><a href="#lang.runtime">FileOpsPathIsInside</a></li>
|
<li><a href="#lang.runtime">FileOpsPathIsInside</a></li>
|
||||||
|
<li><a href="#lang.runtime">FileOpsPathNative$</a></li>
|
||||||
<li><a href="#lang.runtime">FileOpsPathSep$</a></li>
|
<li><a href="#lang.runtime">FileOpsPathSep$</a></li>
|
||||||
<li><a href="#lang.runtime">FileOpsReadOnlySystem</a></li>
|
<li><a href="#lang.runtime">FileOpsReadOnlySystem</a></li>
|
||||||
<li><a href="#lang.runtime">FileOpsReplaceFile</a></li>
|
<li><a href="#lang.runtime">FileOpsReplaceFile</a></li>
|
||||||
|
|
@ -819,6 +820,7 @@ img { max-width: 100%; }
|
||||||
<li><a href="#lang.procedures">PARAMARRAY</a></li>
|
<li><a href="#lang.procedures">PARAMARRAY</a></li>
|
||||||
<li><a href="#ctrl.common.props">Parent</a></li>
|
<li><a href="#ctrl.common.props">Parent</a></li>
|
||||||
<li><a href="#ide.menu.edit">Paste</a></li>
|
<li><a href="#ide.menu.edit">Paste</a></li>
|
||||||
|
<li><a href="#lang.runtime">Path Separators</a></li>
|
||||||
<li><a href="#lang.screen">PCOPY</a></li>
|
<li><a href="#lang.screen">PCOPY</a></li>
|
||||||
<li><a href="#lang.screen">PEN</a></li>
|
<li><a href="#lang.screen">PEN</a></li>
|
||||||
<li><a href="#lang.screen">PEN ON</a></li>
|
<li><a href="#lang.screen">PEN ON</a></li>
|
||||||
|
|
@ -2185,6 +2187,9 @@ End Sub</code></pre>
|
||||||
<li>Appending to a String variable with s = s + x or s = s & x no longer copies the whole string each time, so building long strings in a loop is much faster. When x is InputBox$, MsgBox, CreateForm, CreateControl or Shell, s is still read before x runs (left to right).</li>
|
<li>Appending to a String variable with s = s + x or s = s & x no longer copies the whole string each time, so building long strings in a loop is much faster. When x is InputBox$, MsgBox, CreateForm, CreateControl or Shell, s is still read before x runs (left to right).</li>
|
||||||
<li>And, Or, Xor, Eqv and Imp of two Booleans give a Boolean, and Not of a Boolean gives a Boolean, as in Visual Basic 4 and later: Print (a > 1) Or (b > 1) prints True instead of -1, and TypeName gives "Boolean". With a number on either side they still work on the bits and give a number.</li>
|
<li>And, Or, Xor, Eqv and Imp of two Booleans give a Boolean, and Not of a Boolean gives a Boolean, as in Visual Basic 4 and later: Print (a > 1) Or (b > 1) prints True instead of -1, and TypeName gives "Boolean". With a number on either side they still work on the bits and give a number.</li>
|
||||||
<li>Big disks and long file names: where the disk takes long file names (DVX under Windows, or a long file name driver loaded before DVX), OPEN, KILL, NAME, MKDIR, DIR$ and the other file statements use them, and the File Manager, the file dialogs and Terminal downloads keep them. A file can be bigger than 2 GB on a FAT32 disk; LOF, FILELEN, LOC and SEEK raise error 6 (Overflow) for a size or position past 2 GB rather than give a wrong number, and EOF stays right below it. FileOpsFreeBytes and FileOpsTotalBytes are right for disks past 2 GB.</li>
|
<li>Big disks and long file names: where the disk takes long file names (DVX under Windows, or a long file name driver loaded before DVX), OPEN, KILL, NAME, MKDIR, DIR$ and the other file statements use them, and the File Manager, the file dialogs and Terminal downloads keep them. A file can be bigger than 2 GB on a FAT32 disk; LOF, FILELEN, LOC and SEEK raise error 6 (Overflow) for a size or position past 2 GB rather than give a wrong number, and EOF stays right below it. FileOpsFreeBytes and FileOpsTotalBytes are right for disks past 2 GB.</li>
|
||||||
|
<li>Paths may be written with / or \ wherever a path is taken (OPEN, KILL, NAME, MKDIR, CHDIR, DIR$, FILELEN and the file and folder functions of the libraries), mixed too; paths DVX gives back (CURDIR$, App.Path, App.Data, the file dialogs, the FileOps results) use \, as DOS shows them. New: FileOpsPathNative$ gives a path of your own that form. DIR$("C:\*.*") lists the root of C: (before, it listed C:'s current folder). SHELL commands still go to DOS as written, where / starts a switch.</li>
|
||||||
|
<li>The IDE shows a project's file paths with \ and keeps writing them with / in the .dbp file, so a project reads the same everywhere.</li>
|
||||||
|
<li>The Open and Save As dialogs (DlgFileOpen$, DlgFileSave$ and the IDE's own) have a Drive list beside the Path box (Alt+D); a drive with no disk in it says it is not ready.</li>
|
||||||
<li>Changed: FileOpsValidName takes the folder the name is for, FileOpsValidName(folder, itemName), since the rules depend on its disk; XferSafeName$ takes the folder a file is received into, XferSafeName$(name, folder). New: FileOpsLongNames, FileOpsShortPath$, FileOpsValidLongName and FileOpsValidShortName (fileops.bas), and ShellArgQuote$ (shell.bas), which puts a path with spaces in quotes for a command line.</li>
|
<li>Changed: FileOpsValidName takes the folder the name is for, FileOpsValidName(folder, itemName), since the rules depend on its disk; XferSafeName$ takes the folder a file is received into, XferSafeName$(name, folder). New: FileOpsLongNames, FileOpsShortPath$, FileOpsValidLongName and FileOpsValidShortName (fileops.bas), and ShellArgQuote$ (shell.bas), which puts a path with spaces in quotes for a command line.</li>
|
||||||
<li>Programs compiled with an older release must be rebuilt. A program whose procedure table records more than 64 locals for a procedure is refused when it is loaded.</li>
|
<li>Programs compiled with an older release must be rebuilt. A program whose procedure table records more than 64 locals for a procedure is refused when it is loaded.</li>
|
||||||
<li>A class may have at most 65536 member variables and a form at most 65536 form-level variables (as many as the compiled program can number); more is a compile error ("Too many member variables in X", "Too many form-level variables"), and a program file that records more is refused when it is loaded.</li>
|
<li>A class may have at most 65536 member variables and a form at most 65536 form-level variables (as many as the compiled program can number); more is a compile error ("Too many member variables in X", "Too many form-level variables"), and a program file that records more is refused when it is loaded.</li>
|
||||||
|
|
@ -3287,6 +3292,7 @@ ErrorHandler:
|
||||||
<p>When used as a function, SHELL returns the exit code of the command.</p>
|
<p>When used as a function, SHELL returns the exit code of the command.</p>
|
||||||
<pre><code>Shell "DIR /B"
|
<pre><code>Shell "DIR /B"
|
||||||
exitCode = Shell("COPY A.TXT B.TXT")</code></pre>
|
exitCode = Shell("COPY A.TXT B.TXT")</code></pre>
|
||||||
|
<p>The command goes to DOS as it is written. DOS commands read / as the start of a switch (DIR /B), so write the paths in a command with \ (SHELL "DEL C:\TEMP\X.TXT"); FileOpsPathNative$ (fileops.bas) turns a path built with / into that form. Everywhere else in DVX BASIC a path may use / or \.</p>
|
||||||
<h2>SLEEP</h2>
|
<h2>SLEEP</h2>
|
||||||
<p>Pauses execution for the given number of seconds. With no argument, sleeps for 1 second. The desktop and the program's own forms keep running while it sleeps, SLEEP 0 just lets pending events run, and closing the program ends the sleep.</p>
|
<p>Pauses execution for the given number of seconds. With no argument, sleeps for 1 second. The desktop and the program's own forms keep running while it sleeps, SLEEP 0 just lets pending events run, and closing the program ends the sleep.</p>
|
||||||
<pre><code>SLEEP [seconds]</code></pre>
|
<pre><code>SLEEP [seconds]</code></pre>
|
||||||
|
|
@ -3624,6 +3630,7 @@ pos = SEEK(channel) ' Function: get current position</code></pre>
|
||||||
<div class="topic" id="lang.filesystem">
|
<div class="topic" id="lang.filesystem">
|
||||||
<h1>File System Statements</h1>
|
<h1>File System Statements</h1>
|
||||||
<p>DVX BASIC provides statements for working with the file system -- creating, deleting, renaming, and copying files and directories, and reading or setting file attributes.</p>
|
<p>DVX BASIC provides statements for working with the file system -- creating, deleting, renaming, and copying files and directories, and reading or setting file attributes.</p>
|
||||||
|
<p>A path may be written with / or \ between its parts, mixed too: OPEN "DATA/SCORES.TXT" and OPEN "DATA\SCORES.TXT" open the same file, and so do the FileOps, Shell and other library functions that take a path (SHELL commands are the exception: see SHELL). Paths DVX gives back -- CURDIR$, App.Path, App.Data, the file dialogs, FileOpsPathJoin$ and the other FileOps results -- use \, the separator DOS shows; FileOpsPathNative$ gives a path of your own that form before you show it.</p>
|
||||||
<h2>KILL</h2>
|
<h2>KILL</h2>
|
||||||
<p>Deletes a file.</p>
|
<p>Deletes a file.</p>
|
||||||
<pre><code>KILL filename$</code></pre>
|
<pre><code>KILL filename$</code></pre>
|
||||||
|
|
@ -3666,7 +3673,7 @@ End If
|
||||||
|
|
||||||
SetAttr "secret.dat", vbHidden + vbReadOnly</code></pre>
|
SetAttr "secret.dat", vbHidden + vbReadOnly</code></pre>
|
||||||
<h2>CURDIR$ / DIR$</h2>
|
<h2>CURDIR$ / DIR$</h2>
|
||||||
<p>CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done). DIR$ returns only files: never ".", ".." or subdirectories.</p>
|
<p>CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done). DIR$ returns only files: never ".", ".." or subdirectories. DIR$("C:\*.*") lists the root of C: (DIR$("C:*.*") the drive's current folder).</p>
|
||||||
<pre><code>cwd$ = CURDIR$
|
<pre><code>cwd$ = CURDIR$
|
||||||
first$ = DIR$("*.txt")
|
first$ = DIR$("*.txt")
|
||||||
WHILE first$ <> ""
|
WHILE first$ <> ""
|
||||||
|
|
@ -5003,7 +5010,7 @@ Sub cmdOK_Click ()
|
||||||
End Sub</code></pre>
|
End Sub</code></pre>
|
||||||
<h2>Files and Folders</h2>
|
<h2>Files and Folders</h2>
|
||||||
<p>With fileops.bas in the project, a program can show and change what is on the disks. FileOpsList(folder, filter) reads a folder once -- its folders and the files matching filter -- and returns a listing (a number; FileOpsListFree frees it). FileOpsListRows$ gives the whole listing as name, size, type and date columns, ready for a ListView's SetRows, so even a folder of hundreds of files fills the list at once; FileOpsListSort re-sorts it by name, size, type or date with folders first. FileOpsListName$, FileOpsListIsFolder and FileOpsListSize read single entries, and FileOpsListFind finds one by name.</p>
|
<p>With fileops.bas in the project, a program can show and change what is on the disks. FileOpsList(folder, filter) reads a folder once -- its folders and the files matching filter -- and returns a listing (a number; FileOpsListFree frees it). FileOpsListRows$ gives the whole listing as name, size, type and date columns, ready for a ListView's SetRows, so even a folder of hundreds of files fills the list at once; FileOpsListSort re-sorts it by name, size, type or date with folders first. FileOpsListName$, FileOpsListIsFolder and FileOpsListSize read single entries, and FileOpsListFind finds one by name.</p>
|
||||||
<p>Paths: FileOpsPathNormalize$ (the full path of an existing folder, or ""), FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (True for a path that is complete by itself, such as "C:\DOCS", False for one that counts from the current folder), FileOpsPathIsInside(path, folder) (True when path is folder itself or anywhere inside it: check it before copying or moving a folder, so it is never copied into itself), FileOpsPathSep$ (the character between folder names, "\" on DOS), FileOpsBaseName$ and FileOpsExtension$. FileOpsFilterMatch(itemName, filter) tells whether a name matches a filter the way the file dialogs match it: wildcards * and ?, several patterns separated by ; such as "*.bmp;*.png", case not mattering.</p>
|
<p>Paths: FileOpsPathNormalize$ (the full path of an existing folder, or ""), FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (True for a path that is complete by itself, such as "C:\DOCS", False for one that counts from the current folder), FileOpsPathIsInside(path, folder) (True when path is folder itself or anywhere inside it: check it before copying or moving a folder, so it is never copied into itself), FileOpsPathSep$ (the character between folder names, "\" on DOS), FileOpsPathNative$ (a path written with that character, for showing it: "C:/DOCS/A.TXT" becomes "C:\DOCS\A.TXT"), FileOpsBaseName$ and FileOpsExtension$. Every one of them takes / and \ alike and gives back \. FileOpsFilterMatch(itemName, filter) tells whether a name matches a filter the way the file dialogs match it: wildcards * and ?, several patterns separated by ; such as "*.bmp;*.png", case not mattering.</p>
|
||||||
<p>Files: FileOpsMakeDir and FileOpsRename (FileOpsError$ says why one failed), FileOpsTempPath$ and FileOpsReplaceFile (save safely: write the new file to FileOpsTempPath$(path$), then FileOpsReplaceFile swaps it in; the old file comes back if that fails), FileOpsValidName(folder, itemName) (whether a name can be used for a new file or folder in folder, by the rules of its disk; FileOpsError$ says why not), FileOpsValidLongName and FileOpsValidShortName (the long-name or the 8.3 rules by themselves, whatever the disk takes), FileOpsShortPath$ (the short 8.3 form of an existing path, for an old program that cannot take long names or blanks), FileOpsMakePath (a folder and every missing folder above it, which MKDIR does not make), FileOpsIsFolder, FileOpsGetAttr and FileOpsSetAttr (the FILEOPS_ATTR_ bits), FileOpsModified$ and FileOpsSetModified (when a file was last changed, read and set in the form "YYYY-MM-DD HH:MM"; FileOpsSetModified returns False for a date that does not exist), FileOpsTreeSize with FileOpsTreeBytes, FileOpsTreeFiles and FileOpsTreeFolders (the size of a whole folder) and FileOpsFormatSize$.</p>
|
<p>Files: FileOpsMakeDir and FileOpsRename (FileOpsError$ says why one failed), FileOpsTempPath$ and FileOpsReplaceFile (save safely: write the new file to FileOpsTempPath$(path$), then FileOpsReplaceFile swaps it in; the old file comes back if that fails), FileOpsValidName(folder, itemName) (whether a name can be used for a new file or folder in folder, by the rules of its disk; FileOpsError$ says why not), FileOpsValidLongName and FileOpsValidShortName (the long-name or the 8.3 rules by themselves, whatever the disk takes), FileOpsShortPath$ (the short 8.3 form of an existing path, for an old program that cannot take long names or blanks), FileOpsMakePath (a folder and every missing folder above it, which MKDIR does not make), FileOpsIsFolder, FileOpsGetAttr and FileOpsSetAttr (the FILEOPS_ATTR_ bits), FileOpsModified$ and FileOpsSetModified (when a file was last changed, read and set in the form "YYYY-MM-DD HH:MM"; FileOpsSetModified returns False for a date that does not exist), FileOpsTreeSize with FileOpsTreeBytes, FileOpsTreeFiles and FileOpsTreeFolders (the size of a whole folder) and FileOpsFormatSize$.</p>
|
||||||
<p>Drives: FileOpsDriveList$ (the drive letters), FileOpsDriveType (FILEOPS_DRIVE_FIXED for a hard disk, FILEOPS_DRIVE_REMOVABLE for a floppy disk drive, FILEOPS_DRIVE_REMOTE for a network drive or a CD-ROM; asking never makes a drive spin up), FileOpsBootDrive$ (the drive the computer started from), FileOpsFreeBytes, FileOpsFreeKb and FileOpsTotalBytes (the size of the disk; right for disks past 2 GB too), FileOpsLongNames(path) (True when the disk holding path takes long file names: up to 255 characters, with spaces and several dots; otherwise names are 8.3). FileOpsIsWritable(folder) is False where files cannot be made (a CD-ROM, a write-protected floppy), and FileOpsReadOnlySystem() is True when DVX itself runs from such a disk, so settings cannot be saved in its folder. FileOpsTempDir$() is the folder for scratch files ("" when there is none).</p>
|
<p>Drives: FileOpsDriveList$ (the drive letters), FileOpsDriveType (FILEOPS_DRIVE_FIXED for a hard disk, FILEOPS_DRIVE_REMOVABLE for a floppy disk drive, FILEOPS_DRIVE_REMOTE for a network drive or a CD-ROM; asking never makes a drive spin up), FileOpsBootDrive$ (the drive the computer started from), FileOpsFreeBytes, FileOpsFreeKb and FileOpsTotalBytes (the size of the disk; right for disks past 2 GB too), FileOpsLongNames(path) (True when the disk holding path takes long file names: up to 255 characters, with spaces and several dots; otherwise names are 8.3). FileOpsIsWritable(folder) is False where files cannot be made (a CD-ROM, a write-protected floppy), and FileOpsReadOnlySystem() is True when DVX itself runs from such a disk, so settings cannot be saved in its folder. FileOpsTempDir$() is the folder for scratch files ("" when there is none).</p>
|
||||||
<p>FileOpsCopyTree, FileOpsMoveTree and FileOpsDeleteTree do a whole folder at once. For a long copy, move or delete the program stays usable when it runs the work as a job, a little at a time from a Timer: FileOpsJobCreate, then FileOpsJobAddSource for each file or folder, then FileOpsJobRunFor from the timer until it returns FILEOPS_STEP_DONE or FILEOPS_STEP_CANCELLED. When it returns FILEOPS_STEP_ASK (a file already exists, a file is read-only) or FILEOPS_STEP_ERROR, show FileOpsJobMessage$ and pass the user's choice to FileOpsJobAnswer. FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone and FileOpsJobBytesTotal show the progress, and FileOpsJobCancel stops the job. Files copied from a CD-ROM are read-only; FileOpsJobClearAttr job, FILEOPS_ATTR_READONLY before the job runs makes every copy changeable (FILEOPS_ATTR_EDITABLE clears all four attribute bits). Whatever a program leaves open is freed when it ends.</p>
|
<p>FileOpsCopyTree, FileOpsMoveTree and FileOpsDeleteTree do a whole folder at once. For a long copy, move or delete the program stays usable when it runs the work as a job, a little at a time from a Timer: FileOpsJobCreate, then FileOpsJobAddSource for each file or folder, then FileOpsJobRunFor from the timer until it returns FILEOPS_STEP_DONE or FILEOPS_STEP_CANCELLED. When it returns FILEOPS_STEP_ASK (a file already exists, a file is read-only) or FILEOPS_STEP_ERROR, show FileOpsJobMessage$ and pass the user's choice to FileOpsJobAnswer. FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone and FileOpsJobBytesTotal show the progress, and FileOpsJobCancel stops the job. Files copied from a CD-ROM are read-only; FileOpsJobClearAttr job, FILEOPS_ATTR_READONLY before the job runs makes every copy changeable (FILEOPS_ATTR_EDITABLE clears all four attribute bits). Whatever a program leaves open is freed when it ends.</p>
|
||||||
|
|
|
||||||
|
|
@ -1028,6 +1028,7 @@ img { max-width: 100%; }
|
||||||
<li><a href="#user.printing">Page Setup</a></li>
|
<li><a href="#user.printing">Page Setup</a></li>
|
||||||
<li><a href="#api.palette">Palette</a></li>
|
<li><a href="#api.palette">Palette</a></li>
|
||||||
<li><a href="#games.screen">Palette</a></li>
|
<li><a href="#games.screen">Palette</a></li>
|
||||||
|
<li><a href="#api.platform">Path Separators</a></li>
|
||||||
<li><a href="#lib.audio">PC speaker</a></li>
|
<li><a href="#lib.audio">PC speaker</a></li>
|
||||||
<li><a href="#arch.colors">Pixel Format</a></li>
|
<li><a href="#arch.colors">Pixel Format</a></li>
|
||||||
<li><a href="#arch.colors">PixelFormatT</a></li>
|
<li><a href="#arch.colors">PixelFormatT</a></li>
|
||||||
|
|
@ -1071,8 +1072,15 @@ img { max-width: 100%; }
|
||||||
<li><a href="#games.timing">platformMicros</a></li>
|
<li><a href="#games.timing">platformMicros</a></li>
|
||||||
<li><a href="#api.platform">platformMousePoll</a></li>
|
<li><a href="#api.platform">platformMousePoll</a></li>
|
||||||
<li><a href="#api.platform">platformNormalizeFKeyScan</a></li>
|
<li><a href="#api.platform">platformNormalizeFKeyScan</a></li>
|
||||||
|
<li><a href="#api.platform">platformPathDirName</a></li>
|
||||||
<li><a href="#api.platform">platformPathIsAbsolute</a></li>
|
<li><a href="#api.platform">platformPathIsAbsolute</a></li>
|
||||||
|
<li><a href="#api.platform">platformPathIsSep</a></li>
|
||||||
|
<li><a href="#api.platform">platformPathIsUnder</a></li>
|
||||||
|
<li><a href="#api.platform">platformPathJoin</a></li>
|
||||||
|
<li><a href="#api.platform">platformPathNative</a></li>
|
||||||
|
<li><a href="#api.platform">platformPathSame</a></li>
|
||||||
<li><a href="#api.platform">platformPathSibling</a></li>
|
<li><a href="#api.platform">platformPathSibling</a></li>
|
||||||
|
<li><a href="#api.platform">platformPathTidy</a></li>
|
||||||
<li><a href="#api.platform">platformReplaceFile</a></li>
|
<li><a href="#api.platform">platformReplaceFile</a></li>
|
||||||
<li><a href="#api.platform">platformSetAppExitHook</a></li>
|
<li><a href="#api.platform">platformSetAppExitHook</a></li>
|
||||||
<li><a href="#api.platform">platformSetLogMemory</a></li>
|
<li><a href="#api.platform">platformSetLogMemory</a></li>
|
||||||
|
|
@ -1303,7 +1311,6 @@ img { max-width: 100%; }
|
||||||
<li><a href="#shell.basic">ShellOpenDocument</a></li>
|
<li><a href="#shell.basic">ShellOpenDocument</a></li>
|
||||||
<li><a href="#shell.lifecycle">shellOpenHelp</a></li>
|
<li><a href="#shell.lifecycle">shellOpenHelp</a></li>
|
||||||
<li><a href="#shell.desktop">shellOpenTaskManager</a></li>
|
<li><a href="#shell.desktop">shellOpenTaskManager</a></li>
|
||||||
<li><a href="#shell.query">shellPathsMatch</a></li>
|
|
||||||
<li><a href="#shell.basic">ShellPriority</a></li>
|
<li><a href="#shell.basic">ShellPriority</a></li>
|
||||||
<li><a href="#shell.basic">ShellProgramCount</a></li>
|
<li><a href="#shell.basic">ShellProgramCount</a></li>
|
||||||
<li><a href="#shell.basic">ShellProgramDescription</a></li>
|
<li><a href="#shell.basic">ShellProgramDescription</a></li>
|
||||||
|
|
@ -2096,6 +2103,7 @@ img { max-width: 100%; }
|
||||||
<h2>Parts of the Dialog Box</h2>
|
<h2>Parts of the Dialog Box</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Path shows the folder you are looking at. Type a different folder or drive here (for example D: or C:\DOCS) and press Enter to go there.</li>
|
<li>Path shows the folder you are looking at. Type a different folder or drive here (for example D: or C:\DOCS) and press Enter to go there.</li>
|
||||||
|
<li>Drive, beside it, lists the computer's drives and shows which one you are on. Choose another to go to the top folder of that drive. If there is no disk in it (an empty floppy drive, say), the dialog tells you the drive is not ready and stays where it was.</li>
|
||||||
<li>The list shows the folders in square brackets, such as [DOCS], followed by the files. The [..] entry takes you up one folder.</li>
|
<li>The list shows the folders in square brackets, such as [DOCS], followed by the files. The [..] entry takes you up one folder.</li>
|
||||||
<li>Filter, when present, chooses which kinds of files the list shows, for example only text files.</li>
|
<li>Filter, when present, chooses which kinds of files the list shows, for example only text files.</li>
|
||||||
<li>File name is where you type or check the name of the file.</li>
|
<li>File name is where you type or check the name of the file.</li>
|
||||||
|
|
@ -2104,7 +2112,7 @@ img { max-width: 100%; }
|
||||||
<p>Double-click a folder to look inside it. Click a file to put its name in the File name box, then click Open, or just double-click the file. If you type the name of a file that does not exist, the dialog asks whether you want to create it.</p>
|
<p>Double-click a folder to look inside it. Click a file to put its name in the File name box, then click Open, or just double-click the file. If you type the name of a file that does not exist, the dialog asks whether you want to create it.</p>
|
||||||
<h2>Saving a File</h2>
|
<h2>Saving a File</h2>
|
||||||
<p>Go to the folder you want, type a name in the File name box and click Save. If you leave off the ending (such as .txt), the dialog adds the one from the Filter box. If a file with that name already exists, DVX asks whether to replace it.</p>
|
<p>Go to the folder you want, type a name in the File name box and click Save. If you leave off the ending (such as .txt), the dialog adds the one from the Filter box. If a file with that name already exists, DVX asks whether to replace it.</p>
|
||||||
<blockquote><strong>Tip:</strong> There is no separate drive list. To change drives, type the drive letter and a colon, such as A:, in the Path box and press Enter.</blockquote>
|
<blockquote><strong>Tip:</strong> Alt+D opens the Drive list from the keyboard. You can also change drives by typing the drive letter and a colon, such as A:, in the Path box and pressing Enter.</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic" id="user.clipboard">
|
<div class="topic" id="user.clipboard">
|
||||||
<h1>Copying and Pasting</h1>
|
<h1>Copying and Pasting</h1>
|
||||||
|
|
@ -2589,7 +2597,7 @@ img { max-width: 100%; }
|
||||||
dvxMessageBox Generic message box with button/icon flags (MB_OK, MB_YESNO, MB_ICONERROR, etc.)
|
dvxMessageBox Generic message box with button/icon flags (MB_OK, MB_YESNO, MB_ICONERROR, etc.)
|
||||||
dvxErrorBox Shortcut for MB_OK + MB_ICONERROR
|
dvxErrorBox Shortcut for MB_OK + MB_ICONERROR
|
||||||
dvxInfoBox Shortcut for MB_OK + MB_ICONINFO
|
dvxInfoBox Shortcut for MB_OK + MB_ICONINFO
|
||||||
dvxFileDialog File open/save dialog with directory navigation and filter dropdown
|
dvxFileDialog File open/save dialog with directory navigation, a drive dropdown and a filter dropdown
|
||||||
dvxInputBox Single-line text input with OK/Cancel
|
dvxInputBox Single-line text input with OK/Cancel
|
||||||
dvxIntInputBox Integer input with spinner, clamp range, and step
|
dvxIntInputBox Integer input with spinner, clamp range, and step
|
||||||
dvxChoiceDialog List selection with OK/Cancel
|
dvxChoiceDialog List selection with OK/Cancel
|
||||||
|
|
@ -5583,7 +5591,7 @@ int32_t pick = dvxButtonBox(ctx, "Copy", "The disk is full."
|
||||||
FD_SAVE 0x0001 File save dialog (with overwrite prompt)</pre>
|
FD_SAVE 0x0001 File save dialog (with overwrite prompt)</pre>
|
||||||
<h3>dvxFileDialog</h3>
|
<h3>dvxFileDialog</h3>
|
||||||
<pre><code>bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);</code></pre>
|
<pre><code>bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);</code></pre>
|
||||||
<p>Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a filename input, and an optional filter dropdown. The path box always shows the current folder. Typing "D:" or "D:folder" in the path box changes drive/folder; the file name box also accepts a relative or absolute path ("sub\x.txt", "C:\X\A.TXT") and navigates to its folder. A folder that does not exist shows an "Invalid Path" error. The dialog closes at once if the app that owns it is ended.</p>
|
<p>Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a drive dropdown, a filename input, and an optional filter dropdown. The path box always shows the current folder, and the drive dropdown (platformDriveList, so listing the drives never touches empty floppy or CD-ROM drives) its drive; choosing a drive goes to its root, and a drive that cannot be read shows "Drive X: is not ready." and leaves the dialog where it was. Typing "D:" or "D:folder" in the path box changes drive/folder; the file name box also accepts a relative or absolute path ("sub\x.txt", "C:\X\A.TXT") and navigates to its folder. A folder that does not exist shows an "Invalid Path" error. The dialog closes at once if the app that owns it is ended.</p>
|
||||||
<pre> Parameter Description
|
<pre> Parameter Description
|
||||||
--------- -----------
|
--------- -----------
|
||||||
ctx Application context
|
ctx Application context
|
||||||
|
|
@ -6326,7 +6334,7 @@ bool platformShortPath(const char *path, char *out, int32_t outSize);</co
|
||||||
<p>platformLongNames is true when the drive holding path (NULL or "": the current drive) takes long file names. On DOS that is a loaded long-name driver covering that drive; elsewhere always.</p>
|
<p>platformLongNames is true when the drive holding path (NULL or "": the current drive) takes long file names. On DOS that is a loaded long-name driver covering that drive; elsewhere always.</p>
|
||||||
<p>platformValidateLongName and platformValidateShortName are the two DOS name rules by themselves, whatever the disk takes; each returns NULL or why the name is not valid. A long name has 1 to PLATFORM_LONG_NAME_MAX characters and may hold spaces, several dots and + , ; = [ ]; it may not hold a control character or \ / : * ? " < > |, and may not end with a dot or a space (DOS drops them). An 8.3 name has 1 to 8 printable ASCII characters, then at most one dot and up to 3 more, none of them a space or " * + , / : ; < = > ? [ \ ] |. Neither may be . or .., or a device name (CON, PRN, AUX, NUL, CLOCK$, COM1 to COM9, LPT1 to LPT9) before its first dot. They are shared code, so the host tests check the DOS rules too.</p>
|
<p>platformValidateLongName and platformValidateShortName are the two DOS name rules by themselves, whatever the disk takes; each returns NULL or why the name is not valid. A long name has 1 to PLATFORM_LONG_NAME_MAX characters and may hold spaces, several dots and + , ; = [ ]; it may not hold a control character or \ / : * ? " < > |, and may not end with a dot or a space (DOS drops them). An 8.3 name has 1 to 8 printable ASCII characters, then at most one dot and up to 3 more, none of them a space or " * + , / : ; < = > ? [ \ ] |. Neither may be . or .., or a device name (CON, PRN, AUX, NUL, CLOCK$, COM1 to COM9, LPT1 to LPT9) before its first dot. They are shared code, so the host tests check the DOS rules too.</p>
|
||||||
<p>platformLongNameChar is true when a long name may hold character c; a file transfer uses it to turn the characters a sender's name cannot keep into _.</p>
|
<p>platformLongNameChar is true when a long name may hold character c; a file transfer uses it to turn the characters a sender's name cannot keep into _.</p>
|
||||||
<p>platformShortPath writes the short (8.3) form of an existing path into out, for what reads names before a long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (an old program's command line). On a drive with long names DOS gives every part's short name (INT 21h AX=7160h CL=1); elsewhere, and when DOS cannot, path itself is copied. False when out is too small.</p>
|
<p>platformShortPath writes the short (8.3) form of an existing path into out, for what reads names before a long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (an old program's command line). On a drive with long names DOS gives every part's short name (INT 21h AX=7160h CL=1); elsewhere, and when DOS cannot, path itself is copied (platformPathTidy: "C:/DVX" gives "C:\\DVX"). False when out is too small.</p>
|
||||||
<h3>platformStatSize</h3>
|
<h3>platformStatSize</h3>
|
||||||
<pre><code>uint64_t platformStatSize(const struct stat *st);</code></pre>
|
<pre><code>uint64_t platformStatSize(const struct stat *st);</code></pre>
|
||||||
<p>The size stat reported for a file. A FAT32 file reaches 4 GB - 1 byte, but the DOS C library's st_size is a signed 32-bit off_t, so a file past 2 GB reads as negative there; platformStatSize reads it as the unsigned 32-bit size DOS keeps. Use it wherever a file size comes from stat or fstat.</p>
|
<p>The size stat reported for a file. A FAT32 file reaches 4 GB - 1 byte, but the DOS C library's st_size is a signed 32-bit off_t, so a file past 2 GB reads as negative there; platformStatSize reads it as the unsigned 32-bit size DOS keeps. Use it wherever a file size comes from stat or fstat.</p>
|
||||||
|
|
@ -6336,18 +6344,36 @@ bool platformShortPath(const char *path, char *out, int32_t outSize);</co
|
||||||
<h3>platformChdir</h3>
|
<h3>platformChdir</h3>
|
||||||
<pre><code>int32_t platformChdir(const char *path);</code></pre>
|
<pre><code>int32_t platformChdir(const char *path);</code></pre>
|
||||||
<p>Change the working directory, including drive letter on DOS (switches drive first when the path starts with "X:"). If the chdir fails the previous drive is restored. A drive prefix that is not a letter is rejected. Returns 0 on success, -1 on failure.</p>
|
<p>Change the working directory, including drive letter on DOS (switches drive first when the path starts with "X:"). If the chdir fails the previous drive is restored. A drive prefix that is not a letter is rejected. Returns 0 on success, -1 on failure.</p>
|
||||||
|
<h3>Path separators: platformPathIsSep / platformPathNative / platformPathSame / platformPathIsUnder / platformPathJoin / platformPathDirName / platformPathTidy</h3>
|
||||||
|
<pre><code>bool platformPathIsSep(char c);
|
||||||
|
char *platformPathNative(char *path);
|
||||||
|
bool platformPathSame(const char *a, const char *b);
|
||||||
|
const char *platformPathIsUnder(const char *path, const char *dir);
|
||||||
|
bool platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
|
||||||
|
bool platformPathDirName(const char *path, char *out, int32_t outSize);
|
||||||
|
char *platformPathTidy(char *path);</code></pre>
|
||||||
|
<p>DVX paths follow DOS rules on every platform: '/' and '\\' are the same separator, so a path a program writes with '/' works on DOS exactly as with '\\', and names compare without case (DOS and the host build also share INI files, projects and program paths). Every path DVX shows uses the native separator, DVX_PATH_SEP ('\\' on DOS). DVX's own path code goes through these functions; a program that splits, compares or joins paths should too, never testing for one separator by hand.</p>
|
||||||
|
<ul>
|
||||||
|
<li>platformPathIsSep: true for '/' and '\\'.</li>
|
||||||
|
<li>platformPathNative: rewrites path's separators as DVX_PATH_SEP, in place, before it is shown (the DOS C library's getcwd and realpath give "c:/dvx"). Returns path.</li>
|
||||||
|
<li>platformPathSame: true when a and b name the same place by DOS rules: separators alike, a trailing one ignored, case ignored. Text only: "." or a relative path is not resolved.</li>
|
||||||
|
<li>platformPathIsUnder: when path is dir or lies below it, the rest of path after dir and its separator ("" for dir itself); NULL otherwise.</li>
|
||||||
|
<li>platformPathJoin: dir and name with one separator between them (none added when dir ends in one), every separator native; an absolute name, or an empty dir, gives name alone. False when it does not fit.</li>
|
||||||
|
<li>platformPathDirName: the folder part of path in native separators: a root keeps its separator ("C:\\X.TXT" gives "C:\\", since "C:" alone is the drive's current folder), "C:X.TXT" gives "C:", a bare name gives ".". False when it does not fit.</li>
|
||||||
|
<li>platformPathTidy: platformPathNative, then each run of separators made one (a leading pair, a network "\\\\server", kept) and a trailing separator dropped unless it is a root's: the form for a path handed to DOS directly, without the C library in between. platformShortPath and the file operations library's attribute calls use it.</li>
|
||||||
|
</ul>
|
||||||
<h3>platformPathDirEnd</h3>
|
<h3>platformPathDirEnd</h3>
|
||||||
<pre><code>char *platformPathDirEnd(const char *path);</code></pre>
|
<pre><code>char *platformPathDirEnd(const char *path);</code></pre>
|
||||||
<p>Return a pointer to the last directory separator in path, or NULL. On DOS accepts both "/" and "\\".</p>
|
<p>Return a pointer to the last directory separator in path (platformPathIsSep), or NULL.</p>
|
||||||
<h3>platformPathIsAbsolute</h3>
|
<h3>platformPathIsAbsolute</h3>
|
||||||
<pre><code>bool platformPathIsAbsolute(const char *path);</code></pre>
|
<pre><code>bool platformPathIsAbsolute(const char *path);</code></pre>
|
||||||
<p>True for a path rooted at a separator ('/' or '\\') or qualified with a drive letter ("C:..."). Both separators count on every platform. A NULL path is not absolute.</p>
|
<p>True for a path rooted at a separator ('/' or '\\') or qualified with a drive letter ("C:..."). A NULL path is not absolute.</p>
|
||||||
<h3>platformPathBaseName</h3>
|
<h3>platformPathBaseName</h3>
|
||||||
<pre><code>const char *platformPathBaseName(const char *path);</code></pre>
|
<pre><code>const char *platformPathBaseName(const char *path);</code></pre>
|
||||||
<p>Return a pointer to the leaf (basename) portion of path.</p>
|
<p>Return a pointer to the leaf (basename) portion of path: after the last separator, or after a drive ("C:A.TXT" gives "A.TXT").</p>
|
||||||
<h3>platformCopyFile</h3>
|
<h3>platformCopyFile</h3>
|
||||||
<pre><code>bool platformCopyFile(const char *srcPath, const char *dstPath);</code></pre>
|
<pre><code>bool platformCopyFile(const char *srcPath, const char *dstPath);</code></pre>
|
||||||
<p>Byte-for-byte file copy. Returns false on any open, read, or write failure; a partially written destination is removed so no truncated copy is left behind.</p>
|
<p>Byte-for-byte file copy. Returns false on any open, read, or write failure; a partially written destination is removed so no truncated copy is left behind. A copy onto itself (platformPathSame) does nothing and returns true: opening the copy would otherwise empty the original first.</p>
|
||||||
<h3>platformPathSibling</h3>
|
<h3>platformPathSibling</h3>
|
||||||
<pre><code>#define PLATFORM_TMP_EXT ".$"
|
<pre><code>#define PLATFORM_TMP_EXT ".$"
|
||||||
#define PLATFORM_BAK_EXT ".~"
|
#define PLATFORM_BAK_EXT ".~"
|
||||||
|
|
@ -6398,7 +6424,7 @@ bool platformDirIsWritable(const char *dir);
|
||||||
const char *platformTempDir(void);
|
const char *platformTempDir(void);
|
||||||
void platformSetLogMemory(void);
|
void platformSetLogMemory(void);
|
||||||
const char *platformLogMemory(void);</code></pre>
|
const char *platformLogMemory(void);</code></pre>
|
||||||
<p>The loader tests at startup whether the DVX folder can be written to (platformDirIsWritable creates and removes a scratch file in dir) and records the answer with platformSetReadOnly. When DVX runs from a CD-ROM or a write-protected disk (platformIsReadOnly is true): the log goes to DVX.LOG in the TEMP folder (platformTempDir: the TEMP or else TMP variable, NULL when neither is set) or, without one, to memory; the boot-time help rebuild is skipped (its output and counts could not be saved); prefsSave and prefsSaveAs keep a file they cannot write in memory, and prefsLoad reads it back until DVX exits; the shell says so once at startup; and a program that allows several copies runs only once when there is no TEMP folder (a second copy is a temporary copy of the module). platformSetLogMemory sends dvxLog to a buffer holding the latest PLATFORM_LOG_MEM_MAX bytes (the oldest whole lines are dropped), which platformLogMemory returns; platformSetLogPath goes back to a file. SETUP.EXE logs to memory unless /LOG= is given, so it never writes to its own disk.</p>
|
<p>The loader tests at startup whether the DVX folder can be written to (platformDirIsWritable creates and removes a scratch file in dir) and records the answer with platformSetReadOnly. When DVX runs from a CD-ROM or a write-protected disk (platformIsReadOnly is true): the log goes to DVX.LOG in the TEMP folder (platformTempDir: the TEMP or else TMP variable in native separators, NULL when neither is set) or, without one, to memory; the boot-time help rebuild is skipped (its output and counts could not be saved); prefsSave and prefsSaveAs keep a file they cannot write in memory, and prefsLoad reads it back until DVX exits; the shell says so once at startup; and a program that allows several copies runs only once when there is no TEMP folder (a second copy is a temporary copy of the module). platformSetLogMemory sends dvxLog to a buffer holding the latest PLATFORM_LOG_MEM_MAX bytes (the oldest whole lines are dropped), which platformLogMemory returns; platformSetLogPath goes back to a file. SETUP.EXE logs to memory unless /LOG= is given, so it never writes to its own disk.</p>
|
||||||
<h3>platformLineEnding / platformStripLineEndings</h3>
|
<h3>platformLineEnding / platformStripLineEndings</h3>
|
||||||
<pre><code>const char *platformLineEnding(void);
|
<pre><code>const char *platformLineEnding(void);
|
||||||
int32_t platformStripLineEndings(char *buf, int32_t len);</code></pre>
|
int32_t platformStripLineEndings(char *buf, int32_t len);</code></pre>
|
||||||
|
|
@ -7609,10 +7635,7 @@ bool shellCloseApp(AppContextT *ctx, int32_t appId);</code></pre>
|
||||||
<p>Bring an app to the front, as the Task Manager's Switch To does (it calls this): its topmost visible window that is not being destroyed is restored when minimized (dvxRestoreWindow; a maximized window stays maximized), else raised (dvxRaiseWindow), and gets the focus. False when the app has no visible window. BASIC: ShellRunningActivate.</p>
|
<p>Bring an app to the front, as the Task Manager's Switch To does (it calls this): its topmost visible window that is not being destroyed is restored when minimized (dvxRestoreWindow; a maximized window stays maximized), else raised (dvxRaiseWindow), and gets the focus. False when the app has no visible window. BASIC: ShellRunningActivate.</p>
|
||||||
<h2>shellAppFindByPath</h2>
|
<h2>shellAppFindByPath</h2>
|
||||||
<pre><code>ShellAppT *shellAppFindByPath(const char *path);</code></pre>
|
<pre><code>ShellAppT *shellAppFindByPath(const char *path);</code></pre>
|
||||||
<p>Return an active app slot loaded from path, or NULL. Paths are compared with shellPathsMatch.</p>
|
<p>Return an active app slot loaded from path, or NULL. Paths are compared with platformPathSame (/ and \ the same separator, a trailing one ignored, case ignored on DOS).</p>
|
||||||
<h2>shellPathsMatch</h2>
|
|
||||||
<pre><code>bool shellPathsMatch(const char *a, const char *b);</code></pre>
|
|
||||||
<p>True when two paths name the same file under DOS rules: case-insensitive, with / and \ the same separator.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="topic" id="shell.startup">
|
<div class="topic" id="shell.startup">
|
||||||
<h1>Starting Up</h1>
|
<h1>Starting Up</h1>
|
||||||
|
|
@ -7713,7 +7736,7 @@ void shellOpenTaskManager(AppContextT *ctx);</code></pre>
|
||||||
} ShellProgramT;</code></pre>
|
} ShellProgramT;</code></pre>
|
||||||
<h2>shellProgramScan</h2>
|
<h2>shellProgramScan</h2>
|
||||||
<pre><code>int32_t shellProgramScan(const char *dir, const char *skipPath, bool readInfo, ShellProgressFnT progress, void *progressCtx, ShellProgramT **outList);</code></pre>
|
<pre><code>int32_t shellProgramScan(const char *dir, const char *skipPath, bool readInfo, ShellProgressFnT progress, void *progressCtx, ShellProgramT **outList);</code></pre>
|
||||||
<p>Every program under dir (NULL for SHELL_PROG_DIR) and its subfolders, except skipPath (compared with shellPathsMatch; NULL for none), in the order found. With readInfo each entry is filled by shellProgramInfo; without it only path is set, which is fast (no program file is opened) and enough to notice programs being added or removed. progress, when not NULL, is called with progressCtx after each program (for example to keep the screen responsive). Returns the count; *outList is an array of that many entries, NULL when there are none. Free it with shellProgramListFree.</p>
|
<p>Every program under dir (NULL for SHELL_PROG_DIR) and its subfolders, except skipPath (compared with platformPathSame; NULL for none), in the order found. With readInfo each entry is filled by shellProgramInfo; without it only path is set, which is fast (no program file is opened) and enough to notice programs being added or removed. progress, when not NULL, is called with progressCtx after each program (for example to keep the screen responsive). Returns the count; *outList is an array of that many entries, NULL when there are none. Free it with shellProgramListFree.</p>
|
||||||
<h2>shellProgramInfo</h2>
|
<h2>shellProgramInfo</h2>
|
||||||
<pre><code>void shellProgramInfo(const char *path, ShellProgramT *out);</code></pre>
|
<pre><code>void shellProgramInfo(const char *path, ShellProgramT *out);</code></pre>
|
||||||
<p>Fill out with the program's path, display name and description. Always succeeds: without resources (or for a file that is not a program) the name is the file name without its extension, first letter capitalized, and the description is empty.</p>
|
<p>Fill out with the program's path, display name and description. Always succeeds: without resources (or for a file that is not a program) the name is the file name without its extension, first letter capitalized, and the description is empty.</p>
|
||||||
|
|
@ -7825,7 +7848,7 @@ int32_t ShellRunningClose(int32_t appId);
|
||||||
int32_t ShellRunningKill(int32_t appId);
|
int32_t ShellRunningKill(int32_t appId);
|
||||||
int32_t ShellAppIsHung(int32_t appId);
|
int32_t ShellAppIsHung(int32_t appId);
|
||||||
int32_t ShellAppMemory(int32_t appId);</code></pre>
|
int32_t ShellAppMemory(int32_t appId);</code></pre>
|
||||||
<p>The Task Manager's operations, one routine each with it. ShellRunningId is shellRunningAppId. ShellRunningName and ShellRunningPath are the ShellAppT name and path of an app in AppStateRunningE ("" otherwise; the same state every call below requires). ShellRunningFind is shellAppFindByPath (shellPathsMatch rules), 0 when that app is not running. ShellRunningActivate is shellActivateApp. ShellRunningClose is shellRequestAppClose: the app is asked at the main loop's top level, after the BASIC code has returned; True when the request was recorded. ShellRunningKill is shellForceKillApp followed by shellDesktopUpdate (End Task: no shutdownFn, callbacks purged, the unload deferred while the app's code is on a stack); the running program itself is refused (BASIC's END ends it). ShellAppIsHung is dvxAppIsHung and ShellAppMemory dvxMemGetAppUsage (clamped to INT32_MAX; 0 for an app that is not running).</p>
|
<p>The Task Manager's operations, one routine each with it. ShellRunningId is shellRunningAppId. ShellRunningName and ShellRunningPath are the ShellAppT name and path of an app in AppStateRunningE ("" otherwise; the same state every call below requires). ShellRunningFind is shellAppFindByPath (platformPathSame rules), 0 when that app is not running. ShellRunningActivate is shellActivateApp. ShellRunningClose is shellRequestAppClose: the app is asked at the main loop's top level, after the BASIC code has returned; True when the request was recorded. ShellRunningKill is shellForceKillApp followed by shellDesktopUpdate (End Task: no shutdownFn, callbacks purged, the unload deferred while the app's code is on a stack); the running program itself is refused (BASIC's END ends it). ShellAppIsHung is dvxAppIsHung and ShellAppMemory dvxMemGetAppUsage (clamped to INT32_MAX; 0 for an app that is not running).</p>
|
||||||
<h2>Command lines and priority</h2>
|
<h2>Command lines and priority</h2>
|
||||||
<pre><code>const char *ShellCommandProgram(const char *command);
|
<pre><code>const char *ShellCommandProgram(const char *command);
|
||||||
const char *ShellCommandArgs(const char *command);
|
const char *ShellCommandArgs(const char *command);
|
||||||
|
|
@ -8206,11 +8229,11 @@ dvxSqlExec(db, sql);</code></pre>
|
||||||
fileOpsMakeDir(dir, name, err, errSize) Create a folder; a reason in err on failure.
|
fileOpsMakeDir(dir, name, err, errSize) Create a folder; a reason in err on failure.
|
||||||
fileOpsRename(dir, oldName, newName, err, errSize) Rename within a folder; refuses a name already in use.
|
fileOpsRename(dir, oldName, newName, err, errSize) Rename within a folder; refuses a name already in use.
|
||||||
fileOpsValidateName(dir, name, err, errSize) Check a new name for folder dir by the rules of its disk (platformValidateFilename): on DOS long names where the drive takes them (platformLongNames), else 8.3; never a device name.
|
fileOpsValidateName(dir, name, err, errSize) Check a new name for folder dir by the rules of its disk (platformValidateFilename): on DOS long names where the drive takes them (platformLongNames), else 8.3; never a device name.
|
||||||
fileOpsPathJoin(out, size, dir, name) dir + separator + name; false when it does not fit.
|
fileOpsPathJoin(out, size, dir, name) dir + separator + name in native separators (platformPathJoin: no doubled separator; an absolute name stands alone); false when it does not fit.
|
||||||
fileOpsPathParent(path, out, size) Parent folder; false at a root.
|
fileOpsPathParent(path, out, size) Parent folder, in native separators; false at a root.
|
||||||
fileOpsPathIsRoot(path) True for "C:\", "C:" or "/".
|
fileOpsPathIsRoot(path) True for "C:\", "C:" or "/".
|
||||||
fileOpsPathIsInside(path, dir) True when path is dir or below it.
|
fileOpsPathIsInside(path, dir) True when path is dir or below it (platformPathIsUnder).
|
||||||
fileOpsPathSame(a, b) Same place (separators equal, DOS ignores case, a trailing separator ignored).
|
fileOpsPathSame(a, b) Same place (platformPathSame: separators equal, case ignored, a trailing separator ignored).
|
||||||
fileOpsPathNormalize(path, out, size) Canonical absolute form of an existing folder (DOS: upper case, except names on a disk with long names, which keep their case).</pre>
|
fileOpsPathNormalize(path, out, size) Canonical absolute form of an existing folder (DOS: upper case, except names on a disk with long names, which keep their case).</pre>
|
||||||
<h2>Jobs</h2>
|
<h2>Jobs</h2>
|
||||||
<p>A job copies, moves or deletes any number of files and folders (recursively):</p>
|
<p>A job copies, moves or deletes any number of files and folders (recursively):</p>
|
||||||
|
|
@ -8257,7 +8280,7 @@ fileOpsJobFree(job);</code></pre>
|
||||||
<p>fileOpsJobClearAttr(job, attr), called before the first step, clears those attribute bits on every file a copy or move writes: Setup passes FILEOPS_ATTR_READONLY, because every file on a CD-ROM is read-only and the installed copies must not be.</p>
|
<p>fileOpsJobClearAttr(job, attr), called before the first step, clears those attribute bits on every file a copy or move writes: Setup passes FILEOPS_ATTR_READONLY, because every file on a CD-ROM is read-only and the installed copies must not be.</p>
|
||||||
<p>fileOpsJobRun(job, callbacks) runs a job to the end instead: progress(job, ctx) is called after every step (return false to cancel) and ask(job, kind, message, ctx) answers questions. With no callbacks, existing files are kept (No) and failing items are skipped.</p>
|
<p>fileOpsJobRun(job, callbacks) runs a job to the end instead: progress(job, ctx) is called after every step (return false to cancel) and ask(job, kind, message, ctx) answers questions. With no callbacks, existing files are kept (No) and failing items are skipped.</p>
|
||||||
<h2>BASIC</h2>
|
<h2>BASIC</h2>
|
||||||
<p>src/include/basic/fileops.bas declares the BASIC functions (fileOpsBas.c): whole trees at once (FileOpsCopyTree, FileOpsMoveTree, FileOpsDeleteTree), drives and disks (FileOpsDriveList$, FileOpsDriveType (the PlatformDriveTypeE of platformDriveList, CONSTs FILEOPS_DRIVE_*; -1 for no such drive), FileOpsBootDrive$ (platformBootDrive, "" when none), FileOpsFreeKb, FileOpsFreeBytes and FileOpsTotalBytes (platformDiskSpace), FileOpsIsWritable (platformDirIsWritable), FileOpsReadOnlySystem (platformIsReadOnly), FileOpsTempDir$ (platformTempDir, "" when none)), folder listings (FileOpsList, FileOpsListCount, FileOpsListSort, FileOpsListFind, FileOpsListName$, FileOpsListIsFolder, FileOpsListSize, FileOpsListRows$, FileOpsListFree), paths and names (FileOpsPathNormalize$, FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (platformPathIsAbsolute), FileOpsPathIsInside, FileOpsPathSep$ (DVX_PATH_SEP), FileOpsFilterMatch (platformFilterMatch), FileOpsBaseName$, FileOpsExtension$), files and folders (FileOpsMakeDir, FileOpsMakePath (platformMkdirRecursive), FileOpsRename, FileOpsError$, FileOpsTempPath$ and FileOpsReplaceFile (an atomic save: platformPathSibling + platformReplaceFile), FileOpsValidName (platformValidateFilename for a name in a folder), FileOpsValidLongName and FileOpsValidShortName (platformValidateLongName / platformValidateShortName, the rules alone), FileOpsLongNames (platformLongNames), FileOpsShortPath$ (platformShortPath, "" when it does not fit), FileOpsIsFolder, FileOpsGetAttr, FileOpsSetAttr, FileOpsModified$, FileOpsSetModified (fileOpsParseDate + fileOpsSetTime), FileOpsTreeSize with FileOpsTreeBytes / FileOpsTreeFiles / FileOpsTreeFolders, FileOpsFormatSize$) and jobs (FileOpsJobCreate, FileOpsJobAddSource, FileOpsJobClearAttr (masked to FILEOPS_ATTR_EDITABLE), FileOpsJobRunFor, FileOpsJobMessage$, FileOpsJobAnswer, FileOpsJobCancel, FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone, FileOpsJobBytesTotal (FileOpsProgressT; byte counts are DOUBLE), FileOpsJobFree). The fileops.bas CONSTs mirror fileOps.h and dvxPlat.h; the basdev test suite (caseIncludeConsts) checks each against its C value.</p>
|
<p>src/include/basic/fileops.bas declares the BASIC functions (fileOpsBas.c): whole trees at once (FileOpsCopyTree, FileOpsMoveTree, FileOpsDeleteTree), drives and disks (FileOpsDriveList$, FileOpsDriveType (the PlatformDriveTypeE of platformDriveList, CONSTs FILEOPS_DRIVE_*; -1 for no such drive), FileOpsBootDrive$ (platformBootDrive, "" when none), FileOpsFreeKb, FileOpsFreeBytes and FileOpsTotalBytes (platformDiskSpace), FileOpsIsWritable (platformDirIsWritable), FileOpsReadOnlySystem (platformIsReadOnly), FileOpsTempDir$ (platformTempDir, "" when none)), folder listings (FileOpsList, FileOpsListCount, FileOpsListSort, FileOpsListFind, FileOpsListName$, FileOpsListIsFolder, FileOpsListSize, FileOpsListRows$, FileOpsListFree), paths and names (FileOpsPathNormalize$, FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (platformPathIsAbsolute), FileOpsPathIsInside, FileOpsPathSep$ (DVX_PATH_SEP), FileOpsPathNative$ (platformPathNative), FileOpsFilterMatch (platformFilterMatch), FileOpsBaseName$, FileOpsExtension$), files and folders (FileOpsMakeDir, FileOpsMakePath (platformMkdirRecursive), FileOpsRename, FileOpsError$, FileOpsTempPath$ and FileOpsReplaceFile (an atomic save: platformPathSibling + platformReplaceFile), FileOpsValidName (platformValidateFilename for a name in a folder), FileOpsValidLongName and FileOpsValidShortName (platformValidateLongName / platformValidateShortName, the rules alone), FileOpsLongNames (platformLongNames), FileOpsShortPath$ (platformShortPath, "" when it does not fit), FileOpsIsFolder, FileOpsGetAttr, FileOpsSetAttr, FileOpsModified$, FileOpsSetModified (fileOpsParseDate + fileOpsSetTime), FileOpsTreeSize with FileOpsTreeBytes / FileOpsTreeFiles / FileOpsTreeFolders, FileOpsFormatSize$) and jobs (FileOpsJobCreate, FileOpsJobAddSource, FileOpsJobClearAttr (masked to FILEOPS_ATTR_EDITABLE), FileOpsJobRunFor, FileOpsJobMessage$, FileOpsJobAnswer, FileOpsJobCancel, FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone, FileOpsJobBytesTotal (FileOpsProgressT; byte counts are DOUBLE), FileOpsJobFree). The fileops.bas CONSTs mirror fileOps.h and dvxPlat.h; the basdev test suite (caseIncludeConsts) checks each against its C value.</p>
|
||||||
<p>Listings and jobs are handles (small numbers, 0 = none) into a table owned by the library; each records the app that made it, and the shell's app-cleanup hook frees what an app leaves open when it ends. FileOpsJobRunFor(job, ms) steps the job until the time is used or it needs an answer, so a BASIC Timer can run it without blocking the program. A returned string stays valid until the next call returning one (the BASIC runtime copies it at once). Single files are handled by the built-in FILECOPY, KILL, NAME, MKDIR, RMDIR, GETATTR and SETATTR statements.</p>
|
<p>Listings and jobs are handles (small numbers, 0 = none) into a table owned by the library; each records the app that made it, and the shell's app-cleanup hook frees what an app leaves open when it ends. FileOpsJobRunFor(job, ms) steps the job until the time is used or it needs an answer, so a BASIC Timer can run it without blocking the program. A returned string stays valid until the next call returning one (the BASIC runtime copies it at once). Single files are handled by the built-in FILECOPY, KILL, NAME, MKDIR, RMDIR, GETATTR and SETATTR statements.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic" id="lib.game">
|
<div class="topic" id="lib.game">
|
||||||
|
|
|
||||||
BIN
screenshots/86box/cpanel-cp-types.png
(Stored with Git LFS)
BIN
screenshots/86box/cpanel-cp-types.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/filedlg-fd-back.png
(Stored with Git LFS)
Normal file
BIN
screenshots/86box/filedlg-fd-back.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/86box/filedlg-fd-list.png
(Stored with Git LFS)
Normal file
BIN
screenshots/86box/filedlg-fd-list.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/86box/filedlg-fd-nordy.png
(Stored with Git LFS)
Normal file
BIN
screenshots/86box/filedlg-fd-nordy.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/86box/filedlg-fd-open.png
(Stored with Git LFS)
Normal file
BIN
screenshots/86box/filedlg-fd-open.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/86box/paths-paths.png
(Stored with Git LFS)
Normal file
BIN
screenshots/86box/paths-paths.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/dosbox/cpanel-cp-types.png
(Stored with Git LFS)
BIN
screenshots/dosbox/cpanel-cp-types.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/filedlg-fd-back.png
(Stored with Git LFS)
Normal file
BIN
screenshots/dosbox/filedlg-fd-back.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/dosbox/filedlg-fd-list.png
(Stored with Git LFS)
Normal file
BIN
screenshots/dosbox/filedlg-fd-list.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/dosbox/filedlg-fd-nordy.png
(Stored with Git LFS)
Normal file
BIN
screenshots/dosbox/filedlg-fd-nordy.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/dosbox/filedlg-fd-open.png
(Stored with Git LFS)
Normal file
BIN
screenshots/dosbox/filedlg-fd-open.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/dosbox/paths-paths.png
(Stored with Git LFS)
Normal file
BIN
screenshots/dosbox/paths-paths.png
(Stored with Git LFS)
Normal file
Binary file not shown.
3
screenshots/masks/pardemo-pardemo.mask
Normal file
3
screenshots/masks/pardemo-pardemo.mask
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# The ANSI terminal's blinking cursor (column 0 of the row after the last
|
||||||
|
# line, mostly behind the main window): on or off depends on timing
|
||||||
|
5 428 8 16
|
||||||
|
|
@ -9,8 +9,9 @@ Icon = ICON32.BMP
|
||||||
|
|
||||||
[Modules]
|
[Modules]
|
||||||
File0 = ../../../include/basic/commdlg.bas
|
File0 = ../../../include/basic/commdlg.bas
|
||||||
File1 = ../../../include/basic/help.bas
|
File1 = ../../../include/basic/fileops.bas
|
||||||
Count = 2
|
File2 = ../../../include/basic/help.bas
|
||||||
|
Count = 3
|
||||||
|
|
||||||
[Classes]
|
[Classes]
|
||||||
File0 = shape.cls
|
File0 = shape.cls
|
||||||
|
|
|
||||||
|
|
@ -644,7 +644,7 @@ SUB btnFileIO_Click
|
||||||
|
|
||||||
DIM path AS STRING
|
DIM path AS STRING
|
||||||
DIM f AS INTEGER
|
DIM f AS INTEGER
|
||||||
path = App.Data + "/demo.txt"
|
path = FileOpsPathJoin$(App.Data, "demo.txt")
|
||||||
Say "Writing: " + path
|
Say "Writing: " + path
|
||||||
|
|
||||||
f = FREEFILE
|
f = FREEFILE
|
||||||
|
|
@ -699,7 +699,7 @@ SUB btnIni_Click
|
||||||
Header "INI Read/Write"
|
Header "INI Read/Write"
|
||||||
|
|
||||||
DIM path AS STRING
|
DIM path AS STRING
|
||||||
path = App.Data + "/demo.ini"
|
path = FileOpsPathJoin$(App.Data, "demo.ini")
|
||||||
Say "Writing: " + path
|
Say "Writing: " + path
|
||||||
|
|
||||||
IniWrite path, "General", "UserName", "Scott"
|
IniWrite path, "General", "UserName", "Scott"
|
||||||
|
|
|
||||||
|
|
@ -160,15 +160,22 @@ static int32_t addDriver(const char *name, const char *label) {
|
||||||
|
|
||||||
|
|
||||||
// The item of a sound file, added when it is not listed yet ("" is
|
// The item of a sound file, added when it is not listed yet ("" is
|
||||||
// "(None)"). Files are told apart ignoring case: DOS names have none.
|
// "(None)"). Files are kept with native separators (DVX.INI may say
|
||||||
|
// "config/sounds/x.wav") and told apart ignoring case: DOS names have
|
||||||
|
// none.
|
||||||
static int32_t addFile(const char *path) {
|
static int32_t addFile(const char *path) {
|
||||||
|
char native[DVX_MAX_PATH];
|
||||||
|
|
||||||
|
snprintf(native, sizeof(native), "%s", path);
|
||||||
|
platformPathNative(native);
|
||||||
|
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(sFileValues); i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(sFileValues); i++) {
|
||||||
if (strcasecmp(sFileValues[i], path) == 0) {
|
if (strcasecmp(sFileValues[i], native) == 0) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *value = strdup(path);
|
char *value = strdup(native);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -179,7 +186,7 @@ static int32_t addFile(const char *path) {
|
||||||
|
|
||||||
if (!value[0]) {
|
if (!value[0]) {
|
||||||
label = "(None)";
|
label = "(None)";
|
||||||
} else if (strncasecmp(value, AUDIO_SOUNDS_DIR, dirLen) == 0 && value[dirLen] == '/') {
|
} else if (strncasecmp(value, AUDIO_SOUNDS_DIR, dirLen) == 0 && value[dirLen] == DVX_PATH_SEP[0]) {
|
||||||
label = value + dirLen + 1;
|
label = value + dirLen + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +222,7 @@ static void addSoundsDir(void) {
|
||||||
if (dvxHasExt(names[i], sSoundExts[e])) {
|
if (dvxHasExt(names[i], sSoundExts[e])) {
|
||||||
char path[DVX_MAX_PATH];
|
char path[DVX_MAX_PATH];
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "%s/%s", AUDIO_SOUNDS_DIR, names[i]);
|
snprintf(path, sizeof(path), "%s" DVX_PATH_SEP "%s", AUDIO_SOUNDS_DIR, names[i]);
|
||||||
addFile(path);
|
addFile(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@
|
||||||
|
|
||||||
#include "stb_ds_wrap.h"
|
#include "stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -74,10 +73,6 @@
|
||||||
// Longest progress line handed to the log callback.
|
// Longest progress line handed to the log callback.
|
||||||
#define BAS_BUILD_LOG_BUF 128
|
#define BAS_BUILD_LOG_BUF 128
|
||||||
|
|
||||||
// Separator used when joining project-relative paths. '/' is accepted by
|
|
||||||
// DJGPP as well as every host OS bascomp runs on; DVX_PATH_SEP is not.
|
|
||||||
#define BAS_BUILD_SEP "/"
|
|
||||||
|
|
||||||
// Growth unit of the project source buffer.
|
// Growth unit of the project source buffer.
|
||||||
#define BAS_BUILD_SOURCE_CHUNK 8192
|
#define BAS_BUILD_SOURCE_CHUNK 8192
|
||||||
// Room a wrapper line needs besides its directive, name and control
|
// Room a wrapper line needs besides its directive, name and control
|
||||||
|
|
@ -133,7 +128,6 @@ bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, i
|
||||||
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err);
|
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err);
|
||||||
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
|
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
|
||||||
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec);
|
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec);
|
||||||
void basBuildDirOf(const char *path, char *out, int32_t outSize);
|
|
||||||
const char *basBuildErrorText(const char *message);
|
const char *basBuildErrorText(const char *message);
|
||||||
void basBuildFreeProject(BasProjectSourceT *out);
|
void basBuildFreeProject(BasProjectSourceT *out);
|
||||||
bool basBuildIsControlProject(PrefsHandleT *prefs);
|
bool basBuildIsControlProject(PrefsHandleT *prefs);
|
||||||
|
|
@ -151,7 +145,6 @@ static const char *emitCtlTexts(const char *outPath, const BasBuildSpecT *spec);
|
||||||
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec);
|
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec);
|
||||||
static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount);
|
static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount);
|
||||||
static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms);
|
static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms);
|
||||||
static bool pathsEqual(const char *a, const char *b);
|
|
||||||
static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen);
|
static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen);
|
||||||
static const char *stubCarrier(const BasBuildSpecT *spec);
|
static const char *stubCarrier(const BasBuildSpecT *spec);
|
||||||
static const char *writeMarker(const char *outPath);
|
static const char *writeMarker(const char *outPath);
|
||||||
|
|
@ -497,7 +490,7 @@ bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProje
|
||||||
}
|
}
|
||||||
|
|
||||||
char path[DVX_MAX_PATH];
|
char path[DVX_MAX_PATH];
|
||||||
snprintf(path, sizeof(path), "%s%s%s", projectDir, BAS_BUILD_SEP, val);
|
platformPathJoin(path, sizeof(path), projectDir, val);
|
||||||
|
|
||||||
int32_t srcLen = 0;
|
int32_t srcLen = 0;
|
||||||
char *src = platformReadFile(path, &srcLen);
|
char *src = platformReadFile(path, &srcLen);
|
||||||
|
|
@ -563,19 +556,6 @@ const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void basBuildDirOf(const char *path, char *out, int32_t outSize) {
|
|
||||||
snprintf(out, outSize, "%s", path);
|
|
||||||
|
|
||||||
char *sep = platformPathDirEnd(out);
|
|
||||||
|
|
||||||
if (sep) {
|
|
||||||
*sep = '\0';
|
|
||||||
} else {
|
|
||||||
snprintf(out, outSize, ".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const char *basBuildErrorText(const char *message) {
|
const char *basBuildErrorText(const char *message) {
|
||||||
if (strncasecmp(message, BAS_BUILD_LINE_PREFIX, sizeof(BAS_BUILD_LINE_PREFIX) - 1) != 0) {
|
if (strncasecmp(message, BAS_BUILD_LINE_PREFIX, sizeof(BAS_BUILD_LINE_PREFIX) - 1) != 0) {
|
||||||
return message;
|
return message;
|
||||||
|
|
@ -623,7 +603,7 @@ int32_t basBuildMapFileLine(const BasSourceMapT *map, const char *path, int32_t
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(map); i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(map); i++) {
|
||||||
const BasSourceMapT *m = &map[i];
|
const BasSourceMapT *m = &map[i];
|
||||||
|
|
||||||
if (pathsEqual(m->path, path)) {
|
if (platformPathSame(m->path, path)) {
|
||||||
int32_t offset = fileLine - m->firstFileLine;
|
int32_t offset = fileLine - m->firstFileLine;
|
||||||
|
|
||||||
return (offset >= 0 && offset < m->lineCount) ? m->startLine + offset : 0;
|
return (offset >= 0 && offset < m->lineCount) ? m->startLine + offset : 0;
|
||||||
|
|
@ -666,8 +646,7 @@ bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projec
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
basBuildDirOf(dbpPath, projectDir, projectDirSize);
|
return platformPathDirName(dbpPath, projectDir, projectDirSize);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -724,13 +703,13 @@ static const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
char helpSrc[DVX_MAX_PATH];
|
char helpSrc[DVX_MAX_PATH];
|
||||||
snprintf(helpSrc, sizeof(helpSrc), "%s" BAS_BUILD_SEP "%s", spec->projectDir, spec->helpFile);
|
platformPathJoin(helpSrc, sizeof(helpSrc), spec->projectDir, spec->helpFile);
|
||||||
|
|
||||||
char outDir[DVX_MAX_PATH];
|
char appFolder[DVX_MAX_PATH];
|
||||||
basBuildDirOf(outPath, outDir, sizeof(outDir));
|
platformPathDirName(outPath, appFolder, sizeof(appFolder));
|
||||||
|
|
||||||
char helpDst[DVX_MAX_PATH];
|
char helpDst[DVX_MAX_PATH];
|
||||||
snprintf(helpDst, sizeof(helpDst), "%s" BAS_BUILD_SEP "%s", outDir, platformPathBaseName(spec->helpFile));
|
platformPathJoin(helpDst, sizeof(helpDst), appFolder, platformPathBaseName(spec->helpFile));
|
||||||
|
|
||||||
if (!platformCopyFile(helpSrc, helpDst)) {
|
if (!platformCopyFile(helpSrc, helpDst)) {
|
||||||
return "Failed copying help file to output directory.";
|
return "Failed copying help file to output directory.";
|
||||||
|
|
@ -784,7 +763,7 @@ static const char *emitControls(const char *outPath, const BasBuildSpecT *spec)
|
||||||
char iconPath[DVX_MAX_PATH];
|
char iconPath[DVX_MAX_PATH];
|
||||||
int32_t iconLen = 0;
|
int32_t iconLen = 0;
|
||||||
|
|
||||||
snprintf(iconPath, sizeof(iconPath), "%s" BAS_BUILD_SEP "%s", spec->projectDir, value);
|
platformPathJoin(iconPath, sizeof(iconPath), spec->projectDir, value);
|
||||||
|
|
||||||
void *iconData = platformReadFile(iconPath, &iconLen);
|
void *iconData = platformReadFile(iconPath, &iconLen);
|
||||||
|
|
||||||
|
|
@ -843,7 +822,7 @@ static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
|
||||||
int32_t iconLen = 0;
|
int32_t iconLen = 0;
|
||||||
|
|
||||||
if (spec->iconPath && spec->iconPath[0]) {
|
if (spec->iconPath && spec->iconPath[0]) {
|
||||||
snprintf(iconPath, sizeof(iconPath), "%s" BAS_BUILD_SEP "%s", spec->projectDir, spec->iconPath);
|
platformPathJoin(iconPath, sizeof(iconPath), spec->projectDir, spec->iconPath);
|
||||||
iconData = platformReadFile(iconPath, &iconLen);
|
iconData = platformReadFile(iconPath, &iconLen);
|
||||||
|
|
||||||
if (!iconData) {
|
if (!iconData) {
|
||||||
|
|
@ -969,22 +948,6 @@ static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// True when a and b name the same file the way DOS compares names:
|
|
||||||
// letters in any case, '/' and '\\' alike.
|
|
||||||
static bool pathsEqual(const char *a, const char *b) {
|
|
||||||
for (; *a && *b; a++, b++) {
|
|
||||||
bool sepA = (*a == '/' || *a == '\\');
|
|
||||||
bool sepB = (*b == '/' || *b == '\\');
|
|
||||||
|
|
||||||
if (sepA != sepB || (!sepA && tolower((unsigned char)*a) != tolower((unsigned char)*b))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return *a == *b;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Serialize the module (and, for debug builds, its debug info). Release
|
// Serialize the module (and, for debug builds, its debug info). Release
|
||||||
// builds run strip -> obfuscate -> compact on a private copy so the
|
// builds run strip -> obfuscate -> compact on a private copy so the
|
||||||
// caller's module stays intact; obfuscation also fills obfFrms.
|
// caller's module stays intact; obfuscation also fills obfFrms.
|
||||||
|
|
|
||||||
|
|
@ -159,9 +159,6 @@ bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, Ba
|
||||||
// call basBuildFreeProject either way.
|
// call basBuildFreeProject either way.
|
||||||
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
|
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
|
||||||
|
|
||||||
// Writes the directory part of path into out, or "." when path has none.
|
|
||||||
void basBuildDirOf(const char *path, char *out, int32_t outSize);
|
|
||||||
|
|
||||||
// Releases everything basBuildConcatProject allocated.
|
// Releases everything basBuildConcatProject allocated.
|
||||||
void basBuildFreeProject(BasProjectSourceT *out);
|
void basBuildFreeProject(BasProjectSourceT *out);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,16 +96,20 @@ _Static_assert(DVX_MAX_PATH <= PRJ_MAX_FIELD && PRJ_MAX_STRING <= PRJ_MAX_FIELD,
|
||||||
// A [Project] or [Settings] value of PrjStateT: a text buffer (size bytes)
|
// A [Project] or [Settings] value of PrjStateT: a text buffer (size bytes)
|
||||||
// or a flag (size 0). always: written even when empty / false, the rest
|
// or a flag (size 0). always: written even when empty / false, the rest
|
||||||
// only when set, so projects that never use a setting stay unchanged.
|
// only when set, so projects that never use a setting stay unchanged.
|
||||||
|
// path: a file path, native in memory and written with '/' (as the file
|
||||||
|
// lists are: prjPortableSeps).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *section;
|
const char *section;
|
||||||
const char *key;
|
const char *key;
|
||||||
size_t offset;
|
size_t offset;
|
||||||
int32_t size;
|
int32_t size;
|
||||||
bool always;
|
bool always;
|
||||||
|
bool path;
|
||||||
} PrjFieldT;
|
} PrjFieldT;
|
||||||
|
|
||||||
#define PRJ_TEXT(sec, k, field, alw) { sec, k, offsetof(PrjStateT, field), (int32_t)sizeof(((PrjStateT *)0)->field), alw }
|
#define PRJ_TEXT(sec, k, field, alw) { sec, k, offsetof(PrjStateT, field), (int32_t)sizeof(((PrjStateT *)0)->field), alw, false }
|
||||||
#define PRJ_FLAG(sec, k, field, alw) { sec, k, offsetof(PrjStateT, field), 0, alw }
|
#define PRJ_PATH(sec, k, field) { sec, k, offsetof(PrjStateT, field), (int32_t)sizeof(((PrjStateT *)0)->field), false, true }
|
||||||
|
#define PRJ_FLAG(sec, k, field, alw) { sec, k, offsetof(PrjStateT, field), 0, alw, false }
|
||||||
|
|
||||||
// The one list of .dbp settings: load, save, prjFieldGet / prjFieldSet
|
// The one list of .dbp settings: load, save, prjFieldGet / prjFieldSet
|
||||||
static const PrjFieldT sFields[] = {
|
static const PrjFieldT sFields[] = {
|
||||||
|
|
@ -116,8 +120,8 @@ static const PrjFieldT sFields[] = {
|
||||||
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_VERSION, version, false),
|
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_VERSION, version, false),
|
||||||
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_COPYRIGHT, copyright, false),
|
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_COPYRIGHT, copyright, false),
|
||||||
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_DESCRIPTION, description, false),
|
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_DESCRIPTION, description, false),
|
||||||
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_ICON, iconPath, false),
|
PRJ_PATH(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_ICON, iconPath),
|
||||||
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_HELPFILE, helpFile, false),
|
PRJ_PATH(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_HELPFILE, helpFile),
|
||||||
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_COMMANDARGS, commandArgs, false),
|
PRJ_TEXT(BAS_INI_SECTION_PROJECT, BAS_INI_KEY_COMMANDARGS, commandArgs, false),
|
||||||
PRJ_TEXT(BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_STARTUPFORM, startupForm, true),
|
PRJ_TEXT(BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_STARTUPFORM, startupForm, true),
|
||||||
PRJ_FLAG(BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_OPTIONEXPLICIT, optionExplicit, true),
|
PRJ_FLAG(BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_OPTIONEXPLICIT, optionExplicit, true),
|
||||||
|
|
@ -205,6 +209,7 @@ bool prjMapLine(const PrjStateT *prj, int32_t concatLine, int
|
||||||
void prjNew(PrjStateT *prj, const char *name, const char *directory);
|
void prjNew(PrjStateT *prj, const char *name, const char *directory);
|
||||||
static bool prjParseFlag(const char *text);
|
static bool prjParseFlag(const char *text);
|
||||||
bool prjPathExists(const char *path);
|
bool prjPathExists(const char *path);
|
||||||
|
static void prjPortableSeps(char *path);
|
||||||
char *prjReadFileText(const PrjStateT *prj, int32_t fileIdx, bool *outMissing);
|
char *prjReadFileText(const PrjStateT *prj, int32_t fileIdx, bool *outMissing);
|
||||||
void prjRemoveFile(PrjStateT *prj, int32_t idx);
|
void prjRemoveFile(PrjStateT *prj, int32_t idx);
|
||||||
bool prjRenameFile(PrjStateT *prj, int32_t idx, const char *newName, char *err, int32_t errSize);
|
bool prjRenameFile(PrjStateT *prj, int32_t idx, const char *newName, char *err, int32_t errSize);
|
||||||
|
|
@ -218,6 +223,7 @@ int32_t prjAddFile(PrjStateT *prj, const char *relativePath, PrjFileKindE kind)
|
||||||
PrjFileT entry;
|
PrjFileT entry;
|
||||||
memset(&entry, 0, sizeof(entry));
|
memset(&entry, 0, sizeof(entry));
|
||||||
snprintf(entry.path, sizeof(entry.path), "%s", relativePath);
|
snprintf(entry.path, sizeof(entry.path), "%s", relativePath);
|
||||||
|
platformPathNative(entry.path);
|
||||||
entry.kind = (uint8_t)kind;
|
entry.kind = (uint8_t)kind;
|
||||||
arrput(prj->files, entry);
|
arrput(prj->files, entry);
|
||||||
prj->dirty = true;
|
prj->dirty = true;
|
||||||
|
|
@ -530,18 +536,10 @@ char *prjCtlNew(const char *name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set prj->projectDir to the directory containing dbpPath, or "." when
|
// Set prj->projectDir to the directory containing dbpPath (native
|
||||||
// dbpPath has no directory component.
|
// separators), or "." when dbpPath has no directory component.
|
||||||
static void prjDeriveDir(PrjStateT *prj, const char *dbpPath) {
|
static void prjDeriveDir(PrjStateT *prj, const char *dbpPath) {
|
||||||
snprintf(prj->projectDir, sizeof(prj->projectDir), "%s", dbpPath);
|
platformPathDirName(dbpPath, prj->projectDir, sizeof(prj->projectDir));
|
||||||
char *sep = platformPathDirEnd(prj->projectDir);
|
|
||||||
|
|
||||||
if (sep) {
|
|
||||||
*sep = '\0';
|
|
||||||
} else {
|
|
||||||
prj->projectDir[0] = '.';
|
|
||||||
prj->projectDir[1] = '\0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -590,6 +588,10 @@ bool prjFieldSet(PrjStateT *prj, const char *key, const char *value) {
|
||||||
prjSingleLine(at, f->size, value);
|
prjSingleLine(at, f->size, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (f->path) {
|
||||||
|
platformPathNative(at);
|
||||||
|
}
|
||||||
|
|
||||||
prj->dirty = true;
|
prj->dirty = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -602,7 +604,7 @@ int32_t prjFileCount(const PrjStateT *prj) {
|
||||||
|
|
||||||
int32_t prjFileIndexByPath(const PrjStateT *prj, const char *path) {
|
int32_t prjFileIndexByPath(const PrjStateT *prj, const char *path) {
|
||||||
for (int32_t i = 0; i < prjFileCount(prj); i++) {
|
for (int32_t i = 0; i < prjFileCount(prj); i++) {
|
||||||
if (strcasecmp(prj->files[i].path, path) == 0) {
|
if (platformPathSame(prj->files[i].path, path)) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -617,7 +619,7 @@ void prjFullPath(const PrjStateT *prj, int32_t fileIdx, char *outPath, int32_t o
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(outPath, outSize, "%s" DVX_PATH_SEP "%s", prj->projectDir, prj->files[fileIdx].path);
|
platformPathJoin(outPath, outSize, prj->projectDir, prj->files[fileIdx].path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -667,6 +669,7 @@ bool prjLoad(PrjStateT *prj, const char *dbpPath) {
|
||||||
// starting from a blank state.
|
// starting from a blank state.
|
||||||
prjClose(prj);
|
prjClose(prj);
|
||||||
snprintf(prj->projectPath, sizeof(prj->projectPath), "%s", dbpPath);
|
snprintf(prj->projectPath, sizeof(prj->projectPath), "%s", dbpPath);
|
||||||
|
platformPathNative(prj->projectPath);
|
||||||
|
|
||||||
// Derive project directory
|
// Derive project directory
|
||||||
prjDeriveDir(prj, dbpPath);
|
prjDeriveDir(prj, dbpPath);
|
||||||
|
|
@ -728,6 +731,10 @@ static void prjLoadFields(PrjStateT *prj, PrefsHandleT *h) {
|
||||||
} else {
|
} else {
|
||||||
snprintf(at, (size_t)f->size, "%s", prefsGetString(h, f->section, f->key, at));
|
snprintf(at, (size_t)f->size, "%s", prefsGetString(h, f->section, f->key, at));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (f->path) {
|
||||||
|
platformPathNative(at);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -783,8 +790,12 @@ bool prjMapLine(const PrjStateT *prj, int32_t concatLine, int32_t *outFileIdx, i
|
||||||
void prjNew(PrjStateT *prj, const char *name, const char *directory) {
|
void prjNew(PrjStateT *prj, const char *name, const char *directory) {
|
||||||
prjInit(prj);
|
prjInit(prj);
|
||||||
snprintf(prj->name, sizeof(prj->name), "%s", name);
|
snprintf(prj->name, sizeof(prj->name), "%s", name);
|
||||||
|
char file[DVX_MAX_PATH];
|
||||||
|
|
||||||
snprintf(prj->projectDir, sizeof(prj->projectDir), "%s", directory);
|
snprintf(prj->projectDir, sizeof(prj->projectDir), "%s", directory);
|
||||||
snprintf(prj->projectPath, sizeof(prj->projectPath), "%s" DVX_PATH_SEP "%s" BAS_DBP_EXT, directory, name);
|
platformPathNative(prj->projectDir);
|
||||||
|
snprintf(file, sizeof(file), "%s" BAS_DBP_EXT, name);
|
||||||
|
platformPathJoin(prj->projectPath, sizeof(prj->projectPath), directory, file);
|
||||||
prj->dirty = true;
|
prj->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -810,6 +821,18 @@ bool prjPathExists(const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A path as the .dbp keeps it, in place: '/' between its parts on every
|
||||||
|
// platform (the file is shared with the host build, whose Makefile reads
|
||||||
|
// the File entries); in memory project paths are native.
|
||||||
|
static void prjPortableSeps(char *path) {
|
||||||
|
for (char *p = path; *p; p++) {
|
||||||
|
if (platformPathIsSep(*p)) {
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
char *prjReadFileText(const PrjStateT *prj, int32_t fileIdx, bool *outMissing) {
|
char *prjReadFileText(const PrjStateT *prj, int32_t fileIdx, bool *outMissing) {
|
||||||
char fullPath[DVX_MAX_PATH];
|
char fullPath[DVX_MAX_PATH];
|
||||||
|
|
||||||
|
|
@ -869,7 +892,7 @@ void prjRemoveFile(PrjStateT *prj, int32_t idx) {
|
||||||
// The source map is keyed by path: drop the removed file's lines so
|
// The source map is keyed by path: drop the removed file's lines so
|
||||||
// they no longer map to a file until the next compile rebuilds it.
|
// they no longer map to a file until the next compile rebuilds it.
|
||||||
for (int32_t i = (int32_t)arrlen(prj->sourceMap) - 1; i >= 0; i--) {
|
for (int32_t i = (int32_t)arrlen(prj->sourceMap) - 1; i >= 0; i--) {
|
||||||
if (strcmp(prj->sourceMap[i].path, prj->files[idx].path) == 0) {
|
if (platformPathSame(prj->sourceMap[i].path, prj->files[idx].path)) {
|
||||||
arrdel(prj->sourceMap, i);
|
arrdel(prj->sourceMap, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -916,7 +939,7 @@ bool prjRenameFile(PrjStateT *prj, int32_t idx, const char *newName, char *err,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp(name, file->path) == 0) {
|
if (platformPathSame(name, file->path)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -931,7 +954,7 @@ bool prjRenameFile(PrjStateT *prj, int32_t idx, const char *newName, char *err,
|
||||||
|
|
||||||
snprintf(oldPath, sizeof(oldPath), "%s", file->path);
|
snprintf(oldPath, sizeof(oldPath), "%s", file->path);
|
||||||
prjFullPath(prj, idx, oldFull, sizeof(oldFull));
|
prjFullPath(prj, idx, oldFull, sizeof(oldFull));
|
||||||
snprintf(newFull, sizeof(newFull), "%s" DVX_PATH_SEP "%s", prj->projectDir, name);
|
platformPathJoin(newFull, sizeof(newFull), prj->projectDir, name);
|
||||||
|
|
||||||
if (prjPathExists(newFull)) {
|
if (prjPathExists(newFull)) {
|
||||||
snprintf(err, errSize, "%s already exists.", name);
|
snprintf(err, errSize, "%s already exists.", name);
|
||||||
|
|
@ -1016,6 +1039,11 @@ static void prjSaveFields(PrefsHandleT *h, const PrjStateT *prj, const char *sec
|
||||||
}
|
}
|
||||||
} else if (f->always || at[0]) {
|
} else if (f->always || at[0]) {
|
||||||
prjSingleLine(line, (int32_t)sizeof(line), at);
|
prjSingleLine(line, (int32_t)sizeof(line), at);
|
||||||
|
|
||||||
|
if (f->path) {
|
||||||
|
prjPortableSeps(line);
|
||||||
|
}
|
||||||
|
|
||||||
prefsSetString(h, f->section, f->key, line);
|
prefsSetString(h, f->section, f->key, line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1030,8 +1058,12 @@ static void prjSaveFileSection(PrefsHandleT *h, const PrjStateT *prj, PrjFileKin
|
||||||
for (int32_t i = 0; i < prjFileCount(prj); i++) {
|
for (int32_t i = 0; i < prjFileCount(prj); i++) {
|
||||||
if (prj->files[i].kind == kind) {
|
if (prj->files[i].kind == kind) {
|
||||||
char key[PRJ_INI_KEY_LEN];
|
char key[PRJ_INI_KEY_LEN];
|
||||||
|
char path[DVX_MAX_PATH];
|
||||||
|
|
||||||
snprintf(key, sizeof(key), PRJ_FILE_KEY_FMT, idx++);
|
snprintf(key, sizeof(key), PRJ_FILE_KEY_FMT, idx++);
|
||||||
prefsSetString(h, sKinds[kind].section, key, prj->files[i].path);
|
snprintf(path, sizeof(path), "%s", prj->files[i].path);
|
||||||
|
prjPortableSeps(path);
|
||||||
|
prefsSetString(h, sKinds[kind].section, key, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1204,13 +1204,7 @@ static bool ucFileReady(UcFileT *file, WidgetT *parent) {
|
||||||
char dir[DVX_MAX_PATH];
|
char dir[DVX_MAX_PATH];
|
||||||
const char *error = NULL;
|
const char *error = NULL;
|
||||||
|
|
||||||
snprintf(dir, sizeof(dir), "%s", file->path);
|
platformPathDirName(file->path, dir, sizeof(dir));
|
||||||
|
|
||||||
char *sep = platformPathDirEnd(dir);
|
|
||||||
|
|
||||||
if (sep) {
|
|
||||||
*sep = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&spec, 0, sizeof(spec));
|
memset(&spec, 0, sizeof(spec));
|
||||||
spec.appDir = dir;
|
spec.appDir = dir;
|
||||||
|
|
|
||||||
|
|
@ -2713,7 +2713,7 @@ void *basFormRtLoadPicture(void *ctx, const char *path, int32_t *outErr) {
|
||||||
BasFormRtT *rt = (BasFormRtT *)ctx;
|
BasFormRtT *rt = (BasFormRtT *)ctx;
|
||||||
|
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(rt->pictures); i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(rt->pictures); i++) {
|
||||||
if (strcmp(rt->pictures[i]->path, path) == 0) {
|
if (platformPathSame(rt->pictures[i]->path, path)) {
|
||||||
return rt->pictures[i];
|
return rt->pictures[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -945,23 +945,15 @@ static bool addFileToProject(const char *path) {
|
||||||
const char *fileName = platformPathBaseName(path);
|
const char *fileName = platformPathBaseName(path);
|
||||||
char dir[DVX_MAX_PATH];
|
char dir[DVX_MAX_PATH];
|
||||||
|
|
||||||
snprintf(dir, sizeof(dir), "%s", path);
|
platformPathDirName(path, dir, sizeof(dir));
|
||||||
char *sep = platformPathDirEnd(dir);
|
|
||||||
|
|
||||||
if (sep) {
|
if (!platformPathSame(dir, sProject.projectDir)) {
|
||||||
*sep = '\0';
|
|
||||||
} else {
|
|
||||||
dir[0] = '.';
|
|
||||||
dir[1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcasecmp(dir, sProject.projectDir) != 0) {
|
|
||||||
ideErrorBox("Files must be located in the project directory.");
|
ideErrorBox("Files must be located in the project directory.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < prjFileCount(&sProject); i++) {
|
for (int32_t i = 0; i < prjFileCount(&sProject); i++) {
|
||||||
if (strcasecmp(sProject.files[i].path, fileName) == 0) {
|
if (platformPathSame(sProject.files[i].path, fileName)) {
|
||||||
return activateFile(i, ViewAutoE);
|
return activateFile(i, ViewAutoE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1412,7 +1404,7 @@ static bool classNameUsed(const char *name, const char *fileName) {
|
||||||
for (int32_t i = 0; i < prjFileCount(&sProject) && !used; i++) {
|
for (int32_t i = 0; i < prjFileCount(&sProject) && !used; i++) {
|
||||||
PrjFileT *file = &sProject.files[i];
|
PrjFileT *file = &sProject.files[i];
|
||||||
|
|
||||||
used = strcasecmp(file->path, fileName) == 0 || (file->kind == PrjKindFormE && strcasecmp(file->formName, name) == 0);
|
used = platformPathSame(file->path, fileName) || (file->kind == PrjKindFormE && strcasecmp(file->formName, name) == 0);
|
||||||
|
|
||||||
if (!used && (file->kind == PrjKindClassE || file->kind == PrjKindControlE)) {
|
if (!used && (file->kind == PrjKindClassE || file->kind == PrjKindControlE)) {
|
||||||
bool missing = false;
|
bool missing = false;
|
||||||
|
|
@ -4435,7 +4427,7 @@ static void newClassFile(PrjFileKindE kind) {
|
||||||
for (int32_t n = 1; ; n++) {
|
for (int32_t n = 1; ; n++) {
|
||||||
snprintf(name, sizeof(name), control ? IDE_NEW_CTL_FMT : IDE_NEW_CLASS_FMT, (int)n);
|
snprintf(name, sizeof(name), control ? IDE_NEW_CTL_FMT : IDE_NEW_CLASS_FMT, (int)n);
|
||||||
snprintf(fileName, sizeof(fileName), "%s%s", name, ext);
|
snprintf(fileName, sizeof(fileName), "%s%s", name, ext);
|
||||||
snprintf(path, sizeof(path), "%s" DVX_PATH_SEP "%s", sProject.projectDir, fileName);
|
platformPathJoin(path, sizeof(path), sProject.projectDir, fileName);
|
||||||
|
|
||||||
if (!classNameUsed(name, fileName) && !prjPathExists(path)) {
|
if (!classNameUsed(name, fileName) && !prjPathExists(path)) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -6415,7 +6407,7 @@ static void recentAdd(const char *path) {
|
||||||
|
|
||||||
// If already in the list, move it to the top
|
// If already in the list, move it to the top
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(sRecentFiles); i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(sRecentFiles); i++) {
|
||||||
if (strcasecmp(sRecentFiles[i], path) == 0) {
|
if (platformPathSame(sRecentFiles[i], path)) {
|
||||||
char *existing = sRecentFiles[i];
|
char *existing = sRecentFiles[i];
|
||||||
arrdel(sRecentFiles, i);
|
arrdel(sRecentFiles, i);
|
||||||
arrins(sRecentFiles, 0, existing);
|
arrins(sRecentFiles, 0, existing);
|
||||||
|
|
@ -6517,7 +6509,7 @@ static void recentRebuildMenu(void) {
|
||||||
// recentRemove -- drop path from the Recent list (it no longer opens)
|
// recentRemove -- drop path from the Recent list (it no longer opens)
|
||||||
static void recentRemove(const char *path) {
|
static void recentRemove(const char *path) {
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(sRecentFiles); i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(sRecentFiles); i++) {
|
||||||
if (strcasecmp(sRecentFiles[i], path) == 0) {
|
if (platformPathSame(sRecentFiles[i], path)) {
|
||||||
free(sRecentFiles[i]);
|
free(sRecentFiles[i]);
|
||||||
arrdel(sRecentFiles, i);
|
arrdel(sRecentFiles, i);
|
||||||
recentSave();
|
recentSave();
|
||||||
|
|
@ -6845,7 +6837,7 @@ static void runModule(BasModuleT *mod) {
|
||||||
BasRunSpecT spec;
|
BasRunSpecT spec;
|
||||||
|
|
||||||
if (sProject.helpFile[0]) {
|
if (sProject.helpFile[0]) {
|
||||||
snprintf(helpPath, sizeof(helpPath), "%s" DVX_PATH_SEP "%s", sProject.projectDir, sProject.helpFile);
|
platformPathJoin(helpPath, sizeof(helpPath), sProject.projectDir, sProject.helpFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&spec, 0, sizeof(spec));
|
memset(&spec, 0, sizeof(spec));
|
||||||
|
|
@ -9044,7 +9036,7 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
sAc = ctx->shellCtx;
|
sAc = ctx->shellCtx;
|
||||||
|
|
||||||
// Set help file and context-sensitive F1 handler
|
// Set help file and context-sensitive F1 handler
|
||||||
snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s" DVX_PATH_SEP "%s", ctx->appDir, IDE_HELP_FILE);
|
platformPathJoin(ctx->helpFile, sizeof(ctx->helpFile), ctx->appDir, IDE_HELP_FILE);
|
||||||
snprintf(sIdeHelpFile, sizeof(sIdeHelpFile), "%s", ctx->helpFile);
|
snprintf(sIdeHelpFile, sizeof(sIdeHelpFile), "%s", ctx->helpFile);
|
||||||
ctx->onHelpQuery = helpQueryHandler;
|
ctx->onHelpQuery = helpQueryHandler;
|
||||||
ctx->helpQueryCtx = NULL;
|
ctx->helpQueryCtx = NULL;
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ static void ppdLoadIconPreview(void) {
|
||||||
const char *relPath = sPpd.iconPath;
|
const char *relPath = sPpd.iconPath;
|
||||||
|
|
||||||
char fullPath[DVX_MAX_PATH * 2];
|
char fullPath[DVX_MAX_PATH * 2];
|
||||||
snprintf(fullPath, sizeof(fullPath), "%s" DVX_PATH_SEP "%s", sPpd.prj->projectDir, relPath);
|
platformPathJoin(fullPath, sizeof(fullPath), sPpd.prj->projectDir, relPath);
|
||||||
|
|
||||||
if (!validateIcon(fullPath, true)) {
|
if (!validateIcon(fullPath, true)) {
|
||||||
sPpd.iconPath[0] = '\0';
|
sPpd.iconPath[0] = '\0';
|
||||||
|
|
@ -281,7 +281,7 @@ static void ppdOnBrowseIcon(WidgetT *w) {
|
||||||
|
|
||||||
// Check if destination already exists
|
// Check if destination already exists
|
||||||
char destPath[DVX_MAX_PATH * 2];
|
char destPath[DVX_MAX_PATH * 2];
|
||||||
snprintf(destPath, sizeof(destPath), "%s" DVX_PATH_SEP "%s", sPpd.prj->projectDir, fname);
|
platformPathJoin(destPath, sizeof(destPath), sPpd.prj->projectDir, fname);
|
||||||
|
|
||||||
if (prjPathExists(destPath)) {
|
if (prjPathExists(destPath)) {
|
||||||
char msg[DVX_MAX_PATH + PRJ_MSG_BUF];
|
char msg[DVX_MAX_PATH + PRJ_MSG_BUF];
|
||||||
|
|
@ -327,7 +327,7 @@ static void ppdOnOk(WidgetT *w) {
|
||||||
// Validate icon path if set
|
// Validate icon path if set
|
||||||
if (sPpd.iconPath[0] && sPpd.prj) {
|
if (sPpd.iconPath[0] && sPpd.prj) {
|
||||||
char fullPath[DVX_MAX_PATH * 2];
|
char fullPath[DVX_MAX_PATH * 2];
|
||||||
snprintf(fullPath, sizeof(fullPath), "%s" DVX_PATH_SEP "%s", sPpd.prj->projectDir, sPpd.iconPath);
|
platformPathJoin(fullPath, sizeof(fullPath), sPpd.prj->projectDir, sPpd.iconPath);
|
||||||
|
|
||||||
if (!validateIcon(fullPath, true)) {
|
if (!validateIcon(fullPath, true)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -428,14 +428,9 @@ int32_t prjGetSelectedFileIdx(void) {
|
||||||
// Return path relative to prj->projectDir when path lives under it, else NULL
|
// Return path relative to prj->projectDir when path lives under it, else NULL
|
||||||
// (callers apply their own fallback).
|
// (callers apply their own fallback).
|
||||||
static const char *prjMakeRelative(const PrjStateT *prj, const char *path) {
|
static const char *prjMakeRelative(const PrjStateT *prj, const char *path) {
|
||||||
int32_t dirLen = (int32_t)strlen(prj->projectDir);
|
const char *rel = platformPathIsUnder(path, prj->projectDir);
|
||||||
|
|
||||||
if (strncasecmp(path, prj->projectDir, dirLen) == 0 &&
|
return (rel && rel[0]) ? rel : NULL;
|
||||||
(path[dirLen] == '/' || path[dirLen] == '\\')) {
|
|
||||||
return path + dirLen + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1496,18 +1496,9 @@ void prpShowCtl(const char *text, PrpCtlSetFnT onSet) {
|
||||||
|
|
||||||
// resolveDbPath -- resolve a DatabaseName against the project directory
|
// resolveDbPath -- resolve a DatabaseName against the project directory
|
||||||
static void resolveDbPath(const char *dbName, char *out, int32_t outSize) {
|
static void resolveDbPath(const char *dbName, char *out, int32_t outSize) {
|
||||||
// If it's already an absolute path (starts with drive letter or /), use as-is
|
// An absolute name stands as it is; else it counts from the project
|
||||||
if ((dbName[0] && dbName[1] == ':') || dbName[0] == '/' || dbName[0] == '\\') {
|
// folder (platformPathJoin)
|
||||||
snprintf(out, outSize, "%s", dbName);
|
platformPathJoin(out, outSize, (sDs && sDs->projectDir) ? sDs->projectDir : "", dbName);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve relative to project directory
|
|
||||||
if (sDs && sDs->projectDir && sDs->projectDir[0]) {
|
|
||||||
snprintf(out, outSize, "%s" DVX_PATH_SEP "%s", sDs->projectDir, dbName);
|
|
||||||
} else {
|
|
||||||
snprintf(out, outSize, "%s", dbName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1324,6 +1324,9 @@ DVX BASIC now uses the QBasic scope rules. A SUB or FUNCTION sees a variable dec
|
||||||
.item Appending to a String variable with s = s + x or s = s & x no longer copies the whole string each time, so building long strings in a loop is much faster. When x is InputBox$, MsgBox, CreateForm, CreateControl or Shell, s is still read before x runs (left to right).
|
.item Appending to a String variable with s = s + x or s = s & x no longer copies the whole string each time, so building long strings in a loop is much faster. When x is InputBox$, MsgBox, CreateForm, CreateControl or Shell, s is still read before x runs (left to right).
|
||||||
.item And, Or, Xor, Eqv and Imp of two Booleans give a Boolean, and Not of a Boolean gives a Boolean, as in Visual Basic 4 and later: Print (a > 1) Or (b > 1) prints True instead of -1, and TypeName gives "Boolean". With a number on either side they still work on the bits and give a number.
|
.item And, Or, Xor, Eqv and Imp of two Booleans give a Boolean, and Not of a Boolean gives a Boolean, as in Visual Basic 4 and later: Print (a > 1) Or (b > 1) prints True instead of -1, and TypeName gives "Boolean". With a number on either side they still work on the bits and give a number.
|
||||||
.item Big disks and long file names: where the disk takes long file names (DVX under Windows, or a long file name driver loaded before DVX), OPEN, KILL, NAME, MKDIR, DIR$ and the other file statements use them, and the File Manager, the file dialogs and Terminal downloads keep them. A file can be bigger than 2 GB on a FAT32 disk; LOF, FILELEN, LOC and SEEK raise error 6 (Overflow) for a size or position past 2 GB rather than give a wrong number, and EOF stays right below it. FileOpsFreeBytes and FileOpsTotalBytes are right for disks past 2 GB.
|
.item Big disks and long file names: where the disk takes long file names (DVX under Windows, or a long file name driver loaded before DVX), OPEN, KILL, NAME, MKDIR, DIR$ and the other file statements use them, and the File Manager, the file dialogs and Terminal downloads keep them. A file can be bigger than 2 GB on a FAT32 disk; LOF, FILELEN, LOC and SEEK raise error 6 (Overflow) for a size or position past 2 GB rather than give a wrong number, and EOF stays right below it. FileOpsFreeBytes and FileOpsTotalBytes are right for disks past 2 GB.
|
||||||
|
.item Paths may be written with / or \ wherever a path is taken (OPEN, KILL, NAME, MKDIR, CHDIR, DIR$, FILELEN and the file and folder functions of the libraries), mixed too; paths DVX gives back (CURDIR$, App.Path, App.Data, the file dialogs, the FileOps results) use \, as DOS shows them. New: FileOpsPathNative$ gives a path of your own that form. DIR$("C:\*.*") lists the root of C: (before, it listed C:'s current folder). SHELL commands still go to DOS as written, where / starts a switch.
|
||||||
|
.item The IDE shows a project's file paths with \ and keeps writing them with / in the .dbp file, so a project reads the same everywhere.
|
||||||
|
.item The Open and Save As dialogs (DlgFileOpen$, DlgFileSave$ and the IDE's own) have a Drive list beside the Path box (Alt+D); a drive with no disk in it says it is not ready.
|
||||||
.item Changed: FileOpsValidName takes the folder the name is for, FileOpsValidName(folder, itemName), since the rules depend on its disk; XferSafeName$ takes the folder a file is received into, XferSafeName$(name, folder). New: FileOpsLongNames, FileOpsShortPath$, FileOpsValidLongName and FileOpsValidShortName (fileops.bas), and ShellArgQuote$ (shell.bas), which puts a path with spaces in quotes for a command line.
|
.item Changed: FileOpsValidName takes the folder the name is for, FileOpsValidName(folder, itemName), since the rules depend on its disk; XferSafeName$ takes the folder a file is received into, XferSafeName$(name, folder). New: FileOpsLongNames, FileOpsShortPath$, FileOpsValidLongName and FileOpsValidShortName (fileops.bas), and ShellArgQuote$ (shell.bas), which puts a path with spaces in quotes for a command line.
|
||||||
.item Programs compiled with an older release must be rebuilt. A program whose procedure table records more than 64 locals for a procedure is refused when it is loaded.
|
.item Programs compiled with an older release must be rebuilt. A program whose procedure table records more than 64 locals for a procedure is refused when it is loaded.
|
||||||
.item A class may have at most 65536 member variables and a form at most 65536 form-level variables (as many as the compiled program can number); more is a compile error ("Too many member variables in X", "Too many form-level variables"), and a program file that records more is refused when it is loaded.
|
.item A class may have at most 65536 member variables and a form at most 65536 form-level variables (as many as the compiled program can number); more is a compile error ("Too many member variables in X", "Too many form-level variables"), and a program file that records more is refused when it is loaded.
|
||||||
|
|
|
||||||
|
|
@ -1807,6 +1807,8 @@ Shell "DIR /B"
|
||||||
exitCode = Shell("COPY A.TXT B.TXT")
|
exitCode = Shell("COPY A.TXT B.TXT")
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
|
The command goes to DOS as it is written. DOS commands read / as the start of a switch (DIR /B), so write the paths in a command with \ (SHELL "DEL C:\TEMP\X.TXT"); FileOpsPathNative$ (fileops.bas) turns a path built with / into that form. Everywhere else in DVX BASIC a path may use / or \.
|
||||||
|
|
||||||
.h2 SLEEP
|
.h2 SLEEP
|
||||||
|
|
||||||
Pauses execution for the given number of seconds. With no argument, sleeps for 1 second. The desktop and the program's own forms keep running while it sleeps, SLEEP 0 just lets pending events run, and closing the program ends the sleep.
|
Pauses execution for the given number of seconds. With no argument, sleeps for 1 second. The desktop and the program's own forms keep running while it sleeps, SLEEP 0 just lets pending events run, and closing the program ends the sleep.
|
||||||
|
|
@ -2469,6 +2471,8 @@ pos = SEEK(channel) ' Function: get current position
|
||||||
|
|
||||||
DVX BASIC provides statements for working with the file system -- creating, deleting, renaming, and copying files and directories, and reading or setting file attributes.
|
DVX BASIC provides statements for working with the file system -- creating, deleting, renaming, and copying files and directories, and reading or setting file attributes.
|
||||||
|
|
||||||
|
A path may be written with / or \ between its parts, mixed too: OPEN "DATA/SCORES.TXT" and OPEN "DATA\SCORES.TXT" open the same file, and so do the FileOps, Shell and other library functions that take a path (SHELL commands are the exception: see SHELL). Paths DVX gives back -- CURDIR$, App.Path, App.Data, the file dialogs, FileOpsPathJoin$ and the other FileOps results -- use \, the separator DOS shows; FileOpsPathNative$ gives a path of your own that form before you show it.
|
||||||
|
|
||||||
.h2 KILL
|
.h2 KILL
|
||||||
|
|
||||||
Deletes a file.
|
Deletes a file.
|
||||||
|
|
@ -2563,7 +2567,7 @@ SetAttr "secret.dat", vbHidden + vbReadOnly
|
||||||
|
|
||||||
.h2 CURDIR$ / DIR$
|
.h2 CURDIR$ / DIR$
|
||||||
|
|
||||||
CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done). DIR$ returns only files: never ".", ".." or subdirectories.
|
CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done). DIR$ returns only files: never ".", ".." or subdirectories. DIR$("C:\*.*") lists the root of C: (DIR$("C:*.*") the drive's current folder).
|
||||||
|
|
||||||
.code
|
.code
|
||||||
cwd$ = CURDIR$
|
cwd$ = CURDIR$
|
||||||
|
|
@ -4865,6 +4869,8 @@ Colors in the form RGB makes (&HBBGGRR, see Color Functions), as in Visual Basic
|
||||||
.index FileOpsMakePath
|
.index FileOpsMakePath
|
||||||
.index FileOpsReplaceFile
|
.index FileOpsReplaceFile
|
||||||
.index FileOpsValidName
|
.index FileOpsValidName
|
||||||
|
.index FileOpsPathNative$
|
||||||
|
.index Path Separators
|
||||||
.index FileOpsValidLongName
|
.index FileOpsValidLongName
|
||||||
.index FileOpsValidShortName
|
.index FileOpsValidShortName
|
||||||
.index FileOpsShortPath$
|
.index FileOpsShortPath$
|
||||||
|
|
@ -5286,7 +5292,7 @@ End Sub
|
||||||
|
|
||||||
With fileops.bas in the project, a program can show and change what is on the disks. FileOpsList(folder, filter) reads a folder once -- its folders and the files matching filter -- and returns a listing (a number; FileOpsListFree frees it). FileOpsListRows$ gives the whole listing as name, size, type and date columns, ready for a ListView's SetRows, so even a folder of hundreds of files fills the list at once; FileOpsListSort re-sorts it by name, size, type or date with folders first. FileOpsListName$, FileOpsListIsFolder and FileOpsListSize read single entries, and FileOpsListFind finds one by name.
|
With fileops.bas in the project, a program can show and change what is on the disks. FileOpsList(folder, filter) reads a folder once -- its folders and the files matching filter -- and returns a listing (a number; FileOpsListFree frees it). FileOpsListRows$ gives the whole listing as name, size, type and date columns, ready for a ListView's SetRows, so even a folder of hundreds of files fills the list at once; FileOpsListSort re-sorts it by name, size, type or date with folders first. FileOpsListName$, FileOpsListIsFolder and FileOpsListSize read single entries, and FileOpsListFind finds one by name.
|
||||||
|
|
||||||
Paths: FileOpsPathNormalize$ (the full path of an existing folder, or ""), FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (True for a path that is complete by itself, such as "C:\DOCS", False for one that counts from the current folder), FileOpsPathIsInside(path, folder) (True when path is folder itself or anywhere inside it: check it before copying or moving a folder, so it is never copied into itself), FileOpsPathSep$ (the character between folder names, "\" on DOS), FileOpsBaseName$ and FileOpsExtension$. FileOpsFilterMatch(itemName, filter) tells whether a name matches a filter the way the file dialogs match it: wildcards * and ?, several patterns separated by ; such as "*.bmp;*.png", case not mattering.
|
Paths: FileOpsPathNormalize$ (the full path of an existing folder, or ""), FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (True for a path that is complete by itself, such as "C:\DOCS", False for one that counts from the current folder), FileOpsPathIsInside(path, folder) (True when path is folder itself or anywhere inside it: check it before copying or moving a folder, so it is never copied into itself), FileOpsPathSep$ (the character between folder names, "\" on DOS), FileOpsPathNative$ (a path written with that character, for showing it: "C:/DOCS/A.TXT" becomes "C:\DOCS\A.TXT"), FileOpsBaseName$ and FileOpsExtension$. Every one of them takes / and \ alike and gives back \. FileOpsFilterMatch(itemName, filter) tells whether a name matches a filter the way the file dialogs match it: wildcards * and ?, several patterns separated by ; such as "*.bmp;*.png", case not mattering.
|
||||||
|
|
||||||
Files: FileOpsMakeDir and FileOpsRename (FileOpsError$ says why one failed), FileOpsTempPath$ and FileOpsReplaceFile (save safely: write the new file to FileOpsTempPath$(path$), then FileOpsReplaceFile swaps it in; the old file comes back if that fails), FileOpsValidName(folder, itemName) (whether a name can be used for a new file or folder in folder, by the rules of its disk; FileOpsError$ says why not), FileOpsValidLongName and FileOpsValidShortName (the long-name or the 8.3 rules by themselves, whatever the disk takes), FileOpsShortPath$ (the short 8.3 form of an existing path, for an old program that cannot take long names or blanks), FileOpsMakePath (a folder and every missing folder above it, which MKDIR does not make), FileOpsIsFolder, FileOpsGetAttr and FileOpsSetAttr (the FILEOPS_ATTR_ bits), FileOpsModified$ and FileOpsSetModified (when a file was last changed, read and set in the form "YYYY-MM-DD HH:MM"; FileOpsSetModified returns False for a date that does not exist), FileOpsTreeSize with FileOpsTreeBytes, FileOpsTreeFiles and FileOpsTreeFolders (the size of a whole folder) and FileOpsFormatSize$.
|
Files: FileOpsMakeDir and FileOpsRename (FileOpsError$ says why one failed), FileOpsTempPath$ and FileOpsReplaceFile (save safely: write the new file to FileOpsTempPath$(path$), then FileOpsReplaceFile swaps it in; the old file comes back if that fails), FileOpsValidName(folder, itemName) (whether a name can be used for a new file or folder in folder, by the rules of its disk; FileOpsError$ says why not), FileOpsValidLongName and FileOpsValidShortName (the long-name or the 8.3 rules by themselves, whatever the disk takes), FileOpsShortPath$ (the short 8.3 form of an existing path, for an old program that cannot take long names or blanks), FileOpsMakePath (a folder and every missing folder above it, which MKDIR does not make), FileOpsIsFolder, FileOpsGetAttr and FileOpsSetAttr (the FILEOPS_ATTR_ bits), FileOpsModified$ and FileOpsSetModified (when a file was last changed, read and set in the form "YYYY-MM-DD HH:MM"; FileOpsSetModified returns False for a date that does not exist), FileOpsTreeSize with FileOpsTreeBytes, FileOpsTreeFiles and FileOpsTreeFolders (the size of a whole folder) and FileOpsFormatSize$.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5815,9 +5815,7 @@ static const char *dirNext(BasVmT *vm) {
|
||||||
char full[DVX_MAX_PATH];
|
char full[DVX_MAX_PATH];
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
snprintf(full, sizeof(full), "%s/%s", vm->dirPath, ent->d_name);
|
if (platformPathJoin(full, sizeof(full), vm->dirPath, ent->d_name) && stat(full, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
|
||||||
if (stat(full, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7820,7 +7818,7 @@ static BasVmResultE execFsOp(BasVmT *vm, uint8_t op) {
|
||||||
cwd[0] = '\0';
|
cwd[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!push(vm, basValStringFromC(cwd))) {
|
if (!push(vm, basValStringFromC(platformPathNative(cwd)))) {
|
||||||
return BAS_VM_STACK_OVERFLOW;
|
return BAS_VM_STACK_OVERFLOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7836,30 +7834,16 @@ static BasVmResultE execFsOp(BasVmT *vm, uint8_t op) {
|
||||||
|
|
||||||
dirClose(vm);
|
dirClose(vm);
|
||||||
|
|
||||||
// Split pattern into directory + filename pattern
|
// Split pattern into directory + filename pattern (a root
|
||||||
|
// keeps its separator: "C:\\*.*" lists the root, not C:'s
|
||||||
|
// current folder; nothing readable when it is too long)
|
||||||
const char *pat = patStr.strVal->data;
|
const char *pat = patStr.strVal->data;
|
||||||
const char *lastSep = NULL;
|
|
||||||
|
|
||||||
for (const char *c = pat; *c; c++) {
|
if (!platformPathDirName(pat, vm->dirPath, (int32_t)sizeof(vm->dirPath))) {
|
||||||
if (*c == '/' || *c == '\\') {
|
vm->dirPath[0] = '\0';
|
||||||
lastSep = c;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastSep) {
|
snprintf(vm->dirPattern, sizeof(vm->dirPattern), "%s", platformPathBaseName(pat));
|
||||||
int32_t dirLen = (int32_t)(lastSep - pat);
|
|
||||||
|
|
||||||
if (dirLen >= (int32_t)sizeof(vm->dirPath)) {
|
|
||||||
dirLen = (int32_t)sizeof(vm->dirPath) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(vm->dirPath, pat, dirLen);
|
|
||||||
vm->dirPath[dirLen] = '\0';
|
|
||||||
snprintf(vm->dirPattern, sizeof(vm->dirPattern), "%s", lastSep + 1);
|
|
||||||
} else {
|
|
||||||
snprintf(vm->dirPath, sizeof(vm->dirPath), ".");
|
|
||||||
snprintf(vm->dirPattern, sizeof(vm->dirPattern), "%s", pat);
|
|
||||||
}
|
|
||||||
|
|
||||||
basValRelease(&patStr);
|
basValRelease(&patStr);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,15 +144,17 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
if (!outputPath) {
|
if (!outputPath) {
|
||||||
if (projName[0]) {
|
if (projName[0]) {
|
||||||
snprintf(outBuf, sizeof(outBuf), "%s/%s%s", projectDir, projName, ext);
|
snprintf(outBuf, sizeof(outBuf), "%s" DVX_PATH_SEP "%s%s", projectDir, projName, ext);
|
||||||
} else {
|
} else {
|
||||||
const char *base = platformPathBaseName(dbpPath);
|
const char *base = platformPathBaseName(dbpPath);
|
||||||
const char *dot = strrchr(base, '.');
|
const char *dot = strrchr(base, '.');
|
||||||
int32_t baseLen = dot ? (int32_t)(dot - base) : (int32_t)strlen(base);
|
int32_t baseLen = dot ? (int32_t)(dot - base) : (int32_t)strlen(base);
|
||||||
|
|
||||||
snprintf(outBuf, sizeof(outBuf), "%s/%.*s%s", projectDir, (int)baseLen, base, ext);
|
snprintf(outBuf, sizeof(outBuf), "%s" DVX_PATH_SEP "%.*s%s", projectDir, (int)baseLen, base, ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
platformPathNative(outBuf);
|
||||||
|
|
||||||
outputPath = outBuf;
|
outputPath = outBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ static int32_t runModule(BasModuleT *mod, const OptionsT *opt) {
|
||||||
basVmSetDoEventsCallback(vm, hostDoEvents, NULL);
|
basVmSetDoEventsCallback(vm, hostDoEvents, NULL);
|
||||||
|
|
||||||
char dir[DVX_MAX_PATH];
|
char dir[DVX_MAX_PATH];
|
||||||
basBuildDirOf(opt->program, dir, sizeof(dir));
|
platformPathDirName(opt->program, dir, sizeof(dir));
|
||||||
|
|
||||||
const char *dataDir = opt->dataDir ? opt->dataDir : dir;
|
const char *dataDir = opt->dataDir ? opt->dataDir : dir;
|
||||||
basVmSetAppPaths(vm, dir, dataDir, dataDir);
|
basVmSetAppPaths(vm, dir, dataDir, dataDir);
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ static void applyNewArgs(const char *args) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shellPathsMatch(path, sHlpPath)) {
|
if (!platformPathSame(path, sHlpPath)) {
|
||||||
HlpFileT *newFile = hlpFileOpen(path);
|
HlpFileT *newFile = hlpFileOpen(path);
|
||||||
|
|
||||||
if (!newFile) {
|
if (!newFile) {
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,10 @@ Icon = ICON32.BMP
|
||||||
|
|
||||||
[Modules]
|
[Modules]
|
||||||
File0 = ../../../include/basic/commdlg.bas
|
File0 = ../../../include/basic/commdlg.bas
|
||||||
File1 = ../../../include/basic/help.bas
|
File1 = ../../../include/basic/fileops.bas
|
||||||
File2 = ../../../include/basic/print.bas
|
File2 = ../../../include/basic/help.bas
|
||||||
Count = 3
|
File3 = ../../../include/basic/print.bas
|
||||||
|
Count = 4
|
||||||
|
|
||||||
[Forms]
|
[Forms]
|
||||||
File0 = iconed.frm
|
File0 = iconed.frm
|
||||||
|
|
|
||||||
|
|
@ -521,7 +521,7 @@ END SUB
|
||||||
SUB mnuPrint_Click
|
SUB mnuPrint_Click
|
||||||
DIM tmp AS STRING
|
DIM tmp AS STRING
|
||||||
DIM title AS STRING
|
DIM title AS STRING
|
||||||
tmp = App.Data + "/PRINTICO.BMP"
|
tmp = FileOpsPathJoin$(App.Data, "PRINTICO.BMP")
|
||||||
IF NOT SaveIcon(tmp) THEN
|
IF NOT SaveIcon(tmp) THEN
|
||||||
MsgBox "Could not prepare the icon for printing."
|
MsgBox "Could not prepare the icon for printing."
|
||||||
EXIT SUB
|
EXIT SUB
|
||||||
|
|
|
||||||
|
|
@ -461,6 +461,7 @@ When a program opens or saves a file, it shows the same Open or Save As dialog b
|
||||||
|
|
||||||
.list
|
.list
|
||||||
.item Path shows the folder you are looking at. Type a different folder or drive here (for example D: or C:\DOCS) and press Enter to go there.
|
.item Path shows the folder you are looking at. Type a different folder or drive here (for example D: or C:\DOCS) and press Enter to go there.
|
||||||
|
.item Drive, beside it, lists the computer's drives and shows which one you are on. Choose another to go to the top folder of that drive. If there is no disk in it (an empty floppy drive, say), the dialog tells you the drive is not ready and stays where it was.
|
||||||
.item The list shows the folders in square brackets, such as [DOCS], followed by the files. The [..] entry takes you up one folder.
|
.item The list shows the folders in square brackets, such as [DOCS], followed by the files. The [..] entry takes you up one folder.
|
||||||
.item Filter, when present, chooses which kinds of files the list shows, for example only text files.
|
.item Filter, when present, chooses which kinds of files the list shows, for example only text files.
|
||||||
.item File name is where you type or check the name of the file.
|
.item File name is where you type or check the name of the file.
|
||||||
|
|
@ -475,7 +476,7 @@ Double-click a folder to look inside it. Click a file to put its name in the Fil
|
||||||
Go to the folder you want, type a name in the File name box and click Save. If you leave off the ending (such as .txt), the dialog adds the one from the Filter box. If a file with that name already exists, DVX asks whether to replace it.
|
Go to the folder you want, type a name in the File name box and click Save. If you leave off the ending (such as .txt), the dialog adds the one from the Filter box. If a file with that name already exists, DVX asks whether to replace it.
|
||||||
|
|
||||||
.note tip
|
.note tip
|
||||||
There is no separate drive list. To change drives, type the drive letter and a colon, such as A:, in the Path box and press Enter.
|
Alt+D opens the Drive list from the keyboard. You can also change drives by typing the drive letter and a colon, such as A:, in the Path box and pressing Enter.
|
||||||
.endnote
|
.endnote
|
||||||
|
|
||||||
.topic user.clipboard
|
.topic user.clipboard
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,11 @@ DECLARE LIBRARY "fileops"
|
||||||
' The character between folder names in a path ("\" on DOS).
|
' The character between folder names in a path ("\" on DOS).
|
||||||
DECLARE FUNCTION FileOpsPathSep$() AS STRING
|
DECLARE FUNCTION FileOpsPathSep$() AS STRING
|
||||||
|
|
||||||
|
' path written with that character, for showing it to the user: on DOS
|
||||||
|
' "C:/DOCS/A.TXT" becomes "C:\DOCS\A.TXT" (a program may write paths
|
||||||
|
' with / on DOS too; everything that takes a path accepts both).
|
||||||
|
DECLARE FUNCTION FileOpsPathNative$(BYVAL path AS STRING) AS STRING
|
||||||
|
|
||||||
' True when itemName matches filter the way the file dialogs and
|
' True when itemName matches filter the way the file dialogs and
|
||||||
' FileOpsList match: wildcards * and ?, several patterns separated by
|
' FileOpsList match: wildcards * and ?, several patterns separated by
|
||||||
' ; , or blanks, case does not matter; "" and "*.*" match everything.
|
' ; , or blanks, case does not matter; "" and "*.*" match everything.
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ struct PkgJobT {
|
||||||
char msg[PKG_MSG_MAX];
|
char msg[PKG_MSG_MAX];
|
||||||
// the file being unpacked
|
// the file being unpacked
|
||||||
char name[PKG_NAME_MAX];
|
char name[PKG_NAME_MAX];
|
||||||
|
char shownName[PKG_NAME_MAX]; // name in native separators, for people
|
||||||
char outPath[DVX_MAX_PATH];
|
char outPath[DVX_MAX_PATH];
|
||||||
FILE *out;
|
FILE *out;
|
||||||
bool skipping; // kept: its data is read, not written
|
bool skipping; // kept: its data is read, not written
|
||||||
|
|
@ -117,7 +118,6 @@ struct PkgJobT {
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void dirOfPath(const char *path, char *out, int32_t outSize);
|
|
||||||
static time_t dosTimeToTime(uint16_t date, uint16_t time);
|
static time_t dosTimeToTime(uint16_t date, uint16_t time);
|
||||||
static bool ensureDirs(PkgJobT *job, const char *relName, bool lastIsDir);
|
static bool ensureDirs(PkgJobT *job, const char *relName, bool lastIsDir);
|
||||||
static void entryAbort(PkgJobT *job);
|
static void entryAbort(PkgJobT *job);
|
||||||
|
|
@ -145,19 +145,6 @@ static bool readBytes(PkgJobT *job, uint8_t *buf, uint32_t len);
|
||||||
static bool writeOut(PkgJobT *job, const uint8_t *data, uint32_t len);
|
static bool writeOut(PkgJobT *job, const uint8_t *data, uint32_t len);
|
||||||
|
|
||||||
|
|
||||||
// The folder part of path ("." when it has none).
|
|
||||||
static void dirOfPath(const char *path, char *out, int32_t outSize) {
|
|
||||||
const char *end = platformPathDirEnd(path);
|
|
||||||
|
|
||||||
if (!end) {
|
|
||||||
snprintf(out, outSize, ".");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(out, outSize, "%.*s", (int)(end - path), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static time_t dosTimeToTime(uint16_t date, uint16_t time) {
|
static time_t dosTimeToTime(uint16_t date, uint16_t time) {
|
||||||
struct tm t;
|
struct tm t;
|
||||||
|
|
||||||
|
|
@ -271,7 +258,7 @@ static PkgStepE entryData(PkgJobT *job) {
|
||||||
|
|
||||||
if (status < TINFL_STATUS_DONE || (status == TINFL_STATUS_NEEDS_MORE_INPUT && job->packedLeft == 0)) {
|
if (status < TINFL_STATUS_DONE || (status == TINFL_STATUS_NEEDS_MORE_INPUT && job->packedLeft == 0)) {
|
||||||
entryAbort(job);
|
entryAbort(job);
|
||||||
return fail(job, "The file %s in the DVX package is damaged (disk %d).", job->name, (int)job->disk);
|
return fail(job, "The file %s in the DVX package is damaged (disk %d).", job->shownName, (int)job->disk);
|
||||||
}
|
}
|
||||||
|
|
||||||
done = status == TINFL_STATUS_DONE;
|
done = status == TINFL_STATUS_DONE;
|
||||||
|
|
@ -291,7 +278,7 @@ static bool entryFinish(PkgJobT *job) {
|
||||||
if (!job->skipping) {
|
if (!job->skipping) {
|
||||||
if (job->packedLeft != 0 || job->sizeDone != job->size || job->crcHave != job->crcWant) {
|
if (job->packedLeft != 0 || job->sizeDone != job->size || job->crcHave != job->crcWant) {
|
||||||
entryAbort(job);
|
entryAbort(job);
|
||||||
fail(job, "The file %s in the DVX package is damaged (disk %d).", job->name, (int)job->disk);
|
fail(job, "The file %s in the DVX package is damaged (disk %d).", job->shownName, (int)job->disk);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,8 +367,11 @@ static PkgStepE entryHeader(PkgJobT *job) {
|
||||||
job->name[nameLen - 1] = '\0';
|
job->name[nameLen - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memcpy(job->shownName, job->name, sizeof(job->shownName));
|
||||||
|
platformPathNative(job->shownName);
|
||||||
|
|
||||||
if (!pkgNameIsValid(job->name)) {
|
if (!pkgNameIsValid(job->name)) {
|
||||||
return fail(job, "The DVX package holds a name Setup does not accept: %s", job->name);
|
return fail(job, "The DVX package holds a name Setup does not accept: %s", job->shownName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
|
|
@ -412,7 +402,7 @@ static bool entryOpen(PkgJobT *job) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pkgLocalPath(job->destDir, job->name, job->outPath, sizeof(job->outPath))) {
|
if (!pkgLocalPath(job->destDir, job->name, job->outPath, sizeof(job->outPath))) {
|
||||||
fail(job, "The path of %s is too long.", job->name);
|
fail(job, "The path of %s is too long.", job->shownName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,7 +588,7 @@ void pkgFind(const char *exePath, PkgSourceT *src) {
|
||||||
memset(&h, 0, sizeof(h));
|
memset(&h, 0, sizeof(h));
|
||||||
memset(src, 0, sizeof(*src));
|
memset(src, 0, sizeof(*src));
|
||||||
src->found = PkgFoundNoneE;
|
src->found = PkgFoundNoneE;
|
||||||
dirOfPath(exePath, src->partDir, sizeof(src->partDir));
|
platformPathDirName(exePath, src->partDir, sizeof(src->partDir));
|
||||||
|
|
||||||
FILE *f = fopen(exePath, "rb");
|
FILE *f = fopen(exePath, "rb");
|
||||||
|
|
||||||
|
|
@ -715,7 +705,7 @@ void pkgJobProgress(const PkgJobT *job, PkgProgressT *out) {
|
||||||
out->fileCount = (int32_t)job->src.info.fileCount;
|
out->fileCount = (int32_t)job->src.info.fileCount;
|
||||||
out->percent = out->bytesTotal ? (int32_t)(out->bytesDone * PERCENT_FULL / out->bytesTotal) : PERCENT_FULL;
|
out->percent = out->bytesTotal ? (int32_t)(out->bytesDone * PERCENT_FULL / out->bytesTotal) : PERCENT_FULL;
|
||||||
out->disk = job->disk;
|
out->disk = job->disk;
|
||||||
out->current = job->state == JobDataE ? job->name : "";
|
out->current = job->state == JobDataE ? job->shownName : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -786,7 +776,7 @@ static bool readBytes(PkgJobT *job, uint8_t *buf, uint32_t len) {
|
||||||
static bool writeOut(PkgJobT *job, const uint8_t *data, uint32_t len) {
|
static bool writeOut(PkgJobT *job, const uint8_t *data, uint32_t len) {
|
||||||
if (job->sizeDone + len > job->size) {
|
if (job->sizeDone + len > job->size) {
|
||||||
entryAbort(job);
|
entryAbort(job);
|
||||||
fail(job, "The file %s in the DVX package is damaged (disk %d).", job->name, (int)job->disk);
|
fail(job, "The file %s in the DVX package is damaged (disk %d).", job->shownName, (int)job->disk);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ typedef struct {
|
||||||
int32_t fileCount;
|
int32_t fileCount;
|
||||||
int32_t percent;
|
int32_t percent;
|
||||||
int32_t disk; // disk being read
|
int32_t disk; // disk being read
|
||||||
const char *current; // file being unpacked (relative name)
|
const char *current; // file being unpacked (relative name, native separators)
|
||||||
} PkgProgressT;
|
} PkgProgressT;
|
||||||
|
|
||||||
// Ask for disk (1-based) because of problem; true = look again, false =
|
// Ask for disk (1-based) because of problem; true = look again, false =
|
||||||
|
|
|
||||||
|
|
@ -760,6 +760,7 @@ static void installRun(void) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
dvxCopyTrimmed(destDir, sizeof(destDir), sSetup.autoMode ? sSetup.autoDest : wgtGetText(sSetup.dest));
|
dvxCopyTrimmed(destDir, sizeof(destDir), sSetup.autoMode ? sSetup.autoDest : wgtGetText(sSetup.dest));
|
||||||
|
platformPathNative(destDir);
|
||||||
|
|
||||||
if (!installCheck(destDir, &upgrade, &keepSettings)) {
|
if (!installCheck(destDir, &upgrade, &keepSettings)) {
|
||||||
sSetup.exitCode = SETUP_EXIT_FAILED;
|
sSetup.exitCode = SETUP_EXIT_FAILED;
|
||||||
|
|
@ -1122,6 +1123,7 @@ static void uninstallRun(void) {
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
memset(&sSetup, 0, sizeof(sSetup));
|
memset(&sSetup, 0, sizeof(sSetup));
|
||||||
snprintf(sSetup.exePath, sizeof(sSetup.exePath), "%s", argv[0]);
|
snprintf(sSetup.exePath, sizeof(sSetup.exePath), "%s", argv[0]);
|
||||||
|
platformPathNative(sSetup.exePath);
|
||||||
parseArgs(argc, argv);
|
parseArgs(argc, argv);
|
||||||
|
|
||||||
// Never a log file on the disk Setup runs from.
|
// Never a log file on the disk Setup runs from.
|
||||||
|
|
|
||||||
|
|
@ -102,10 +102,12 @@ static int byLengthDesc(const void *a, const void *b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A line of a batch file (trimmed in place) is line, case ignored.
|
// A line of a batch file (trimmed in place) is line: case ignored, and
|
||||||
|
// '/' and '\\' alike (platformPathSame), so "C:/DVX/DVX.EXE" written by
|
||||||
|
// hand is the same line.
|
||||||
static bool lineMatches(char *l, const char *line) {
|
static bool lineMatches(char *l, const char *line) {
|
||||||
dvxTrimRight(l);
|
dvxTrimRight(l);
|
||||||
return strcasecmp(dvxSkipWs(l), line) == 0;
|
return platformPathSame(dvxSkipWs(l), line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -433,14 +435,14 @@ bool setupUninstall(const char *destDir, bool removeSettings, char *err, int32_t
|
||||||
// A folder of the package: removed with the others when empty.
|
// A folder of the package: removed with the others when empty.
|
||||||
size_t len = strlen(path);
|
size_t len = strlen(path);
|
||||||
|
|
||||||
if (path[len - 1] == DVX_PATH_SEP[0]) {
|
if (platformPathIsSep(path[len - 1])) {
|
||||||
path[len - 1] = '\0';
|
path[len - 1] = '\0';
|
||||||
arrput(dirs, strdup(path));
|
arrput(dirs, strdup(path));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every folder the name is in, for the empty-folder pass.
|
// Every folder the name is in, for the empty-folder pass.
|
||||||
for (char *sep = strrchr(path, DVX_PATH_SEP[0]); sep && sep > path + strlen(destDir); sep = strrchr(path, DVX_PATH_SEP[0])) {
|
for (char *sep = platformPathDirEnd(path); sep && sep > path + strlen(destDir); sep = platformPathDirEnd(path)) {
|
||||||
*sep = '\0';
|
*sep = '\0';
|
||||||
arrput(dirs, strdup(path));
|
arrput(dirs, strdup(path));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -458,7 +458,7 @@ int32_t audioMidiRelease(AudioCtxT *ctx);
|
||||||
// AUDIO_SOUNDS_DIR.
|
// AUDIO_SOUNDS_DIR.
|
||||||
|
|
||||||
#define AUDIO_SOUNDS_SECTION "sounds"
|
#define AUDIO_SOUNDS_SECTION "sounds"
|
||||||
#define AUDIO_SOUNDS_DIR "config/sounds"
|
#define AUDIO_SOUNDS_DIR "config" DVX_PATH_SEP "sounds"
|
||||||
|
|
||||||
// The DVX.INI key of an event ("startup", "error", ...), and its name for
|
// The DVX.INI key of an event ("startup", "error", ...), and its name for
|
||||||
// people ("Start DVX", "Error", ...); NULL for an event out of range.
|
// people ("Start DVX", "Error", ...); NULL for an event out of range.
|
||||||
|
|
|
||||||
|
|
@ -613,9 +613,13 @@ int32_t SoundEventCount(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The file DVX.INI (a prefs.bas handle) names for the event ("" none).
|
// The file DVX.INI (a prefs.bas handle) names for the event ("" none), in
|
||||||
|
// native separators (the shipped DVX.INI says "config/sounds/...").
|
||||||
const char *SoundEventFile(int32_t prefs, int32_t eventIdx) {
|
const char *SoundEventFile(int32_t prefs, int32_t eventIdx) {
|
||||||
return audioDeskFile(prefsBasHandle(prefs), (DvxSoundE)eventIdx);
|
static char file[DVX_MAX_PATH];
|
||||||
|
|
||||||
|
snprintf(file, sizeof(file), "%s", audioDeskFile(prefsBasHandle(prefs), (DvxSoundE)eventIdx));
|
||||||
|
return platformPathNative(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -380,7 +380,6 @@ The shell itself exits DVX when the desktop app ends in any other way -- its las
|
||||||
.index shellRunningAppId
|
.index shellRunningAppId
|
||||||
.index shellActivateApp
|
.index shellActivateApp
|
||||||
.index shellAppFindByPath
|
.index shellAppFindByPath
|
||||||
.index shellPathsMatch
|
|
||||||
|
|
||||||
.h1 Query API
|
.h1 Query API
|
||||||
|
|
||||||
|
|
@ -430,15 +429,7 @@ Bring an app to the front, as the Task Manager's Switch To does (it calls this):
|
||||||
ShellAppT *shellAppFindByPath(const char *path);
|
ShellAppT *shellAppFindByPath(const char *path);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Return an active app slot loaded from path, or NULL. Paths are compared with shellPathsMatch.
|
Return an active app slot loaded from path, or NULL. Paths are compared with platformPathSame (/ and \ the same separator, a trailing one ignored, case ignored on DOS).
|
||||||
|
|
||||||
.h2 shellPathsMatch
|
|
||||||
|
|
||||||
.code
|
|
||||||
bool shellPathsMatch(const char *a, const char *b);
|
|
||||||
.endcode
|
|
||||||
|
|
||||||
True when two paths name the same file under DOS rules: case-insensitive, with / and \ the same separator.
|
|
||||||
|
|
||||||
.topic shell.startup
|
.topic shell.startup
|
||||||
.title Starting Up
|
.title Starting Up
|
||||||
|
|
@ -676,7 +667,7 @@ typedef struct {
|
||||||
int32_t shellProgramScan(const char *dir, const char *skipPath, bool readInfo, ShellProgressFnT progress, void *progressCtx, ShellProgramT **outList);
|
int32_t shellProgramScan(const char *dir, const char *skipPath, bool readInfo, ShellProgressFnT progress, void *progressCtx, ShellProgramT **outList);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Every program under dir (NULL for SHELL_PROG_DIR) and its subfolders, except skipPath (compared with shellPathsMatch; NULL for none), in the order found. With readInfo each entry is filled by shellProgramInfo; without it only path is set, which is fast (no program file is opened) and enough to notice programs being added or removed. progress, when not NULL, is called with progressCtx after each program (for example to keep the screen responsive). Returns the count; *outList is an array of that many entries, NULL when there are none. Free it with shellProgramListFree.
|
Every program under dir (NULL for SHELL_PROG_DIR) and its subfolders, except skipPath (compared with platformPathSame; NULL for none), in the order found. With readInfo each entry is filled by shellProgramInfo; without it only path is set, which is fast (no program file is opened) and enough to notice programs being added or removed. progress, when not NULL, is called with progressCtx after each program (for example to keep the screen responsive). Returns the count; *outList is an array of that many entries, NULL when there are none. Free it with shellProgramListFree.
|
||||||
|
|
||||||
.h2 shellProgramInfo
|
.h2 shellProgramInfo
|
||||||
|
|
||||||
|
|
@ -974,7 +965,7 @@ int32_t ShellAppIsHung(int32_t appId);
|
||||||
int32_t ShellAppMemory(int32_t appId);
|
int32_t ShellAppMemory(int32_t appId);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
The Task Manager's operations, one routine each with it. ShellRunningId is shellRunningAppId. ShellRunningName and ShellRunningPath are the ShellAppT name and path of an app in AppStateRunningE ("" otherwise; the same state every call below requires). ShellRunningFind is shellAppFindByPath (shellPathsMatch rules), 0 when that app is not running. ShellRunningActivate is shellActivateApp. ShellRunningClose is shellRequestAppClose: the app is asked at the main loop's top level, after the BASIC code has returned; True when the request was recorded. ShellRunningKill is shellForceKillApp followed by shellDesktopUpdate (End Task: no shutdownFn, callbacks purged, the unload deferred while the app's code is on a stack); the running program itself is refused (BASIC's END ends it). ShellAppIsHung is dvxAppIsHung and ShellAppMemory dvxMemGetAppUsage (clamped to INT32_MAX; 0 for an app that is not running).
|
The Task Manager's operations, one routine each with it. ShellRunningId is shellRunningAppId. ShellRunningName and ShellRunningPath are the ShellAppT name and path of an app in AppStateRunningE ("" otherwise; the same state every call below requires). ShellRunningFind is shellAppFindByPath (platformPathSame rules), 0 when that app is not running. ShellRunningActivate is shellActivateApp. ShellRunningClose is shellRequestAppClose: the app is asked at the main loop's top level, after the BASIC code has returned; True when the request was recorded. ShellRunningKill is shellForceKillApp followed by shellDesktopUpdate (End Task: no shutdownFn, callbacks purged, the unload deferred while the app's code is on a stack); the running program itself is refused (BASIC's END ends it). ShellAppIsHung is dvxAppIsHung and ShellAppMemory dvxMemGetAppUsage (clamped to INT32_MAX; 0 for an app that is not running).
|
||||||
|
|
||||||
.h2 Command lines and priority
|
.h2 Command lines and priority
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,21 +68,24 @@
|
||||||
#define DUP_SUFFIX_MAX 8 // " (64)" plus NUL
|
#define DUP_SUFFIX_MAX 8 // " (64)" plus NUL
|
||||||
#define DECIMAL 10
|
#define DECIMAL 10
|
||||||
|
|
||||||
// An item's text fields by name (PROGMAN.INI's item keys)
|
// An item's text fields by name (PROGMAN.INI's item keys); path: kept in
|
||||||
|
// native separators (itemPathsNative)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
size_t offset;
|
size_t offset;
|
||||||
int32_t size;
|
int32_t size;
|
||||||
|
bool path;
|
||||||
} PmTextFieldT;
|
} PmTextFieldT;
|
||||||
|
|
||||||
#define PM_TEXT_FIELD(n, field) { n, offsetof(PmItemT, field), (int32_t)sizeof(((PmItemT *)0)->field) }
|
#define PM_TEXT_FIELD(n, field) { n, offsetof(PmItemT, field), (int32_t)sizeof(((PmItemT *)0)->field), false }
|
||||||
|
#define PM_PATH_FIELD(n, field) { n, offsetof(PmItemT, field), (int32_t)sizeof(((PmItemT *)0)->field), true }
|
||||||
|
|
||||||
static const PmTextFieldT sTextFields[] = {
|
static const PmTextFieldT sTextFields[] = {
|
||||||
PM_TEXT_FIELD(INI_ITEM_NAME, name),
|
PM_TEXT_FIELD(INI_ITEM_NAME, name),
|
||||||
PM_TEXT_FIELD(INI_ITEM_PROGRAM, program),
|
PM_PATH_FIELD(INI_ITEM_PROGRAM, program),
|
||||||
PM_TEXT_FIELD(INI_ITEM_ARGS, args),
|
PM_TEXT_FIELD(INI_ITEM_ARGS, args),
|
||||||
PM_TEXT_FIELD(INI_ITEM_FOLDER, folder),
|
PM_PATH_FIELD(INI_ITEM_FOLDER, folder),
|
||||||
PM_TEXT_FIELD(INI_ITEM_ICON, iconPath),
|
PM_PATH_FIELD(INI_ITEM_ICON, iconPath),
|
||||||
PM_TEXT_FIELD(PM_FIELD_DESCRIPTION, description)
|
PM_TEXT_FIELD(PM_FIELD_DESCRIPTION, description)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -99,6 +102,7 @@ static const PrefsChoiceT sViewChoices[PmViewCountE] = {
|
||||||
|
|
||||||
static void groupFree(PmGroupT *g);
|
static void groupFree(PmGroupT *g);
|
||||||
static void itemKey(char *out, int32_t outSize, int32_t n, const char *field);
|
static void itemKey(char *out, int32_t outSize, int32_t n, const char *field);
|
||||||
|
static void itemPathsNative(PmItemT *item);
|
||||||
static void loadGroup(PmModelT *m, PrefsHandleT *h, int32_t n);
|
static void loadGroup(PmModelT *m, PrefsHandleT *h, int32_t n);
|
||||||
static void loadItem(PmModelT *m, int32_t g, PrefsHandleT *h, const char *section, int32_t n);
|
static void loadItem(PmModelT *m, int32_t g, PrefsHandleT *h, const char *section, int32_t n);
|
||||||
void pmArrangeGroup(PmGroupT *g, int32_t cols);
|
void pmArrangeGroup(PmGroupT *g, int32_t cols);
|
||||||
|
|
@ -151,6 +155,16 @@ static void itemKey(char *out, int32_t outSize, int32_t n, const char *field) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The item's path fields (program, folder, icon) in native separators.
|
||||||
|
static void itemPathsNative(PmItemT *item) {
|
||||||
|
for (int32_t i = 0; i < DVX_ARRAY_LEN(sTextFields); i++) {
|
||||||
|
if (sTextFields[i].path) {
|
||||||
|
platformPathNative((char *)item + sTextFields[i].offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// [groupN]: a group without a usable name gets "Group N"; a duplicate name
|
// [groupN]: a group without a usable name gets "Group N"; a duplicate name
|
||||||
// gets " (N)" appended; items past PM_ITEM_MAX are ignored.
|
// gets " (N)" appended; items past PM_ITEM_MAX are ignored.
|
||||||
static void loadGroup(PmModelT *m, PrefsHandleT *h, int32_t n) {
|
static void loadGroup(PmModelT *m, PrefsHandleT *h, int32_t n) {
|
||||||
|
|
@ -207,6 +221,7 @@ static void loadItem(PmModelT *m, int32_t g, PrefsHandleT *h, const char *sectio
|
||||||
dvxCopyTrimmed(item.folder, sizeof(item.folder), prefsGetString(h, section, key, ""));
|
dvxCopyTrimmed(item.folder, sizeof(item.folder), prefsGetString(h, section, key, ""));
|
||||||
itemKey(key, sizeof(key), n, INI_ITEM_ICON);
|
itemKey(key, sizeof(key), n, INI_ITEM_ICON);
|
||||||
dvxCopyTrimmed(item.iconPath, sizeof(item.iconPath), prefsGetString(h, section, key, ""));
|
dvxCopyTrimmed(item.iconPath, sizeof(item.iconPath), prefsGetString(h, section, key, ""));
|
||||||
|
itemPathsNative(&item);
|
||||||
itemKey(key, sizeof(key), n, INI_ITEM_MIN);
|
itemKey(key, sizeof(key), n, INI_ITEM_MIN);
|
||||||
item.runMinimized = prefsGetBool(h, section, key, false);
|
item.runMinimized = prefsGetBool(h, section, key, false);
|
||||||
itemKey(key, sizeof(key), n, INI_ITEM_COL);
|
itemKey(key, sizeof(key), n, INI_ITEM_COL);
|
||||||
|
|
@ -345,7 +360,7 @@ void pmHistoryAdd(PmModelT *m, const char *command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t i = (int32_t)arrlen(m->history) - 1; i >= 0; i--) {
|
for (int32_t i = (int32_t)arrlen(m->history) - 1; i >= 0; i--) {
|
||||||
if (strcasecmp(m->history[i], trimmed) == 0) {
|
if (platformPathSame(m->history[i], trimmed)) {
|
||||||
free(m->history[i]);
|
free(m->history[i]);
|
||||||
arrdel(m->history, i);
|
arrdel(m->history, i);
|
||||||
}
|
}
|
||||||
|
|
@ -396,7 +411,7 @@ bool pmInstalledChanged(const PmModelT *m, const char *skipPath) {
|
||||||
bool known = false;
|
bool known = false;
|
||||||
|
|
||||||
for (int32_t j = 0; j < arrlen(installed->items) && !known; j++) {
|
for (int32_t j = 0; j < arrlen(installed->items) && !known; j++) {
|
||||||
known = shellPathsMatch(found[i].path, installed->items[j].program);
|
known = platformPathSame(found[i].path, installed->items[j].program);
|
||||||
}
|
}
|
||||||
|
|
||||||
changed = !known;
|
changed = !known;
|
||||||
|
|
@ -459,6 +474,7 @@ int32_t pmItemAdd(PmModelT *m, int32_t g, const PmItemT *item) {
|
||||||
|
|
||||||
PmItemT copy = *item;
|
PmItemT copy = *item;
|
||||||
|
|
||||||
|
itemPathsNative(©);
|
||||||
copy.icon = NULL;
|
copy.icon = NULL;
|
||||||
copy.iconSmall = NULL;
|
copy.iconSmall = NULL;
|
||||||
copy.iconTried = false;
|
copy.iconTried = false;
|
||||||
|
|
@ -598,6 +614,10 @@ bool pmItemSetField(PmItemT *item, const char *field, const char *value) {
|
||||||
|
|
||||||
dvxCopyTrimmed((char *)item + text->offset, text->size, v);
|
dvxCopyTrimmed((char *)item + text->offset, text->size, v);
|
||||||
|
|
||||||
|
if (text->path) {
|
||||||
|
platformPathNative((char *)item + text->offset);
|
||||||
|
}
|
||||||
|
|
||||||
if (strcasecmp(field, INI_ITEM_PROGRAM) == 0 || strcasecmp(field, INI_ITEM_ICON) == 0) {
|
if (strcasecmp(field, INI_ITEM_PROGRAM) == 0 || strcasecmp(field, INI_ITEM_ICON) == 0) {
|
||||||
pmItemFreeIcons(item);
|
pmItemFreeIcons(item);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@
|
||||||
#include "dvxPlat.h"
|
#include "dvxPlat.h"
|
||||||
#include "stb_ds_wrap.h"
|
#include "stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -102,8 +101,6 @@ static ShellAppT *findRunningPath(const char *path);
|
||||||
static void forceKillNow(AppContextT *ctx, ShellAppT *app);
|
static void forceKillNow(AppContextT *ctx, ShellAppT *app);
|
||||||
static void killAppTask(ShellAppT *app);
|
static void killAppTask(ShellAppT *app);
|
||||||
static bool makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize);
|
static bool makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize);
|
||||||
static bool pathCharsMatch(char a, char b);
|
|
||||||
static const char *pathUnderDir(const char *path, const char *dir);
|
|
||||||
static int32_t refuseSecondCopy(AppContextT *ctx, const char *path);
|
static int32_t refuseSecondCopy(AppContextT *ctx, const char *path);
|
||||||
static void releaseAppResources(ShellAppT *app);
|
static void releaseAppResources(ShellAppT *app);
|
||||||
bool shellActivateApp(AppContextT *ctx, int32_t appId);
|
bool shellActivateApp(AppContextT *ctx, int32_t appId);
|
||||||
|
|
@ -126,7 +123,6 @@ int32_t shellLoadAppWithArgs(AppContextT *ctx, const char *path, cons
|
||||||
static int32_t shellLoadFail(void *handle, const char *tempPath);
|
static int32_t shellLoadFail(void *handle, const char *tempPath);
|
||||||
void shellOpenHelp(AppContextT *ctx, const char *args);
|
void shellOpenHelp(AppContextT *ctx, const char *args);
|
||||||
void shellOpenTaskManager(AppContextT *ctx);
|
void shellOpenTaskManager(AppContextT *ctx);
|
||||||
bool shellPathsMatch(const char *a, const char *b);
|
|
||||||
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
||||||
bool shellReapApps(AppContextT *ctx);
|
bool shellReapApps(AppContextT *ctx);
|
||||||
void shellRegisterAppCleanup(ShellAppCleanupFnT fn);
|
void shellRegisterAppCleanup(ShellAppCleanupFnT fn);
|
||||||
|
|
@ -390,11 +386,12 @@ static void deriveConfigDir(DxeAppContextT *dxeCtx, const char *path) {
|
||||||
if (platformPathIsAbsolute(rel)) {
|
if (platformPathIsAbsolute(rel)) {
|
||||||
char cwd[DVX_MAX_PATH];
|
char cwd[DVX_MAX_PATH];
|
||||||
|
|
||||||
rel = getcwd(cwd, sizeof(cwd)) ? pathUnderDir(rel, cwd) : NULL;
|
rel = getcwd(cwd, sizeof(cwd)) ? platformPathIsUnder(rel, cwd) : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rel && rel[0] && strcmp(rel, ".") != 0) {
|
if (rel && rel[0] && strcmp(rel, ".") != 0) {
|
||||||
snprintf(dxeCtx->configDir, sizeof(dxeCtx->configDir), SHELL_CONFIG_ROOT "/%s", rel);
|
snprintf(dxeCtx->configDir, sizeof(dxeCtx->configDir), SHELL_CONFIG_ROOT DVX_PATH_SEP "%s", rel);
|
||||||
|
platformPathNative(dxeCtx->configDir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -402,7 +399,7 @@ static void deriveConfigDir(DxeAppContextT *dxeCtx, const char *path) {
|
||||||
const char *dot = strrchr(base, '.');
|
const char *dot = strrchr(base, '.');
|
||||||
int32_t len = dot ? (int32_t)(dot - base) : (int32_t)strlen(base);
|
int32_t len = dot ? (int32_t)(dot - base) : (int32_t)strlen(base);
|
||||||
|
|
||||||
snprintf(dxeCtx->configDir, sizeof(dxeCtx->configDir), SHELL_CONFIG_ROOT "/" SHELL_CONFIG_APPS "/%.*s", (int)len, base);
|
snprintf(dxeCtx->configDir, sizeof(dxeCtx->configDir), SHELL_CONFIG_ROOT DVX_PATH_SEP SHELL_CONFIG_APPS DVX_PATH_SEP "%.*s", (int)len, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -463,12 +460,12 @@ static void destroyAppWindows(AppContextT *ctx, int32_t appId) {
|
||||||
|
|
||||||
// Find an active slot with the given DXE path. Returns the slot pointer,
|
// Find an active slot with the given DXE path. Returns the slot pointer,
|
||||||
// or NULL if no running app was loaded from this path.
|
// or NULL if no running app was loaded from this path.
|
||||||
// Paths are compared with DOS semantics (shellPathsMatch), so the same app
|
// Paths are compared with platformPathSame, so the same app
|
||||||
// launched as "APPS\KPUNCH\DVXHELP\DVXHELP.APP" and as a directory-scan
|
// launched as "APPS\KPUNCH\DVXHELP\DVXHELP.APP" and as a directory-scan
|
||||||
// spelling like "apps/kpunch/dvxhelp/dvxhelp.app" is one loaded app.
|
// spelling like "apps/kpunch/dvxhelp/dvxhelp.app" is one loaded app.
|
||||||
static ShellAppT *findLoadedPath(const char *path) {
|
static ShellAppT *findLoadedPath(const char *path) {
|
||||||
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
||||||
if (sApps[i]->state != AppStateFreeE && shellPathsMatch(sApps[i]->path, path)) {
|
if (sApps[i]->state != AppStateFreeE && platformPathSame(sApps[i]->path, path)) {
|
||||||
return sApps[i];
|
return sApps[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -477,11 +474,11 @@ static ShellAppT *findLoadedPath(const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Running app loaded from this path (shellPathsMatch), or NULL. Unlike
|
// Running app loaded from this path (platformPathSame), or NULL. Unlike
|
||||||
// findLoadedPath it skips an instance whose deferred kill is pending.
|
// findLoadedPath it skips an instance whose deferred kill is pending.
|
||||||
static ShellAppT *findRunningPath(const char *path) {
|
static ShellAppT *findRunningPath(const char *path) {
|
||||||
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
||||||
if (sApps[i]->state == AppStateRunningE && shellPathsMatch(sApps[i]->path, path)) {
|
if (sApps[i]->state == AppStateRunningE && platformPathSame(sApps[i]->path, path)) {
|
||||||
return sApps[i];
|
return sApps[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -546,54 +543,15 @@ static bool makeTempPath(const char *origPath, int32_t id, char *out, int32_t ou
|
||||||
|
|
||||||
// The fallback is anchored to the working directory: a bare file name
|
// The fallback is anchored to the working directory: a bare file name
|
||||||
// would make dlopen search the library path instead of opening it.
|
// would make dlopen search the library path instead of opening it.
|
||||||
if (tmpDir) {
|
char name[DVX_MAX_PATH];
|
||||||
snprintf(out, outSize, "%s" DVX_PATH_SEP "_dvx%02ld%s", tmpDir, (long)id, dot);
|
|
||||||
} else if (platformIsReadOnly()) {
|
snprintf(name, sizeof(name), "_dvx%02ld%s", (long)id, dot);
|
||||||
|
|
||||||
|
if (!tmpDir && platformIsReadOnly()) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
snprintf(out, outSize, "." DVX_PATH_SEP "_dvx%02ld%s", (long)id, dot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return platformPathJoin(out, outSize, tmpDir ? tmpDir : ".", name);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// DOS path semantics on every platform: case-insensitive, and '/' and
|
|
||||||
// '\\' are the same separator (DJGPP's getcwd uses '/', DOS paths typed or
|
|
||||||
// picked by the user use '\\').
|
|
||||||
static bool pathCharsMatch(char a, char b) {
|
|
||||||
if (a == '\\') {
|
|
||||||
a = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b == '\\') {
|
|
||||||
b = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
return tolower((unsigned char)a) == tolower((unsigned char)b);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// If path lies under dir, return the remainder after dir and its separator;
|
|
||||||
// otherwise NULL. Compared with pathCharsMatch.
|
|
||||||
static const char *pathUnderDir(const char *path, const char *dir) {
|
|
||||||
size_t n = strlen(dir);
|
|
||||||
|
|
||||||
while (n > 0 && (dir[n - 1] == '/' || dir[n - 1] == '\\')) {
|
|
||||||
n--;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < n; i++) {
|
|
||||||
if (!pathCharsMatch(path[i], dir[i])) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path[n] != '/' && path[n] != '\\') {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path + n + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -715,7 +673,7 @@ void shellAppTestReset(void) {
|
||||||
|
|
||||||
|
|
||||||
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize) {
|
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize) {
|
||||||
snprintf(outPath, outSize, "%s" DVX_PATH_SEP "%s", ctx->configDir, filename);
|
platformPathJoin(outPath, outSize, ctx->configDir, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -900,6 +858,7 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
|
||||||
// desc comes from an arbitrary DXE: never read past its name field.
|
// desc comes from an arbitrary DXE: never read past its name field.
|
||||||
snprintf(app->name, SHELL_APP_NAME_MAX, "%.*s", SHELL_APP_NAME_MAX - 1, desc->name);
|
snprintf(app->name, SHELL_APP_NAME_MAX, "%.*s", SHELL_APP_NAME_MAX - 1, desc->name);
|
||||||
snprintf(app->path, sizeof(app->path), "%s", path);
|
snprintf(app->path, sizeof(app->path), "%s", path);
|
||||||
|
platformPathNative(app->path);
|
||||||
snprintf(app->tempPath, sizeof(app->tempPath), "%s", tempPath);
|
snprintf(app->tempPath, sizeof(app->tempPath), "%s", tempPath);
|
||||||
app->dxeHandle = handle;
|
app->dxeHandle = handle;
|
||||||
app->hasMainLoop = desc->hasMainLoop;
|
app->hasMainLoop = desc->hasMainLoop;
|
||||||
|
|
@ -921,22 +880,14 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
|
||||||
app->dxeCtx->shellCtx = ctx;
|
app->dxeCtx->shellCtx = ctx;
|
||||||
app->dxeCtx->appId = id;
|
app->dxeCtx->appId = id;
|
||||||
|
|
||||||
// Store the full app path so the app can load its own resources.
|
// Store the full app path so the app can load its own resources
|
||||||
snprintf(app->dxeCtx->appPath, sizeof(app->dxeCtx->appPath), "%s", path);
|
// (native separators: programs show it, as App.Path and the like).
|
||||||
|
snprintf(app->dxeCtx->appPath, sizeof(app->dxeCtx->appPath), "%s", app->path);
|
||||||
|
|
||||||
// Derive app directory from path (everything up to the last separator).
|
// Derive app directory from path (everything up to the last separator).
|
||||||
// This lets apps load resources relative to their own location rather
|
// This lets apps load resources relative to their own location rather
|
||||||
// than the shell's working directory.
|
// than the shell's working directory.
|
||||||
snprintf(app->dxeCtx->appDir, sizeof(app->dxeCtx->appDir), "%s", path);
|
platformPathDirName(app->path, app->dxeCtx->appDir, sizeof(app->dxeCtx->appDir));
|
||||||
|
|
||||||
char *sep = platformPathDirEnd(app->dxeCtx->appDir);
|
|
||||||
|
|
||||||
if (sep) {
|
|
||||||
*sep = '\0';
|
|
||||||
} else {
|
|
||||||
app->dxeCtx->appDir[0] = '.';
|
|
||||||
app->dxeCtx->appDir[1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
deriveConfigDir(app->dxeCtx, path);
|
deriveConfigDir(app->dxeCtx, path);
|
||||||
defaultHelpFile(app->dxeCtx);
|
defaultHelpFile(app->dxeCtx);
|
||||||
|
|
@ -1061,21 +1012,6 @@ void shellOpenTaskManager(AppContextT *ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Whole-path match under pathCharsMatch.
|
|
||||||
bool shellPathsMatch(const char *a, const char *b) {
|
|
||||||
while (*a && *b) {
|
|
||||||
if (!pathCharsMatch(*a, *b)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
a++;
|
|
||||||
b++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *a == *b;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Graceful reap -- called from shellReapApps when an app has reached
|
// Graceful reap -- called from shellReapApps when an app has reached
|
||||||
// the Terminating state. Unlike forceKill, this calls the app's
|
// the Terminating state. Unlike forceKill, this calls the app's
|
||||||
// shutdown hook (if provided) giving it a chance to save state, close
|
// shutdown hook (if provided) giving it a chance to save state, close
|
||||||
|
|
|
||||||
|
|
@ -197,10 +197,6 @@ void shellAppInit(void);
|
||||||
// application Help menus both use this.
|
// application Help menus both use this.
|
||||||
void shellOpenHelp(AppContextT *ctx, const char *args);
|
void shellOpenHelp(AppContextT *ctx, const char *args);
|
||||||
|
|
||||||
// True when two paths name the same file under DOS rules: case-insensitive,
|
|
||||||
// with '/' and '\\' the same separator.
|
|
||||||
bool shellPathsMatch(const char *a, const char *b);
|
|
||||||
|
|
||||||
// Load and start an app from a DXE file. Returns app ID (>= 1) or -1 on error.
|
// Load and start an app from a DXE file. Returns app ID (>= 1) or -1 on error.
|
||||||
// For callback-only apps, appMain runs synchronously and returns before
|
// For callback-only apps, appMain runs synchronously and returns before
|
||||||
// shellLoadApp returns. For main-loop apps, a task is created and the
|
// shellLoadApp returns. For main-loop apps, a task is created and the
|
||||||
|
|
@ -296,7 +292,7 @@ ShellAppT *shellGetApp(int32_t appId);
|
||||||
// False when it has no visible window.
|
// False when it has no visible window.
|
||||||
bool shellActivateApp(AppContextT *ctx, int32_t appId);
|
bool shellActivateApp(AppContextT *ctx, int32_t appId);
|
||||||
|
|
||||||
// Get an active app slot loaded from this path (shellPathsMatch), or NULL.
|
// Get an active app slot loaded from this path (platformPathSame), or NULL.
|
||||||
ShellAppT *shellAppFindByPath(const char *path);
|
ShellAppT *shellAppFindByPath(const char *path);
|
||||||
|
|
||||||
// Total number of app slots (for iteration bounds)
|
// Total number of app slots (for iteration bounds)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ static ShellAssocLaunchFnT sLaunch = shellLoadAppWithArgs;
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
static void assocProgramNative(char *value);
|
||||||
static bool assocSave(void);
|
static bool assocSave(void);
|
||||||
static PrefsHandleT *assocStore(void);
|
static PrefsHandleT *assocStore(void);
|
||||||
bool shellAssocCommand(const char *documentPath, char *program, int32_t programSize, char *args, int32_t argsSize);
|
bool shellAssocCommand(const char *documentPath, char *program, int32_t programSize, char *args, int32_t argsSize);
|
||||||
|
|
@ -65,6 +66,24 @@ void shellAssocTestReset(const char *storePath, ShellAssocLaunch
|
||||||
|
|
||||||
// Writes the store; on failure the cached copy (which holds the change
|
// Writes the store; on failure the cached copy (which holds the change
|
||||||
// that did not reach the disk) is dropped.
|
// that did not reach the disk) is dropped.
|
||||||
|
// A stored "program,arguments" value with its program part in native
|
||||||
|
// separators (the shipped ASSOC.INI says "apps/kpunch/..."); the
|
||||||
|
// arguments are left alone ("/doc" is a switch, not a path).
|
||||||
|
static void assocProgramNative(char *value) {
|
||||||
|
char *argsSep = strchr(value, SHELL_ASSOC_ARGS_SEP);
|
||||||
|
|
||||||
|
if (argsSep) {
|
||||||
|
*argsSep = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
platformPathNative(value);
|
||||||
|
|
||||||
|
if (argsSep) {
|
||||||
|
*argsSep = SHELL_ASSOC_ARGS_SEP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool assocSave(void) {
|
static bool assocSave(void) {
|
||||||
if (prefsSave(sStore)) {
|
if (prefsSave(sStore)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -164,6 +183,7 @@ bool shellAssocGet(int32_t index, char *extension, int32_t extensionSize, char *
|
||||||
|
|
||||||
snprintf(extension, extensionSize, "%s", key);
|
snprintf(extension, extensionSize, "%s", key);
|
||||||
snprintf(programPath, programPathSize, "%s", prefsGetString(store, SHELL_ASSOC_SECTION, key, ""));
|
snprintf(programPath, programPathSize, "%s", prefsGetString(store, SHELL_ASSOC_SECTION, key, ""));
|
||||||
|
assocProgramNative(programPath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,6 +197,7 @@ bool shellAssocLookup(const char *extension, char *programPath, int32_t programP
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(programPath, programPathSize, "%s", value);
|
snprintf(programPath, programPathSize, "%s", value);
|
||||||
|
assocProgramNative(programPath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -589,7 +589,7 @@ int32_t ShellInstanceCount(void) {
|
||||||
for (int32_t i = 1; self && i < shellAppSlotCount(); i++) {
|
for (int32_t i = 1; self && i < shellAppSlotCount(); i++) {
|
||||||
const ShellAppT *app = shellGetApp(i);
|
const ShellAppT *app = shellGetApp(i);
|
||||||
|
|
||||||
if (app && app->state != AppStateTerminatingE && shellPathsMatch(app->path, self->path)) {
|
if (app && app->state != AppStateTerminatingE && platformPathSame(app->path, self->path)) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -611,7 +611,7 @@ const char *ShellItemIcon(const char *iconPath, const char *path) {
|
||||||
static char file[DVX_MAX_PATH];
|
static char file[DVX_MAX_PATH];
|
||||||
|
|
||||||
shellItemIconFile(shellContext(), orEmpty(iconPath), orEmpty(path), file, (int32_t)sizeof(file));
|
shellItemIconFile(shellContext(), orEmpty(iconPath), orEmpty(path), file, (int32_t)sizeof(file));
|
||||||
return file;
|
return platformPathNative(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,14 +94,16 @@ static void scanDir(const char *dir, const char *skipPath, bool readInfo, ShellP
|
||||||
char path[DVX_MAX_PATH];
|
char path[DVX_MAX_PATH];
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "%s" DVX_PATH_SEP "%s", dir, names[i]);
|
if (!platformPathJoin(path, sizeof(path), dir, names[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
scanDir(path, skipPath, readInfo, progress, progressCtx, list);
|
scanDir(path, skipPath, readInfo, progress, progressCtx, list);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shellIsProgram(path) || (skipPath && shellPathsMatch(path, skipPath))) {
|
if (!shellIsProgram(path) || (skipPath && platformPathSame(path, skipPath))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ static bool fileInFolder(const char *folder, const char *name, char *out, int32_
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dvxStrAppendf(out, outSize, 0, "%s" DVX_PATH_SEP "%s", folder, name) >= 0 && stat(out, &st) == 0 && !S_ISDIR(st.st_mode);
|
return platformPathJoin(out, outSize, folder, name) && stat(out, &st) == 0 && !S_ISDIR(st.st_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,8 +165,6 @@ static int compareEntries(const void *a, const void *b);
|
||||||
static const char *errorText(int32_t err);
|
static const char *errorText(int32_t err);
|
||||||
static void keepAncestors(FileOpsJobT *job, int32_t idx);
|
static void keepAncestors(FileOpsJobT *job, int32_t idx);
|
||||||
static bool listScanEntry(const FileOpsSysEntryT *e, void *ctx);
|
static bool listScanEntry(const FileOpsSysEntryT *e, void *ctx);
|
||||||
static bool pathCharEq(char a, char b);
|
|
||||||
static int32_t pathTrimmedLen(const char *path);
|
|
||||||
static int32_t percentOf(const FileOpsJobT *job);
|
static int32_t percentOf(const FileOpsJobT *job);
|
||||||
static bool scanFolder(FileOpsJobT *job, int32_t idx);
|
static bool scanFolder(FileOpsJobT *job, int32_t idx);
|
||||||
static bool scanFolderEntry(const FileOpsSysEntryT *e, void *ctx);
|
static bool scanFolderEntry(const FileOpsSysEntryT *e, void *ctx);
|
||||||
|
|
@ -651,34 +649,21 @@ bool fileOpsParseDate(const char *text, time_t *out) {
|
||||||
|
|
||||||
|
|
||||||
bool fileOpsPathIsInside(const char *path, const char *dir) {
|
bool fileOpsPathIsInside(const char *path, const char *dir) {
|
||||||
int32_t len = pathTrimmedLen(dir);
|
return platformPathIsUnder(path, dir) != NULL;
|
||||||
|
|
||||||
for (int32_t i = 0; i < len; i++) {
|
|
||||||
if (!pathCharEq(path[i], dir[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path[len] == '\0' || path[len] == '/' || path[len] == '\\';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool fileOpsPathIsRoot(const char *path) {
|
bool fileOpsPathIsRoot(const char *path) {
|
||||||
if ((path[0] == '/' || path[0] == '\\') && path[1] == '\0') {
|
if (platformPathIsSep(path[0]) && path[1] == '\0') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return path[0] && path[1] == ':' && (path[2] == '\0' || ((path[2] == '/' || path[2] == '\\') && path[3] == '\0'));
|
return path[0] && path[1] == ':' && (path[2] == '\0' || (platformPathIsSep(path[2]) && path[3] == '\0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool fileOpsPathJoin(char *out, int32_t outSize, const char *dir, const char *name) {
|
bool fileOpsPathJoin(char *out, int32_t outSize, const char *dir, const char *name) {
|
||||||
size_t dirLen = strlen(dir);
|
return platformPathJoin(out, outSize, dir, name);
|
||||||
bool hasSep = dirLen > 0 && (dir[dirLen - 1] == '/' || dir[dirLen - 1] == '\\');
|
|
||||||
const char *sep = hasSep ? "" : DVX_PATH_SEP;
|
|
||||||
int32_t n = snprintf(out, (size_t)outSize, "%s%s%s", dir, sep, name);
|
|
||||||
|
|
||||||
return n >= 0 && n < outSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -700,17 +685,13 @@ bool fileOpsPathNormalize(const char *path, char *out, int32_t outSize) {
|
||||||
int32_t upperTo = 0;
|
int32_t upperTo = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (int32_t i = 0; canon[i]; i++) {
|
memcpy(out, canon, strlen(canon) + 1);
|
||||||
char c = canon[i];
|
|
||||||
|
|
||||||
if (c == '/' || c == '\\') {
|
for (int32_t i = 0; i < upperTo; i++) {
|
||||||
c = DVX_PATH_SEP[0];
|
out[i] = (char)toupper((unsigned char)out[i]);
|
||||||
}
|
|
||||||
|
|
||||||
out[i] = (i < upperTo) ? (char)toupper((unsigned char)c) : c;
|
|
||||||
out[i + 1] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
platformPathNative(out);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,11 +701,15 @@ bool fileOpsPathParent(const char *path, char *out, int32_t outSize) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t len = pathTrimmedLen(path);
|
int32_t len = (int32_t)strlen(path);
|
||||||
int32_t sep = -1;
|
int32_t sep = -1;
|
||||||
|
|
||||||
|
if (len > 0 && platformPathIsSep(path[len - 1])) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < len; i++) {
|
for (int32_t i = 0; i < len; i++) {
|
||||||
if (path[i] == '/' || path[i] == '\\') {
|
if (platformPathIsSep(path[i])) {
|
||||||
sep = i;
|
sep = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -743,14 +728,13 @@ bool fileOpsPathParent(const char *path, char *out, int32_t outSize) {
|
||||||
|
|
||||||
memcpy(out, path, (size_t)keep);
|
memcpy(out, path, (size_t)keep);
|
||||||
out[keep] = '\0';
|
out[keep] = '\0';
|
||||||
|
platformPathNative(out);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool fileOpsPathSame(const char *a, const char *b) {
|
bool fileOpsPathSame(const char *a, const char *b) {
|
||||||
int32_t lenA = pathTrimmedLen(a);
|
return platformPathSame(a, b);
|
||||||
|
|
||||||
return pathTrimmedLen(b) == lenA && fileOpsPathIsInside(a, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -768,9 +752,15 @@ bool fileOpsRename(const char *dir, const char *oldName, const char *newName, ch
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A different item already using the name (a change of case alone on
|
// A different item already using the name. On DOS a change of case
|
||||||
// a case-insensitive disk names the same item).
|
// alone names the same item; elsewhere the disk tells names apart by
|
||||||
bool clash = !fileOpsPathSame(oldPath, newPath) && stat(newPath, &st) == 0;
|
// case, so only the very same name is the same item.
|
||||||
|
#ifdef __DJGPP__
|
||||||
|
bool sameName = fileOpsPathSame(oldPath, newPath);
|
||||||
|
#else
|
||||||
|
bool sameName = strcmp(oldPath, newPath) == 0;
|
||||||
|
#endif
|
||||||
|
bool clash = !sameName && stat(newPath, &st) == 0;
|
||||||
bool ok = !clash && rename(oldPath, newPath) == 0;
|
bool ok = !clash && rename(oldPath, newPath) == 0;
|
||||||
int32_t e = clash ? EEXIST : errno;
|
int32_t e = clash ? EEXIST : errno;
|
||||||
|
|
||||||
|
|
@ -855,35 +845,6 @@ static bool listScanEntry(const FileOpsSysEntryT *e, void *ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Separators are interchangeable; DOS names compare without case.
|
|
||||||
static bool pathCharEq(char a, char b) {
|
|
||||||
bool sepA = a == '/' || a == '\\';
|
|
||||||
bool sepB = b == '/' || b == '\\';
|
|
||||||
|
|
||||||
if (sepA || sepB) {
|
|
||||||
return sepA && sepB;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __DJGPP__
|
|
||||||
return toupper((unsigned char)a) == toupper((unsigned char)b);
|
|
||||||
#else
|
|
||||||
return a == b;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Length of path without a trailing separator (a lone "/" becomes 0).
|
|
||||||
static int32_t pathTrimmedLen(const char *path) {
|
|
||||||
int32_t len = (int32_t)strlen(path);
|
|
||||||
|
|
||||||
if (len > 0 && (path[len - 1] == '/' || path[len - 1] == '\\')) {
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Percent complete by bytes, or by files when there are no bytes.
|
// Percent complete by bytes, or by files when there are no bytes.
|
||||||
static int32_t percentOf(const FileOpsJobT *job) {
|
static int32_t percentOf(const FileOpsJobT *job) {
|
||||||
if (job->phase == FileOpsPhaseDoneE) {
|
if (job->phase == FileOpsPhaseDoneE) {
|
||||||
|
|
|
||||||
|
|
@ -197,13 +197,15 @@ void fileOpsListSort(FileOpsEntryT *entries, int32_t count, FileOpsSortE
|
||||||
// Create folder dir/name. False with a reason in err.
|
// Create folder dir/name. False with a reason in err.
|
||||||
bool fileOpsMakeDir(const char *dir, const char *name, char *err, int32_t errSize);
|
bool fileOpsMakeDir(const char *dir, const char *name, char *err, int32_t errSize);
|
||||||
|
|
||||||
// True when path is dir itself or anywhere below it.
|
// True when path is dir itself or anywhere below it (platformPathIsUnder).
|
||||||
bool fileOpsPathIsInside(const char *path, const char *dir);
|
bool fileOpsPathIsInside(const char *path, const char *dir);
|
||||||
|
|
||||||
// True for a drive or file-system root ("C:\", "/").
|
// True for a drive or file-system root ("C:\", "/").
|
||||||
bool fileOpsPathIsRoot(const char *path);
|
bool fileOpsPathIsRoot(const char *path);
|
||||||
|
|
||||||
// out = dir + separator + name. False when it does not fit.
|
// out = dir + separator + name in native separators (platformPathJoin: no
|
||||||
|
// doubled separator; an absolute name stands alone). False when it does
|
||||||
|
// not fit.
|
||||||
bool fileOpsPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
|
bool fileOpsPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
|
||||||
|
|
||||||
// Canonical absolute form of an existing folder (native separators; on
|
// Canonical absolute form of an existing folder (native separators; on
|
||||||
|
|
@ -211,10 +213,10 @@ bool fileOpsPathJoin(char *out, int32_t outSize, const char *dir, const
|
||||||
// their case). False when path is not an existing folder.
|
// their case). False when path is not an existing folder.
|
||||||
bool fileOpsPathNormalize(const char *path, char *out, int32_t outSize);
|
bool fileOpsPathNormalize(const char *path, char *out, int32_t outSize);
|
||||||
|
|
||||||
// Parent folder of path. False at a root.
|
// Parent folder of path, in native separators. False at a root.
|
||||||
bool fileOpsPathParent(const char *path, char *out, int32_t outSize);
|
bool fileOpsPathParent(const char *path, char *out, int32_t outSize);
|
||||||
|
|
||||||
// True when the two paths name the same place.
|
// True when the two paths name the same place (platformPathSame).
|
||||||
bool fileOpsPathSame(const char *a, const char *b);
|
bool fileOpsPathSame(const char *a, const char *b);
|
||||||
|
|
||||||
// Rename dir/oldName to dir/newName. False with a reason in err.
|
// Rename dir/oldName to dir/newName. False with a reason in err.
|
||||||
|
|
@ -373,6 +375,9 @@ int32_t FileOpsPathIsAbsolute(const char *path);
|
||||||
int32_t FileOpsPathIsInside(const char *path, const char *dir);
|
int32_t FileOpsPathIsInside(const char *path, const char *dir);
|
||||||
int32_t FileOpsPathIsRoot(const char *path);
|
int32_t FileOpsPathIsRoot(const char *path);
|
||||||
const char *FileOpsPathJoin(const char *dir, const char *name);
|
const char *FileOpsPathJoin(const char *dir, const char *name);
|
||||||
|
|
||||||
|
// platformPathNative on a copy of path.
|
||||||
|
const char *FileOpsPathNative(const char *path);
|
||||||
const char *FileOpsPathNormalize(const char *path);
|
const char *FileOpsPathNormalize(const char *path);
|
||||||
const char *FileOpsPathParent(const char *path);
|
const char *FileOpsPathParent(const char *path);
|
||||||
int32_t FileOpsPathSame(const char *a, const char *b);
|
int32_t FileOpsPathSame(const char *a, const char *b);
|
||||||
|
|
|
||||||
|
|
@ -604,6 +604,15 @@ const char *FileOpsPathJoin(const char *dir, const char *name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// path with the platform's separators ('\\' on DOS) for showing it, ""
|
||||||
|
// when it is too long for a path.
|
||||||
|
const char *FileOpsPathNative(const char *path) {
|
||||||
|
char native[DVX_MAX_PATH];
|
||||||
|
|
||||||
|
return (path && snprintf(native, sizeof(native), "%s", path) < (int)sizeof(native)) ? textOf(platformPathNative(native)) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const char *FileOpsPathNormalize(const char *path) {
|
const char *FileOpsPathNormalize(const char *path) {
|
||||||
char norm[DVX_MAX_PATH];
|
char norm[DVX_MAX_PATH];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
#include "fileOpsSys.h"
|
#include "fileOpsSys.h"
|
||||||
#include "dvxPlat.h"
|
#include "dvxPlat.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|
@ -369,16 +370,23 @@ bool fileOpsSysScan(const char *dir, bool followLinks, FileOpsScanFnT fn, void *
|
||||||
|
|
||||||
#ifdef __DJGPP__
|
#ifdef __DJGPP__
|
||||||
// Copy a NUL-terminated path into the transfer buffer and point DS:DX
|
// Copy a NUL-terminated path into the transfer buffer and point DS:DX
|
||||||
// at it. False when it does not fit.
|
// at it, in the form DOS takes (platformPathTidy: the C library is not in
|
||||||
|
// between to accept '/', doubled or trailing separators). False when it
|
||||||
|
// is longer than DVX_MAX_PATH (which the transfer buffer, 2 KB at least,
|
||||||
|
// always holds).
|
||||||
static bool putPath(const char *path, __dpmi_regs *r) {
|
static bool putPath(const char *path, __dpmi_regs *r) {
|
||||||
uint32_t len = (uint32_t)strlen(path) + 1;
|
char tidy[DVX_MAX_PATH];
|
||||||
|
|
||||||
if (len > __tb_size) {
|
if (snprintf(tidy, sizeof(tidy), "%s", path) >= (int)sizeof(tidy)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
platformPathTidy(tidy);
|
||||||
|
|
||||||
|
uint32_t len = (uint32_t)strlen(tidy) + 1;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
_farpokeb(_dos_ds, __tb + i, (uint8_t)path[i]);
|
_farpokeb(_dos_ds, __tb + i, (uint8_t)tidy[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
r->x.ds = (uint16_t)(__tb >> PARAGRAPH_SHIFT);
|
r->x.ds = (uint16_t)(__tb >> PARAGRAPH_SHIFT);
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,11 @@ Disks past 2 GB report their real size and free space: platformDiskSpace asks DO
|
||||||
fileOpsMakeDir(dir, name, err, errSize) Create a folder; a reason in err on failure.
|
fileOpsMakeDir(dir, name, err, errSize) Create a folder; a reason in err on failure.
|
||||||
fileOpsRename(dir, oldName, newName, err, errSize) Rename within a folder; refuses a name already in use.
|
fileOpsRename(dir, oldName, newName, err, errSize) Rename within a folder; refuses a name already in use.
|
||||||
fileOpsValidateName(dir, name, err, errSize) Check a new name for folder dir by the rules of its disk (platformValidateFilename): on DOS long names where the drive takes them (platformLongNames), else 8.3; never a device name.
|
fileOpsValidateName(dir, name, err, errSize) Check a new name for folder dir by the rules of its disk (platformValidateFilename): on DOS long names where the drive takes them (platformLongNames), else 8.3; never a device name.
|
||||||
fileOpsPathJoin(out, size, dir, name) dir + separator + name; false when it does not fit.
|
fileOpsPathJoin(out, size, dir, name) dir + separator + name in native separators (platformPathJoin: no doubled separator; an absolute name stands alone); false when it does not fit.
|
||||||
fileOpsPathParent(path, out, size) Parent folder; false at a root.
|
fileOpsPathParent(path, out, size) Parent folder, in native separators; false at a root.
|
||||||
fileOpsPathIsRoot(path) True for "C:\", "C:" or "/".
|
fileOpsPathIsRoot(path) True for "C:\", "C:" or "/".
|
||||||
fileOpsPathIsInside(path, dir) True when path is dir or below it.
|
fileOpsPathIsInside(path, dir) True when path is dir or below it (platformPathIsUnder).
|
||||||
fileOpsPathSame(a, b) Same place (separators equal, DOS ignores case, a trailing separator ignored).
|
fileOpsPathSame(a, b) Same place (platformPathSame: separators equal, case ignored, a trailing separator ignored).
|
||||||
fileOpsPathNormalize(path, out, size) Canonical absolute form of an existing folder (DOS: upper case, except names on a disk with long names, which keep their case).
|
fileOpsPathNormalize(path, out, size) Canonical absolute form of an existing folder (DOS: upper case, except names on a disk with long names, which keep their case).
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
|
|
@ -143,6 +143,6 @@ fileOpsJobRun(job, callbacks) runs a job to the end instead: progress(job, ctx)
|
||||||
|
|
||||||
.h2 BASIC
|
.h2 BASIC
|
||||||
|
|
||||||
src/include/basic/fileops.bas declares the BASIC functions (fileOpsBas.c): whole trees at once (FileOpsCopyTree, FileOpsMoveTree, FileOpsDeleteTree), drives and disks (FileOpsDriveList$, FileOpsDriveType (the PlatformDriveTypeE of platformDriveList, CONSTs FILEOPS_DRIVE_*; -1 for no such drive), FileOpsBootDrive$ (platformBootDrive, "" when none), FileOpsFreeKb, FileOpsFreeBytes and FileOpsTotalBytes (platformDiskSpace), FileOpsIsWritable (platformDirIsWritable), FileOpsReadOnlySystem (platformIsReadOnly), FileOpsTempDir$ (platformTempDir, "" when none)), folder listings (FileOpsList, FileOpsListCount, FileOpsListSort, FileOpsListFind, FileOpsListName$, FileOpsListIsFolder, FileOpsListSize, FileOpsListRows$, FileOpsListFree), paths and names (FileOpsPathNormalize$, FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (platformPathIsAbsolute), FileOpsPathIsInside, FileOpsPathSep$ (DVX_PATH_SEP), FileOpsFilterMatch (platformFilterMatch), FileOpsBaseName$, FileOpsExtension$), files and folders (FileOpsMakeDir, FileOpsMakePath (platformMkdirRecursive), FileOpsRename, FileOpsError$, FileOpsTempPath$ and FileOpsReplaceFile (an atomic save: platformPathSibling + platformReplaceFile), FileOpsValidName (platformValidateFilename for a name in a folder), FileOpsValidLongName and FileOpsValidShortName (platformValidateLongName / platformValidateShortName, the rules alone), FileOpsLongNames (platformLongNames), FileOpsShortPath$ (platformShortPath, "" when it does not fit), FileOpsIsFolder, FileOpsGetAttr, FileOpsSetAttr, FileOpsModified$, FileOpsSetModified (fileOpsParseDate + fileOpsSetTime), FileOpsTreeSize with FileOpsTreeBytes / FileOpsTreeFiles / FileOpsTreeFolders, FileOpsFormatSize$) and jobs (FileOpsJobCreate, FileOpsJobAddSource, FileOpsJobClearAttr (masked to FILEOPS_ATTR_EDITABLE), FileOpsJobRunFor, FileOpsJobMessage$, FileOpsJobAnswer, FileOpsJobCancel, FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone, FileOpsJobBytesTotal (FileOpsProgressT; byte counts are DOUBLE), FileOpsJobFree). The fileops.bas CONSTs mirror fileOps.h and dvxPlat.h; the basdev test suite (caseIncludeConsts) checks each against its C value.
|
src/include/basic/fileops.bas declares the BASIC functions (fileOpsBas.c): whole trees at once (FileOpsCopyTree, FileOpsMoveTree, FileOpsDeleteTree), drives and disks (FileOpsDriveList$, FileOpsDriveType (the PlatformDriveTypeE of platformDriveList, CONSTs FILEOPS_DRIVE_*; -1 for no such drive), FileOpsBootDrive$ (platformBootDrive, "" when none), FileOpsFreeKb, FileOpsFreeBytes and FileOpsTotalBytes (platformDiskSpace), FileOpsIsWritable (platformDirIsWritable), FileOpsReadOnlySystem (platformIsReadOnly), FileOpsTempDir$ (platformTempDir, "" when none)), folder listings (FileOpsList, FileOpsListCount, FileOpsListSort, FileOpsListFind, FileOpsListName$, FileOpsListIsFolder, FileOpsListSize, FileOpsListRows$, FileOpsListFree), paths and names (FileOpsPathNormalize$, FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (platformPathIsAbsolute), FileOpsPathIsInside, FileOpsPathSep$ (DVX_PATH_SEP), FileOpsPathNative$ (platformPathNative), FileOpsFilterMatch (platformFilterMatch), FileOpsBaseName$, FileOpsExtension$), files and folders (FileOpsMakeDir, FileOpsMakePath (platformMkdirRecursive), FileOpsRename, FileOpsError$, FileOpsTempPath$ and FileOpsReplaceFile (an atomic save: platformPathSibling + platformReplaceFile), FileOpsValidName (platformValidateFilename for a name in a folder), FileOpsValidLongName and FileOpsValidShortName (platformValidateLongName / platformValidateShortName, the rules alone), FileOpsLongNames (platformLongNames), FileOpsShortPath$ (platformShortPath, "" when it does not fit), FileOpsIsFolder, FileOpsGetAttr, FileOpsSetAttr, FileOpsModified$, FileOpsSetModified (fileOpsParseDate + fileOpsSetTime), FileOpsTreeSize with FileOpsTreeBytes / FileOpsTreeFiles / FileOpsTreeFolders, FileOpsFormatSize$) and jobs (FileOpsJobCreate, FileOpsJobAddSource, FileOpsJobClearAttr (masked to FILEOPS_ATTR_EDITABLE), FileOpsJobRunFor, FileOpsJobMessage$, FileOpsJobAnswer, FileOpsJobCancel, FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone, FileOpsJobBytesTotal (FileOpsProgressT; byte counts are DOUBLE), FileOpsJobFree). The fileops.bas CONSTs mirror fileOps.h and dvxPlat.h; the basdev test suite (caseIncludeConsts) checks each against its C value.
|
||||||
|
|
||||||
Listings and jobs are handles (small numbers, 0 = none) into a table owned by the library; each records the app that made it, and the shell's app-cleanup hook frees what an app leaves open when it ends. FileOpsJobRunFor(job, ms) steps the job until the time is used or it needs an answer, so a BASIC Timer can run it without blocking the program. A returned string stays valid until the next call returning one (the BASIC runtime copies it at once). Single files are handled by the built-in FILECOPY, KILL, NAME, MKDIR, RMDIR, GETATTR and SETATTR statements.
|
Listings and jobs are handles (small numbers, 0 = none) into a table owned by the library; each records the app that made it, and the shell's app-cleanup hook frees what an app leaves open when it ends. FileOpsJobRunFor(job, ms) steps the job until the time is used or it needs an answer, so a BASIC Timer can run it without blocking the program. A returned string stays valid until the next call returning one (the BASIC runtime copies it at once). Single files are handled by the built-in FILECOPY, KILL, NAME, MKDIR, RMDIR, GETATTR and SETATTR statements.
|
||||||
|
|
|
||||||
|
|
@ -5316,7 +5316,7 @@ Describes a filter entry in the file dialog's format dropdown. The glob pattern
|
||||||
bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);
|
bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a filename input, and an optional filter dropdown. The path box always shows the current folder. Typing "D:" or "D:folder" in the path box changes drive/folder; the file name box also accepts a relative or absolute path ("sub\x.txt", "C:\X\A.TXT") and navigates to its folder. A folder that does not exist shows an "Invalid Path" error. The dialog closes at once if the app that owns it is ended.
|
Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a drive dropdown, a filename input, and an optional filter dropdown. The path box always shows the current folder, and the drive dropdown (platformDriveList, so listing the drives never touches empty floppy or CD-ROM drives) its drive; choosing a drive goes to its root, and a drive that cannot be read shows "Drive X: is not ready." and leaves the dialog where it was. Typing "D:" or "D:folder" in the path box changes drive/folder; the file name box also accepts a relative or absolute path ("sub\x.txt", "C:\X\A.TXT") and navigates to its folder. A folder that does not exist shows an "Invalid Path" error. The dialog closes at once if the app that owns it is ended.
|
||||||
|
|
||||||
.table
|
.table
|
||||||
Parameter Description
|
Parameter Description
|
||||||
|
|
@ -6547,6 +6547,14 @@ stb_ds dynamic array. Each widget DXE appends its class pointer via wgtRegisterC
|
||||||
.index platformNormalizeFKeyScan
|
.index platformNormalizeFKeyScan
|
||||||
.index platformLockRegion
|
.index platformLockRegion
|
||||||
.index platformPathIsAbsolute
|
.index platformPathIsAbsolute
|
||||||
|
.index platformPathIsSep
|
||||||
|
.index platformPathNative
|
||||||
|
.index platformPathSame
|
||||||
|
.index platformPathIsUnder
|
||||||
|
.index platformPathJoin
|
||||||
|
.index platformPathDirName
|
||||||
|
.index platformPathTidy
|
||||||
|
.index Path Separators
|
||||||
.index platformPathSibling
|
.index platformPathSibling
|
||||||
.index platformReplaceFile
|
.index platformReplaceFile
|
||||||
.index platformFilterMatch
|
.index platformFilterMatch
|
||||||
|
|
@ -6960,7 +6968,7 @@ platformValidateLongName and platformValidateShortName are the two DOS name rule
|
||||||
|
|
||||||
platformLongNameChar is true when a long name may hold character c; a file transfer uses it to turn the characters a sender's name cannot keep into _.
|
platformLongNameChar is true when a long name may hold character c; a file transfer uses it to turn the characters a sender's name cannot keep into _.
|
||||||
|
|
||||||
platformShortPath writes the short (8.3) form of an existing path into out, for what reads names before a long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (an old program's command line). On a drive with long names DOS gives every part's short name (INT 21h AX=7160h CL=1); elsewhere, and when DOS cannot, path itself is copied. False when out is too small.
|
platformShortPath writes the short (8.3) form of an existing path into out, for what reads names before a long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (an old program's command line). On a drive with long names DOS gives every part's short name (INT 21h AX=7160h CL=1); elsewhere, and when DOS cannot, path itself is copied (platformPathTidy: "C:/DVX" gives "C:\\DVX"). False when out is too small.
|
||||||
|
|
||||||
.h3 platformStatSize
|
.h3 platformStatSize
|
||||||
|
|
||||||
|
|
@ -6986,13 +6994,37 @@ int32_t platformChdir(const char *path);
|
||||||
|
|
||||||
Change the working directory, including drive letter on DOS (switches drive first when the path starts with "X:"). If the chdir fails the previous drive is restored. A drive prefix that is not a letter is rejected. Returns 0 on success, -1 on failure.
|
Change the working directory, including drive letter on DOS (switches drive first when the path starts with "X:"). If the chdir fails the previous drive is restored. A drive prefix that is not a letter is rejected. Returns 0 on success, -1 on failure.
|
||||||
|
|
||||||
|
.h3 Path separators: platformPathIsSep / platformPathNative / platformPathSame / platformPathIsUnder / platformPathJoin / platformPathDirName / platformPathTidy
|
||||||
|
|
||||||
|
.code
|
||||||
|
bool platformPathIsSep(char c);
|
||||||
|
char *platformPathNative(char *path);
|
||||||
|
bool platformPathSame(const char *a, const char *b);
|
||||||
|
const char *platformPathIsUnder(const char *path, const char *dir);
|
||||||
|
bool platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
|
||||||
|
bool platformPathDirName(const char *path, char *out, int32_t outSize);
|
||||||
|
char *platformPathTidy(char *path);
|
||||||
|
.endcode
|
||||||
|
|
||||||
|
DVX paths follow DOS rules on every platform: '/' and '\\' are the same separator, so a path a program writes with '/' works on DOS exactly as with '\\', and names compare without case (DOS and the host build also share INI files, projects and program paths). Every path DVX shows uses the native separator, DVX_PATH_SEP ('\\' on DOS). DVX's own path code goes through these functions; a program that splits, compares or joins paths should too, never testing for one separator by hand.
|
||||||
|
|
||||||
|
.list
|
||||||
|
.item platformPathIsSep: true for '/' and '\\'.
|
||||||
|
.item platformPathNative: rewrites path's separators as DVX_PATH_SEP, in place, before it is shown (the DOS C library's getcwd and realpath give "c:/dvx"). Returns path.
|
||||||
|
.item platformPathSame: true when a and b name the same place by DOS rules: separators alike, a trailing one ignored, case ignored. Text only: "." or a relative path is not resolved.
|
||||||
|
.item platformPathIsUnder: when path is dir or lies below it, the rest of path after dir and its separator ("" for dir itself); NULL otherwise.
|
||||||
|
.item platformPathJoin: dir and name with one separator between them (none added when dir ends in one), every separator native; an absolute name, or an empty dir, gives name alone. False when it does not fit.
|
||||||
|
.item platformPathDirName: the folder part of path in native separators: a root keeps its separator ("C:\\X.TXT" gives "C:\\", since "C:" alone is the drive's current folder), "C:X.TXT" gives "C:", a bare name gives ".". False when it does not fit.
|
||||||
|
.item platformPathTidy: platformPathNative, then each run of separators made one (a leading pair, a network "\\\\server", kept) and a trailing separator dropped unless it is a root's: the form for a path handed to DOS directly, without the C library in between. platformShortPath and the file operations library's attribute calls use it.
|
||||||
|
.endlist
|
||||||
|
|
||||||
.h3 platformPathDirEnd
|
.h3 platformPathDirEnd
|
||||||
|
|
||||||
.code
|
.code
|
||||||
char *platformPathDirEnd(const char *path);
|
char *platformPathDirEnd(const char *path);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Return a pointer to the last directory separator in path, or NULL. On DOS accepts both "/" and "\\".
|
Return a pointer to the last directory separator in path (platformPathIsSep), or NULL.
|
||||||
|
|
||||||
.h3 platformPathIsAbsolute
|
.h3 platformPathIsAbsolute
|
||||||
|
|
||||||
|
|
@ -7000,7 +7032,7 @@ Return a pointer to the last directory separator in path, or NULL. On DOS accept
|
||||||
bool platformPathIsAbsolute(const char *path);
|
bool platformPathIsAbsolute(const char *path);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
True for a path rooted at a separator ('/' or '\\') or qualified with a drive letter ("C:..."). Both separators count on every platform. A NULL path is not absolute.
|
True for a path rooted at a separator ('/' or '\\') or qualified with a drive letter ("C:..."). A NULL path is not absolute.
|
||||||
|
|
||||||
.h3 platformPathBaseName
|
.h3 platformPathBaseName
|
||||||
|
|
||||||
|
|
@ -7008,7 +7040,7 @@ True for a path rooted at a separator ('/' or '\\') or qualified with a drive le
|
||||||
const char *platformPathBaseName(const char *path);
|
const char *platformPathBaseName(const char *path);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Return a pointer to the leaf (basename) portion of path.
|
Return a pointer to the leaf (basename) portion of path: after the last separator, or after a drive ("C:A.TXT" gives "A.TXT").
|
||||||
|
|
||||||
.h3 platformCopyFile
|
.h3 platformCopyFile
|
||||||
|
|
||||||
|
|
@ -7016,7 +7048,7 @@ Return a pointer to the leaf (basename) portion of path.
|
||||||
bool platformCopyFile(const char *srcPath, const char *dstPath);
|
bool platformCopyFile(const char *srcPath, const char *dstPath);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Byte-for-byte file copy. Returns false on any open, read, or write failure; a partially written destination is removed so no truncated copy is left behind.
|
Byte-for-byte file copy. Returns false on any open, read, or write failure; a partially written destination is removed so no truncated copy is left behind. A copy onto itself (platformPathSame) does nothing and returns true: opening the copy would otherwise empty the original first.
|
||||||
|
|
||||||
.h3 platformPathSibling
|
.h3 platformPathSibling
|
||||||
|
|
||||||
|
|
@ -7104,7 +7136,7 @@ void platformSetLogMemory(void);
|
||||||
const char *platformLogMemory(void);
|
const char *platformLogMemory(void);
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
The loader tests at startup whether the DVX folder can be written to (platformDirIsWritable creates and removes a scratch file in dir) and records the answer with platformSetReadOnly. When DVX runs from a CD-ROM or a write-protected disk (platformIsReadOnly is true): the log goes to DVX.LOG in the TEMP folder (platformTempDir: the TEMP or else TMP variable, NULL when neither is set) or, without one, to memory; the boot-time help rebuild is skipped (its output and counts could not be saved); prefsSave and prefsSaveAs keep a file they cannot write in memory, and prefsLoad reads it back until DVX exits; the shell says so once at startup; and a program that allows several copies runs only once when there is no TEMP folder (a second copy is a temporary copy of the module). platformSetLogMemory sends dvxLog to a buffer holding the latest PLATFORM_LOG_MEM_MAX bytes (the oldest whole lines are dropped), which platformLogMemory returns; platformSetLogPath goes back to a file. SETUP.EXE logs to memory unless /LOG= is given, so it never writes to its own disk.
|
The loader tests at startup whether the DVX folder can be written to (platformDirIsWritable creates and removes a scratch file in dir) and records the answer with platformSetReadOnly. When DVX runs from a CD-ROM or a write-protected disk (platformIsReadOnly is true): the log goes to DVX.LOG in the TEMP folder (platformTempDir: the TEMP or else TMP variable in native separators, NULL when neither is set) or, without one, to memory; the boot-time help rebuild is skipped (its output and counts could not be saved); prefsSave and prefsSaveAs keep a file they cannot write in memory, and prefsLoad reads it back until DVX exits; the shell says so once at startup; and a program that allows several copies runs only once when there is no TEMP folder (a second copy is a temporary copy of the module). platformSetLogMemory sends dvxLog to a buffer holding the latest PLATFORM_LOG_MEM_MAX bytes (the oldest whole lines are dropped), which platformLogMemory returns; platformSetLogPath goes back to a file. SETUP.EXE logs to memory unless /LOG= is given, so it never writes to its own disk.
|
||||||
|
|
||||||
.h3 platformLineEnding / platformStripLineEndings
|
.h3 platformLineEnding / platformStripLineEndings
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -401,7 +401,7 @@ Pre-built modal dialog boxes block the caller and run their own event loop via d
|
||||||
dvxMessageBox Generic message box with button/icon flags (MB_OK, MB_YESNO, MB_ICONERROR, etc.)
|
dvxMessageBox Generic message box with button/icon flags (MB_OK, MB_YESNO, MB_ICONERROR, etc.)
|
||||||
dvxErrorBox Shortcut for MB_OK + MB_ICONERROR
|
dvxErrorBox Shortcut for MB_OK + MB_ICONERROR
|
||||||
dvxInfoBox Shortcut for MB_OK + MB_ICONINFO
|
dvxInfoBox Shortcut for MB_OK + MB_ICONINFO
|
||||||
dvxFileDialog File open/save dialog with directory navigation and filter dropdown
|
dvxFileDialog File open/save dialog with directory navigation, a drive dropdown and a filter dropdown
|
||||||
dvxInputBox Single-line text input with OK/Cancel
|
dvxInputBox Single-line text input with OK/Cancel
|
||||||
dvxIntInputBox Integer input with spinner, clamp range, and step
|
dvxIntInputBox Integer input with spinner, clamp range, and step
|
||||||
dvxChoiceDialog List selection with OK/Cancel
|
dvxChoiceDialog List selection with OK/Cancel
|
||||||
|
|
|
||||||
|
|
@ -4463,6 +4463,7 @@ bool dvxSetWallpaper(AppContextT *ctx, const char *path) {
|
||||||
ctx->wallpaperBuf = buf;
|
ctx->wallpaperBuf = buf;
|
||||||
ctx->wallpaperPitch = pitch;
|
ctx->wallpaperPitch = pitch;
|
||||||
memcpy(ctx->wallpaperPath, newPath, sizeof(ctx->wallpaperPath));
|
memcpy(ctx->wallpaperPath, newPath, sizeof(ctx->wallpaperPath));
|
||||||
|
platformPathNative(ctx->wallpaperPath);
|
||||||
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
|
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -4845,7 +4846,7 @@ bool dvxWallpaperFromPrefs(AppContextT *ctx, PrefsHandleT *h) {
|
||||||
WallpaperModeE mode = (WallpaperModeE)prefsWallpaperChoices[prefsGetChoice(h, DESKTOP_SECTION, DESKTOP_KEY_MODE, prefsWallpaperChoices, PREFS_WALLPAPER_CHOICE_COUNT, WALLPAPER_MODE_DEFAULT)].value;
|
WallpaperModeE mode = (WallpaperModeE)prefsWallpaperChoices[prefsGetChoice(h, DESKTOP_SECTION, DESKTOP_KEY_MODE, prefsWallpaperChoices, PREFS_WALLPAPER_CHOICE_COUNT, WALLPAPER_MODE_DEFAULT)].value;
|
||||||
const char *path = prefsGetString(h, DESKTOP_SECTION, DESKTOP_KEY_WALLPAPER, "");
|
const char *path = prefsGetString(h, DESKTOP_SECTION, DESKTOP_KEY_WALLPAPER, "");
|
||||||
|
|
||||||
if (strcmp(path, ctx->wallpaperPath) != 0) {
|
if (!platformPathSame(path, ctx->wallpaperPath)) {
|
||||||
ctx->wallpaperMode = mode;
|
ctx->wallpaperMode = mode;
|
||||||
return dvxSetWallpaper(ctx, path[0] ? path : NULL);
|
return dvxSetWallpaper(ctx, path[0] ? path : NULL);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,12 @@
|
||||||
#define FD_MSG_MAX (DVX_MAX_PATH + 64) // confirmation text: file name + wording
|
#define FD_MSG_MAX (DVX_MAX_PATH + 64) // confirmation text: file name + wording
|
||||||
#define FD_ENTRY_INITIAL_CAP 64 // initial directory listing capacity (doubles)
|
#define FD_ENTRY_INITIAL_CAP 64 // initial directory listing capacity (doubles)
|
||||||
|
|
||||||
|
// File dialog widgets that keep Enter for themselves (enterOwners slots)
|
||||||
|
#define FD_OWNER_PATH 0
|
||||||
|
#define FD_OWNER_LIST 1
|
||||||
|
#define FD_OWNER_FILTER 2
|
||||||
|
#define FD_OWNER_DRIVE 3
|
||||||
|
|
||||||
// Input box layout constants
|
// Input box layout constants
|
||||||
#define IB_DIALOG_WIDTH 300
|
#define IB_DIALOG_WIDTH 300
|
||||||
#define IB_PADDING 8
|
#define IB_PADDING 8
|
||||||
|
|
@ -165,7 +171,7 @@
|
||||||
#define DLG_FIELD_GAP 4
|
#define DLG_FIELD_GAP 4
|
||||||
|
|
||||||
// Widgets per dialog that keep Enter for themselves (see dialogOnKey)
|
// Widgets per dialog that keep Enter for themselves (see dialogOnKey)
|
||||||
#define DLG_ENTER_OWNERS_MAX 3
|
#define DLG_ENTER_OWNERS_MAX 4
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -279,6 +285,10 @@ typedef struct {
|
||||||
int32_t filterCount;
|
int32_t filterCount;
|
||||||
int32_t activeFilter; // index into filters[]
|
int32_t activeFilter; // index into filters[]
|
||||||
const char *filterLabels[FD_MAX_FILTERS]; // dropdown items (borrowed labels)
|
const char *filterLabels[FD_MAX_FILTERS]; // dropdown items (borrowed labels)
|
||||||
|
PlatformDriveT drives[PLATFORM_DRIVE_MAX]; // platformDriveList
|
||||||
|
char driveLabels[PLATFORM_DRIVE_MAX][PLATFORM_DRIVE_ROOT_MAX]; // "C:" ("/" elsewhere)
|
||||||
|
const char *driveItems[PLATFORM_DRIVE_MAX]; // drive dropdown items (driveLabels)
|
||||||
|
int32_t driveCount;
|
||||||
FdEntryT *entries; // directory listing, sorted dirs first
|
FdEntryT *entries; // directory listing, sorted dirs first
|
||||||
int32_t entryCount;
|
int32_t entryCount;
|
||||||
int32_t entryCap;
|
int32_t entryCap;
|
||||||
|
|
@ -287,6 +297,7 @@ typedef struct {
|
||||||
WidgetT *pathInput;
|
WidgetT *pathInput;
|
||||||
WidgetT *nameInput;
|
WidgetT *nameInput;
|
||||||
WidgetT *filterDd;
|
WidgetT *filterDd;
|
||||||
|
WidgetT *driveDd;
|
||||||
char outPath[DVX_MAX_PATH]; // accepted full path
|
char outPath[DVX_MAX_PATH]; // accepted full path
|
||||||
} FileDialogStateT;
|
} FileDialogStateT;
|
||||||
|
|
||||||
|
|
@ -332,15 +343,17 @@ static bool dialogWindowLive(const AppContextT *ctx, const WindowT *win,
|
||||||
static void drawIconCircle(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, uint32_t color);
|
static void drawIconCircle(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, uint32_t color);
|
||||||
static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t iconType, uint32_t color);
|
static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t iconType, uint32_t color);
|
||||||
static bool fdAcceptFile(FileDialogStateT *fd, const char *name);
|
static bool fdAcceptFile(FileDialogStateT *fd, const char *name);
|
||||||
|
static void fdAddDrives(FileDialogStateT *fd, WidgetT *row);
|
||||||
static bool fdAddEntry(FileDialogStateT *fd, const char *name, bool isDir);
|
static bool fdAddEntry(FileDialogStateT *fd, const char *name, bool isDir);
|
||||||
static int32_t fdDirPartLen(const char *name);
|
static int32_t fdDirPartLen(const char *name);
|
||||||
static void fdDisplayToDirName(const char *display, char *out, int32_t outSize);
|
static void fdDisplayToDirName(const char *display, char *out, int32_t outSize);
|
||||||
|
static int32_t fdDriveOf(const FileDialogStateT *fd, const char *dir);
|
||||||
static int fdEntryCompare(const void *a, const void *b);
|
static int fdEntryCompare(const void *a, const void *b);
|
||||||
static const char *fdExtractPattern(const FileFilterT *f, char *buf, int32_t bufSize);
|
static const char *fdExtractPattern(const FileFilterT *f, char *buf, int32_t bufSize);
|
||||||
static void fdFreeEntries(FileDialogStateT *fd);
|
static void fdFreeEntries(FileDialogStateT *fd);
|
||||||
static bool fdJoinPath(char *out, size_t outSize, const char *dir, const char *name);
|
|
||||||
static void fdLoadDir(FileDialogStateT *fd);
|
static void fdLoadDir(FileDialogStateT *fd);
|
||||||
static bool fdNavigate(FileDialogStateT *fd, const char *path);
|
static bool fdNavigate(FileDialogStateT *fd, const char *path);
|
||||||
|
static void fdOnDriveChange(WidgetT *w);
|
||||||
static void fdOnFilterChange(WidgetT *w);
|
static void fdOnFilterChange(WidgetT *w);
|
||||||
static void fdOnListClick(WidgetT *w);
|
static void fdOnListClick(WidgetT *w);
|
||||||
static void fdOnListDblClick(WidgetT *w);
|
static void fdOnListDblClick(WidgetT *w);
|
||||||
|
|
@ -1001,7 +1014,7 @@ int32_t dvxErrorBox(AppContextT *ctx, const char *title, const char *message) {
|
||||||
|
|
||||||
|
|
||||||
// Creates a modal file dialog using the widget system. The layout is:
|
// Creates a modal file dialog using the widget system. The layout is:
|
||||||
// - Path input (with Enter-to-navigate)
|
// - Path input (with Enter-to-navigate) and the drive dropdown
|
||||||
// - File listbox (single-click selects, double-click opens/accepts)
|
// - File listbox (single-click selects, double-click opens/accepts)
|
||||||
// - Optional filter dropdown (only shown if filters are provided)
|
// - Optional filter dropdown (only shown if filters are provided)
|
||||||
// - Filename input (a typed name may include a directory or drive)
|
// - Filename input (a typed name may include a directory or drive)
|
||||||
|
|
@ -1020,13 +1033,16 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
|
||||||
fd.filterCount = filterCount;
|
fd.filterCount = filterCount;
|
||||||
|
|
||||||
// Set initial directory. Unknown cwd: resolve accepted names
|
// Set initial directory. Unknown cwd: resolve accepted names
|
||||||
// relative to '.' rather than the filesystem root.
|
// relative to '.' rather than the filesystem root. Shown (and
|
||||||
|
// returned) with the native separators.
|
||||||
if (initialDir && initialDir[0]) {
|
if (initialDir && initialDir[0]) {
|
||||||
snprintf(fd.curDir, sizeof(fd.curDir), "%s", initialDir);
|
snprintf(fd.curDir, sizeof(fd.curDir), "%s", initialDir);
|
||||||
} else if (!getcwd(fd.curDir, sizeof(fd.curDir))) {
|
} else if (!getcwd(fd.curDir, sizeof(fd.curDir))) {
|
||||||
strcpy(fd.curDir, ".");
|
strcpy(fd.curDir, ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
platformPathNative(fd.curDir);
|
||||||
|
|
||||||
// Create dialog window (FD_DIALOG_WIDTH/HEIGHT are the outer size)
|
// Create dialog window (FD_DIALOG_WIDTH/HEIGHT are the outer size)
|
||||||
int32_t contentW = FD_DIALOG_WIDTH - CHROME_TOTAL_SIDE * 2;
|
int32_t contentW = FD_DIALOG_WIDTH - CHROME_TOTAL_SIDE * 2;
|
||||||
int32_t contentH = FD_DIALOG_HEIGHT - CHROME_TOTAL_TOP - CHROME_TOTAL_BOTTOM;
|
int32_t contentH = FD_DIALOG_HEIGHT - CHROME_TOTAL_TOP - CHROME_TOTAL_BOTTOM;
|
||||||
|
|
@ -1051,8 +1067,10 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
|
||||||
// mid-edit (and stat() on each key).
|
// mid-edit (and stat() on each key).
|
||||||
if (fd.pathInput) {
|
if (fd.pathInput) {
|
||||||
fd.pathInput->onKeyDown = fdOnPathKey;
|
fd.pathInput->onKeyDown = fdOnPathKey;
|
||||||
fd.base.enterOwners[0] = fd.pathInput;
|
fd.base.enterOwners[FD_OWNER_PATH] = fd.pathInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fdAddDrives(&fd, pathRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// File list
|
// File list
|
||||||
|
|
@ -1062,7 +1080,7 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
|
||||||
fd.fileList->weight = WGT_WEIGHT_FILL;
|
fd.fileList->weight = WGT_WEIGHT_FILL;
|
||||||
fd.fileList->onChange = fdOnListClick;
|
fd.fileList->onChange = fdOnListClick;
|
||||||
fd.fileList->onDblClick = fdOnListDblClick;
|
fd.fileList->onDblClick = fdOnListDblClick;
|
||||||
fd.base.enterOwners[1] = fd.fileList;
|
fd.base.enterOwners[FD_OWNER_LIST] = fd.fileList;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter row (if filters provided). The dropdown borrows the label
|
// Filter row (if filters provided). The dropdown borrows the label
|
||||||
|
|
@ -1084,7 +1102,7 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
|
||||||
wgtDropdownSetItems(fd.filterDd, fd.filterLabels, fc);
|
wgtDropdownSetItems(fd.filterDd, fd.filterLabels, fc);
|
||||||
wgtDropdownSetSelected(fd.filterDd, 0);
|
wgtDropdownSetSelected(fd.filterDd, 0);
|
||||||
fd.filterDd->onChange = fdOnFilterChange;
|
fd.filterDd->onChange = fdOnFilterChange;
|
||||||
fd.base.enterOwners[2] = fd.filterDd;
|
fd.base.enterOwners[FD_OWNER_FILTER] = fd.filterDd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1307,7 +1325,7 @@ static bool fdAcceptFile(FileDialogStateT *fd, const char *name) {
|
||||||
|
|
||||||
char fullPath[DVX_MAX_PATH];
|
char fullPath[DVX_MAX_PATH];
|
||||||
|
|
||||||
if (!fdJoinPath(fullPath, sizeof(fullPath), fd->curDir, name)) {
|
if (!platformPathJoin(fullPath, sizeof(fullPath), fd->curDir, name)) {
|
||||||
dvxErrorBox(fd->ctx, "Invalid Filename", "The path is too long.");
|
dvxErrorBox(fd->ctx, "Invalid Filename", "The path is too long.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1339,6 +1357,41 @@ static bool fdAcceptFile(FileDialogStateT *fd, const char *name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The drive dropdown at the end of row: every drive platformDriveList
|
||||||
|
// reports (it never touches the media, so empty floppy and CD-ROM drives
|
||||||
|
// cost nothing), each shown as its root without the separator ("C:";
|
||||||
|
// "/" stays). Nothing when the list is empty.
|
||||||
|
static void fdAddDrives(FileDialogStateT *fd, WidgetT *row) {
|
||||||
|
fd->driveCount = platformDriveList(fd->drives, PLATFORM_DRIVE_MAX);
|
||||||
|
|
||||||
|
if (fd->driveCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < fd->driveCount; i++) {
|
||||||
|
const char *rootPath = fd->drives[i].root;
|
||||||
|
size_t len = strlen(rootPath);
|
||||||
|
|
||||||
|
if (len > 1 && platformPathIsSep(rootPath[len - 1])) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(fd->driveLabels[i], rootPath, len);
|
||||||
|
fd->driveLabels[i][len] = '\0';
|
||||||
|
fd->driveItems[i] = fd->driveLabels[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtLabel(row, "&Drive:");
|
||||||
|
fd->driveDd = wgtDropdown(row);
|
||||||
|
|
||||||
|
if (fd->driveDd) {
|
||||||
|
wgtDropdownSetItems(fd->driveDd, fd->driveItems, fd->driveCount);
|
||||||
|
fd->driveDd->onChange = fdOnDriveChange;
|
||||||
|
fd->base.enterOwners[FD_OWNER_DRIVE] = fd->driveDd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Appends one listing row, growing the array geometrically. Returns
|
// Appends one listing row, growing the array geometrically. Returns
|
||||||
// false (listing left consistent) when memory runs out.
|
// false (listing left consistent) when memory runs out.
|
||||||
static bool fdAddEntry(FileDialogStateT *fd, const char *name, bool isDir) {
|
static bool fdAddEntry(FileDialogStateT *fd, const char *name, bool isDir) {
|
||||||
|
|
@ -1373,7 +1426,7 @@ static int32_t fdDirPartLen(const char *name) {
|
||||||
int32_t len = 0;
|
int32_t len = 0;
|
||||||
|
|
||||||
for (int32_t i = 0; name[i]; i++) {
|
for (int32_t i = 0; name[i]; i++) {
|
||||||
if (name[i] == '/' || name[i] == '\\' || (i == 1 && name[i] == ':')) {
|
if (platformPathIsSep(name[i]) || (i == 1 && name[i] == ':')) {
|
||||||
len = i + 1;
|
len = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1396,6 +1449,27 @@ static void fdDisplayToDirName(const char *display, char *out, int32_t outSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The drive list entry that holds dir: the longest drive label ("C:",
|
||||||
|
// "/") its full path starts with, case ignored; -1 when none does.
|
||||||
|
static int32_t fdDriveOf(const FileDialogStateT *fd, const char *dir) {
|
||||||
|
char canon[PATH_MAX];
|
||||||
|
const char *full = realpath(dir, canon) ? canon : dir;
|
||||||
|
int32_t best = -1;
|
||||||
|
size_t bestLen = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < fd->driveCount; i++) {
|
||||||
|
size_t len = strlen(fd->driveLabels[i]);
|
||||||
|
|
||||||
|
if (len > bestLen && strncasecmp(full, fd->driveLabels[i], len) == 0) {
|
||||||
|
best = i;
|
||||||
|
bestLen = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sort comparator for the directory listing: directories first, then
|
// Sort comparator for the directory listing: directories first, then
|
||||||
// case-insensitive by name.
|
// case-insensitive by name.
|
||||||
static int fdEntryCompare(const void *a, const void *b) {
|
static int fdEntryCompare(const void *a, const void *b) {
|
||||||
|
|
@ -1449,23 +1523,19 @@ static void fdFreeEntries(FileDialogStateT *fd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Joins dir and name with the platform separator into out. Returns
|
|
||||||
// false when the result does not fit.
|
|
||||||
static bool fdJoinPath(char *out, size_t outSize, const char *dir, const char *name) {
|
|
||||||
int n = snprintf(out, outSize, "%s" DVX_PATH_SEP "%s", dir, name);
|
|
||||||
|
|
||||||
return n >= 0 && (size_t)n < outSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Reads the current directory, applies the active file filter, sorts
|
// Reads the current directory, applies the active file filter, sorts
|
||||||
// (directories first, then alphabetical), and updates the listbox and
|
// (directories first, then alphabetical), and updates the listbox, the
|
||||||
// the path box. The path box is updated first, so it shows the current
|
// path box and the drive dropdown. The path box and drive are updated
|
||||||
// directory even when the listing is empty or unreadable.
|
// first, so they show the current directory even when the listing is
|
||||||
|
// empty or unreadable.
|
||||||
|
|
||||||
static void fdLoadDir(FileDialogStateT *fd) {
|
static void fdLoadDir(FileDialogStateT *fd) {
|
||||||
wgtSetText(fd->pathInput, fd->curDir);
|
wgtSetText(fd->pathInput, fd->curDir);
|
||||||
|
|
||||||
|
if (fd->driveDd) {
|
||||||
|
wgtDropdownSetSelected(fd->driveDd, fdDriveOf(fd, fd->curDir));
|
||||||
|
}
|
||||||
|
|
||||||
// The listbox holds the listItems array uncopied: drop its reference
|
// The listbox holds the listItems array uncopied: drop its reference
|
||||||
// before freeing so no early return below leaves it painting freed
|
// before freeing so no early return below leaves it painting freed
|
||||||
// memory.
|
// memory.
|
||||||
|
|
@ -1496,7 +1566,7 @@ static void fdLoadDir(FileDialogStateT *fd) {
|
||||||
char fullPath[DVX_MAX_PATH];
|
char fullPath[DVX_MAX_PATH];
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (!fdJoinPath(fullPath, sizeof(fullPath), fd->curDir, ent->d_name) || stat(fullPath, &st) != 0) {
|
if (!platformPathJoin(fullPath, sizeof(fullPath), fd->curDir, ent->d_name) || stat(fullPath, &st) != 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1553,16 +1623,16 @@ static void fdLoadDir(FileDialogStateT *fd) {
|
||||||
// bare "D:" meaning that drive's current directory) are used as given;
|
// bare "D:" meaning that drive's current directory) are used as given;
|
||||||
// relative paths are resolved against curDir. realpath canonicalizes
|
// relative paths are resolved against curDir. realpath canonicalizes
|
||||||
// the result (resolving "..", symlinks and drive-relative specs) so the
|
// the result (resolving "..", symlinks and drive-relative specs) so the
|
||||||
// path box always shows a clean absolute path. Returns false, leaving
|
// path box always shows a clean absolute path, in native separators. Returns false, leaving
|
||||||
// the dialog unchanged, when the path is not an existing directory.
|
// the dialog unchanged, when the path is not an existing directory.
|
||||||
|
|
||||||
static bool fdNavigate(FileDialogStateT *fd, const char *path) {
|
static bool fdNavigate(FileDialogStateT *fd, const char *path) {
|
||||||
char resolved[DVX_MAX_PATH];
|
char resolved[DVX_MAX_PATH];
|
||||||
bool absolute = (path[0] == '/' || path[0] == '\\' || (path[0] && path[1] == ':'));
|
bool absolute = platformPathIsAbsolute(path);
|
||||||
|
|
||||||
if (absolute) {
|
if (absolute) {
|
||||||
snprintf(resolved, sizeof(resolved), "%s", path);
|
snprintf(resolved, sizeof(resolved), "%s", path);
|
||||||
} else if (!fdJoinPath(resolved, sizeof(resolved), fd->curDir, path)) {
|
} else if (!platformPathJoin(resolved, sizeof(resolved), fd->curDir, path)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1577,11 +1647,33 @@ static bool fdNavigate(FileDialogStateT *fd, const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(fd->curDir, sizeof(fd->curDir), "%s", target);
|
snprintf(fd->curDir, sizeof(fd->curDir), "%s", target);
|
||||||
|
platformPathNative(fd->curDir);
|
||||||
fdLoadDir(fd);
|
fdLoadDir(fd);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Drive dropdown changed: go to that drive's root. A drive that cannot
|
||||||
|
// be read (no disk in it) says so, and the dropdown shows the current
|
||||||
|
// folder's drive again.
|
||||||
|
static void fdOnDriveChange(WidgetT *w) {
|
||||||
|
FileDialogStateT *fd = (FileDialogStateT *)w->window->userData;
|
||||||
|
int32_t sel = wgtDropdownGetSelected(w);
|
||||||
|
|
||||||
|
if (sel < 0 || sel >= fd->driveCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fdNavigate(fd, fd->drives[sel].root)) {
|
||||||
|
char msg[FD_MSG_MAX];
|
||||||
|
|
||||||
|
snprintf(msg, sizeof(msg), "Drive %s is not ready.", fd->driveLabels[sel]);
|
||||||
|
wgtDropdownSetSelected(w, fdDriveOf(fd, fd->curDir));
|
||||||
|
dvxErrorBox(fd->ctx, "Drive Not Ready", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Filter dropdown changed.
|
// Filter dropdown changed.
|
||||||
|
|
||||||
static void fdOnFilterChange(WidgetT *w) {
|
static void fdOnFilterChange(WidgetT *w) {
|
||||||
|
|
@ -1694,7 +1786,7 @@ static void fdOnOk(WidgetT *w) {
|
||||||
char testPath[DVX_MAX_PATH];
|
char testPath[DVX_MAX_PATH];
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (fdJoinPath(testPath, sizeof(testPath), fd->curDir, name) && stat(testPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
if (platformPathJoin(testPath, sizeof(testPath), fd->curDir, name) && stat(testPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
fdNavigate(fd, testPath);
|
fdNavigate(fd, testPath);
|
||||||
wgtSetText(fd->nameInput, "");
|
wgtSetText(fd->nameInput, "");
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ static bool matchWord(const char *val, const char *const *words, int32_t count)
|
||||||
// none is held.
|
// none is held.
|
||||||
static char *memFileCopy(const char *path, int32_t *len) {
|
static char *memFileCopy(const char *path, int32_t *len) {
|
||||||
for (int32_t i = 0; i < arrlen(sMemFiles); i++) {
|
for (int32_t i = 0; i < arrlen(sMemFiles); i++) {
|
||||||
if (strcasecmp(sMemFiles[i].path, path) == 0) {
|
if (platformPathSame(sMemFiles[i].path, path)) {
|
||||||
char *copy = (char *)malloc((size_t)sMemFiles[i].len + 1);
|
char *copy = (char *)malloc((size_t)sMemFiles[i].len + 1);
|
||||||
|
|
||||||
if (copy) {
|
if (copy) {
|
||||||
|
|
@ -281,7 +281,7 @@ static bool memFileStore(const char *path, const char *text, int32_t len) {
|
||||||
copy[len] = '\0';
|
copy[len] = '\0';
|
||||||
|
|
||||||
for (int32_t i = 0; i < arrlen(sMemFiles); i++) {
|
for (int32_t i = 0; i < arrlen(sMemFiles); i++) {
|
||||||
if (strcasecmp(sMemFiles[i].path, path) == 0) {
|
if (platformPathSame(sMemFiles[i].path, path)) {
|
||||||
free(sMemFiles[i].text);
|
free(sMemFiles[i].text);
|
||||||
sMemFiles[i].text = copy;
|
sMemFiles[i].text = copy;
|
||||||
sMemFiles[i].len = len;
|
sMemFiles[i].len = len;
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,8 @@ void platformSetReadOnly(bool readOnly);
|
||||||
bool platformDirIsWritable(const char *dir);
|
bool platformDirIsWritable(const char *dir);
|
||||||
|
|
||||||
// The folder for scratch files: the TEMP (or else TMP) environment
|
// The folder for scratch files: the TEMP (or else TMP) environment
|
||||||
// variable; NULL when neither is set.
|
// variable, in native separators (a copy, valid until the next call);
|
||||||
|
// NULL when neither is set.
|
||||||
const char *platformTempDir(void);
|
const char *platformTempDir(void);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -543,8 +544,9 @@ const char *platformGetSystemInfo(const DisplayT *display);
|
||||||
// The short (8.3) form of an existing path, for what reads names before a
|
// The short (8.3) form of an existing path, for what reads names before a
|
||||||
// long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (a DOS
|
// long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (a DOS
|
||||||
// command line): on a drive with long names DOS gives every part's short
|
// command line): on a drive with long names DOS gives every part's short
|
||||||
// name; elsewhere, and when DOS cannot, path itself. False when out is
|
// name; elsewhere, and when DOS cannot, path itself (platformPathTidy:
|
||||||
// too small.
|
// native separators, so "C:/DVX" works the same). False when out is too
|
||||||
|
// small.
|
||||||
bool platformShortPath(const char *path, char *out, int32_t outSize);
|
bool platformShortPath(const char *path, char *out, int32_t outSize);
|
||||||
|
|
||||||
struct stat;
|
struct stat;
|
||||||
|
|
@ -603,15 +605,54 @@ int32_t platformChdir(const char *path);
|
||||||
// when switching between graphics modes live.
|
// when switching between graphics modes live.
|
||||||
void platformVideoFreeBuffers(DisplayT *d);
|
void platformVideoFreeBuffers(DisplayT *d);
|
||||||
|
|
||||||
// Return a pointer to the last directory separator in path, or NULL if
|
// Return a pointer to the last directory separator in path
|
||||||
// none is found. On DOS this checks both '/' and '\\' since DJGPP
|
// (platformPathIsSep), or NULL if none is found.
|
||||||
// accepts either.
|
|
||||||
char *platformPathDirEnd(const char *path);
|
char *platformPathDirEnd(const char *path);
|
||||||
|
|
||||||
|
// out = the folder part of path in native separators: everything before
|
||||||
|
// the last separator, a root keeping its own ("C:\\X.TXT" -> "C:\\", since
|
||||||
|
// "C:" alone is the drive's current folder), the drive of "C:X.TXT", or
|
||||||
|
// "." when path names no folder. False when it does not fit.
|
||||||
|
bool platformPathDirName(const char *path, char *out, int32_t outSize);
|
||||||
|
|
||||||
// True for a path rooted at a separator ('/' or '\\') or qualified with
|
// True for a path rooted at a separator ('/' or '\\') or qualified with
|
||||||
// a drive letter ("C:..."). Both separators count on every platform.
|
// a drive letter ("C:...").
|
||||||
bool platformPathIsAbsolute(const char *path);
|
bool platformPathIsAbsolute(const char *path);
|
||||||
|
|
||||||
|
// True when c separates the parts of a path: '/' and '\\' alike, on every
|
||||||
|
// platform, so a path a program writes with '/' works on DOS exactly as
|
||||||
|
// with '\\' (DOS and the host build also share INI files, projects and
|
||||||
|
// program paths). The one rule all of DVX's path handling follows; the
|
||||||
|
// price is that a host file name cannot hold a literal '\\'.
|
||||||
|
bool platformPathIsSep(char c);
|
||||||
|
|
||||||
|
// When path is dir itself or lies below it, the rest of path after dir
|
||||||
|
// and its separator ("" for dir itself); NULL otherwise. Compared as
|
||||||
|
// platformPathSame does.
|
||||||
|
const char *platformPathIsUnder(const char *path, const char *dir);
|
||||||
|
|
||||||
|
// out = dir and name with one separator between them (none added when
|
||||||
|
// dir ends in one), every separator native; an absolute name, or an empty
|
||||||
|
// dir, gives name alone. False when it does not fit in outSize.
|
||||||
|
bool platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
|
||||||
|
|
||||||
|
// Rewrite path's separators as the platform's own (DVX_PATH_SEP), in
|
||||||
|
// place, before it is shown: on DOS every '/' becomes '\\' (the C
|
||||||
|
// library hands back "c:/dvx"). Returns path (NULL stays NULL).
|
||||||
|
char *platformPathNative(char *path);
|
||||||
|
|
||||||
|
// True when a and b name the same place by DOS rules, on every platform
|
||||||
|
// (DVX's own paths mix spellings such as "APPS/X.APP" and "apps\\x.app"):
|
||||||
|
// the separators interchangeable, a trailing one ignored, case ignored.
|
||||||
|
// (Text only: "." or a relative path is not resolved.)
|
||||||
|
bool platformPathSame(const char *a, const char *b);
|
||||||
|
|
||||||
|
// platformPathNative, then a run of separators made one (a leading pair,
|
||||||
|
// a network "\\\\server", kept) and a trailing one dropped unless it is a
|
||||||
|
// root's ("C:\\", "\\"): the form for a path handed to DOS directly,
|
||||||
|
// without the C library's own clean-up. In place; returns path.
|
||||||
|
char *platformPathTidy(char *path);
|
||||||
|
|
||||||
// Scratch-file extension markers for crash-safe rewrites
|
// Scratch-file extension markers for crash-safe rewrites
|
||||||
// (platformPathSibling + platformReplaceFile). The sibling's extension is
|
// (platformPathSibling + platformReplaceFile). The sibling's extension is
|
||||||
// the marker followed by the leading characters of the original one, so an
|
// the marker followed by the leading characters of the original one, so an
|
||||||
|
|
@ -638,13 +679,15 @@ bool platformPathSibling(char *out, int32_t outSize, const char *path, const cha
|
||||||
// deleted, and prefsLoad falls back to reading it.
|
// deleted, and prefsLoad falls back to reading it.
|
||||||
bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);
|
bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);
|
||||||
|
|
||||||
// Return a pointer to the leaf (basename) portion of path. Never returns
|
// Return a pointer to the leaf (basename) portion of path: after the last
|
||||||
// NULL -- if the path has no separator, the whole path is the basename.
|
// separator, or after a drive ("C:A.TXT" -> "A.TXT"). Never returns
|
||||||
|
// NULL -- with neither, the whole path is the basename.
|
||||||
const char *platformPathBaseName(const char *path);
|
const char *platformPathBaseName(const char *path);
|
||||||
|
|
||||||
// Byte-for-byte file copy. Returns false on any open/read/write failure;
|
// Byte-for-byte file copy. Returns false on any open/read/write failure;
|
||||||
// a partially written destination is removed so no truncated copy is
|
// a partially written destination is removed so no truncated copy is
|
||||||
// left behind.
|
// left behind. A copy onto itself (platformPathSame) does nothing and
|
||||||
|
// returns true.
|
||||||
bool platformCopyFile(const char *srcPath, const char *dstPath);
|
bool platformCopyFile(const char *srcPath, const char *dstPath);
|
||||||
|
|
||||||
// Slurp a whole file into a freshly-malloc'd, NUL-terminated buffer.
|
// Slurp a whole file into a freshly-malloc'd, NUL-terminated buffer.
|
||||||
|
|
|
||||||
|
|
@ -2162,14 +2162,22 @@ void platformRegisterSymOverrides(const PlatformSymOverrideT *entries) {
|
||||||
// name), which Windows answers in short names but DOSLFN in long ones.
|
// name), which Windows answers in short names but DOSLFN in long ones.
|
||||||
bool platformShortPath(const char *path, char *out, int32_t outSize) {
|
bool platformShortPath(const char *path, char *out, int32_t outSize) {
|
||||||
char sfn[DVX_MAX_PATH];
|
char sfn[DVX_MAX_PATH];
|
||||||
size_t len = strlen(path) + 1;
|
char full[DVX_MAX_PATH];
|
||||||
uint32_t dst = __tb + (uint32_t)len;
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
|
|
||||||
if (len <= sizeof(sfn) && platformLongNames(path)) {
|
// DOS gets the path in its own form ('/' accepted from callers)
|
||||||
|
if (snprintf(full, sizeof(full), "%s", path) >= (int)sizeof(full)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
platformPathTidy(full);
|
||||||
|
|
||||||
|
if (platformLongNames(full)) {
|
||||||
|
size_t len = strlen(full) + 1;
|
||||||
|
uint32_t dst = __tb + (uint32_t)len;
|
||||||
__dpmi_regs r;
|
__dpmi_regs r;
|
||||||
|
|
||||||
dosmemput(path, len, __tb);
|
dosmemput(full, len, __tb);
|
||||||
memset(&r, 0, sizeof(r));
|
memset(&r, 0, sizeof(r));
|
||||||
r.x.ax = DOS_FN_LFN_TRUENAME;
|
r.x.ax = DOS_FN_LFN_TRUENAME;
|
||||||
r.h.cl = LFN_TRUENAME_SHORT;
|
r.h.cl = LFN_TRUENAME_SHORT;
|
||||||
|
|
@ -2186,7 +2194,7 @@ bool platformShortPath(const char *path, char *out, int32_t outSize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return snprintf(out, (size_t)outSize, "%s", ok ? sfn : path) < outSize;
|
return snprintf(out, (size_t)outSize, "%s", ok ? sfn : full) < outSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3424,8 +3432,15 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
DXE_EXPORT(platformNormalizeFKeyScan)
|
DXE_EXPORT(platformNormalizeFKeyScan)
|
||||||
DXE_EXPORT(platformPathBaseName)
|
DXE_EXPORT(platformPathBaseName)
|
||||||
DXE_EXPORT(platformPathDirEnd)
|
DXE_EXPORT(platformPathDirEnd)
|
||||||
|
DXE_EXPORT(platformPathDirName)
|
||||||
DXE_EXPORT(platformPathIsAbsolute)
|
DXE_EXPORT(platformPathIsAbsolute)
|
||||||
|
DXE_EXPORT(platformPathIsSep)
|
||||||
|
DXE_EXPORT(platformPathIsUnder)
|
||||||
|
DXE_EXPORT(platformPathJoin)
|
||||||
|
DXE_EXPORT(platformPathNative)
|
||||||
|
DXE_EXPORT(platformPathSame)
|
||||||
DXE_EXPORT(platformPathSibling)
|
DXE_EXPORT(platformPathSibling)
|
||||||
|
DXE_EXPORT(platformPathTidy)
|
||||||
DXE_EXPORT(platformReadFile)
|
DXE_EXPORT(platformReadFile)
|
||||||
DXE_EXPORT(platformRegisterDxeExports)
|
DXE_EXPORT(platformRegisterDxeExports)
|
||||||
DXE_EXPORT(platformRegisterSymOverrides)
|
DXE_EXPORT(platformRegisterSymOverrides)
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,10 @@
|
||||||
// Scratch file platformDirIsWritable makes and removes
|
// Scratch file platformDirIsWritable makes and removes
|
||||||
#define WRITE_PROBE_NAME "DVXWTEST.$$$"
|
#define WRITE_PROBE_NAME "DVXWTEST.$$$"
|
||||||
|
|
||||||
|
// "C:" and "C:\\" (platformPathBaseName, platformPathTidy)
|
||||||
|
#define DOS_DRIVE_SPEC_LEN 2
|
||||||
|
#define DOS_ROOT_LEN 3
|
||||||
|
|
||||||
// File name rules (platformValidateShortName / platformValidateLongName):
|
// File name rules (platformValidateShortName / platformValidateLongName):
|
||||||
// an 8.3 name's parts, and the characters each kind of name cannot hold
|
// an 8.3 name's parts, and the characters each kind of name cannot hold
|
||||||
// (besides control characters)
|
// (besides control characters)
|
||||||
|
|
@ -142,6 +146,8 @@ int32_t dvxTrimRight(char *buf);
|
||||||
static const ScanFoldT *findScanFold(int32_t scancode);
|
static const ScanFoldT *findScanFold(int32_t scancode);
|
||||||
static bool globMatchLen(const char *pat, int32_t patLen, const char *name);
|
static bool globMatchLen(const char *pat, int32_t patLen, const char *name);
|
||||||
static void logMemAppend(const char *fmt, va_list ap);
|
static void logMemAppend(const char *fmt, va_list ap);
|
||||||
|
static bool pathCharEq(char a, char b);
|
||||||
|
static int32_t pathTrimmedLen(const char *path);
|
||||||
char platformAltScanToChar(int32_t scancode);
|
char platformAltScanToChar(int32_t scancode);
|
||||||
void platformAppAbort(void);
|
void platformAppAbort(void);
|
||||||
void platformAppExit(int status);
|
void platformAppExit(int status);
|
||||||
|
|
@ -160,8 +166,15 @@ void platformNormalizeKeyEvent(PlatformKeyEventT *evt, int32_
|
||||||
void platformOutOfMemory(size_t bytes);
|
void platformOutOfMemory(size_t bytes);
|
||||||
const char *platformPathBaseName(const char *path);
|
const char *platformPathBaseName(const char *path);
|
||||||
char *platformPathDirEnd(const char *path);
|
char *platformPathDirEnd(const char *path);
|
||||||
|
bool platformPathDirName(const char *path, char *out, int32_t outSize);
|
||||||
bool platformPathIsAbsolute(const char *path);
|
bool platformPathIsAbsolute(const char *path);
|
||||||
|
bool platformPathIsSep(char c);
|
||||||
|
const char *platformPathIsUnder(const char *path, const char *dir);
|
||||||
|
bool platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
|
||||||
|
char *platformPathNative(char *path);
|
||||||
|
bool platformPathSame(const char *a, const char *b);
|
||||||
bool platformPathSibling(char *out, int32_t outSize, const char *path, const char *ext);
|
bool platformPathSibling(char *out, int32_t outSize, const char *path, const char *ext);
|
||||||
|
char *platformPathTidy(char *path);
|
||||||
char *platformReadFile(const char *path, int32_t *outLen);
|
char *platformReadFile(const char *path, int32_t *outLen);
|
||||||
bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);
|
bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);
|
||||||
void platformSetAppExitHook(PlatformAppExitFnT fn);
|
void platformSetAppExitHook(PlatformAppExitFnT fn);
|
||||||
|
|
@ -530,6 +543,32 @@ static void logMemAppend(const char *fmt, va_list ap) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DVX's path rules are DOS's on every platform: the separators are
|
||||||
|
// interchangeable and names compare without case.
|
||||||
|
static bool pathCharEq(char a, char b) {
|
||||||
|
bool sepA = platformPathIsSep(a);
|
||||||
|
bool sepB = platformPathIsSep(b);
|
||||||
|
|
||||||
|
if (sepA || sepB) {
|
||||||
|
return sepA && sepB;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toupper((unsigned char)a) == toupper((unsigned char)b);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Length of path without a trailing separator (a lone "/" becomes 0).
|
||||||
|
static int32_t pathTrimmedLen(const char *path) {
|
||||||
|
int32_t len = (int32_t)strlen(path);
|
||||||
|
|
||||||
|
if (len > 0 && platformPathIsSep(path[len - 1])) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
char platformAltScanToChar(int32_t scancode) {
|
char platformAltScanToChar(int32_t scancode) {
|
||||||
if (scancode < 0 || scancode >= ALT_SCAN_TABLE_SIZE) {
|
if (scancode < 0 || scancode >= ALT_SCAN_TABLE_SIZE) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -620,6 +659,11 @@ int32_t platformChdir(const char *path) {
|
||||||
|
|
||||||
|
|
||||||
bool platformCopyFile(const char *srcPath, const char *dstPath) {
|
bool platformCopyFile(const char *srcPath, const char *dstPath) {
|
||||||
|
// Onto itself: opening the copy would empty the original first
|
||||||
|
if (platformPathSame(srcPath, dstPath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *src = fopen(srcPath, "rb");
|
FILE *src = fopen(srcPath, "rb");
|
||||||
|
|
||||||
if (!src) {
|
if (!src) {
|
||||||
|
|
@ -653,13 +697,11 @@ bool platformCopyFile(const char *srcPath, const char *dstPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Tokens are the runs between FILTER_SEPARATORS; no copy of the filter is
|
// Make and remove a probe file in dir.
|
||||||
// made, so its length is not limited.
|
|
||||||
bool platformDirIsWritable(const char *dir) {
|
bool platformDirIsWritable(const char *dir) {
|
||||||
char path[DVX_MAX_PATH];
|
char path[DVX_MAX_PATH];
|
||||||
int32_t n = snprintf(path, sizeof(path), "%s" DVX_PATH_SEP WRITE_PROBE_NAME, dir);
|
|
||||||
|
|
||||||
if (n <= 0 || n >= (int32_t)sizeof(path)) {
|
if (!dir || !dir[0] || !platformPathJoin(path, sizeof(path), dir, WRITE_PROBE_NAME)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -675,6 +717,8 @@ bool platformDirIsWritable(const char *dir) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Tokens are the runs between FILTER_SEPARATORS; no copy of the filter is
|
||||||
|
// made, so its length is not limited.
|
||||||
bool platformFilterMatch(const char *name, const char *filter) {
|
bool platformFilterMatch(const char *name, const char *filter) {
|
||||||
int32_t allLen = (int32_t)strlen(PLATFORM_FILTER_ALL);
|
int32_t allLen = (int32_t)strlen(PLATFORM_FILTER_ALL);
|
||||||
bool anyToken = false;
|
bool anyToken = false;
|
||||||
|
|
@ -815,44 +859,123 @@ void platformOutOfMemory(size_t bytes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The name after the last separator, or after a drive ("C:A.TXT").
|
||||||
const char *platformPathBaseName(const char *path) {
|
const char *platformPathBaseName(const char *path) {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *sep = platformPathDirEnd(path);
|
const char *sep = platformPathDirEnd(path);
|
||||||
return sep ? sep + 1 : path;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (sep) {
|
||||||
// Return the last path separator in `path`, or NULL if none.
|
return sep + 1;
|
||||||
//
|
|
||||||
// Platform-specific, because what counts as a separator varies:
|
|
||||||
// - DJGPP/DOS and Win32 accept both '/' and '\\' -- we have to check
|
|
||||||
// both and return whichever appears last.
|
|
||||||
// - Unix-like systems use only '/'. '\\' is a legal filename
|
|
||||||
// character there, so treating it as a separator would incorrectly
|
|
||||||
// split paths containing literal backslashes.
|
|
||||||
char *platformPathDirEnd(const char *path) {
|
|
||||||
#if defined(__DJGPP__) || defined(_WIN32) || defined(_WIN64)
|
|
||||||
char *fwd = strrchr(path, '/');
|
|
||||||
char *back = strrchr(path, '\\');
|
|
||||||
|
|
||||||
if (back > fwd) {
|
|
||||||
return back;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fwd;
|
return (isalpha((unsigned char)path[0]) && path[1] == ':') ? path + DOS_DRIVE_SPEC_LEN : path;
|
||||||
#else
|
}
|
||||||
return strrchr(path, '/');
|
|
||||||
#endif
|
|
||||||
|
// Return the last path separator in `path` (platformPathIsSep), or NULL
|
||||||
|
// if none.
|
||||||
|
char *platformPathDirEnd(const char *path) {
|
||||||
|
const char *end = NULL;
|
||||||
|
|
||||||
|
for (const char *p = path; *p; p++) {
|
||||||
|
if (platformPathIsSep(*p)) {
|
||||||
|
end = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (char *)end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool platformPathDirName(const char *path, char *out, int32_t outSize) {
|
||||||
|
const char *end = platformPathDirEnd(path);
|
||||||
|
bool drive = isalpha((unsigned char)path[0]) && path[1] == ':';
|
||||||
|
int32_t len = 1;
|
||||||
|
const char *from = ".";
|
||||||
|
|
||||||
|
if (end) {
|
||||||
|
bool root = end == path || (drive && end == path + DOS_DRIVE_SPEC_LEN);
|
||||||
|
|
||||||
|
from = path;
|
||||||
|
len = (int32_t)(end - path) + (root ? 1 : 0);
|
||||||
|
} else if (drive) {
|
||||||
|
from = path;
|
||||||
|
len = DOS_DRIVE_SPEC_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len >= outSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(out, from, (size_t)len);
|
||||||
|
out[len] = '\0';
|
||||||
|
platformPathNative(out);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// DVX paths may use either separator on every platform (INI files and app
|
|
||||||
// arguments are shared between DOS and the host build), so both count.
|
|
||||||
bool platformPathIsAbsolute(const char *path) {
|
bool platformPathIsAbsolute(const char *path) {
|
||||||
return path && (path[0] == '/' || path[0] == '\\' || (isalpha((unsigned char)path[0]) && path[1] == ':'));
|
return path && (platformPathIsSep(path[0]) || (isalpha((unsigned char)path[0]) && path[1] == ':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool platformPathIsSep(char c) {
|
||||||
|
return c == '/' || c == '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *platformPathIsUnder(const char *path, const char *dir) {
|
||||||
|
if (!path || !dir) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t len = pathTrimmedLen(dir);
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < len; i++) {
|
||||||
|
if (!pathCharEq(path[i], dir[i])) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path[len] == '\0') {
|
||||||
|
return path + len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return platformPathIsSep(path[len]) ? path + len + 1 : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name) {
|
||||||
|
size_t dirLen = strlen(dir);
|
||||||
|
bool alone = dirLen == 0 || platformPathIsAbsolute(name);
|
||||||
|
bool hasSep = dirLen > 0 && platformPathIsSep(dir[dirLen - 1]);
|
||||||
|
int n = alone ? snprintf(out, (size_t)outSize, "%s", name) : snprintf(out, (size_t)outSize, "%s%s%s", dir, hasSep ? "" : DVX_PATH_SEP, name);
|
||||||
|
|
||||||
|
if (n < 0 || n >= outSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
platformPathNative(out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *platformPathNative(char *path) {
|
||||||
|
for (char *p = path; p && *p; p++) {
|
||||||
|
if (platformPathIsSep(*p)) {
|
||||||
|
*p = DVX_PATH_SEP[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool platformPathSame(const char *a, const char *b) {
|
||||||
|
return a && b && pathTrimmedLen(a) == pathTrimmedLen(b) && platformPathIsUnder(a, b) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -887,6 +1010,35 @@ bool platformPathSibling(char *out, int32_t outSize, const char *path, const cha
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *platformPathTidy(char *path) {
|
||||||
|
if (!path) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
platformPathNative(path);
|
||||||
|
|
||||||
|
// A leading pair (a network "\\\\server") stays; any other run is one
|
||||||
|
int32_t keep = platformPathIsSep(path[0]) && platformPathIsSep(path[1]) ? 2 : 0;
|
||||||
|
int32_t dst = keep;
|
||||||
|
|
||||||
|
for (int32_t src = keep; path[src]; src++) {
|
||||||
|
if (!(platformPathIsSep(path[src]) && dst > keep && platformPathIsSep(path[dst - 1]))) {
|
||||||
|
path[dst++] = path[src];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A trailing separator goes, except a root's ("\\", "C:\\")
|
||||||
|
bool root = dst == 1 || (dst == DOS_ROOT_LEN && path[1] == ':');
|
||||||
|
|
||||||
|
if (dst > 0 && platformPathIsSep(path[dst - 1]) && !root) {
|
||||||
|
dst--;
|
||||||
|
}
|
||||||
|
|
||||||
|
path[dst] = '\0';
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
char *platformReadFile(const char *path, int32_t *outLen) {
|
char *platformReadFile(const char *path, int32_t *outLen) {
|
||||||
if (outLen) {
|
if (outLen) {
|
||||||
*outLen = 0;
|
*outLen = 0;
|
||||||
|
|
@ -1025,13 +1177,19 @@ int32_t platformStripLineEndings(char *buf, int32_t len) {
|
||||||
|
|
||||||
|
|
||||||
const char *platformTempDir(void) {
|
const char *platformTempDir(void) {
|
||||||
|
static char native[DVX_MAX_PATH];
|
||||||
const char *dir = getenv("TEMP");
|
const char *dir = getenv("TEMP");
|
||||||
|
|
||||||
if (!dir || !dir[0]) {
|
if (!dir || !dir[0]) {
|
||||||
dir = getenv("TMP");
|
dir = getenv("TMP");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (dir && dir[0]) ? dir : NULL;
|
if (!dir || !dir[0]) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(native, sizeof(native), "%s", dir);
|
||||||
|
return platformPathNative(native);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ int32_t PrintChoiceDest(void) {
|
||||||
|
|
||||||
// The file name chosen in the last PrintChoose when printing to a file.
|
// The file name chosen in the last PrintChoose when printing to a file.
|
||||||
const char *PrintChoiceFile(void) {
|
const char *PrintChoiceFile(void) {
|
||||||
return sChoice.filePath;
|
return platformPathNative(sChoice.filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -399,6 +399,7 @@ void printSettingsRead(PrefsHandleT *h, PrintSettingsT *s) {
|
||||||
s->page.tabWidth = prefsGetInt(h, PRINT_SECTION, INI_TAB, def.page.tabWidth);
|
s->page.tabWidth = prefsGetInt(h, PRINT_SECTION, INI_TAB, def.page.tabWidth);
|
||||||
s->page.wrap = prefsGetBool(h, PRINT_SECTION, INI_WRAP, def.page.wrap);
|
s->page.wrap = prefsGetBool(h, PRINT_SECTION, INI_WRAP, def.page.wrap);
|
||||||
snprintf(s->filePath, sizeof(s->filePath), "%s", prefsGetString(h, PRINT_SECTION, INI_FILE, def.filePath));
|
snprintf(s->filePath, sizeof(s->filePath), "%s", prefsGetString(h, PRINT_SECTION, INI_FILE, def.filePath));
|
||||||
|
platformPathNative(s->filePath);
|
||||||
snprintf(s->page.header, sizeof(s->page.header), "%s", prefsGetString(h, PRINT_SECTION, INI_HEADER, def.page.header));
|
snprintf(s->page.header, sizeof(s->page.header), "%s", prefsGetString(h, PRINT_SECTION, INI_HEADER, def.page.header));
|
||||||
snprintf(s->page.footer, sizeof(s->page.footer), "%s", prefsGetString(h, PRINT_SECTION, INI_FOOTER, def.page.footer));
|
snprintf(s->page.footer, sizeof(s->page.footer), "%s", prefsGetString(h, PRINT_SECTION, INI_FOOTER, def.page.footer));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,6 @@ static bool drainWhileAsking(XferT *x);
|
||||||
static void failOpen(XferT *x, const char *path, bool tellPeer);
|
static void failOpen(XferT *x, const char *path, bool tellPeer);
|
||||||
static int64_t fileSizeOf(const char *path);
|
static int64_t fileSizeOf(const char *path);
|
||||||
static bool initCommon(XferT *x, XferProtoE proto, const XferIoT *io, const XferOptionsT *opt, bool sending);
|
static bool initCommon(XferT *x, XferProtoE proto, const XferIoT *io, const XferOptionsT *opt, bool sending);
|
||||||
static void joinPath(const char *dir, const char *name, char *out);
|
|
||||||
static bool makeUniquePath(const char *dir, const char *name, char *out);
|
static bool makeUniquePath(const char *dir, const char *name, char *out);
|
||||||
static void mapNamePart(const char *src, int32_t srcLen, char *dst, int32_t dstMax);
|
static void mapNamePart(const char *src, int32_t srcLen, char *dst, int32_t dstMax);
|
||||||
static bool openReceiveFile(XferT *x, const char *path, bool append, int32_t localSize);
|
static bool openReceiveFile(XferT *x, const char *path, bool append, int32_t localSize);
|
||||||
|
|
@ -286,16 +285,6 @@ static bool initCommon(XferT *x, XferProtoE proto, const XferIoT *io, const Xfer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// dir + separator + name (no separator after an empty dir or one that
|
|
||||||
// already ends in one).
|
|
||||||
static void joinPath(const char *dir, const char *name, char *out) {
|
|
||||||
size_t dirLen = strlen(dir);
|
|
||||||
const char *sep = (dirLen == 0 || dir[dirLen - 1] == '/' || dir[dirLen - 1] == '\\') ? "" : DVX_PATH_SEP;
|
|
||||||
|
|
||||||
snprintf(out, XFER_PATH_MAX, "%s%s%s", dir, sep, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Build dir + name, adding "~N" to the base name until no file or folder
|
// Build dir + name, adding "~N" to the base name until no file or folder
|
||||||
// of that name exists. The base is what comes before the extension (the
|
// of that name exists. The base is what comes before the extension (the
|
||||||
// last dot of a long name, the only one of an 8.3 name) and stays within
|
// last dot of a long name, the only one of an 8.3 name) and stays within
|
||||||
|
|
@ -320,7 +309,7 @@ static bool makeUniquePath(const char *dir, const char *name, char *out) {
|
||||||
int32_t keep = DVX_MIN(baseLen, baseMax - (int32_t)strlen(suffix));
|
int32_t keep = DVX_MIN(baseLen, baseMax - (int32_t)strlen(suffix));
|
||||||
|
|
||||||
snprintf(cand, sizeof(cand), "%.*s%s%s", (int)keep, name, suffix, ext);
|
snprintf(cand, sizeof(cand), "%.*s%s%s", (int)keep, name, suffix, ext);
|
||||||
joinPath(dir, cand, out);
|
platformPathJoin(out, XFER_PATH_MAX, dir, cand);
|
||||||
|
|
||||||
if (fileSizeOf(out) < 0) {
|
if (fileSizeOf(out) < 0) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1104,7 +1093,7 @@ void xferFlushOut(XferT *x) {
|
||||||
|
|
||||||
XferOpenE xferOpenReceived(XferT *x, const char *remoteName, int32_t remoteSize, bool canResume, bool resumeRequested) {
|
XferOpenE xferOpenReceived(XferT *x, const char *remoteName, int32_t remoteSize, bool canResume, bool resumeRequested) {
|
||||||
xferSafeName(remoteName, platformLongNames(x->paths[0]), x->recvName);
|
xferSafeName(remoteName, platformLongNames(x->paths[0]), x->recvName);
|
||||||
joinPath(x->paths[0], x->recvName, x->askPath);
|
platformPathJoin(x->askPath, sizeof(x->askPath), x->paths[0], x->recvName);
|
||||||
x->prog.askRemoteSize = remoteSize;
|
x->prog.askRemoteSize = remoteSize;
|
||||||
x->prog.askLocalSize = 0;
|
x->prog.askLocalSize = 0;
|
||||||
x->prog.askCanResume = false;
|
x->prog.askCanResume = false;
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define XFER_PATH_MAX 260
|
#define XFER_PATH_MAX DVX_MAX_PATH
|
||||||
#define XFER_NAME_MAX (PLATFORM_LONG_NAME_MAX + 1) // a long name plus NUL (xferSafeName)
|
#define XFER_NAME_MAX (PLATFORM_LONG_NAME_MAX + 1) // a long name plus NUL (xferSafeName)
|
||||||
#define XFER_MSG_MAX 96
|
#define XFER_MSG_MAX 96
|
||||||
#define XFER_MAX_FILES 16
|
#define XFER_MAX_FILES 16
|
||||||
|
|
|
||||||
|
|
@ -327,11 +327,14 @@ int32_t XferErrors(int32_t handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The file being transferred, as stored locally ("" for a bad handle)
|
// The file being transferred, as stored locally, in native separators
|
||||||
|
// ("" for a bad handle)
|
||||||
const char *XferFile(int32_t handle) {
|
const char *XferFile(int32_t handle) {
|
||||||
|
static char file[XFER_PATH_MAX];
|
||||||
const XferProgressT *p = progressOf(handle);
|
const XferProgressT *p = progressOf(handle);
|
||||||
|
|
||||||
return p ? p->fileName : "";
|
snprintf(file, sizeof(file), "%s", p ? p->fileName : "");
|
||||||
|
return platformPathNative(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -842,7 +842,7 @@ static void logStart(void) {
|
||||||
if (!platformIsReadOnly()) {
|
if (!platformIsReadOnly()) {
|
||||||
snprintf(sLogPath, sizeof(sLogPath), "%s", LOG_PATH);
|
snprintf(sLogPath, sizeof(sLogPath), "%s", LOG_PATH);
|
||||||
} else if (tmpDir && platformDirIsWritable(tmpDir)) {
|
} else if (tmpDir && platformDirIsWritable(tmpDir)) {
|
||||||
snprintf(sLogPath, sizeof(sLogPath), "%s" DVX_PATH_SEP "%s", tmpDir, LOG_PATH);
|
platformPathJoin(sLogPath, sizeof(sLogPath), tmpDir, LOG_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sLogPath[0]) {
|
if (!sLogPath[0]) {
|
||||||
|
|
@ -1166,13 +1166,8 @@ int main(int argc, char *argv[]) {
|
||||||
// Change to the directory containing the executable so relative
|
// Change to the directory containing the executable so relative
|
||||||
// paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly.
|
// paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly.
|
||||||
char exeDir[DVX_MAX_PATH];
|
char exeDir[DVX_MAX_PATH];
|
||||||
strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
|
|
||||||
exeDir[sizeof(exeDir) - 1] = '\0';
|
|
||||||
|
|
||||||
char *sep = platformPathDirEnd(exeDir);
|
if (platformPathDirName(argv[0], exeDir, sizeof(exeDir))) {
|
||||||
|
|
||||||
if (sep) {
|
|
||||||
*sep = '\0';
|
|
||||||
platformChdir(exeDir);
|
platformChdir(exeDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1206,7 +1201,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
if (!handles || arrlen(handles) == 0) {
|
if (!handles || arrlen(handles) == 0) {
|
||||||
splashShutdownIfActive();
|
splashShutdownIfActive();
|
||||||
fprintf(stderr, "No modules loaded from %s/ or %s/\n", LIBS_DIR, WIDGET_DIR);
|
fprintf(stderr, "No modules loaded from %s" DVX_PATH_SEP " or %s" DVX_PATH_SEP "\n", LIBS_DIR, WIDGET_DIR);
|
||||||
arrfree(handles);
|
arrfree(handles);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ static void caseBasicIde(void) {
|
||||||
// app), and the STUB carrier CompBuildApp reads (bascomp has one)
|
// app), and the STUB carrier CompBuildApp reads (bascomp has one)
|
||||||
snprintf(kidDir, sizeof(kidDir), "%s/paride/kid", tTempDir());
|
snprintf(kidDir, sizeof(kidDir), "%s/paride/kid", tTempDir());
|
||||||
snprintf(kidApp, sizeof(kidApp), "%s/kid.app", tTempDir());
|
snprintf(kidApp, sizeof(kidApp), "%s/kid.app", tTempDir());
|
||||||
basBuildDirOf(BAS_BUILD_STUB_CARRIER, carrierDir, sizeof(carrierDir));
|
platformPathDirName(BAS_BUILD_STUB_CARRIER, carrierDir, sizeof(carrierDir));
|
||||||
platformMkdirRecursive(carrierDir);
|
platformMkdirRecursive(carrierDir);
|
||||||
remove(kidApp);
|
remove(kidApp);
|
||||||
TEST_TRUE(copyKid(kidDir));
|
TEST_TRUE(copyKid(kidDir));
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
#include "box/box.h"
|
#include "box/box.h"
|
||||||
#include "button/button.h"
|
#include "button/button.h"
|
||||||
#include "canvas/canvas.h"
|
#include "canvas/canvas.h"
|
||||||
|
#include "dropdown/dropdown.h"
|
||||||
#include "listBox/listBox.h"
|
#include "listBox/listBox.h"
|
||||||
#include "slider/slider.h"
|
#include "slider/slider.h"
|
||||||
#include "spinner/spinner.h"
|
#include "spinner/spinner.h"
|
||||||
|
|
@ -213,7 +214,8 @@ typedef enum {
|
||||||
FileOpTypeNameE,
|
FileOpTypeNameE,
|
||||||
FileOpDblClickFileE,
|
FileOpDblClickFileE,
|
||||||
FileOpSelectDirOpenE,
|
FileOpSelectDirOpenE,
|
||||||
FileOpEnterInNameE
|
FileOpEnterInNameE,
|
||||||
|
FileOpDriveE
|
||||||
} FileOpE;
|
} FileOpE;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -225,6 +227,11 @@ typedef struct {
|
||||||
char nameAfter[DVX_MAX_PATH];
|
char nameAfter[DVX_MAX_PATH];
|
||||||
int32_t itemsAfter;
|
int32_t itemsAfter;
|
||||||
int32_t itemsBefore;
|
int32_t itemsBefore;
|
||||||
|
int32_t driveIdx; // FileOpDriveE: the drive to pick (-1: none)
|
||||||
|
int32_t driveItems; // drive dropdown: its items, first label,
|
||||||
|
int32_t driveBefore; // and the selection before / after
|
||||||
|
int32_t driveAfter;
|
||||||
|
char driveLabel[MAX_TITLE_LEN];
|
||||||
bool stillModalAfter;
|
bool stillModalAfter;
|
||||||
bool nestedSeen;
|
bool nestedSeen;
|
||||||
char nestedTitle[MAX_TITLE_LEN];
|
char nestedTitle[MAX_TITLE_LEN];
|
||||||
|
|
@ -719,6 +726,26 @@ static void caseFileDialog(void) {
|
||||||
TEST_STR_EQ(f.pathAfter, sub);
|
TEST_STR_EQ(f.pathAfter, sub);
|
||||||
TEST_TRUE(f.stillModalAfter);
|
TEST_TRUE(f.stillModalAfter);
|
||||||
|
|
||||||
|
// Drives: the host has one, "/", selected for the folder; picking it
|
||||||
|
// goes to its root, picking none does nothing.
|
||||||
|
memset(&f, 0, sizeof(f));
|
||||||
|
f.op = FileOpDriveE;
|
||||||
|
f.driveIdx = 0;
|
||||||
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
||||||
|
TEST_EQ(f.driveItems, 1);
|
||||||
|
TEST_STR_EQ(f.driveLabel, "/");
|
||||||
|
TEST_EQ(f.driveBefore, 0);
|
||||||
|
TEST_EQ(f.driveAfter, 0);
|
||||||
|
TEST_STR_EQ(f.pathAfter, "/");
|
||||||
|
TEST_TRUE(f.stillModalAfter);
|
||||||
|
|
||||||
|
memset(&f, 0, sizeof(f));
|
||||||
|
f.op = FileOpDriveE;
|
||||||
|
f.driveIdx = -1;
|
||||||
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
||||||
|
TEST_STR_EQ(f.pathAfter, real);
|
||||||
|
TEST_EQ(f.driveAfter, -1);
|
||||||
|
|
||||||
// Filters: single glob, then switching to the next filter.
|
// Filters: single glob, then switching to the next filter.
|
||||||
memset(&f, 0, sizeof(f));
|
memset(&f, 0, sizeof(f));
|
||||||
f.op = FileOpFilterNextE;
|
f.op = FileOpFilterNextE;
|
||||||
|
|
@ -1522,9 +1549,10 @@ static bool customOk(WindowT *dlg, void *userCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// File dialog script. Layout: pathRow(label,input), list, [filterRow
|
// File dialog script. Layout: pathRow(label,input,label,drive
|
||||||
// (label,dropdown)], nameRow(label,input), buttons. Nested confirmation
|
// dropdown), list, [filterRow(label,dropdown)], nameRow(label,input),
|
||||||
// boxes (different title) are answered with nestedButton.
|
// buttons. Nested confirmation boxes (different title) are answered
|
||||||
|
// with nestedButton.
|
||||||
static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
||||||
FileScriptT *f = (FileScriptT *)userCtx;
|
FileScriptT *f = (FileScriptT *)userCtx;
|
||||||
|
|
||||||
|
|
@ -1546,6 +1574,7 @@ static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
||||||
|
|
||||||
WidgetT *root = modal->widgetRoot;
|
WidgetT *root = modal->widgetRoot;
|
||||||
WidgetT *pathInput = childAt(childAt(root, 0), 1);
|
WidgetT *pathInput = childAt(childAt(root, 0), 1);
|
||||||
|
WidgetT *driveDd = childAt(childAt(root, 0), 3);
|
||||||
WidgetT *list = childAt(root, 1);
|
WidgetT *list = childAt(root, 1);
|
||||||
bool hasFilter = childAt(root, 4) != NULL;
|
bool hasFilter = childAt(root, 4) != NULL;
|
||||||
WidgetT *filterDd = hasFilter ? childAt(childAt(root, 2), 1) : NULL;
|
WidgetT *filterDd = hasFilter ? childAt(childAt(root, 2), 1) : NULL;
|
||||||
|
|
@ -1554,9 +1583,9 @@ static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
||||||
int32_t sx = 0;
|
int32_t sx = 0;
|
||||||
int32_t sy = 0;
|
int32_t sy = 0;
|
||||||
|
|
||||||
TEST_TRUE(pathInput && list && nameInput && okBtn);
|
TEST_TRUE(pathInput && driveDd && list && nameInput && okBtn);
|
||||||
|
|
||||||
if (!pathInput || !list || !nameInput || !okBtn) {
|
if (!pathInput || !driveDd || !list || !nameInput || !okBtn) {
|
||||||
scriptLeave();
|
scriptLeave();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1567,6 +1596,7 @@ static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
||||||
if (f->acted) {
|
if (f->acted) {
|
||||||
f->stillModalAfter = true;
|
f->stillModalAfter = true;
|
||||||
f->itemsAfter = dvxListBoxApi()->getItemCount(list);
|
f->itemsAfter = dvxListBoxApi()->getItemCount(list);
|
||||||
|
f->driveAfter = wgtDropdownGetSelected(driveDd);
|
||||||
snprintf(f->pathAfter, sizeof(f->pathAfter), "%s", wgtGetText(pathInput));
|
snprintf(f->pathAfter, sizeof(f->pathAfter), "%s", wgtGetText(pathInput));
|
||||||
tDialogClickButton(modal, "Cancel");
|
tDialogClickButton(modal, "Cancel");
|
||||||
scriptLeave();
|
scriptLeave();
|
||||||
|
|
@ -1575,6 +1605,9 @@ static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
||||||
|
|
||||||
f->acted = true;
|
f->acted = true;
|
||||||
f->itemsBefore = dvxListBoxApi()->getItemCount(list);
|
f->itemsBefore = dvxListBoxApi()->getItemCount(list);
|
||||||
|
f->driveItems = wgtDropdownGetItemCount(driveDd);
|
||||||
|
f->driveBefore = wgtDropdownGetSelected(driveDd);
|
||||||
|
snprintf(f->driveLabel, sizeof(f->driveLabel), "%s", wgtDropdownGetItem(driveDd, 0));
|
||||||
|
|
||||||
switch (f->op) {
|
switch (f->op) {
|
||||||
case FileOpCancelE:
|
case FileOpCancelE:
|
||||||
|
|
@ -1639,6 +1672,12 @@ static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
||||||
tWidgetCenter(okBtn, &sx, &sy);
|
tWidgetCenter(okBtn, &sx, &sy);
|
||||||
tClick(sx, sy);
|
tClick(sx, sy);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FileOpDriveE:
|
||||||
|
wgtSetFocused(driveDd);
|
||||||
|
wgtDropdownSetSelected(driveDd, f->driveIdx);
|
||||||
|
driveDd->onChange(driveDd);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptLeave();
|
scriptLeave();
|
||||||
|
|
|
||||||
19
src/test/dos/fixtures/pathsmk/pathsmk.dbp
Normal file
19
src/test/dos/fixtures/pathsmk/pathsmk.dbp
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Project]
|
||||||
|
Name = Path Smoke
|
||||||
|
Author = Scott Duensing
|
||||||
|
Publisher = Kangaroo Punch Studios
|
||||||
|
Version = 1.00
|
||||||
|
Copyright = Copyright 2026 Scott Duensing
|
||||||
|
Description = DOS harness fixture: paths written with / work, paths shown use \
|
||||||
|
|
||||||
|
[Modules]
|
||||||
|
File0 = ../../../../include/basic/fileops.bas
|
||||||
|
File1 = ../../../../include/basic/shell.bas
|
||||||
|
Count = 2
|
||||||
|
|
||||||
|
[Forms]
|
||||||
|
File0 = pathsmk.frm
|
||||||
|
Count = 1
|
||||||
|
|
||||||
|
[Settings]
|
||||||
|
StartupForm = PathSmoke
|
||||||
133
src/test/dos/fixtures/pathsmk/pathsmk.frm
Normal file
133
src/test/dos/fixtures/pathsmk/pathsmk.frm
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
VERSION DVX 1.00
|
||||||
|
|
||||||
|
Begin Form PathSmoke
|
||||||
|
Caption = "Path Smoke"
|
||||||
|
Layout = VBox
|
||||||
|
AutoSize = False
|
||||||
|
Resizable = False
|
||||||
|
Width = 360
|
||||||
|
Height = 90
|
||||||
|
|
||||||
|
Begin Label lblResult
|
||||||
|
Caption = "Running..."
|
||||||
|
Weight = 1
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
' The MIT License (MIT)
|
||||||
|
'
|
||||||
|
' Copyright (C) 2026 Scott Duensing
|
||||||
|
'
|
||||||
|
' 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.
|
||||||
|
|
||||||
|
' pathsmk -- DOS harness fixture for paths.scr. Started as
|
||||||
|
' TEST/PATHSMK.APP (a path written with /), it checks that paths a program
|
||||||
|
' writes with / work exactly as with \ (OPEN, FILELEN, DIR$, NAME, KILL,
|
||||||
|
' MKDIR, CHDIR, RMDIR, attributes, the FileOps path functions, DIR$ of a
|
||||||
|
' drive root) and that every path DVX hands back uses \ (CURDIR$,
|
||||||
|
' App.Path, App.Data, ShellAppPath$, ShellConfigFile$, FileOpsPathJoin$,
|
||||||
|
' FileOpsPathParent$, FileOpsShortPath$, FileOpsPathNative$). The window
|
||||||
|
' title reports "PATHS PASS", or "PATHS FAIL <first check that failed>".
|
||||||
|
|
||||||
|
OPTION EXPLICIT
|
||||||
|
|
||||||
|
CONST SLASH = "/"
|
||||||
|
CONST BACKSLASH = "\"
|
||||||
|
CONST TEST_FILE = "TEST/PS1.TXT"
|
||||||
|
CONST TEST_RENAMED = "TEST/PS2.TXT"
|
||||||
|
CONST TEST_DIR = "TEST/PSDIR"
|
||||||
|
CONST ATTR_FILE = "TEST//PS3.TXT"
|
||||||
|
|
||||||
|
DIM SHARED failText AS STRING
|
||||||
|
|
||||||
|
|
||||||
|
SUB Check(BYVAL ok AS BOOLEAN, BYVAL what AS STRING)
|
||||||
|
IF NOT ok AND failText = "" THEN
|
||||||
|
failText = what
|
||||||
|
END IF
|
||||||
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
' A path DVX handed back: the native separator, never /.
|
||||||
|
SUB CheckNative(BYVAL path AS STRING, BYVAL what AS STRING)
|
||||||
|
Check path <> "" AND INSTR(path, SLASH) = 0, what + " " + path
|
||||||
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
SUB PathSmoke_Load
|
||||||
|
DIM ch AS INTEGER
|
||||||
|
DIM home AS STRING
|
||||||
|
DIM rootSlash AS STRING
|
||||||
|
DIM rootBack AS STRING
|
||||||
|
|
||||||
|
' Paths DVX hands back
|
||||||
|
home = CURDIR$
|
||||||
|
CheckNative home, "CURDIR$"
|
||||||
|
Check INSTR(home, BACKSLASH) > 0, "CURDIR$ has no \ " + home
|
||||||
|
CheckNative App.Path, "App.Path"
|
||||||
|
CheckNative App.Data, "App.Data"
|
||||||
|
CheckNative ShellAppPath$(), "ShellAppPath$"
|
||||||
|
CheckNative ShellConfigFile$("X.INI"), "ShellConfigFile$"
|
||||||
|
Check FileOpsPathNative$("C:/A/B.TXT") = "C:\A\B.TXT", "PathNative " + FileOpsPathNative$("C:/A/B.TXT")
|
||||||
|
Check FileOpsPathJoin$("C:/DVX/", "A/B.TXT") = "C:\DVX\A\B.TXT", "PathJoin " + FileOpsPathJoin$("C:/DVX/", "A/B.TXT")
|
||||||
|
Check FileOpsPathParent$("C:/DVX/TEST/") = "C:\DVX", "PathParent " + FileOpsPathParent$("C:/DVX/TEST/")
|
||||||
|
CheckNative FileOpsShortPath$(home + "/TEST"), "ShortPath"
|
||||||
|
Check FileOpsPathSame("C:/DVX", "c:\dvx\") AND FileOpsPathIsInside("C:/DVX/TEST/X", "C:\DVX"), "PathSame / IsInside"
|
||||||
|
|
||||||
|
' Paths written with / work as with \
|
||||||
|
ch = FREEFILE
|
||||||
|
OPEN TEST_FILE FOR OUTPUT AS #ch
|
||||||
|
PRINT #ch, "slash"
|
||||||
|
CLOSE #ch
|
||||||
|
Check FILELEN("TEST\PS1.TXT") > 0, "OPEN with /"
|
||||||
|
Check UCASE$(DIR$("TEST/PS*.TXT")) = "PS1.TXT", "DIR$ with / " + DIR$("TEST/PS*.TXT")
|
||||||
|
NAME TEST_FILE AS TEST_RENAMED
|
||||||
|
Check FILELEN("test\ps2.txt") > 0 AND FileOpsIsFolder("TEST/") AND NOT FileOpsIsFolder(TEST_RENAMED), "NAME with /"
|
||||||
|
KILL TEST_RENAMED
|
||||||
|
Check DIR$("TEST/PS*.TXT") = "", "KILL with /"
|
||||||
|
|
||||||
|
MKDIR TEST_DIR
|
||||||
|
CHDIR TEST_DIR
|
||||||
|
Check UCASE$(RIGHT$(CURDIR$, 11)) = "\TEST\PSDIR", "CHDIR with / " + CURDIR$
|
||||||
|
CHDIR home
|
||||||
|
RMDIR TEST_DIR
|
||||||
|
Check NOT FileOpsIsFolder(TEST_DIR), "RMDIR with /"
|
||||||
|
|
||||||
|
' Attributes go to DOS directly: / and a doubled separator work too
|
||||||
|
ch = FREEFILE
|
||||||
|
OPEN "TEST\PS3.TXT" FOR OUTPUT AS #ch
|
||||||
|
CLOSE #ch
|
||||||
|
Check FileOpsSetAttr(ATTR_FILE, FILEOPS_ATTR_READONLY) AND (FileOpsGetAttr("TEST/PS3.TXT") AND FILEOPS_ATTR_READONLY) <> 0, "SetAttr with /"
|
||||||
|
Check FileOpsSetAttr(ATTR_FILE, 0) AND FileOpsGetAttr(ATTR_FILE) = 0, "clear attributes with /"
|
||||||
|
KILL "TEST\PS3.TXT"
|
||||||
|
|
||||||
|
' DIR$ of a drive root lists the root (not the drive's current folder,
|
||||||
|
' which holds DVX.EXE), written either way
|
||||||
|
rootSlash = DIR$(LEFT$(home, 2) + "/*.*")
|
||||||
|
rootBack = DIR$(LEFT$(home, 2) + "\*.*")
|
||||||
|
Check rootSlash = rootBack AND rootBack <> DIR$("*.*"), "DIR$ of the root " + rootSlash + " / " + rootBack
|
||||||
|
|
||||||
|
lblResult.Caption = home
|
||||||
|
|
||||||
|
IF failText = "" THEN
|
||||||
|
PathSmoke.Caption = "PATHS PASS"
|
||||||
|
ELSE
|
||||||
|
PathSmoke.Caption = "PATHS FAIL " + failText
|
||||||
|
END IF
|
||||||
|
END SUB
|
||||||
32
src/test/dos/scripts/filedlg.scr
Normal file
32
src/test/dos/scripts/filedlg.scr
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# filedlg.scr -- the file dialog's drive drop-down.
|
||||||
|
#
|
||||||
|
# Notepad's File > Open dialog has the drive drop-down at the end of the
|
||||||
|
# path row, showing the folder's drive (C:). Alt+D opens it: every drive
|
||||||
|
# DOS has, none of them read. A: (an empty floppy drive) says "Drive A:
|
||||||
|
# is not ready.", and the drop-down shows C: again; the dialog is then
|
||||||
|
# cancelled.
|
||||||
|
TIMEOUT 60
|
||||||
|
WAITWIN "Program Manager"
|
||||||
|
MOVE 639 479
|
||||||
|
LAUNCH apps/kpunch/notepad/notepad.app
|
||||||
|
WAITWIN "Untitled - Notepad"
|
||||||
|
KEY Alt+F O
|
||||||
|
WAITWIN "Open"
|
||||||
|
WAIT 5
|
||||||
|
SHOT fd-open
|
||||||
|
KEY Alt+D
|
||||||
|
WAIT 5
|
||||||
|
SHOT fd-list
|
||||||
|
KEY Up Enter
|
||||||
|
WAITWIN "Drive Not Ready"
|
||||||
|
WAIT 5
|
||||||
|
SHOT fd-nordy
|
||||||
|
KEY Enter
|
||||||
|
WAITNOWIN "Drive Not Ready"
|
||||||
|
WAIT 5
|
||||||
|
SHOT fd-back
|
||||||
|
KEY Esc
|
||||||
|
WAITNOWIN "Open"
|
||||||
|
KEY Alt+F4
|
||||||
|
WAITNOWIN "Untitled - Notepad"
|
||||||
|
QUIT
|
||||||
19
src/test/dos/scripts/paths.scr
Normal file
19
src/test/dos/scripts/paths.scr
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# paths.scr -- paths written with / work on DOS, paths shown use \.
|
||||||
|
#
|
||||||
|
# TEST/PATHSMK.APP (fixtures/pathsmk) is started by a path written with /;
|
||||||
|
# it checks that OPEN, FILELEN, DIR$, NAME, KILL, MKDIR, CHDIR, RMDIR, the
|
||||||
|
# attribute calls and the FileOps path functions take / as \ (doubled and
|
||||||
|
# trailing separators too), that DIR$ of "C:/*.*" lists the root, and that
|
||||||
|
# CURDIR$, App.Path, App.Data, ShellAppPath$, ShellConfigFile$ and the
|
||||||
|
# FileOps results use \. Its title is "PATHS PASS" only when all held.
|
||||||
|
TIMEOUT 60
|
||||||
|
WAITWIN "Program Manager"
|
||||||
|
MOVE 639 479
|
||||||
|
LAUNCH TEST/PATHSMK.APP
|
||||||
|
WAITWIN "PATHS *"
|
||||||
|
WAIT 10
|
||||||
|
SHOT paths
|
||||||
|
ASSERTWIN "PATHS PASS"
|
||||||
|
KEY Alt+F4
|
||||||
|
WAITNOWIN "PATHS *"
|
||||||
|
QUIT
|
||||||
|
|
@ -242,6 +242,8 @@ static void caseBasicPlatform(void) {
|
||||||
TEST_EQ(FileOpsPathIsInside(tp("bp"), tp("bp/src")), BAS_FALSE);
|
TEST_EQ(FileOpsPathIsInside(tp("bp"), tp("bp/src")), BAS_FALSE);
|
||||||
TEST_EQ(FileOpsPathIsInside(NULL, tp("bp")), BAS_FALSE);
|
TEST_EQ(FileOpsPathIsInside(NULL, tp("bp")), BAS_FALSE);
|
||||||
TEST_STR_EQ(FileOpsPathSep(), DVX_PATH_SEP);
|
TEST_STR_EQ(FileOpsPathSep(), DVX_PATH_SEP);
|
||||||
|
TEST_STR_EQ(FileOpsPathNative("/a/b.txt"), "/a/b.txt");
|
||||||
|
TEST_STR_EQ(FileOpsPathNative(NULL), "");
|
||||||
TEST_EQ(FileOpsFilterMatch("A.TXT", "*.txt"), BAS_TRUE);
|
TEST_EQ(FileOpsFilterMatch("A.TXT", "*.txt"), BAS_TRUE);
|
||||||
TEST_EQ(FileOpsFilterMatch("a.bmp", "*.txt;*.bmp"), BAS_TRUE);
|
TEST_EQ(FileOpsFilterMatch("a.bmp", "*.txt;*.bmp"), BAS_TRUE);
|
||||||
TEST_EQ(FileOpsFilterMatch("a.png", "*.txt;*.bmp"), BAS_FALSE);
|
TEST_EQ(FileOpsFilterMatch("a.png", "*.txt;*.bmp"), BAS_FALSE);
|
||||||
|
|
@ -952,7 +954,13 @@ static void casePaths(void) {
|
||||||
TEST_TRUE(fileOpsPathJoin(out, sizeof(out), "/a", "b"));
|
TEST_TRUE(fileOpsPathJoin(out, sizeof(out), "/a", "b"));
|
||||||
TEST_STR_EQ(out, "/a/b");
|
TEST_STR_EQ(out, "/a/b");
|
||||||
TEST_TRUE(fileOpsPathJoin(out, sizeof(out), "C:\\", "B"));
|
TEST_TRUE(fileOpsPathJoin(out, sizeof(out), "C:\\", "B"));
|
||||||
TEST_STR_EQ(out, "C:\\B");
|
TEST_STR_EQ(out, "C:" DVX_PATH_SEP "B");
|
||||||
|
TEST_TRUE(fileOpsPathJoin(out, sizeof(out), "C:/A\\", "B/C"));
|
||||||
|
TEST_STR_EQ(out, "C:" DVX_PATH_SEP "A" DVX_PATH_SEP "B" DVX_PATH_SEP "C");
|
||||||
|
TEST_TRUE(fileOpsPathJoin(out, sizeof(out), "/a", "C:\\X"));
|
||||||
|
TEST_STR_EQ(out, "C:" DVX_PATH_SEP "X");
|
||||||
|
TEST_TRUE(fileOpsPathJoin(out, sizeof(out), "", "x"));
|
||||||
|
TEST_STR_EQ(out, "x");
|
||||||
TEST_TRUE(!fileOpsPathJoin(small, sizeof(small), "/abc", "def"));
|
TEST_TRUE(!fileOpsPathJoin(small, sizeof(small), "/abc", "def"));
|
||||||
|
|
||||||
TEST_TRUE(fileOpsPathParent("/a/b", out, sizeof(out)));
|
TEST_TRUE(fileOpsPathParent("/a/b", out, sizeof(out)));
|
||||||
|
|
@ -962,9 +970,11 @@ static void casePaths(void) {
|
||||||
TEST_TRUE(fileOpsPathParent("/a", out, sizeof(out)));
|
TEST_TRUE(fileOpsPathParent("/a", out, sizeof(out)));
|
||||||
TEST_STR_EQ(out, "/");
|
TEST_STR_EQ(out, "/");
|
||||||
TEST_TRUE(fileOpsPathParent("C:\\DVX", out, sizeof(out)));
|
TEST_TRUE(fileOpsPathParent("C:\\DVX", out, sizeof(out)));
|
||||||
TEST_STR_EQ(out, "C:\\");
|
TEST_STR_EQ(out, "C:" DVX_PATH_SEP);
|
||||||
TEST_TRUE(fileOpsPathParent("C:\\DVX\\APPS", out, sizeof(out)));
|
TEST_TRUE(fileOpsPathParent("C:\\DVX\\APPS", out, sizeof(out)));
|
||||||
TEST_STR_EQ(out, "C:\\DVX");
|
TEST_STR_EQ(out, "C:" DVX_PATH_SEP "DVX");
|
||||||
|
TEST_TRUE(fileOpsPathParent("C:/DVX/APPS/", out, sizeof(out)));
|
||||||
|
TEST_STR_EQ(out, "C:" DVX_PATH_SEP "DVX");
|
||||||
TEST_TRUE(!fileOpsPathParent("/", out, sizeof(out)));
|
TEST_TRUE(!fileOpsPathParent("/", out, sizeof(out)));
|
||||||
TEST_TRUE(!fileOpsPathParent("C:\\", out, sizeof(out)));
|
TEST_TRUE(!fileOpsPathParent("C:\\", out, sizeof(out)));
|
||||||
TEST_TRUE(!fileOpsPathParent("name", out, sizeof(out)));
|
TEST_TRUE(!fileOpsPathParent("name", out, sizeof(out)));
|
||||||
|
|
|
||||||
|
|
@ -525,20 +525,23 @@ static const WgtMethodDescT sProbeMethods[] = {
|
||||||
// Build a shipped BASIC app's .frm with stubs spliced in at the start of
|
// Build a shipped BASIC app's .frm with stubs spliced in at the start of
|
||||||
// its code section (standing in for the app's commdlg.bas module, so the
|
// its code section (standing in for the app's commdlg.bas module, so the
|
||||||
// dialogs are scripted) and test SUBs appended at the end. The real
|
// dialogs are scripted) and test SUBs appended at the end. The real
|
||||||
// help.bas and print.bas are the module source, as in the apps' projects
|
// fileops.bas, help.bas and print.bas are the module source, as in the
|
||||||
// (HelpView resolves to the stub library; the printing functions are only
|
// apps' projects (HelpView resolves to the stub library; the file
|
||||||
// declared, the tests never call them).
|
// operations and printing functions are only declared, the tests never
|
||||||
|
// call them).
|
||||||
static bool appBuild(FrtAppT *app, const char *frmPath, const char *clsPath, const char *stubs, const char *tests) {
|
static bool appBuild(FrtAppT *app, const char *frmPath, const char *clsPath, const char *stubs, const char *tests) {
|
||||||
int32_t len = 0;
|
int32_t len = 0;
|
||||||
|
int32_t fopsLen = 0;
|
||||||
int32_t helpLen = 0;
|
int32_t helpLen = 0;
|
||||||
int32_t printLen = 0;
|
int32_t printLen = 0;
|
||||||
int32_t clsLen = 0;
|
int32_t clsLen = 0;
|
||||||
char *frm = platformReadFile(frmPath, &len);
|
char *frm = platformReadFile(frmPath, &len);
|
||||||
|
char *fopsBas = platformReadFile(FRT_INCLUDE_DIR "/fileops.bas", &fopsLen);
|
||||||
char *helpOnly = platformReadFile(FRT_INCLUDE_DIR "/help.bas", &helpLen);
|
char *helpOnly = platformReadFile(FRT_INCLUDE_DIR "/help.bas", &helpLen);
|
||||||
char *printBas = platformReadFile(FRT_INCLUDE_DIR "/print.bas", &printLen);
|
char *printBas = platformReadFile(FRT_INCLUDE_DIR "/print.bas", &printLen);
|
||||||
char *cls = clsPath ? platformReadFile(clsPath, &clsLen) : NULL;
|
char *cls = clsPath ? platformReadFile(clsPath, &clsLen) : NULL;
|
||||||
size_t helpCap = (size_t)helpLen + (size_t)printLen + (size_t)clsLen + FRT_CLASS_WRAP_LEN;
|
size_t helpCap = (size_t)fopsLen + (size_t)helpLen + (size_t)printLen + (size_t)clsLen + FRT_CLASS_WRAP_LEN;
|
||||||
char *helpBas = (helpOnly && printBas) ? (char *)malloc(helpCap) : NULL;
|
char *helpBas = (fopsBas && helpOnly && printBas) ? (char *)malloc(helpCap) : NULL;
|
||||||
|
|
||||||
TEST_TRUE(frm != NULL);
|
TEST_TRUE(frm != NULL);
|
||||||
TEST_TRUE(helpBas != NULL);
|
TEST_TRUE(helpBas != NULL);
|
||||||
|
|
@ -553,9 +556,10 @@ static bool appBuild(FrtAppT *app, const char *frmPath, const char *clsPath, con
|
||||||
basExtractClassName(cls, clsLen, clsPath, className, (int32_t)sizeof(className));
|
basExtractClassName(cls, clsLen, clsPath, className, (int32_t)sizeof(className));
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(helpBas, helpCap, cls ? "%s\n%s\nBEGINCLASS \"%s\"\n%s\nENDCLASS\n" : "%s\n%s", helpOnly, printBas, className, cls ? cls + basClassCodePos(cls, clsLen) : "");
|
snprintf(helpBas, helpCap, cls ? "%s\n%s\n%s\nBEGINCLASS \"%s\"\n%s\nENDCLASS\n" : "%s\n%s\n%s", fopsBas, helpOnly, printBas, className, cls ? cls + basClassCodePos(cls, clsLen) : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(fopsBas);
|
||||||
free(helpOnly);
|
free(helpOnly);
|
||||||
free(printBas);
|
free(printBas);
|
||||||
free(cls);
|
free(cls);
|
||||||
|
|
@ -1174,10 +1178,16 @@ static void caseBuildProject(void) {
|
||||||
// Directory helper and project open.
|
// Directory helper and project open.
|
||||||
char out[DVX_MAX_PATH];
|
char out[DVX_MAX_PATH];
|
||||||
|
|
||||||
basBuildDirOf("a/b/c.dbp", out, sizeof(out));
|
TEST_TRUE(platformPathDirName("a/b/c.dbp", out, sizeof(out)));
|
||||||
TEST_STR_EQ(out, "a/b");
|
TEST_STR_EQ(out, "a/b");
|
||||||
basBuildDirOf("c.dbp", out, sizeof(out));
|
TEST_TRUE(platformPathDirName("c.dbp", out, sizeof(out)));
|
||||||
TEST_STR_EQ(out, ".");
|
TEST_STR_EQ(out, ".");
|
||||||
|
TEST_TRUE(platformPathDirName("C:\\c.dbp", out, sizeof(out)));
|
||||||
|
TEST_STR_EQ(out, "C:" DVX_PATH_SEP);
|
||||||
|
TEST_TRUE(platformPathDirName("C:c.dbp", out, sizeof(out)));
|
||||||
|
TEST_STR_EQ(out, "C:");
|
||||||
|
TEST_TRUE(platformPathDirName("/c.dbp", out, sizeof(out)));
|
||||||
|
TEST_STR_EQ(out, "/");
|
||||||
TEST_TRUE(!basBuildOpenProject("frtBuildProj/none.dbp", &prefs, out, sizeof(out)));
|
TEST_TRUE(!basBuildOpenProject("frtBuildProj/none.dbp", &prefs, out, sizeof(out)));
|
||||||
|
|
||||||
// An unreadable icon is reported by name, not as a write failure.
|
// An unreadable icon is reported by name, not as a write failure.
|
||||||
|
|
|
||||||
|
|
@ -1552,7 +1552,7 @@ static void snapshotBascomp(void) {
|
||||||
// relative to the DVX folder (here: the working directory)
|
// relative to the DVX folder (here: the working directory)
|
||||||
char carrierDir[DVX_MAX_PATH];
|
char carrierDir[DVX_MAX_PATH];
|
||||||
|
|
||||||
basBuildDirOf(BAS_BUILD_STUB_CARRIER, carrierDir, sizeof(carrierDir));
|
platformPathDirName(BAS_BUILD_STUB_CARRIER, carrierDir, sizeof(carrierDir));
|
||||||
platformMkdirRecursive(carrierDir);
|
platformMkdirRecursive(carrierDir);
|
||||||
TEST_TRUE(tWriteFile(BAS_BUILD_STUB_CARRIER, data, len));
|
TEST_TRUE(tWriteFile(BAS_BUILD_STUB_CARRIER, data, len));
|
||||||
free(data);
|
free(data);
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@
|
||||||
4 src/apps/kpunch/dvxbasic/runtime/vm.c|execStringOp|use of uninitialized value <x> [CWE-457] [-Wanalyzer-use-of-uninitialized-value]
|
4 src/apps/kpunch/dvxbasic/runtime/vm.c|execStringOp|use of uninitialized value <x> [CWE-457] [-Wanalyzer-use-of-uninitialized-value]
|
||||||
# FP: copy is stored via arrins at m->history[0] (trimmed by the pop loop, freed with the model); stb_ds grow aborts rather than failing.
|
# FP: copy is stored via arrins at m->history[0] (trimmed by the pop loop, freed with the model); stb_ds grow aborts rather than failing.
|
||||||
1 src/libs/kpunch/dvxshell/pmModel.c|pmHistoryAdd|leak of <x> [CWE-401] [-Wanalyzer-malloc-leak]
|
1 src/libs/kpunch/dvxshell/pmModel.c|pmHistoryAdd|leak of <x> [CWE-401] [-Wanalyzer-malloc-leak]
|
||||||
|
# FP: allocSlot appends the calloc'd slot to sApps with arrput; the stb_ds OOM hook never returns, so the slot is never dropped (freed with the slot index).
|
||||||
|
1 src/libs/kpunch/dvxshell/shellApp.c|shellLoadAppInternal|leak of <x> [CWE-401] [-Wanalyzer-malloc-leak]
|
||||||
# FP: name copy is stored in fd->entries[] and freed by the listing free loop.
|
# FP: name copy is stored in fd->entries[] and freed by the listing free loop.
|
||||||
1 src/libs/kpunch/libdvx/dvxDialog.c|fdAddEntry|leak of <x> [CWE-401] [-Wanalyzer-malloc-leak]
|
1 src/libs/kpunch/libdvx/dvxDialog.c|fdAddEntry|leak of <x> [CWE-401] [-Wanalyzer-malloc-leak]
|
||||||
# FP: listItems malloc'd in fdLoadDir is freed by the free(fd->listItems) just above this NULL store; analyzer loses the pointer after the per-entry free loop.
|
# FP: listItems malloc'd in fdLoadDir is freed by the free(fd->listItems) just above this NULL store; analyzer loses the pointer after the per-entry free loop.
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@
|
||||||
2 src/libs/kpunch/dvxnet/netResolve.c|clang-analyzer-security.insecureAPI.strcpy|Call to function 'strcpy' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcpy'. CWE-119
|
2 src/libs/kpunch/dvxnet/netResolve.c|clang-analyzer-security.insecureAPI.strcpy|Call to function 'strcpy' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcpy'. CWE-119
|
||||||
# FP: name is cut to sizeof(name) - sizeof(suffix) before the strcat and the suffix is shorter than its buffer, so it always fits
|
# FP: name is cut to sizeof(name) - sizeof(suffix) before the strcat and the suffix is shorter than its buffer, so it always fits
|
||||||
1 src/libs/kpunch/dvxshell/pmModel.c|clang-analyzer-security.insecureAPI.strcpy|Call to function 'strcat' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcat'. CWE-119
|
1 src/libs/kpunch/dvxshell/pmModel.c|clang-analyzer-security.insecureAPI.strcpy|Call to function 'strcat' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcat'. CWE-119
|
||||||
# FP: allocSlot scans from slot 1; every slot past 0 is a ShellAppT allocSlot itself calloc'd and appended only when non-NULL
|
# FP: allocSlot and findLoadedPath scan from slot 1; every slot past 0 is a ShellAppT allocSlot itself calloc'd and appended only when non-NULL
|
||||||
1 src/libs/kpunch/dvxshell/shellApp.c|clang-analyzer-core.NullDereference|Access to field 'state' results in a dereference of a null pointer
|
2 src/libs/kpunch/dvxshell/shellApp.c|clang-analyzer-core.NullDereference|Access to field 'state' results in a dereference of a null pointer
|
||||||
# FP: the app id is passed as the task argument on purpose
|
# FP: the app id is passed as the task argument on purpose
|
||||||
1 src/libs/kpunch/dvxshell/shellApp.c|performance-no-int-to-ptr|integer to pointer cast pessimizes optimization opportunities
|
1 src/libs/kpunch/dvxshell/shellApp.c|performance-no-int-to-ptr|integer to pointer cast pessimizes optimization opportunities
|
||||||
# FP: the BASIC hotkey id is passed as dvxRegisterHotkey's user pointer on purpose
|
# FP: the BASIC hotkey id is passed as dvxRegisterHotkey's user pointer on purpose
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1049,9 +1049,9 @@ static void caseProgScan(void) {
|
||||||
TEST_EQ(count, APP_COUNT_TREE - 1);
|
TEST_EQ(count, APP_COUNT_TREE - 1);
|
||||||
|
|
||||||
for (int32_t i = 0; i < count; i++) {
|
for (int32_t i = 0; i < count; i++) {
|
||||||
TEST_TRUE(!shellPathsMatch(list[i].path, APP_PROGMAN));
|
TEST_TRUE(!platformPathSame(list[i].path, APP_PROGMAN));
|
||||||
|
|
||||||
if (shellPathsMatch(list[i].path, APP_DVXDEMO)) {
|
if (platformPathSame(list[i].path, APP_DVXDEMO)) {
|
||||||
demo = true;
|
demo = true;
|
||||||
TEST_STR_EQ(list[i].name, "DVX Demo");
|
TEST_STR_EQ(list[i].name, "DVX Demo");
|
||||||
TEST_TRUE(list[i].description[0] != '\0');
|
TEST_TRUE(list[i].description[0] != '\0');
|
||||||
|
|
|
||||||
|
|
@ -436,9 +436,55 @@ static void testPaths(void) {
|
||||||
TEST_PTR_NULL(platformPathDirEnd("c.txt"));
|
TEST_PTR_NULL(platformPathDirEnd("c.txt"));
|
||||||
TEST_PTR_NULL(platformPathDirEnd(""));
|
TEST_PTR_NULL(platformPathDirEnd(""));
|
||||||
|
|
||||||
// On a POSIX host a backslash is an ordinary character.
|
// Both separators separate on every platform (DVX's path syntax); the
|
||||||
TEST_STR_EQ(platformPathBaseName("a\\b"), "a\\b");
|
// native form on the host is '/'. A drive also ends a directory part
|
||||||
|
// for the base name.
|
||||||
|
TEST_STR_EQ(platformPathBaseName("a\\b"), "b");
|
||||||
|
TEST_STR_EQ(platformPathBaseName("C:A.TXT"), "A.TXT");
|
||||||
|
TEST_STR_EQ(platformPathBaseName("C:\\A.TXT"), "A.TXT");
|
||||||
TEST_STR_EQ(DVX_PATH_SEP, "/");
|
TEST_STR_EQ(DVX_PATH_SEP, "/");
|
||||||
|
TEST_TRUE(platformPathIsSep('/'));
|
||||||
|
TEST_TRUE(platformPathIsSep('\\'));
|
||||||
|
TEST_TRUE(!platformPathIsSep(':'));
|
||||||
|
|
||||||
|
char native[UTIL_SMALL_BUF] = "/a\\b/c";
|
||||||
|
|
||||||
|
TEST_STR_EQ(platformPathNative(native), "/a/b/c");
|
||||||
|
TEST_PTR_NULL(platformPathNative(NULL));
|
||||||
|
|
||||||
|
// Same / under: DOS rules everywhere -- separators alike, a trailing
|
||||||
|
// one ignored, case ignored
|
||||||
|
TEST_TRUE(platformPathSame("C:\\A\\", "C:/A"));
|
||||||
|
TEST_TRUE(!platformPathSame("C:/A", "C:/AB"));
|
||||||
|
TEST_TRUE(platformPathSame("/a", "/A"));
|
||||||
|
TEST_TRUE(!platformPathSame(NULL, "/a"));
|
||||||
|
TEST_STR_EQ(platformPathIsUnder("C:\\A\\B.TXT", "C:/A/"), "B.TXT");
|
||||||
|
TEST_STR_EQ(platformPathIsUnder("/a", "/a"), "");
|
||||||
|
TEST_STR_EQ(platformPathIsUnder("/a/b", "/"), "a/b");
|
||||||
|
TEST_PTR_NULL(platformPathIsUnder("/ab", "/a"));
|
||||||
|
TEST_PTR_NULL(platformPathIsUnder("/a", "/a/b"));
|
||||||
|
TEST_PTR_NULL(platformPathIsUnder(NULL, "/a"));
|
||||||
|
|
||||||
|
char joined[UTIL_SMALL_BUF];
|
||||||
|
|
||||||
|
TEST_TRUE(platformPathJoin(joined, sizeof(joined), "C:\\", "A\\B"));
|
||||||
|
TEST_STR_EQ(joined, "C:/A/B");
|
||||||
|
TEST_TRUE(platformPathJoin(joined, sizeof(joined), "dir", "\\x"));
|
||||||
|
TEST_STR_EQ(joined, "/x");
|
||||||
|
TEST_TRUE(!platformPathJoin(joined, sizeof(joined), "a-long-folder", "name"));
|
||||||
|
|
||||||
|
// Tidy: one separator per run (a leading pair kept), no trailing one
|
||||||
|
// but a root's
|
||||||
|
char tidy[UTIL_SMALL_BUF] = "C://a\\\\b/";
|
||||||
|
|
||||||
|
TEST_STR_EQ(platformPathTidy(tidy), "C:/a/b");
|
||||||
|
snprintf(tidy, sizeof(tidy), "%s", "C:\\");
|
||||||
|
TEST_STR_EQ(platformPathTidy(tidy), "C:/");
|
||||||
|
snprintf(tidy, sizeof(tidy), "%s", "\\\\srv\\x\\");
|
||||||
|
TEST_STR_EQ(platformPathTidy(tidy), "//srv/x");
|
||||||
|
snprintf(tidy, sizeof(tidy), "%s", "/");
|
||||||
|
TEST_STR_EQ(platformPathTidy(tidy), "/");
|
||||||
|
TEST_PTR_NULL(platformPathTidy(NULL));
|
||||||
TEST_STR_EQ(platformLineEnding(), "\n");
|
TEST_STR_EQ(platformLineEnding(), "\n");
|
||||||
TEST_STR_EQ(DVX_SYM("wgtRegister"), "wgtRegister");
|
TEST_STR_EQ(DVX_SYM("wgtRegister"), "wgtRegister");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ typedef struct {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[DVX_MAX_PATH]; // filename as written in .image
|
char name[DVX_MAX_PATH]; // filename as written in .image
|
||||||
char path[520]; // imageDir/name
|
char path[DVX_MAX_PATH * 2]; // imageDir/name
|
||||||
int32_t poolOffset;
|
int32_t poolOffset;
|
||||||
int32_t fileSize;
|
int32_t fileSize;
|
||||||
} ImageRefT;
|
} ImageRefT;
|
||||||
|
|
@ -360,7 +360,7 @@ static void addImageRef(const char *filename) {
|
||||||
}
|
}
|
||||||
ImageRefT *img = &imageRefs[imageCount++];
|
ImageRefT *img = &imageRefs[imageCount++];
|
||||||
snprintf(img->name, sizeof(img->name), "%s", filename);
|
snprintf(img->name, sizeof(img->name), "%s", filename);
|
||||||
snprintf(img->path, sizeof(img->path), "%s/%s", imageDir, filename);
|
platformPathJoin(img->path, sizeof(img->path), imageDir, filename);
|
||||||
img->poolOffset = 0;
|
img->poolOffset = 0;
|
||||||
img->fileSize = 0;
|
img->fileSize = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1058,11 +1058,11 @@ static void fatalOom(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Look an image up by the filename exactly as written in .image (which
|
// Look an image up by the filename as written in .image (which may
|
||||||
// may include a subdirectory).
|
// include a subdirectory: '/' and '\\' alike, case ignored).
|
||||||
static int32_t findImage(const char *filename) {
|
static int32_t findImage(const char *filename) {
|
||||||
for (int32_t i = 0; i < imageCount; i++) {
|
for (int32_t i = 0; i < imageCount; i++) {
|
||||||
if (strcmp(imageRefs[i].name, filename) == 0) {
|
if (platformPathSame(imageRefs[i].name, filename)) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue