This fixes all the code so that all the calls to glut functions
happen on the same thread that is running GLUT.
We achieve this by creating a queue of GLUT commands to execute.
Commands can be added to the queue anywhere in the code, and then the
queue is processed on the GLUT thread via it's idle func or timer func.
The command is run and if necessary the result is provided in the
message queue object. Each object contains a completion which can be
waited on to block until the GLUT thread has processed the command.
Fixes: #66
The current GLUT initialization logic is flawed because it allows the
QB64 code portion of the program to start on a separate thread at the
same time that the GLUT code is starting. This results in a race where
some commands won't work for a brief period at the beginning of the
program (with "won't work" being very inconsistent, some return invalid
values, some have a chance at seg faulting).
The same issue also leads to us adding many `while (!window_exists)`
checks in an attempt to solve this race for some of the commands.
Unfortunately this solution is very inconsistently applied leading to
some deadlock situations, and really it's a silly solution when this
race is entirely our creation anyway.
To fix this, the logic was changed such that we perform all of the GLUT
initialization besides calling `glutMainLoop()` before we ever start the
thread that runs the actual QB64 code. By doing it this way we ensure
that the GLUT initialization has already taken place before the code
runs and thus the race is gone.
Things get a bit more interesting with $SCREENHIDE, because that simply
delays the execution of the GLUT initialization indefinitely until
_ScreenShow is done. This was previously very buggy since some commands
rely on FreeGLUT being up and will simply hang the entire program if
run. Other commands have logic to catch this and simply return zero.
The above issue is solved with the `NEEDS_GLUT()` and `OPTIONAL_GLUT()`
macros. Both of them simply check if the GLUT initialization has taken
place and exit the current function if it has not. The difference
between the two is that `NEEDS_GLUT()` throws an 'illegal function call'
error while `OPTIONAL_GLUT()` simply exits with no error. The choice of
behavior of each function was based upon its previous behavior - if it
checked `screen_hide` and exited with no error previously, then
`OPTIONAL_GLUT()` was used. If it deadlocked or similar then
`NEEDS_GLUT()` was used (so instead of deadlocking, it now produces an
error). In this way, programs can now never get stuck due to the use of
`$SCREENHIDE` and all the commands have consistent behavior that can be
relied upon.
Fixes: #234
Fonts 9, 15, and 17 can *only* be used in SCREEN 0. Attempting to use them in graphic screens results in a seg fault and an instant program crash on Windows. This is definitely the most undesirable of behaviors for a program, and can easily be caught and dealt with just by tossing a simple "Illegal Function Call" error for the issue.
Registering a warning function keeps FreeGLUT from writing warnings to
the console (which is not desirable since it conflicts with our own
console output).
Modifies libqb.cpp to add support for opening HTTP connections via
_OPENCLIENT(). This makes use of the libqb_http API in ./libqb, which is
backed by libcurl.
This also includes a bit of refactoring for some of the code that
required additions. I replaced a few of the integer values with enums,
and added a new entry for Http and then implemented it in all the
necessary locations. In addition to `_OPENCLIENT()`, there is also
support for using HTTP connections with `EOF()`, `LOF()`, `GET #`
(variable and fixed length), and `CLOSE`.
I additionally fixed an issue of parsing the colon parts of the
`_OPENCLIENT()` parameter, where having too many colons would cause the
program to crash. Since we only allow so many parts to begin with it I
simply limited the max number of parts it will split to 10.
Fixes: #98
Fixes: #46
This adds the libqb_http API, which is designed to support HTTP and
HTTPS usage from QB64-PE source.
The design consists of a single thread which services all the HTTP(s)
connections. There are then various libqb_http APIs exposed that allow
interacting with this thread to create a new connection, query
connection status, read data, or close the connection.
Internally the thread makes use of the curl_multi interface to allow a
single thread to service multiple HTTP(s) connections in parallel. This
means you can _OPENCLIENT() multiple HTTP connection in a row and all of
them will be serviced at the same time in whatever order data is
available.
HTTP is optional and selected via a Makefile setting. A stub is swapped
in if HTTP support is not used, which avoids need to add another build
flag to libqb.cpp.
We were incorrectly treating the empty string and NULL the same and
using a password dialog for both. It now has the correct behavior of
displaying the password when provided the empty string, but just
displaying a blank input box when provided NULL.
It seems we weren't including stdint.h on Windows by mistake. Likely we
were getting it anywy from some other header, but it sounds like that
got changed in newer versions of MinGW and either way we shouldn't rely
on that.
Being that we include stdint.h on all platforms, I also changed os.h to
always use these types when defining the `int32` and friends sized
types. Ideally we get rid of those defines in the future but we can at
least use the stdint.h types going forward (as we already have).
This applies various dialog settings so that the dialog is always on
top, and also so that Tab works as expected to move between the
controls. The Edit control is moved first so that it's focused when the
window appears.
Currently we have two different ways of determining what Window handle
to tie our dialogs too - we either use GetForegroundWindow(), or create
a completely new and hidden handle. The associated window determines
what process names shows up on notifications, and also which window
can't be interacted with while a dialog is open.
Both of those approaches aren't really good. In the case of
GetForegroundWindow(), it just returns whatever window the user has in
focus, which might be a completely different process. With the hidden
window, it means the dialog and notification aren't really tied to the
QB64-PE program, so you can still interact with the window even when a
dialog is open, and the notification doesn't show an exectuable name.
To solve this we're now using EnumWindows() to enumerate over all the
Windows on the system and find one associated with our ProcessId. We
then check if it's the top-level window and return it if it is.
If that process fails to find a window (such as if this is a
console-only program, or $SCREENHIDE is used) then we check if
GetConsoleWindow() gives us a handle and use that.
If neither approach works, then we fall back to creating a hidden window
so that the dialogs can still work.
I accidentally declared these as uint32_t even though I store -1 in
them. It was working anyway due to the implicit conversion that happens
when adding it to an int32_t, but it should be fixed regardless.
The border color parameter to PAINT is optional, but sub_paint was not
handling that case. What it should do in that situation is keep painting
until it finds pixels that are not the same color as the original
starting pixel was. Instead it would simply assume border color was
valid and paint until it finds color zero (the default parameter value
when it's not provided).
This was originally reported in QB64Official/qb64#2, and Walt
(@TheJoyfulProgrammer) fixed it in QB64Official/qb64#38. Functionally
this is the same change, however I'm checking `passed & 4` to see
whether a border color was provided rather than `bordercol == NULL`.
The later has problems if the provided bordercolor is zero, which is
allowed since zero is a valid color. The `passed` argument indicates
which of the function arguments were actually provided in the QB64
source that called PAINT.
Additionally, along with the `while (true)` loop that Walt changed I
went ahead and removed the duplication of the sections for each
direction. We now just use a couple of arrays to determine which
direction we're checking and loop over all 4.
A function was made unnecessary but wasn't removed, triggering a
warning. Additionally gcc likes parenthesis around `=` assignments used
for conditions.
Fairly simple, this sets the ES_PASSWORD flag when the default text is
blank.
Additionally I fixed the dialog callback to correctly call EndDialog()
rather than DestroyWindow().
These are the last of the quote checks, these don't require any
associated changes as the underlying Win32 functions already allow
quotes. They were simply added to keep parity with the functionality on
other platforms, that that has since been fixed.
This removes the VBS based InputBox for Windows and replaces it with a
version that uses DialogBoxIndirect to create the dialog. While it is a
bit more complicated in some respects, it removes any concerns about the
contents of the strings as they're no longer being inserted into the
generated script.
It also has the advantage that it doesn't spawn another process (which
then shows up in the task bar in some situations).
With this changes quote characters are allowed in all of the parameters.
Escaping the osascript commands requires two layers of escaping: One for the
script itself, and one for the sh arguments.
For the script itself, we simply have to escape the " character with \". When
then take the result of that and escape it sutably for sh arguments in single
quotes. We already have a function for that so we simply call that to do the
job.
Fairly straight forward, the Powershell code was fundimentally just
calling Shell_NotifyIcon under the covers which we can easily do
ourself. The notification has to be tied to a window, so I simply create
a hidden one to use.
A nice improvement here is that instead of having to use the Powershell
icon we can make use of the EXE icon if one was provided. If one is not
provided then we use the default 'application' icon.
tinyfiledialogs had lots of issue surounding the quoting of the
arguments to the dialog functions. The arguments are effectively placed
verbadim into shell commands, and without any proper treatment they
would get accidentally evaluated by the shell, which is a big problem.
Additionally, any arguments containing quotes would just not work since
they would screw up the underlying shell command.
This change fixes all that for Linux. Every argument to the dialog
functions is now surrounded in single quotes, which prevents any
evaluation of the string by the shell. Additionally, because this is
standard the only chracters that need special treatment are the single
quote characters, and proper escaping for them has been added. The
result is that the dialog functions can now accept arguments that hvae
single and double quotes in them (on Linux).
Unfortunately InForm code calls this function directly via
`DECLARE LIBRARY`. For the moment we're adding it back in to keep those
programs functioning, in the future it may be removed.