Add commandline conversion for Windows (no-op on other platforms)

- add Fl::args_to_utf8() to convert commandline arguments to UTF-8

This new function closes the gap that previously only Visual Studio
applications converted their commandlines to UTF-8.

Tested with MinGW, MSYS2/MinGW-w64, and Visual Studio (2019).
This commit is contained in:
Albrecht Schlosser 2023-11-20 20:12:02 +01:00
parent a0e4a3fd5d
commit 727bd94560
9 changed files with 189 additions and 31 deletions

View File

@ -1398,6 +1398,9 @@ public:
static int system(const char *command);
// Convert Windows commandline arguments to UTF-8 (documented in src/Fl.cxx)
static int args_to_utf8(int argc, char ** &argv);
#ifdef FLTK_HAVE_CAIRO
/** \defgroup group_cairo Cairo Support Functions and Classes
@{

View File

@ -11,7 +11,16 @@
// usual *nix idiom of "option=value", and provides no validation nor
// conversion of the parameter string into ints or floats.
//
// Copyright 1998-2020 by Bill Spitzak and others.
// Example 1:
//
// ./howto-parse-args -ti "FLTK is great" -o "FLTK is a great GUI tool"
//
// Example 2: translated to Japanese and simplified Chinese, respectively,
// by a well known internet translation service.
//
// ./howto-parse-args -ti "FLTKは素晴らしいです" -o "FLTK 是一个很棒的 GUI 工具"
//
// Copyright 1998-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -42,13 +51,14 @@ char *optionString = 0;
* returns 1 if argv[i] matches on its own,
* returns 0 if argv[i] does not match.
*/
int arg(int argc, char **argv, int &i)
{
int arg(int argc, char **argv, int &i) {
if (strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) {
helpFlag = 1;
i += 1;
return 1;
}
if (strcmp("-o", argv[i]) == 0 || strcmp("--option", argv[i]) == 0) {
if (i < argc-1 && argv[i+1] != 0) {
optionString = argv[i+1];
@ -59,8 +69,13 @@ int arg(int argc, char **argv, int &i)
return 0;
}
int main(int argc, char** argv)
{
int main(int argc, char** argv) {
// Convert commandline arguments in 'argv' to UTF-8 on Windows.
// This is a no-op on all other platforms (see documentation).
Fl::args_to_utf8(argc, argv);
int i = 1;
if (Fl::args(argc, argv, i, arg) < argc)
// note the concatenated strings to give a single format string!
@ -84,6 +99,8 @@ int main(int argc, char** argv)
textBox->label(optionString);
else
textBox->label("re-run with [-o|--option] text");
mainWin->resizable(mainWin);
mainWin->show(argc, argv);
return Fl::run();
}

View File

@ -578,9 +578,14 @@ list (APPEND SHARED_FILES ${HEADER_FILES} ${DRIVER_HEADER_FILES})
set (STATIC_FILES ${SHARED_FILES})
if (MSVC)
# Visual Studio (MSVC) is known to need WinMain() and maybe BORLAND
# needs it as well, hence we include it on all Windows platforms.
# The GNU compilers (MinGW, MSYS2, Cygwin) disable compilation inside
# the source file which is what we finally want and need.
if (WIN32)
list (APPEND STATIC_FILES fl_call_main.c)
endif (MSVC)
endif ()
#######################################################################

View File

@ -2281,3 +2281,76 @@ FL_EXPORT const char* fl_local_shift = Fl::system_driver()->shift_name();
FL_EXPORT const char* fl_local_meta = Fl::system_driver()->meta_name();
FL_EXPORT const char* fl_local_alt = Fl::system_driver()->alt_name();
FL_EXPORT const char* fl_local_ctrl = Fl::system_driver()->control_name();
/**
Convert Windows commandline arguments to UTF-8.
\note This function does nothing on other (non-Windows) platforms, hence
you may call it on all platforms or only on Windows by using platform
specific code like <tt>'\#ifdef _WIN32'</tt> etc. - it's your choice.
Calling it on other platforms returns quickly w/o wasting much CPU time.
This function <i>must be called <b>on Windows platforms</b></i> in \c main()
before the array \c argv is used if your program uses any commandline
argument strings (these should be UTF-8 encoded).
This applies also to standard FLTK commandline arguments like
"-name" (class name) and "-title" (window title in the title bar).
Unfortunately Windows \b neither provides commandline arguments in UTF-8
encoding \b nor as Windows "Wide Character" strings in the standard
\c main() and/or the Windows specific \c WinMain() function.
On Windows platforms (no matter which build system) this function calls
a Windows specific function to retrieve commandline arguments as Windows
"Wide Character" strings, converts these strings to an internally allocated
buffer (or multiple buffers) and returns the result in \c argv.
For implementation details please refer to the source code; however these
details may be changed in the future.
Note that \c argv is provided by reference so it can be overwritten.
In the recommended simple form the function overwrites the variable
\c argv and allocates a new array of strings pointed to by \c argv.
You may use this form on all platforms and it is as simple as adding
one line to old programs to make them work with international (UTF-8)
commandline arguments.
\code
int main(int argc, char **argv) {
Fl::args_to_utf8(argc, argv); // add this line
// ... use argc and argv, e.g. for commandline parsing
window->show(argc, argv);
return Fl::run();
}
\endcode
For an example see 'examples/howto-parse-args.cxx' in the FLTK sources.
If you want to retain the original \c argc and \c argv variables the
following slightly longer and more complicated code works as well on
all platforms.
\code
int main(int argc, char **argv) {
char **argvn = argv; // must copy argv to work on all platforms
int argcn = Fl::args_to_utf8(argc, argvn);
// ... use argcn and argvn, e.g. for commandline parsing
window->show(argcn, argvn);
return Fl::run();
}
\endcode
\param[in] argc used only on non-Windows platforms
\param[out] argv modified only on Windows platforms
\returns argument count (always the same as argc)
\since 1.4.0
\internal This function must not open the display, otherwise
commandline processing (e.g. by fluid) would open the display.
OTOH calling it when the display is opened wouldn't work either
for the same reasons ('fluid -c' doesn't open the display).
*/
int Fl::args_to_utf8(int argc, char ** &argv) {
return Fl::system_driver()->args_to_utf8(argc, argv);
}

View File

@ -110,6 +110,10 @@ public:
virtual int rmdir(const char*) {return -1;}
virtual int rename(const char* /*f*/, const char * /*n*/) {return -1;}
// Windows commandline argument conversion to UTF-8.
// Default implementation: no-op, overridden only on Windows
virtual int args_to_utf8(int argc, char ** &argv) { return argc; }
// the default implementation of these utf8... functions should be enough
virtual unsigned utf8towc(const char* src, unsigned srclen, wchar_t* dst, unsigned dstlen);
virtual unsigned utf8fromwc(char* dst, unsigned dstlen, const wchar_t* src, unsigned srclen);
@ -124,9 +128,9 @@ public:
virtual int filename_list(const char * /*d*/, dirent ***,
int (* /*sort*/)(struct dirent **, struct dirent **),
char *errmsg=NULL, int errmsg_sz=0) {
(void)errmsg; (void)errmsg_sz;
return -1;
}
(void)errmsg; (void)errmsg_sz;
return -1;
}
// the default implementation of filename_expand() may be enough
virtual int filename_expand(char *to, int tolen, const char *from);
// to implement

View File

@ -61,11 +61,15 @@ public:
int mkdir(const char *fnam, int mode) FL_OVERRIDE;
int rmdir(const char *fnam) FL_OVERRIDE;
int rename(const char *fnam, const char *newnam) FL_OVERRIDE;
// Windows commandline argument conversion to UTF-8
int args_to_utf8(int argc, char ** &argv) FL_OVERRIDE;
// Windows specific UTF-8 conversions
unsigned utf8towc(const char *src, unsigned srclen, wchar_t* dst, unsigned dstlen) FL_OVERRIDE;
unsigned utf8fromwc(char *dst, unsigned dstlen, const wchar_t* src, unsigned srclen) FL_OVERRIDE;
int utf8locale() FL_OVERRIDE;
unsigned utf8to_mb(const char *src, unsigned srclen, char *dst, unsigned dstlen) FL_OVERRIDE;
unsigned utf8from_mb(char *dst, unsigned dstlen, const char *src, unsigned srclen) FL_OVERRIDE;
int clocale_vprintf(FILE *output, const char *format, va_list args) FL_OVERRIDE;
int clocale_vsnprintf(char *output, size_t output_size, const char *format, va_list args) FL_OVERRIDE;
int clocale_vsscanf(const char *input, const char *format, va_list args) FL_OVERRIDE;

View File

@ -299,6 +299,40 @@ int Fl_WinAPI_System_Driver::rename(const char *fnam, const char *newnam) {
return _wrename(wbuf, wbuf1);
}
// See Fl::args_to_utf8()
int Fl_WinAPI_System_Driver::args_to_utf8(int argc, char ** &argv) {
int i;
char strbuf[2048]; // FIXME: allocate argv and strings dynamically
// Convert the command line arguments to UTF-8
LPWSTR *wideArgv = CommandLineToArgvW(GetCommandLineW(), &argc);
argv = (char **)malloc((argc + 1) * sizeof(char *));
for (i = 0; i < argc; i++) {
int ret = WideCharToMultiByte(CP_UTF8, // CodePage
0, // dwFlags
wideArgv[i], // lpWideCharStr
-1, // cchWideChar
strbuf, // lpMultiByteStr
sizeof(strbuf), // cbMultiByte
NULL, // lpDefaultChar
NULL); // lpUsedDefaultChar
if (!ret)
strbuf[0] = '\0'; // return empty string
argv[i] = fl_strdup(strbuf);
}
argv[argc] = NULL; // required NULL pointer at end of list
// Free the wide character string array
LocalFree(wideArgv);
// Note: the allocated memory or argv[] will not be free'd by the system
// on exit. This does not constitute a memory leak.
return argc;
}
// Two Windows-specific functions fl_utf8_to_locale() and fl_locale_to_utf8()
// from file fl_utf8.cxx are put here for API compatibility

View File

@ -22,29 +22,40 @@
* "main()". This will allow you to build a Windows Application
* without any special settings.
*
* Because of problems with the Microsoft Visual C++ header files
* and/or compiler, you cannot have a WinMain function in a DLL.
* I don't know why. Thus, this nifty feature is only available
* if you link to the static library.
* You cannot have this WinMain() function in a DLL because it would have
* to call \c main() outside the DLL. Thus, this nifty feature is only
* available if you link to the static library.
*
* Currently the debug version of this library will create a
* console window for your application so you can put printf()
* statements for debugging or informational purposes. Ultimately
* we want to update this to always use the parent's console,
* but at present we have not identified a function or API in
* Microsoft(r) Windows(r) that allows for it.
* However, it is possible to build this module separately so you can
* use it in progams that link to the shared library.
*
* Currently the debug version of this library will create a console window
* for your application so you can put printf() statements for debugging or
* informational purposes. Ultimately we want to update this to always use
* the parent's console, but at present we have not identified a function
* or API in Microsoft(r) Windows(r) that allows for it.
*/
/*
* This file is compiled only on Windows platforms (since FLTK 1.4.0).
* Therefore we don't need to test the _WIN32 macro anymore.
* The _MSC_VER macro is tested to compile it only for Visual Studio
* platforms because GNU platforms (MinGW, MSYS) don't need it.
* Notes for FLTK developers:
*
* 1) Since FLTK 1.4.0 this file is compiled only on Windows, hence we don't
* need to test the _WIN32 macro.
* 2) This file must not call any FLTK library functions because this would
* not work with /both/ the DLL /and/ the static library (linkage stuff).
* 3) Converting the commandline arguments to UTF-8 is therefore implemented
* here *and* in the library but this seems to be an acceptable compromise.
* 4) (Unless someone finds a better solution, of course. Albrecht)
* 5) The condition "!defined(FL_DLL)" prevents building this in the shared
* library, i.e. "WinMain()" will not be defined in the shared lib (DLL).
* 6) The condition "!defined (__GNUC__)" prevents compilation of this
* module with MinGW, MSYS, and Cygwin which don't use WinMain().
* 7) It is unclear if there are other build systems on Windows that need a
* WinMain() entry point. Earlier comments and code seem to indicate that
* Borland C++ would require it.
*/
#if !defined(FL_DLL) && !defined (__GNUC__)
#include <FL/fl_utf8.h>
#include <FL/fl_string_functions.h>
#if !defined(FL_DLL) && !defined (__GNUC__)
#include <windows.h>
#include <stdio.h>
@ -78,9 +89,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
freopen("conout$", "w", stderr);
#endif /* _DEBUG */
/* Convert the command line arguments to UTF-8 */
/* Get the command line arguments as Windows Wide Character strings */
LPWSTR *wideArgv = CommandLineToArgvW(GetCommandLineW(), &argc);
/* Allocate an array of 'argc + 1' string pointers */
argv = (char **)malloc((argc + 1) * sizeof(char *));
/* Convert the command line arguments to UTF-8 */
for (i = 0; i < argc; i++) {
int ret = WideCharToMultiByte(CP_UTF8, /* CodePage */
0, /* dwFlags */
@ -90,7 +105,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
sizeof(strbuf), /* cbMultiByte */
NULL, /* lpDefaultChar */
NULL); /* lpUsedDefaultChar */
argv[i] = fl_strdup(strbuf);
argv[i] = _strdup(strbuf);
}
argv[argc] = NULL; // required by C standard at end of list
@ -118,6 +133,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
}
#else
/* STR# 2973: solves "empty translation unit" error */
typedef int dummy;
#endif /* !defined(FL_DLL) && !defined (__GNUC__) */

View File

@ -1,7 +1,7 @@
//
// A shared image test program for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2022 by Bill Spitzak and others.
// Copyright 1998-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -143,7 +143,8 @@ int main(int argc, char **argv) {
setlocale(LC_ALL, ""); // enable multilanguage errors in file chooser
fl_register_images();
Fl::args(argc,argv,i,arg);
Fl::args_to_utf8(argc, argv); // enable multilanguage commandlines on Windows
Fl::args(argc, argv, i, arg); // parse commandline
if (animate)
Fl_GIF_Image::animate = true; // create animated shared .GIF images (e.g. file chooser)