diff --git a/mcsrc/src/args.c b/mcsrc/src/args.c index fb1e2ef8..4d107a57 100644 --- a/mcsrc/src/args.c +++ b/mcsrc/src/args.c @@ -441,7 +441,7 @@ mc_args_add_extended_info_to_help (void) "Please send any bug reports (including the output of 'mc -V')\n" "as tickets at www.midnight-commander.org\n") #if defined(BUILD_NUMBER) //WIN32, build - , "win32, see tickets at https://github/adamyg/mcwin32\n" + , "win32, tickets at https://github/adamyg/mcwin32\n" #endif ); mc_args__loc__header_string = @@ -646,6 +646,15 @@ mc_setup_run_mode (char **argv) /* --------------------------------------------------------------------------------------------- */ +#if defined(WIN32) //WIN32, utf8 +static void +print_handler (const gchar *string) +{ // alternative g_printf(), avoids issues with + // tool-chain specific utf8 stream inconsistent behaviour. + tty_oputs (string); +} +#endif + gboolean mc_args_parse (int *argc, char ***argv, const char *translation_domain, GError **mcerror) { @@ -686,9 +695,13 @@ mc_args_parse (int *argc, char ***argv, const char *translation_domain, GError * g_option_context_add_group (context, color_group); g_option_group_set_translation_domain (color_group, translation_domain); +#if defined(WIN32) + g_set_print_handler (print_handler); +#endif + if (!g_option_context_parse (context, argc, argv, mcerror)) { - if (*mcerror == NULL) + if (mcerror && *mcerror == NULL) mc_propagate_error (mcerror, 0, "%s\n", _("Arguments parse error!")); else { @@ -718,6 +731,10 @@ mc_args_parse (int *argc, char ***argv, const char *translation_domain, GError * g_option_context_free (context); mc_args_clean_temp_help_strings (); +#if defined(WIN32) //WIN32, utf8 + g_set_print_handler (NULL); +#endif + #ifdef ENABLE_NLS if (!str_isutf8 (_system_codepage)) bind_textdomain_codeset ("mc", _system_codepage); @@ -728,6 +745,22 @@ mc_args_parse (int *argc, char ***argv, const char *translation_domain, GError * /* --------------------------------------------------------------------------------------------- */ +#if defined(WIN32) //WIN32, utf8 +gboolean mc_args_show_info_original (void); + +gboolean +mc_args_show_info(void) // replacement +{ +#define mc_args_show_info mc_args_show_info_original + int old_utf8_mode, ret; + + old_utf8_mode = tty_utf8_mode (1); + ret = mc_args_show_info_original (); + tty_utf8_mode (old_utf8_mode); + return ret; +} +#endif + gboolean mc_args_show_info (void) { diff --git a/mcsrc/src/main.c b/mcsrc/src/main.c index fe5efae1..9ecfd6e8 100644 --- a/mcsrc/src/main.c +++ b/mcsrc/src/main.c @@ -44,7 +44,6 @@ #include /* getsid() */ #include "lib/global.h" - #include "lib/event.h" #include "lib/tty/tty.h" #include "lib/tty/key.h" /* For init_key() */ @@ -269,8 +268,9 @@ main (int argc, char *argv[]) int exit_code = EXIT_FAILURE; const char *tmpdir = NULL; -#if defined(WIN32) +#if defined(WIN32) //WIN32, config WIN32_HeapInit (); + argc = WIN32_Arguments (argc, &argv); #endif mc_global.run_from_parent_mc = !check_sid (); @@ -283,30 +283,22 @@ main (int argc, char *argv[]) (void) textdomain (PACKAGE); #if defined(WIN32) //WIN32, config - WIN32_Setup(); + WIN32_Setup (); #endif /* do this before args parsing */ str_init_strings (NULL); -#if defined(WIN32) - { // tool-chain independent wide-char/utf8 conversion command-line parser. - int uargc = 0; - char **uargv; - - uargv = GetUTF8Arguments(&uargc); - assert(uargv && uargc == argc); - argv = uargv; - argc = uargc; - } -#endif //WIN32 - mc_setup_run_mode (argv); /* are we mc? editor? viewer? etc... */ if (!mc_args_parse (&argc, &argv, "mc", &mcerror)) { startup_exit_falure: +#if defined(WIN32) //WIN32, utf8 + tty_eprintf (_("Failed to run:\n%s\n"), mcerror->message); +#else fprintf (stderr, _("Failed to run:\n%s\n"), mcerror->message); +#endif g_error_free (mcerror); startup_exit_ok: mc_shell_deinit (); diff --git a/mcsrc/src/textconf.c b/mcsrc/src/textconf.c index c642c062..a77bdbc6 100644 --- a/mcsrc/src/textconf.c +++ b/mcsrc/src/textconf.c @@ -229,16 +229,6 @@ show_version (void) void show_datadirs_extended (void) { -#if defined(WIN32) -#ifndef CP_UTF8 -#define CP_UTF8 65001 /* UTF-8 translation */ -#endif - DWORD codepage; - if (CP_UTF8 != (codepage = GetConsoleOutputCP())) { - (void) SetConsoleOutputCP(CP_UTF8); - } -#endif - (void) printf ("%s %s\n", _("Home directory:"), mc_config_get_home_dir ()); (void) printf ("%s %s\n", _("Profile root directory:"), mc_get_profile_root ()); #if defined(WIN32) @@ -302,12 +292,6 @@ show_datadirs_extended (void) #endif //WIN32, conf #endif PRINTF_SECTION2 (_("Cache directory:"), mc_config_get_cache_path ()); - -#if defined(WIN32) - if (CP_UTF8 != codepage) { - (void) SetConsoleOutputCP(codepage); - } -#endif } #undef PRINTF diff --git a/mcwin32/BUILDNUMBER b/mcwin32/BUILDNUMBER index 7b5813c6..dc0b8827 100644 --- a/mcwin32/BUILDNUMBER +++ b/mcwin32/BUILDNUMBER @@ -1 +1,2 @@ -234 +235 + diff --git a/mcwin32/ChangeLog.txt b/mcwin32/ChangeLog.txt index 64352f1f..f0407dfd 100644 --- a/mcwin32/ChangeLog.txt +++ b/mcwin32/ChangeLog.txt @@ -1,3 +1,11 @@ +Thu May 22 14:03:24 2025 adamy + + * build-235 + + o improve command-line locale/utf-8 support (#147) + o screen buffer save/restore (#143) + o network drive enumeration (#141) + Wed May 14 10:50:13 2025 adamy * build-234 diff --git a/mcwin32/Makefile.in b/mcwin32/Makefile.in index b1733d1d..b70ac37e 100644 --- a/mcwin32/Makefile.in +++ b/mcwin32/Makefile.in @@ -740,6 +740,7 @@ TARGETS=\ $(D_BIN)/mcmandoc$(E) \ $(D_BIN)/kbtest$(E) \ $(D_BIN)/coninfo$(E) \ + $(D_BIN)/volinfo$(E) \ $(D_BIN)/man2hlp.pl IMPORT=\ @@ -868,7 +869,7 @@ directories: $(DIRECTORIES) artifacts: buildinfo busyboxcmds BUILDNUMBER %/.created: - -@$(PERL) ./support/mkdir_p.pl $(@D) + -@$(MKDIR_P) $(@D) @echo "++ do not delete, midnight commander managed content ++" > $@ targets: $(LIBRARIES) $(TARGETS) @@ -973,6 +974,7 @@ clean: $(MAKE) -C mandoc clean $(MAKE) -C src/kbtest clean $(MAKE) -C src/coninfo clean + $(MAKE) -C src/volinfo clean $(MAKE) -C autoupdater clean -$(RM) $(MSGOBJS) $(MSGDIRS) $(QUIETOUT) -$(RM) $(CONFIGURATION) $(QUIETOUT) @@ -1008,6 +1010,9 @@ $(D_BIN)/kbtest$(E): $(D_BIN)/coninfo$(E): $(MAKE) -C src/coninfo +$(D_BIN)/volinfo$(E): + $(MAKE) -C src/volinfo + $(MC_RES): config.h buildinfo.h BUILD_DATE= $(shell $(BUSYBOX) date +'%Y%m%d') diff --git a/mcwin32/config.h b/mcwin32/config.h index 101a15e4..aa1f51c3 100644 --- a/mcwin32/config.h +++ b/mcwin32/config.h @@ -99,6 +99,8 @@ const char * mc_EXTHELPERSDIR(void); #define EXTHELPERSDIR mc_EXTHELPERSDIR() /* ???, 4.8.7 */ extern void WIN32_Setup(void); +extern int WIN32_Arguments(int argc, char ***argv); + extern void WIN32_HeapInit(void); extern int WIN32_HeapCheck(void); @@ -124,8 +126,12 @@ extern char * strtok_r(char *s, const char *delim, char **lasts); extern char * strcasestr(const char *s, const char *find); #endif -extern char ** GetUTF8Arguments(int *pargc); +extern void tty_oprintf(const char *fmt, ...); +extern void tty_oputs(const char *str); +extern void tty_eprintf(const char *fmt, ...); +extern void tty_eputs(const char *str); +extern int tty_utf8_mode(int state); extern void tty_set_title(const char *title); /* diff --git a/mcwin32/doc/INSTALL.md b/mcwin32/doc/INSTALL.md index 62eaa37c..4827e8db 100644 --- a/mcwin32/doc/INSTALL.md +++ b/mcwin32/doc/INSTALL.md @@ -23,6 +23,7 @@ To build and install mcwin32, you shall need: * Git tooling for windows. * Perl 5 with core modules, see [NOTES-PERL.md](doc/NOTES-PERL.md). * CoreUtils, includes various text and system utilities. + * Gettext * Make. * An ANSI C/C++ compiler. * A development environment in the form of development libraries and C header files. @@ -79,7 +80,7 @@ Several alternative profiles are available: * Open-Watcom (OWC) 1.9 or 2.0; or * owcconfig - Open Watcom 1.9 - * owc20config - Open Watcom 2.0 + * owc20config - Open Watcom 2.0 * MingW64, both 32 and 64 bit targets. @@ -169,10 +170,10 @@ The follow offers a more detailed discussion of the requirements and instruction - [Native builds using Visual C++](#native-builds-using-visual-c-c) - [Native builds using MinGW64](#native-builds-using-mingw) -Finally, please review the packaged example alternative configurations as win32 development environments can be problematic, dependent on the host setup: +Finally, please review the packaged example alternative configurations as win32 development environments can be problematic, dependent on the host setup: - .github/workflows, github build actions for owc, msvc and mingw64 toolchains. - - Appveyor CI integration notes [Appveyor CI](CINotes.md). + - Appveyor CI integration notes [Appveyor CI](CINotes.md). Native builds using Open-Watcom C/C++ ==================================== @@ -226,6 +227,14 @@ Minimal tools required are: Once installed the required commands should be visible within the path. + * gettext - gettext utilities are a set of tools that provides a framework to help packages produce multi-lingual messages. + + Several options are available including: + + * msys64 - ```pacman --noconfirm -S mingw-w64-i686-gettext-tools``` + + * [gettext for windows](https://github.com/mlocati/gettext-iconv-windows) + To support native Windows builds, the make tool ``gmake-42``, web tool ``wget`` and the shell support tool ``busybox`` are bundled within the source repository sub-directory ``win32/``. - ``gmake`` was built from its original source available from [GNU binutils](https://www.gnu.org/software/binutils/). @@ -266,7 +275,7 @@ Quick start From the source root, a suitable environment can be setup using the one of the following dependent on the desired toolchain, were ``C:\Watcom`` is the toolchain installation directory. * owcconfig- Open Watcom 1.9 - * owc20config - Open Watcom 2.0 + * owc20config - Open Watcom 2.0 * From the root of the source directory perform the following: @@ -314,7 +323,7 @@ The resulting work flow could look like the following, inside a Open Watcom 1.9 cd c:\projects git clone https://github.com/adamyg/mcwin32.git mc - + cd c:\projects\mc\mcwin32 git submodule update --init --recursive @@ -338,18 +347,18 @@ Microsoft Visual is available in several, all are suitable: * Microsoft Visual C++ 2015 - 2002 Professional - - Standard Microsoft Visual C++ installations. + Standard Microsoft Visual C++ installations. * Microsoft Visual C++ 2015 - 2022 Community Edition - - These free versions of Visual C++ 2015-2022 Professional contain the same compilers and linkers that ship with the full versions, + These free versions of Visual C++ 2015-2022 Professional contain the same compilers and linkers that ship with the full versions, and also contain everything necessary to build mcwin32. - - * Microsoft C++ Build Tools - + + * Microsoft C++ Build Tools - There's also a standalone (IDE-less) version of the build tools mentioned above containing the MSVC compiler available for download from https://visualstudio.microsoft.com/visual-cpp-build-tools/. - + Note: Since these are proprietary and ever-changing I cannot test them all. Older versions may not work, it is recommended to use a recent version wherever possible. * Install _Perl_ @@ -393,7 +402,7 @@ The resulting work flow could look like the following, inside a 2019 developer p cd c:\projects git clone https://github.com/adamyg/mcwin32.git mc - + cd c:\projects\mc\mcwin32 git submodule update --init --recursive @@ -412,8 +421,8 @@ Native builds using Mingw Mingw64 (32/64) offers another alternative way to build native __mcwin32__, similar to Open-Watcom C/C++ builds. -MSYS2 provides GNU tools, a Unix-like command prompt, and a UNIX compatibility layer for applications, -available from https://www.mingw-w64.org. However, in this context it is only used for building mcwin32. +MSYS2 provides GNU tools, a Unix-like command prompt, and a UNIX compatibility layer for applications, +available from https://www.mingw-w64.org. However, in this context it is only used for building mcwin32. The resulting application does not rely on MSYS2 to run and is fully native. * _MSYS2_ shell, from https://www.msys2.org/ @@ -433,9 +442,14 @@ The resulting application does not rely on MSYS2 to run and is fully native. $ pacman --noconfirm -S mingw-w64-x86_64-gcc $ pacman --noconfirm -S mingw-w64-i686-gcc - These compilers must be on your MSYS2 \$PATH, example below assuming the default installation path ``c:/msys64/``. + These compilers must be on your MSYS2 \$PATH, example below assuming the default installation path ``c:/msys64/``. A common error is to not have these on your \$PATH. The MSYS2 version of gcc will not work correctly here. + finally, any additional components + + $ pacman --noconfirm -S mingw-w64-i686-gettext-tools + $ pacman --noconfirm -S zip + * From the root of the source directory perform the following: * Configure and prime the build system. @@ -457,7 +471,7 @@ The resulting application does not rely on MSYS2 to run and is fully native. * Optionally, build the installer. $ .\support\gmake-42 release package - + The resulting work flow could look like the following, inside either a terminal/command or msys prompt: Install any missing components @@ -475,7 +489,7 @@ Prime sandbox and build cd c:\projects git clone https://github.com/adamyg/mcwin32.git mc - + cd c:\projects\mc\mcwin32 git submodule update --init --recursive diff --git a/mcwin32/libglib/Makefile.in b/mcwin32/libglib/Makefile.in index 4af2ae1e..3f3fe424 100644 --- a/mcwin32/libglib/Makefile.in +++ b/mcwin32/libglib/Makefile.in @@ -1,5 +1,5 @@ # -*- mode: mak; indent-tabs-mode: t; tab-width: 8 -*- -# $Id: Makefile.in,v 1.15 2025/01/27 15:10:28 cvsuser Exp $ +# $Id: Makefile.in,v 1.16 2025/05/22 03:45:58 cvsuser Exp $ # libglib makefile. # # @@ -32,6 +32,10 @@ ROOT= @abs_top_builddir@ top_builddir= @top_builddir@ +# see: config.h.win32 + +PACKAGE = glib20 + # File extensions E= @@ -50,6 +54,7 @@ CC= @CC@ AR= @AR@ RANLIB= @RANLIB@ RM= @RM@ +MKDIR_P= @MKDIR_P@ CP= @CP@ PERL= @PERL@ LIBTOOL= @LIBTOOL@ @@ -81,6 +86,11 @@ D_BIN= $(ROOT)/bin@TOOLCHAINEXT@/$(BUILD_TYPE) D_LIB= $(ROOT)/lib@TOOLCHAINEXT@/$(BUILD_TYPE) D_OBJ= $(ROOT)/obj@TOOLCHAINEXT@/$(BUILD_TYPE)/libglib +D_ETC= $(D_BIN)/etc +D_SHARE= $(D_BIN)/share +D_DOC= $(D_BIN)/doc +D_LOCALE= $(D_BIN)/share/locale + # Common flags XFLAGS= @@ -132,6 +142,7 @@ GLIBBASE= ./glib-$(VERSION) GLIBSRC= $(GLIBBASE)/glib GLIBINCLUDE= $(GLIBBASE)/glib GMODULESRC= $(GLIBBASE)/gmodule +POSRC= $(GLIBBASE)/po CINCLUDE+= -I$(GLIBBASE) -I./include-$(VERSION) -I./sdk CEXTRA+= -D_WIN32 @@ -162,6 +173,11 @@ CEXTRA_PCRE = \ ## -DG_LOG_DOMAIN="GLib-GRegex" #see config.h ## -DNEWLINE=-1 #see config.h +MSGLANGS= $(notdir $(wildcard $(POSRC)/*.po)) +BASEMSGDIRS= $(addprefix $(D_LOCALE)/,$(MSGLANGS:.po=)) +MSGDIRS= $(addprefix $(D_LOCALE)/,$(MSGLANGS:.po=/LC_MESSAGES/.created)) +MSGOBJS= $(addprefix $(D_LOCALE)/,$(MSGLANGS:.po=/LC_MESSAGES/$(PACKAGE).mo)) + CEXTRA_GTHREAD = \ -DG_LOG_DOMAIN="GThread" @@ -366,15 +382,19 @@ INSTALLED= ############################################################ # Rules -.PHONY: build release debug +.PHONY: build release debug gettext build: source unpacked -unpacked: object $(LIBS) $(TSKS) installheaders +unpacked: object targets installheaders gettext release: $(MAKE) BUILD_TYPE=release $(filter-out release, $(MAKECMDGOALS)) debug: $(MAKE) BUILD_TYPE=debug $(filter-out debug, $(MAKECMDGOALS)) +targets: $(LIBS) $(TSKS) + +gettext: $(MSGDIRS) $(MSGOBJS) + #see: gtypes.h (+ GLIB_COMPILATION above) $(GLIBLIB): CLOCAL += -I. -I$(GLIBSRC) @@ -506,7 +526,7 @@ installheaders: Makefile ../include/.created \ @$(CP) $(GMODULESRC)/gmodule.h ../include/glib-2.0 %/.created: - -@mkdir $(@D) + -@$(MKDIR_P) $(@D) @echo "do not delete, managed directory" > $@ clean: @@ -516,6 +536,8 @@ clean: -@$(RM) $(subst /,\,$(RMFLAGS) $(BAK) $(TSKS) $(INSTALLED) $(LIBS) $(CLEAN) $(XCLEAN) $(QUIETOUT)) -@$(RM) $(subst /,\,$(GLIBLIBOBJS) $(QUIETOUT)) -@$(RM) $(subst /,\,$(GMODULELIBOBJS) $(QUIETOUT)) + -$(RMDIR) $(subst /,\,$(call reverse, $(BASEMSGDIRS) $(dir $(MSGDIRS)))) $(QUIETOUT)) + -$(RM) $(subst /,\,$(MSGOBJS) $(MSGDIRS) $(QUIETOUT)) ############################################################ @@ -561,4 +583,10 @@ $(D_OBJ)/%.res: %.rc $(D_OBJ)/%_res.o: %.rc $(RC) -o $@ $< +# mo + +$(D_LOCALE)/%/LC_MESSAGES/$(PACKAGE).mo: $(POSRC)/%.po + @echo build $@ .. + msgfmt -c -o $@ $(POSRC)/$*.po + #end diff --git a/mcwin32/libw32/termemu_vio.c b/mcwin32/libw32/termemu_vio.c index 60a21ed1..29664912 100644 --- a/mcwin32/libw32/termemu_vio.c +++ b/mcwin32/libw32/termemu_vio.c @@ -28,7 +28,7 @@ /* * Notes: * o 256 color mode available under both Legacy and Win10 enhanced console. - * o Use of non-monospaced fonts are not advised unless UNICODE characters are required. + * o Use of non monospaced fonts are not advised unless UNICODE characters are required. * o Neither wide nor combined characters are fully addressed. */ @@ -189,7 +189,7 @@ struct sline { WCHAR_INFO * text; }; -typedef struct copyoutctx { +typedef struct CopyOutCtx { int active; int cursormode; int codepagemode; // code-page to restore. @@ -197,7 +197,14 @@ typedef struct copyoutctx { CONSOLE_CURSOR_INFO cursorinfo; // current cursor state. RECT devicerect; // current display area. UINT codepage; // current code page. -} copyoutctx_t; +} CopyOutCtx_t; + +typedef struct VioState { + CHAR_INFO * image; + unsigned top; + unsigned rows; + unsigned cols; +} VioState_t; #define SWAPFGBG(__f, __b) \ { int t_color = __f; __f = __b; __b = t_color; } @@ -231,18 +238,18 @@ static __inline int winnormal(const int color); static __inline int vtnormal(const int color); static void CopyIn(unsigned pos, unsigned cnt, WCHAR_INFO *image); -static void CopyOut(copyoutctx_t *ctx, unsigned offset, unsigned len, unsigned flags); -static void CopyOutLegacy(copyoutctx_t* ctx, unsigned pos, unsigned cnt, unsigned flags); +static void CopyOut(CopyOutCtx_t *ctx, unsigned offset, unsigned len, unsigned flags); +static void CopyOutLegacy(CopyOutCtx_t* ctx, unsigned pos, unsigned cnt, unsigned flags); #if defined(WIN32_CONSOLEEXT) #if defined(WIN32_CONSOLE256) -static void CopyOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt, unsigned flags); +static void CopyOutEx(CopyOutCtx_t *ctx, unsigned pos, unsigned cnt, unsigned flags); #define WIN32_CONSOLEVIRTUAL #if defined(WIN32_CONSOLEVIRTUAL) -static void CopyOutVirtual(copyoutctx_t *ctx, size_t pos, size_t cnt, unsigned flags); +static void CopyOutVirtual(CopyOutCtx_t *ctx, size_t pos, size_t cnt, unsigned flags); #endif //WIN32_CONSOLEVIRTUAL #endif //WIN32_CONSOLE256 -static void UnderOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt); -static void StrikeOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt); +static void UnderOutEx(CopyOutCtx_t *ctx, unsigned pos, unsigned cnt); +static void StrikeOutEx(CopyOutCtx_t *ctx, unsigned pos, unsigned cnt); #endif //WIN32_CONSOLEEXT #if defined(WIN32_CONSOLE256) @@ -251,8 +258,10 @@ static void consolefontsenum(void); static HFONT consolefontcreate(int height, int width, int weight, int italic, const char *facename); #endif -static void ImageSave(HANDLE console, unsigned pos, unsigned cnt); -static void ImageRestore(HANDLE console, unsigned pos, unsigned cnt); +static BOOL ImageInit(VioState_t *state, unsigned rows, unsigned cols); +static BOOL ImageSize(VioState_t *state, unsigned rows, unsigned cols); +static void ImageSave(HANDLE console, VioState_t *state, unsigned at, unsigned nrows); +static void ImageRestore(HANDLE console, VioState_t *state, unsigned at, unsigned nrows); #define FACENAME_MAX 64 #define FONTS_MAX 64 @@ -266,11 +275,10 @@ typedef BOOL (WINAPI *SetCurrentConsoleFontEx_t)(HANDLE, BOOL, CONSOLE_FONT_IN typedef BOOL (WINAPI *GetConsoleScreenBufferInfoEx_t)(HANDLE, CONSOLE_SCREEN_BUFFER_INFOEX *); static struct { - CHAR_INFO * image; - CONSOLE_CURSOR_INFO cursorinfo; - COORD cursorcoord; - int rows; - int cols; + CONSOLE_CURSOR_INFO cursorinfo; // Original cursor information. + COORD cursorcoord; // Original cursor position. + VioState_t active; // Optional visible buffer; end-of-buffer. + VioState_t alt; // Alt screen; top-of-buffer. } vio_state; static struct { /* Video state */ @@ -1475,7 +1483,7 @@ AttributesShadow(const struct WCHAR_COLORINFO *color) * nothing. **/ static void -CopyOutInit(copyoutctx_t *ctx) +CopyOutInit(CopyOutCtx_t *ctx) { (void) memset(ctx, 0, sizeof(*ctx)); ctx->cursormode = -1; @@ -1485,7 +1493,7 @@ CopyOutInit(copyoutctx_t *ctx) static void -CopyOutFinal(copyoutctx_t *ctx) +CopyOutFinal(CopyOutCtx_t *ctx) { assert(42 == ctx->active); if (42 != ctx->active) return; @@ -1510,7 +1518,7 @@ CopyOutFinal(copyoutctx_t *ctx) static void -CopyOut(copyoutctx_t* ctx, unsigned pos, unsigned cnt, unsigned flags) +CopyOut(CopyOutCtx_t* ctx, unsigned pos, unsigned cnt, unsigned flags) { #if defined(WIN32_CONSOLEVIRTUAL) // @@ -1528,7 +1536,7 @@ CopyOut(copyoutctx_t* ctx, unsigned pos, unsigned cnt, unsigned flags) static void -CopyOutLegacy(copyoutctx_t *ctx, unsigned pos, unsigned cnt, unsigned flags) +CopyOutLegacy(CopyOutCtx_t *ctx, unsigned pos, unsigned cnt, unsigned flags) { const unsigned activecolors = (vio.displaymode || 0 == vio.activecolors ? 16 : vio.activecolors); const int /*rows = vio.rows,*/ cols = vio.cols; @@ -1834,7 +1842,7 @@ SameAttributesBG(const WCHAR_INFO *cell, const struct WCHAR_COLORINFO *info, con * nothing. **/ static void -CopyOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt, unsigned flags) +CopyOutEx(CopyOutCtx_t *ctx, unsigned pos, unsigned cnt, unsigned flags) { const WCHAR_INFO *cursor = vio.image + pos, *end = cursor + cnt; const int rows = vio.rows, cols = vio.cols; @@ -2008,7 +2016,7 @@ CopyOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt, unsigned flags) * https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences */ static void -CopyOutVirtual(copyoutctx_t *ctx, size_t pos, size_t cnt, unsigned flags) +CopyOutVirtual(CopyOutCtx_t *ctx, size_t pos, size_t cnt, unsigned flags) { const WCHAR_INFO *cursor = vio.image + pos, *end = cursor + cnt; HANDLE chandle = vio.chandle; @@ -2164,7 +2172,7 @@ CopyOutVirtual(copyoutctx_t *ctx, size_t pos, size_t cnt, unsigned flags) /* Function: UnderOutEx * Underline characters within the specified region. * - * Parameters + * Parameters: * pos - Starting offset in video cells from top left. * cnt - Video cell count. * @@ -2172,7 +2180,7 @@ CopyOutVirtual(copyoutctx_t *ctx, size_t pos, size_t cnt, unsigned flags) * nothing. **/ static void -UnderOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt) +UnderOutEx(CopyOutCtx_t *ctx, unsigned pos, unsigned cnt) { const WCHAR_INFO *cursor = vio.image + pos, *end = cursor + cnt; const int rows = vio.rows, cols = vio.cols; @@ -2235,7 +2243,7 @@ UnderOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt) /* Function: StrikeOutEx * Over-strike characters within the specified region. * - * Parameters + * Parameters: * pos - Starting offset in video cells from top left. * cnt - Video cell count. * @@ -2243,7 +2251,7 @@ UnderOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt) * nothing. **/ static void -StrikeOutEx(copyoutctx_t *ctx, unsigned pos, unsigned cnt) +StrikeOutEx(CopyOutCtx_t *ctx, unsigned pos, unsigned cnt) { const WCHAR_INFO *cursor = vio.image + pos, *end = cursor + cnt; const int rows = vio.rows, cols = vio.cols; @@ -2606,14 +2614,15 @@ consolefontset(int height, int width, const char *facename) #if defined(DO_TRACE_LOG) { DWORD dwGlyphSize; - if ((dwGlyphSize = GetFontUnicodeRanges(wdc, NULL)) > 0) { + if ((dwGlyphSize = GetFontUnicodeRanges(wdc, NULL)) != 0) { GLYPHSET *glyphsets; DWORD r; // TODO -- not supported mapping - if (NULL != (glyphsets = calloc(dwGlyphSize, 1))) { + if (NULL != (glyphsets = calloc(1, dwGlyphSize))) { glyphsets->cbThis = dwGlyphSize; TRACE_LOG(("Font UNICODE Support\n")) - if (GetFontUnicodeRanges(wdc, glyphsets) > 0) { + + if (GetFontUnicodeRanges(wdc, glyphsets)) { TRACE_LOG((" flAccel: %u\n", glyphsets->flAccel)) TRACE_LOG((" total: %u\n", glyphsets->cGlyphsSupported)) for (r = 0; r < glyphsets->cRanges; ++r) { @@ -2621,7 +2630,7 @@ consolefontset(int height, int width, const char *facename) glyphsets->ranges[r].wcLow, glyphsets->ranges[r].wcLow+glyphsets->ranges[r].cGlyphs)) } } - free(glyphsets); + free((void *) glyphsets); } } } @@ -2672,7 +2681,7 @@ consolefontcreate(int height, int width, int weight, int italic, const char *fac ANSI_CHARSET, // DEFAULT (locale), ANSI, OEM ... // DEFAULT, ANSI, BALTIC, CHINESEBIG5, EASTEUROPE, GB2312, GREEK, HANGUL, // MAC_CHARSET, OEM, RUSSIAN, SHIFTJIS, TURKISH, VIETNAMESE - // .. should we match the locale/codepage ? + // .. should we match the locale/code-page ? (isTerminal ? OUT_RASTER_PRECIS : OUT_TT_PRECIS), CLIP_DEFAULT_PRECIS, // default clipping behavior. (italic ? PROOF_QUALITY : ANTIALIASED_QUALITY), @@ -2764,8 +2773,9 @@ parse_color(const char *color, const char *defname, const struct attrmap *map, i const int len = (int)(a - color); strncpy(t_name, color, sizeof(t_name)-1);// remove attribute component. - if (len < (int)sizeof(t_name)) + if (len < (int)sizeof(t_name)) { t_name[len] = 0; + } color = t_name; *attr = parse_attributes(a); @@ -2884,7 +2894,7 @@ check_activecolors(void) /* Function: winnormal * Apply color foreground/background defaults for WIN_COLOR specifications. * - * Parameters + * Parameters: * color - WIN_COLOR value. * * Returns: @@ -2902,7 +2912,7 @@ winnormal(const int color) /* Function: vtnormal * Apply color foreground/background defaults for VT_COLOR specifications. * - * Parameters + * Parameters: * color - VT_COLOR value. * * Returns: @@ -2918,172 +2928,110 @@ vtnormal(const int color) /* - * vio_save --- + * vio_save_lines --- * Save the buffer screen state; for later restoration via via_restore() + * + * Parameter: + * active - If non-zero, save the active visible screen (generally the end-of-buffer), + * in addition to the top-of-buffer alternative screen area. Otherwise the behavior + * is the same as vio_save(). + * + * Returns: + * Nothing **/ LIBVIO_API void -vio_save(void) +vio_save_lines(int active) { - HANDLE console = (vio.inited ? vio.chandle : GetStdHandle(STD_OUTPUT_HANDLE)); - COORD cursor, home = {0, 0}; + HANDLE console = (vio.inited ? // console handle + vio.chandle : GetStdHandle(STD_OUTPUT_HANDLE)); + const COORD home = { 0, 0 }; + COORD cursor; int rows, cols; - /* - * Size arena - */ + // size working buffers cursor = vio_size(console, &rows, &cols); - if (!vio_state.image || // initial or size change - vio_state.rows != rows || vio_state.cols != cols) { - CHAR_INFO *nimage; - - if (rows <= 0 || cols <= 0 || - NULL == (nimage = calloc(rows * cols, sizeof(CHAR_INFO)))) { - return; - } - free(vio_state.image); // release previous; if any - vio_state.image = nimage; - vio_state.rows = rows; - vio_state.cols = cols; + if ((active && !ImageInit(&vio_state.active, rows, cols)) || + !ImageInit(&vio_state.alt, rows, cols)) { + return; } - /* - * Save cursor and image - */ + // original cursor state vio_state.cursorcoord = cursor; GetConsoleCursorInfo(console, &vio_state.cursorinfo); - SetConsoleCursorPosition(console, home); // home cursor. - ImageSave(console, 0, rows * cols); // read image. -} - - -static void -ImageSave(HANDLE console, unsigned pos, unsigned cnt) -{ - const int rows = vio_state.rows, cols = vio_state.cols; - COORD is = {0}, ic = {0}; - SMALL_RECT wr = {0}; - DWORD rc; + // original screen lines; end-of-buffer + vio_state.active.top = 0; // non-zero active - assert(pos < (unsigned)(rows * cols)); - assert(cnt && 0 == (pos % cols)); - assert((pos + cnt) <= (unsigned)(rows * cols)); + if (active) { + CONSOLE_SCREEN_BUFFER_INFO csbi = { 0, 0 }; - wr.Left = 0; // src. screen rectangle. - wr.Right = (SHORT)(cols - 1); - wr.Top = (SHORT)(pos / cols); - wr.Bottom = (SHORT)((pos + (cnt - 1)) / cols); + GetConsoleScreenBufferInfo(console, &csbi); + if (csbi.srWindow.Top) { // differs from top-of-buffer + // Legacy console support + // https://learn.microsoft.com/en-us/windows/console/classic-vs-vt + vio_state.active.top = csbi.srWindow.Top; + ImageSave(console, &vio_state.active, 0, rows); + } + } - is.Y = (SHORT)(rows - wr.Top); // size of image. - is.X = (SHORT)(cols); + // alternative screen; top-of-buffer + ImageSave(console, &vio_state.alt, 0, rows); + SetConsoleCursorPosition(console, home); +} - ic.X = 0; // top left src cell in image. - ic.Y = 0; - // read in image. - rc = ReadConsoleOutputW(console, vio_state.image + pos, is, ic, &wr); - if (0 == rc && ERROR_NOT_ENOUGH_MEMORY == GetLastError()) { - if (cnt > ((unsigned)cols * 2)) { // sub-divide request (max 8k). - const int cnt2 = (cnt / (cols * 2)) * cols; - - ImageSave(console, pos, cnt2); - ImageSave(console, pos + cnt2, cnt - cnt2); - } - } +LIBVIO_API void +vio_save(void) +{ + vio_save_lines(0); } /* - * vio_restore --- - * Restore the buffer; from a previous via_save() + * vio_restore_lines --- + * Restore the buffer; from a previous save lines. **/ LIBVIO_API void vio_restore_lines(int top, int bottom, int to) { - HANDLE console = (vio.inited ? vio.chandle : GetStdHandle(STD_OUTPUT_HANDLE)); - int rows = 0, cols = 0; + VioState_t *state = (vio_state.active.top ? // restore buffer + &vio_state.active : (to >= 0 ? &vio_state.alt : NULL)); + HANDLE console = (vio.inited ? // console handle + vio.chandle : GetStdHandle(STD_OUTPUT_HANDLE)); - if (NULL == vio_state.image) // initialised ? - return; + int rows = 0, cols = 0; assert(to >= -1); - vio_size(console, &rows, &cols); - if (0 == rows || 0 == cols) + if (NULL == state || NULL == state->image) // uninitialised return; - // resize; if required, space fill - if ((rows != vio_state.rows || cols != vio_state.cols)) { - CHAR_INFO *nimage; - - if ((nimage = calloc(rows * cols, sizeof(CHAR_INFO))) != NULL) { - - const CHAR_INFO blank = { {' '}, FOREGROUND_INTENSITY }; - const int cnt = (cols > vio_state.cols ? vio_state.cols : cols) * sizeof(CHAR_INFO); - int r, c; - - for (r = 0; r < rows; ++r) { - if (r < vio_state.rows) { // merge previous image. - memcpy(nimage + (r * cols), - (const void *)(vio_state.image + (r * vio_state.cols)), cnt); - } - // blank new cells. - if ((c = (r >= vio_state.rows ? 0 : vio_state.cols)) < cols) { - CHAR_INFO *p = nimage + (r *cols) + c; - do { - *p++ = blank; - } while (++c < cols); - } - } + // Size arena + vio_size(console, &rows, &cols); - free((void *)vio_state.image); // replace image. - vio_state.image = nimage; - vio_state.rows = rows; - vio_state.cols = cols; - } + if (! ImageSize(state, rows, cols)) { + return; } - // Restore to console - if (top == -1 && bottom == -1) { - // Full restore, image and cursor. - - top = 0; - bottom = rows - 1; + // Update selection + if (bottom > rows) + bottom = rows; // lower limit. - if (to == -1) { - COORD iHome = {0, 0}; // home cursor. + if (top < 0) + top = 0; // upper limit. - SetConsoleCursorPosition(console, iHome); - ImageRestore(console, 0, rows * cols); - - // original cursor. - SetConsoleCursorPosition(console, vio_state.cursorcoord); - SetConsoleCursorInfo(console, &vio_state.cursorinfo); - } - - } else { - // Update selection - - if (bottom > rows) - bottom = rows; // lower limit. - - if (top < 0) - top = 0; // upper limit. - - if (to == -1) { - if (top < bottom) { // non-inclusive bottom. - const unsigned lines = (unsigned)(bottom - top); - - ImageRestore(console, top * cols, lines * cols); - } + if (to == -1) { + if (top < bottom) { // non-inclusive bottom. + const unsigned lines = (unsigned)(bottom - top); + ImageRestore(console, state, top, lines); } } - + // Publish to buffer if (to >= 0) { for (; top < bottom && to < rows; ++top, ++to) { - const CHAR_INFO *icursor = vio_state.image + (top * cols); + const CHAR_INFO *icursor = state->image + (top * cols); WCHAR_INFO *cursor = vio.c_screen[to].text, *end = cursor + cols; WCHAR_INFO nchr = {0}; unsigned flags = 0; @@ -3109,45 +3057,196 @@ vio_restore_lines(int top, int bottom, int to) } +/* + * vio_restore --- + * Restore the buffer state. + **/ LIBVIO_API void vio_restore(void) { - vio_restore_lines(-1, -1, -1); + HANDLE console = (vio.inited ? // console handle + vio.chandle : GetStdHandle(STD_OUTPUT_HANDLE)); + int rows = 0, cols = 0; + + if (NULL == vio_state.alt.image) // uninitialised + return; + + // Size arena + vio_size(console, &rows, &cols); + + if ((vio_state.active.top && !ImageSize(&vio_state.active, rows, cols)) || + !ImageSize(&vio_state.alt, rows, cols)) { + return; + } + + // original end-of-buffer. + if (vio_state.active.top) { + ImageRestore(console, &vio_state.active, 0, rows); + } + + // original cursor. + SetConsoleCursorPosition(console, vio_state.cursorcoord); + SetConsoleCursorInfo(console, &vio_state.cursorinfo); + + // original top-of-buffer; alternative screen. + ImageRestore(console, &vio_state.alt, 0, rows); +} + + +/* + * Save state initialise or reinitialisation. + */ +static BOOL +ImageInit(VioState_t *state, unsigned rows, unsigned cols) +{ + const size_t elements = rows * cols; + + if (state->image && state->rows == rows && state->cols == cols) { + memset(state->image, 0, elements * sizeof(CHAR_INFO)); + + } else { + CHAR_INFO *nimage; + + if (rows == 0 || rows > 0x7fff || cols == 0 || cols > 0x7fff || + NULL == (nimage = calloc(elements, sizeof(CHAR_INFO)))) { + return FALSE; + } + + free((void *)state->image); // release previous; if any + + state->image = nimage; + state->rows = rows; + state->cols = cols; + } + return TRUE; } +/* + * Save state resize, on a size change reorganizing the buffer, + * either extending by padding or remove unneeded cells. + */ +static BOOL +ImageSize(VioState_t *state, unsigned rows, unsigned cols) +{ + if (state->rows != rows || state->cols != cols) { + const size_t elements = rows * cols; + CHAR_INFO *nimage; + + if (rows == 0 || rows > 0x7fff || cols == 0 || cols > 0x7fff || + NULL == (nimage = calloc(elements, sizeof(CHAR_INFO)))) { + return FALSE; + + } else { + const CHAR_INFO blank = { {' '}, FOREGROUND_INTENSITY }; + const unsigned cnt = (cols > state->cols ? state->cols : cols) * sizeof(CHAR_INFO); + unsigned r, c; + + for (r = 0; r < rows; ++r) { + if (r < state->rows) { // merge previous image. + memcpy(nimage + (r * cols), + (const void*)(state->image + (r * state->cols)), cnt); + } + + // blank new cells. + if ((c = (r >= state->rows ? 0 : state->cols)) < cols) { + CHAR_INFO *p = nimage + (r * cols) + c; + + do { + *p++ = blank; + } while (++c < cols); + } + } + + free((void*)state->image); // replace image. + + state->image = nimage; + state->rows = rows; + state->cols = cols; + } + } + + return TRUE; +} + + +/* + * Import the screen buffer. + */ static void -ImageRestore(HANDLE console, unsigned pos, unsigned cnt) +ImageSave(HANDLE console, VioState_t *state, unsigned at, unsigned nrows) { - const int rows = vio_state.rows, cols = vio_state.cols; + const unsigned top = state->top, + rows = state->rows, cols = state->cols; + COORD is = {0}, ic = {0}; SMALL_RECT wr = {0}; DWORD rc; - assert(pos < (unsigned)(rows * cols)); - assert(0 == (pos % cols)); // col=0 - assert(cnt && 0 == (cnt % cols)); // and column width - assert((pos + cnt) <= (unsigned)(rows * cols)); + assert(at < rows); + assert(nrows && nrows <= rows); + assert((at + nrows) <= rows); - wr.Left = 0; // src. screen rectangle. - wr.Right = (SHORT)(cols - 1); - wr.Top = (SHORT)(pos / cols); - wr.Bottom = (SHORT)((pos + (cnt - 1)) / cols); + wr.Left = 0; // readRegion, screen rectangle. + wr.Right = (SHORT)(cols - 1); + wr.Top = (SHORT)(top + at); + wr.Bottom = (SHORT)(top + nrows - 1); - is.Y = (SHORT)(vio.rows - wr.Top); // size of image. - is.X = (SHORT)vio.cols; + is.Y = (SHORT)(nrows); // bufferSize, size of image. + is.X = (SHORT)(cols); - ic.X = 0; // top left src cell in image. - ic.Y = 0; + ic.X = 0; // bufferCoord, top left src cell in image. + ic.Y = 0; // read in image. - rc = WriteConsoleOutputW(console, vio_state.image + pos, is, ic, &wr); + rc = ReadConsoleOutputW(console, state->image + (at * cols), is, ic, &wr); if (0 == rc && ERROR_NOT_ENOUGH_MEMORY == GetLastError()) { - if (cnt > ((unsigned)cols * 2)) { // sub-divide request. - const int cnt2 = (cnt / (cols * 2)) * cols; + if (nrows >= 2) { // sub-divide request (max 8k). + const unsigned slice = nrows / 2; + + ImageSave(console, state, at, slice); + ImageSave(console, state, at + slice, nrows - slice); + } + } +} + + +/* + * Restore the screen buffer. + */ +static void +ImageRestore(HANDLE console, VioState_t *state, unsigned at, unsigned nrows) +{ + const unsigned top = state->top, + rows = state->rows, cols = state->cols; + + COORD is = {0}, ic = {0}; + SMALL_RECT wr = {0}; + DWORD rc; + + assert(at < rows); + assert(nrows && nrows <= rows); + assert((at + nrows) <= rows); + + wr.Left = 0; // writeRegion, screen rectangle. + wr.Right = (SHORT)(cols - 1); + wr.Top = (SHORT)(top + at); + wr.Bottom = (SHORT)(top + nrows - 1); + + is.Y = (SHORT)(nrows); // bufferSize, size of image. + is.X = (SHORT)(cols); + + ic.X = 0; // bufferCoord, top left src cell in image. + ic.Y = 0; + // write out image. + rc = WriteConsoleOutputW(console, state->image + (at * cols), is, ic, &wr); + + if (0 == rc && ERROR_NOT_ENOUGH_MEMORY == GetLastError()) { + if (nrows >= 2) { // sub-divide request (max 8k). + const unsigned slice = nrows / 2; - ImageRestore(console, pos, cnt2); - ImageRestore(console, pos + cnt2, cnt - cnt2); + ImageRestore(console, state, at, slice); + ImageRestore(console, state, at + slice, nrows - slice); } } } @@ -3785,7 +3884,7 @@ vio_normal_video(void) LIBVIO_API void vio_flush(void) { - copyoutctx_t ctx = {0}; + CopyOutCtx_t ctx = {0}; const int trashed = vio.c_trashed; int l, updates; diff --git a/mcwin32/libw32/termemu_vio.h b/mcwin32/libw32/termemu_vio.h index 1186f218..90edfb8a 100644 --- a/mcwin32/libw32/termemu_vio.h +++ b/mcwin32/libw32/termemu_vio.h @@ -1,7 +1,7 @@ #ifndef TERMEMU_VIO_H_INCLUDED #define TERMEMU_VIO_H_INCLUDED #include -__CIDENT_RCSID(termemu_vio_h,"$Id: termemu_vio.h,v 1.13 2025/03/12 17:38:50 cvsuser Exp $") +__CIDENT_RCSID(termemu_vio_h,"$Id: termemu_vio.h,v 1.14 2025/05/20 12:17:30 cvsuser Exp $") __CPRAGMA_ONCE /* -*- mode: c; indent-width: 4; -*- */ @@ -130,6 +130,8 @@ __BEGIN_DECLS LIBVIO_API void vio_save(void); LIBVIO_API void vio_restore(void); +LIBVIO_API void vio_save_lines(int active); +LIBVIO_API void vio_restore_lines(int top, int bottom, int to); LIBVIO_API int vio_screenbuffersize(void); LIBVIO_API int vio_open(int *rows, int *cols); diff --git a/mcwin32/libw32/w32_fopen.c b/mcwin32/libw32/w32_fopen.c index f25377fd..4fd90784 100644 --- a/mcwin32/libw32/w32_fopen.c +++ b/mcwin32/libw32/w32_fopen.c @@ -1,5 +1,5 @@ #include -__CIDENT_RCSID(gr_w32_fopen_c, "$Id: w32_fopen.c,v 1.2 2025/04/01 16:15:14 cvsuser Exp $") +__CIDENT_RCSID(gr_w32_fopen_c, "$Id: w32_fopen.c,v 1.3 2025/05/23 11:21:14 cvsuser Exp $") /* -*- mode: c; indent-width: 4; -*- */ /* @@ -49,6 +49,7 @@ __CIDENT_RCSID(gr_w32_fopen_c, "$Id: w32_fopen.c,v 1.2 2025/04/01 16:15:14 cvsus #endif #include "win32_internal.h" +#include "win32_misc.h" #include "win32_io.h" static FILE *W32OpenStreamA(const char *path, const char *mode); @@ -170,6 +171,27 @@ w32_fopenA(const char *path, const char *mode) if (0 == w32_iostricmpA(path, "/dev/null")) { path = "NUL"; // redirect + } else if (0 == w32_iostricmpA(path, "/dev/stdin")) { + const int flags = _O_RDONLY | (strchr(mode, 'b') ? 0 : _O_TEXT); + int fd = w32_osfdup(GetStdHandle(STD_INPUT_HANDLE), flags); + if (fd != -1) { + return fdopen(fd, mode); + } + + } else if (0 == w32_iostricmpA(path, "/dev/stdout")) { + const int flags = _O_APPEND | _O_WRONLY | (strchr(mode, 'b') ? 0 : _O_TEXT); + int fd = w32_osfdup(GetStdHandle(STD_OUTPUT_HANDLE), flags); + if (fd != -1) { + return fdopen(fd, mode); + } + + } else if (0 == w32_iostricmpA(path, "/dev/stderr")) { + const int flags = _O_APPEND | _O_WRONLY | (strchr(mode, 'b') ? 0 : _O_TEXT); + int fd = w32_osfdup(GetStdHandle(STD_ERROR_HANDLE), flags); + if (fd != -1) { + return _fdopen(fd, mode); + } + } else if (w32_resolvelinkA(path, symbuf, _countof(symbuf), &ret) == NULL) { if (ret < 0) { if ((mode[0] != 'r') && // attempt creation @@ -212,6 +234,27 @@ w32_fopenW(const wchar_t *path, const wchar_t *mode) if (0 == w32_iostricmpW(path, "/dev/null")) { path = L"NUL"; // redirect + } else if (0 == w32_iostricmpW(path, "/dev/stdin")) { + const int flags = _O_RDONLY | (wcschr(mode, 'b') ? 0 : _O_TEXT); + int fd = w32_osfdup(GetStdHandle(STD_INPUT_HANDLE), flags); + if (fd != -1) { + return _wfdopen(fd, mode); + } + + } else if (0 == w32_iostricmpW(path, "/dev/stdout")) { + const int flags = _O_APPEND | _O_WRONLY | (wcschr(mode, 'b') ? 0 : _O_TEXT); + int fd = w32_osfdup(GetStdHandle(STD_OUTPUT_HANDLE), flags); + if (fd != -1) { + return _wfdopen(fd, mode); + } + + } else if (0 == w32_iostricmpW(path, "/dev/stderr")) { + const int flags = _O_APPEND | _O_WRONLY | (wcschr(mode, 'b') ? 0 : _O_TEXT); + int fd = w32_osfdup(GetStdHandle(STD_ERROR_HANDLE), flags); + if (fd != -1) { + return _wfdopen(fd, mode); + } + } else if (w32_resolvelinkW(path, symbuf, _countof(symbuf), &ret) == NULL) { if (ret < 0) { if ((mode[0] != 'r') && // attempt creation @@ -226,7 +269,7 @@ w32_fopenW(const wchar_t *path, const wchar_t *mode) path = symbuf; // follow link } - if ((file = W32OpenStreamW(path, mode)) == NULL) { + if (ret < 0 || (file = W32OpenStreamW(path, mode)) == NULL) { if (ENOTDIR == errno || ENOENT == errno) { if (path != symbuf && // component error, expand embedded shortcut w32_expandlinkW(path, symbuf, _countof(symbuf), SHORTCUT_COMPONENT)) { diff --git a/mcwin32/libw32/w32_io.c b/mcwin32/libw32/w32_io.c index 2250b993..e6611638 100644 --- a/mcwin32/libw32/w32_io.c +++ b/mcwin32/libw32/w32_io.c @@ -1,5 +1,5 @@ #include -__CIDENT_RCSID(gr_w32_io_c, "$Id: w32_io.c,v 1.41 2025/04/30 19:29:57 cvsuser Exp $") +__CIDENT_RCSID(gr_w32_io_c, "$Id: w32_io.c,v 1.42 2025/05/23 11:21:14 cvsuser Exp $") /* -*- mode: c; indent-width: 4; -*- */ /* @@ -333,6 +333,22 @@ w32_osfhandle(int fildes) } +int +w32_osfdup(HANDLE osfhandle, int flags) +{ + if (osfhandle && osfhandle != INVALID_HANDLE_VALUE) { + const HANDLE self = GetCurrentProcess(); + HANDLE dup = INVALID_HANDLE_VALUE; + + if (DuplicateHandle(self, osfhandle, self, &dup, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + return _open_osfhandle ((intptr_t) dup, flags); + } + } + errno = EINVAL; + return -1; +} + + /* // NAME // stat - get file status @@ -2676,7 +2692,7 @@ ReadlinkW(const wchar_t *path, const char **suffixes, wchar_t *buf, size_t maxle /* reparse point - symlink/mount-point */ } else if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) { if ((ret = w32_reparse_readW(path, buf, maxlen)) >= 0) { - ret = (int) wcslen(buf); + ret = (int)wcslen(buf); } else { ret = -EIO; } diff --git a/mcwin32/libw32/w32_open.c b/mcwin32/libw32/w32_open.c index f833734f..2ef23d04 100644 --- a/mcwin32/libw32/w32_open.c +++ b/mcwin32/libw32/w32_open.c @@ -1,5 +1,5 @@ #include -__CIDENT_RCSID(gr_w32_open_c, "$Id: w32_open.c,v 1.3 2025/04/09 08:54:10 cvsuser Exp $") +__CIDENT_RCSID(gr_w32_open_c, "$Id: w32_open.c,v 1.5 2025/05/23 12:01:53 cvsuser Exp $") /* -*- mode: c; indent-width: 4; -*- */ /* @@ -49,6 +49,7 @@ __CIDENT_RCSID(gr_w32_open_c, "$Id: w32_open.c,v 1.3 2025/04/09 08:54:10 cvsuser #endif #include "win32_internal.h" +#include "win32_misc.h" #include "win32_io.h" static int W32OpenA(const char *path, int oflag, int mode); @@ -364,6 +365,15 @@ w32_openA(const char* path, int oflag, int mode) if (0 == w32_iostricmpA(path, "/dev/null")) { path = "NUL"; // redirect + } else if (0 == w32_iostricmpA(path, "/dev/stdin")) { + return w32_osfdup(GetStdHandle(STD_INPUT_HANDLE), oflag); + + } else if (0 == w32_iostricmpA(path, "/dev/stdout")) { + return w32_osfdup(GetStdHandle(STD_OUTPUT_HANDLE), oflag); + + } else if (0 == w32_iostricmpA(path, "/dev/stderr")) { + return w32_osfdup(GetStdHandle(STD_ERROR_HANDLE), oflag); + } else if (w32_resolvelinkA(path, symbuf, _countof(symbuf), &ret) == NULL) { /* * If O_CREAT create the file if it does not exist. @@ -433,7 +443,16 @@ w32_openW(const wchar_t *path, int oflag, int mode) // specials if (0 == w32_iostricmpW(path, "/dev/null")) { - path = L"NUL"; // redirect + path = L"NUL"; // nul device + + } else if (0 == w32_iostricmpW(path, "/dev/stdin")) { + return w32_osfdup(GetStdHandle(STD_INPUT_HANDLE), oflag); + + } else if (0 == w32_iostricmpW(path, "/dev/stdout")) { + return w32_osfdup(GetStdHandle(STD_OUTPUT_HANDLE), oflag); + + } else if (0 == w32_iostricmpW(path, "/dev/stderr")) { + return w32_osfdup(GetStdHandle(STD_ERROR_HANDLE), oflag); } else if (w32_resolvelinkW(path, symbuf, _countof(symbuf), &ret) == NULL) { /* diff --git a/mcwin32/libw32/w32_shell.c b/mcwin32/libw32/w32_shell.c index 521cc754..51cf434b 100644 --- a/mcwin32/libw32/w32_shell.c +++ b/mcwin32/libw32/w32_shell.c @@ -1,5 +1,5 @@ #include -__CIDENT_RCSID(gr_w32_shell_c,"$Id: w32_shell.c,v 1.27 2025/05/14 13:42:32 cvsuser Exp $") +__CIDENT_RCSID(gr_w32_shell_c,"$Id: w32_shell.c,v 1.28 2025/05/23 11:21:14 cvsuser Exp $") /* -*- mode: c; indent-width: 4; -*- */ /* @@ -70,6 +70,10 @@ static int ShellA(const char *shell, const char *cmd, const char *f static int ShellW(const wchar_t *shell, const wchar_t *cmd, const wchar_t *fstdin, const wchar_t *fstdout, const wchar_t *fstderr); static const char * OutDirectA(const char *path, int *append); static const wchar_t * OutDirectW(const wchar_t *path, int *append); +static HANDLE OpenInputA(const char* path, SECURITY_ATTRIBUTES *sa); +static HANDLE OpenInputW(const wchar_t* path, SECURITY_ATTRIBUTES *sa); +static HANDLE OpenOutputA(const char *path, SECURITY_ATTRIBUTES *sa, int append); +static HANDLE OpenOutputW(const wchar_t *path, SECURITY_ATTRIBUTES *sa, int append); static void ShellCleanup(void *p); static int IsAbsPathA(const char *path); @@ -167,7 +171,7 @@ ShellA(const char *shell, const char *cmd, const int interactive = ((NULL == cmd || !*cmd) ? 1 : 0); char *slash, *shname = WIN32_STRDUP(shell && *shell ? shell : w32_getshell()); - int xstdout = FALSE, xstderr = FALSE; // mode (TRUE == append) + int outappend = FALSE, errappend = FALSE; // mode (TRUE == append) SECURITY_ATTRIBUTES sa; HANDLE hInFile, hOutFile, hErrFile; struct procdata pd = {0}; @@ -181,47 +185,25 @@ ShellA(const char *shell, const char *cmd, sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; // inherited - fstdout = OutDirectA(fstdout, &xstdout); - fstderr = OutDirectA(fstderr, &xstderr); + fstdout = OutDirectA(fstdout, &outappend); + fstderr = OutDirectA(fstderr, &errappend); // redirection hInFile = hOutFile = hErrFile = INVALID_HANDLE_VALUE; if (fstdin) { // O_RDONLY - hInFile = CreateFileA(fstdin, GENERIC_READ, - 0, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + hInFile = OpenInputA(fstdin, &sa); } - if (fstdout) { - if (! xstdout) { // O_RDWR|O_CREAT|O_TRUNC - hOutFile = CreateFileA(fstdout, GENERIC_READ | GENERIC_WRITE, - 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - } else { // O_RDWR|O_CREAT|O_APPEND - hOutFile = CreateFileA(fstdout, GENERIC_READ | GENERIC_WRITE | FILE_APPEND_DATA, - 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } + if (fstdout) { // >[>] path + hOutFile = OpenOutputA(fstdout, &sa, outappend); } - if (fstderr) { - if (! xstderr) { // O_RDWR|O_CREAT|O_TRUNC - hErrFile = CreateFileA(fstderr, GENERIC_READ | GENERIC_WRITE, - 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - } else { // O_RDWR|O_CREAT|O_APPEND - hErrFile = CreateFileA(fstderr, GENERIC_READ | GENERIC_WRITE | FILE_APPEND_DATA, - 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } - - } else if (fstdout) { - if (! xstdout) { // O_RDWR|O_CREAT|O_TRUNC - hErrFile = CreateFileA(fstdout, GENERIC_READ | GENERIC_WRITE, - 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (fstderr) { // 2>[>] path + hErrFile = OpenOutputA(fstderr, &sa, errappend); - } else { // O_RDWR|O_CREAT|O_APPEND - hErrFile = CreateFileA(fstdout, GENERIC_READ | GENERIC_WRITE | FILE_APPEND_DATA, - 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } + } else if (fstdout) { // 2>&1 + hErrFile = OpenOutputA(fstdout, &sa, outappend); } // stdin @@ -294,6 +276,7 @@ ShellA(const char *shell, const char *cmd, if (pwshargs[pwsh - 1][interactive]) { argv[idx++] = pwshargs[pwsh - 1][interactive]; } + argv[idx++] = cmd; argv[idx] = NULL; @@ -344,7 +327,7 @@ ShellW(const wchar_t *shell, const wchar_t *cmd, const int interactive = ((NULL == cmd || !*cmd) ? 1 : 0); wchar_t *slash, *shname = WIN32_STRDUPW(shell && *shell ? shell : w32_getshellW()); - int xstdout = FALSE, xstderr = FALSE; // mode (TRUE == append) + int outappend = FALSE, errappend = FALSE; // mode (TRUE == append) SECURITY_ATTRIBUTES sa; HANDLE hInFile, hOutFile, hErrFile; struct procdata pd = {0}; @@ -358,47 +341,25 @@ ShellW(const wchar_t *shell, const wchar_t *cmd, sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; // inherited - fstdout = OutDirectW(fstdout, &xstdout); - fstderr = OutDirectW(fstderr, &xstderr); + fstdout = OutDirectW(fstdout, &outappend); + fstderr = OutDirectW(fstderr, &errappend); // redirection hInFile = hOutFile = hErrFile = INVALID_HANDLE_VALUE; if (fstdin) { // O_RDONLY - hInFile = CreateFileW(fstdin, GENERIC_READ, - 0, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + hInFile = OpenInputW(fstdin, &sa); } - if (fstdout) { - if (! xstdout) { // O_RDWR|O_CREAT|O_TRUNC - hOutFile = CreateFileW(fstdout, GENERIC_READ | GENERIC_WRITE, - 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - } else { // O_RDWR|O_CREAT|O_APPEND - hOutFile = CreateFileW(fstdout, GENERIC_READ | GENERIC_WRITE | FILE_APPEND_DATA, - 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } + if (fstdout) { // >[>] path + hOutFile = OpenOutputW(fstdout, &sa, outappend); } - if (fstderr) { - if (! xstderr) { // O_RDWR|O_CREAT|O_TRUNC - hErrFile = CreateFileW(fstderr, GENERIC_READ | GENERIC_WRITE, - 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (fstderr) { // 2>[>] path + hErrFile = OpenOutputW(fstderr, &sa, errappend); - } else { // O_RDWR|O_CREAT|O_APPEND - hErrFile = CreateFileW(fstderr, GENERIC_READ | GENERIC_WRITE | FILE_APPEND_DATA, - 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } - - } else if (fstdout) { - if (! xstdout) { // O_RDWR|O_CREAT|O_TRUNC - hErrFile = CreateFileW(fstdout, GENERIC_READ | GENERIC_WRITE, - 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - } else { // O_RDWR|O_CREAT|O_APPEND - hErrFile = CreateFileW(fstdout, GENERIC_READ | GENERIC_WRITE | FILE_APPEND_DATA, - 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } + } else if (fstdout) { // 2>&1 + hErrFile = OpenOutputW(fstdout, &sa, outappend); } // stdin @@ -503,12 +464,20 @@ OutDirectA(const char *path, int *append) { *append = FALSE; if (path) { + while (*path == ' ') { + ++path; // leading white-space + } + if ('>' == *path) { // ">name" ++path; if ('>' == *path) { // ">>name" *append = TRUE; ++path; } + + while (*path == ' ') { + ++path; // embedded white-space + } } } return path; @@ -520,18 +489,128 @@ OutDirectW(const wchar_t *path, int *append) { *append = FALSE; if (path) { + while (*path == ' ') { + ++path; // leading white-space + } + if ('>' == *path) { // ">name" ++path; if ('>' == *path) { // ">>name" *append = TRUE; ++path; } + + while (*path == ' ') { + ++path; // embedded white-space + } } } return path; } +static HANDLE +OpenInputA(const char *path, SECURITY_ATTRIBUTES *sa) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + + if (path[0] == '/') { + if (0 == w32_iostricmpA(path, "/dev/stdin")) { + if (Dup(GetStdHandle(STD_INPUT_HANDLE), &handle, TRUE)) { + return handle; + } + } + } + + return CreateFileA(path, GENERIC_READ, 0, sa, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +} + + +static HANDLE +OpenInputW(const wchar_t *path, SECURITY_ATTRIBUTES *sa) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + + if (path[0] == '/') { + if (0 == w32_iostricmpW(path, "/dev/stdin")) { + if (Dup(GetStdHandle(STD_INPUT_HANDLE), &handle, TRUE)) { + return handle; + } + } + } + + return CreateFileW(path, GENERIC_READ, 0, sa, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +} + + +static HANDLE +OpenOutputA(const char *path, SECURITY_ATTRIBUTES *sa, int append) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + + if (path[0] == '/') { + if (0 == w32_iostricmpA(path, "/dev/null")) { + path = "NUL"; + + } else if (0 == w32_iostricmpA(path, "/dev/stdout")) { + if (Dup(GetStdHandle(STD_OUTPUT_HANDLE), &handle, TRUE)) { + return handle; + } + + } else if (0 == w32_iostricmpA(path, "/dev/stderr")) { + if (Dup(GetStdHandle(STD_ERROR_HANDLE), &handle, TRUE)) { + return handle; + } + } + } + + if (path) { + // append = O_RDWR|O_CREAT|O_APPEND, otherwise = O_RDWR|O_CREAT|O_TRUNC + const DWORD dwDesiredAccess = + GENERIC_READ | GENERIC_WRITE | (append ? FILE_APPEND_DATA : 0); + + handle = CreateFileA(path, dwDesiredAccess, 0, sa, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + return handle; +} + + +static HANDLE +OpenOutputW(const wchar_t *path, SECURITY_ATTRIBUTES *sa, int append) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + + if (path[0] == '/') { + if (0 == w32_iostricmpW(path, "/dev/null")) { + path = L"NUL"; + + } else if (0 == w32_iostricmpW(path, "/dev/stdout")) { + if (Dup(GetStdHandle(STD_OUTPUT_HANDLE), &handle, TRUE)) { + return handle; + } + + } else if (0 == w32_iostricmpW(path, "/dev/stderr")) { + if (Dup(GetStdHandle(STD_ERROR_HANDLE), &handle, TRUE)) { + return handle; + } + } + } + + if (path) { + // append = O_RDWR|O_CREAT|O_APPEND, otherwise = O_RDWR|O_CREAT|O_TRUNC + const DWORD dwDesiredAccess = + GENERIC_READ | GENERIC_WRITE | (append ? FILE_APPEND_DATA : 0); + + handle = CreateFileW(path, dwDesiredAccess, 0, sa, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + return handle; +} + + static void ShellCleanup(void *p) { diff --git a/mcwin32/libw32/w32_slang.c b/mcwin32/libw32/w32_slang.c index 8fa8ad4e..b1b96f99 100644 --- a/mcwin32/libw32/w32_slang.c +++ b/mcwin32/libw32/w32_slang.c @@ -244,7 +244,7 @@ SLtt_initialize(const char *term) void SLtt_save(void) { - vio_save(); + vio_save_lines(1); } diff --git a/mcwin32/libw32/w32_statfs.c b/mcwin32/libw32/w32_statfs.c index 428b3ffc..baf54b6e 100644 --- a/mcwin32/libw32/w32_statfs.c +++ b/mcwin32/libw32/w32_statfs.c @@ -1,5 +1,5 @@ #include -__CIDENT_RCSID(gr_w32_statfs_c,"$Id: w32_statfs.c,v 1.22 2025/04/01 16:15:15 cvsuser Exp $") +__CIDENT_RCSID(gr_w32_statfs_c,"$Id: w32_statfs.c,v 1.24 2025/05/18 14:46:08 cvsuser Exp $") /* -*- mode: c; indent-width: 4; -*- */ /* @@ -126,8 +126,8 @@ int statfsA(const char *path, struct statfs *sb) { char volName[MNAMELEN], fsName[MFSNAMELEN]; - DWORD SectorsPerCluster, BytesPerSector, FreeClusters, Clusters; DWORD MaximumComponentLength, FileSystemFlags; + BOOL ready = FALSE, query_free = FALSE; size_t mnamelen; EMODEINIT() @@ -138,24 +138,8 @@ statfsA(const char *path, struct statfs *sb) (void) memset(sb, 0, sizeof(*sb)); - sb->f_bsize = 1024; /* block size */ - EMODESUPPRESS() - if (GetDiskFreeSpaceA(path, &SectorsPerCluster, &BytesPerSector, &FreeClusters, &Clusters)) { - /* KBytes available */ - sb->f_bavail = (unsigned int) - (((__int64)SectorsPerCluster * BytesPerSector * FreeClusters) / 1024); - - /* KBytes total */ - sb->f_blocks = (unsigned int) - (((__int64)SectorsPerCluster * BytesPerSector * Clusters) / 1024); - - /* inodes */ - sb->f_ffree = FreeClusters/10; - sb->f_files = Clusters/10; - } - strncpy(sb->f_mntonname, path, MNAMELEN-1); /* mount point */ w32_dos2unixA(sb->f_mntonname); if ((mnamelen = strlen(sb->f_mntonname)) > 3) { @@ -165,35 +149,66 @@ statfsA(const char *path, struct statfs *sb) } } + sb->f_type = MOUNT_PC; /* TODO */ + + strncpy(sb->f_fstypename, "unknown", MFSNAMELEN); + if (GetVolumeInformationA(path, + volName, MNAMELEN, /* VolumeName and size */ + NULL, &MaximumComponentLength, &FileSystemFlags, fsName, MFSNAMELEN)) /* filesystem type */ + { /* FileSystem type/NTFS, FAT etc */ + if (fsName[0]) { + strncpy(sb->f_fstypename, fsName, MFSNAMELEN); + } + ready = TRUE; + } + switch (GetDriveTypeA(path)) { /* device */ case DRIVE_REMOVABLE: strncpy(sb->f_mntfromname, "Removable", MNAMELEN); + query_free = ready; break; case DRIVE_FIXED: strncpy(sb->f_mntfromname, "Hard Disk", MNAMELEN); + query_free = TRUE; break; case DRIVE_REMOTE: strncpy(sb->f_mntfromname, "Networked", MNAMELEN); + if (0 == strcmp(sb->f_fstypename, "9P")) + query_free = ready; /* WSL2 */ break; case DRIVE_CDROM: strncpy(sb->f_mntfromname, "CD-ROM", MNAMELEN); + query_free = ready; break; case DRIVE_RAMDISK: strncpy(sb->f_mntfromname, "RAM disk", MNAMELEN); + query_free = TRUE; break; + case DRIVE_UNKNOWN: + case DRIVE_NO_ROOT_DIR: default: strncpy(sb->f_mntfromname, "Unknown", MNAMELEN); break; } - sb->f_type = MOUNT_PC; - strncpy(sb->f_fstypename, "unknown", MFSNAMELEN); - if (GetVolumeInformationA(path, - volName, MNAMELEN, /* VolumeName and size */ - NULL, &MaximumComponentLength, &FileSystemFlags, fsName, MFSNAMELEN)) /* filesystem type */ - { /* FileSystem type/NTFS, FAT etc */ - if (fsName[0]) { - strncpy(sb->f_fstypename, fsName, MFSNAMELEN); + sb->f_bsize = 1024; /* block size */ + + if (query_free) + { + DWORD SectorsPerCluster, BytesPerSector, FreeClusters, Clusters; + + if (GetDiskFreeSpaceA(path, &SectorsPerCluster, &BytesPerSector, &FreeClusters, &Clusters)) { + /* KBytes available */ + sb->f_bavail = (unsigned int) + (((__int64)SectorsPerCluster * BytesPerSector * FreeClusters) / 1024); + + /* KBytes total */ + sb->f_blocks = (unsigned int) + (((__int64)SectorsPerCluster * BytesPerSector * Clusters) / 1024); + + /* inodes */ + sb->f_ffree = FreeClusters / 10; + sb->f_files = Clusters / 10; } } @@ -207,8 +222,8 @@ int statfsW(const wchar_t *path, struct statfs *sb) { wchar_t volName[MNAMELEN], fsName[MFSNAMELEN]; - DWORD SectorsPerCluster, BytesPerSector, FreeClusters, Clusters; DWORD MaximumComponentLength, FileSystemFlags; + BOOL ready = FALSE, query_free = FALSE; size_t mnamelen; EMODEINIT() @@ -219,24 +234,8 @@ statfsW(const wchar_t *path, struct statfs *sb) (void) memset(sb, 0, sizeof(*sb)); - sb->f_bsize = 1024; /* block size */ - EMODESUPPRESS() - if (GetDiskFreeSpaceW(path, &SectorsPerCluster, &BytesPerSector, &FreeClusters, &Clusters)) { - /* KBytes available */ - sb->f_bavail = (unsigned int) - (((__int64)SectorsPerCluster * BytesPerSector * FreeClusters) / 1024); - - /* KBytes total */ - sb->f_blocks = (unsigned int) - (((__int64)SectorsPerCluster * BytesPerSector * Clusters) / 1024); - - /* inodes */ - sb->f_ffree = FreeClusters/10; - sb->f_files = Clusters/10; - } - w32_wc2utf(path, sb->f_mntonname, sizeof(sb->f_mntonname)); w32_dos2unixA(sb->f_mntonname); if ((mnamelen = strlen(sb->f_mntonname)) > 3) { @@ -246,35 +245,65 @@ statfsW(const wchar_t *path, struct statfs *sb) } } + sb->f_type = MOUNT_PC; /* TODO */ + + strncpy(sb->f_fstypename, "unknown", MFSNAMELEN); + if (GetVolumeInformationW(path, + volName, MNAMELEN, /* VolumeName and size */ + NULL, &MaximumComponentLength, &FileSystemFlags, fsName, MFSNAMELEN)) /* file system type */ + { /* FileSystem type/NTFS, FAT etc */ + if (fsName[0]) { + w32_wc2utf(fsName, sb->f_fstypename, sizeof(sb->f_fstypename)); + } + ready = TRUE; + } + switch (GetDriveTypeW(path)) { /* device */ case DRIVE_REMOVABLE: strncpy(sb->f_mntfromname, "Removable", MNAMELEN); + query_free = ready; break; case DRIVE_FIXED: strncpy(sb->f_mntfromname, "Hard Disk", MNAMELEN); + query_free = ready; break; case DRIVE_REMOTE: strncpy(sb->f_mntfromname, "Networked", MNAMELEN); + if (0 == strcmp(sb->f_fstypename, "9P")) + query_free = ready; /* WSL2 */ break; case DRIVE_CDROM: strncpy(sb->f_mntfromname, "CD-ROM", MNAMELEN); + query_free = ready; break; case DRIVE_RAMDISK: strncpy(sb->f_mntfromname, "RAM disk", MNAMELEN); + query_free = ready; break; + case DRIVE_UNKNOWN: + case DRIVE_NO_ROOT_DIR: default: strncpy(sb->f_mntfromname, "Unknown", MNAMELEN); break; } - sb->f_type = MOUNT_PC; - strncpy(sb->f_fstypename, "unknown", MFSNAMELEN); - if (GetVolumeInformationW(path, - volName, MNAMELEN, /* VolumeName and size */ - NULL, &MaximumComponentLength, &FileSystemFlags, fsName, MFSNAMELEN)) /* filesystem type */ - { /* FileSystem type/NTFS, FAT etc */ - if (fsName[0]) { - w32_wc2utf(fsName, sb->f_fstypename, sizeof(sb->f_fstypename)); + sb->f_bsize = 1024; /* block size */ + + if (query_free) { + DWORD SectorsPerCluster = 0, BytesPerSector = 0, FreeClusters = 0, Clusters = 0; + + if (GetDiskFreeSpaceW(path, &SectorsPerCluster, &BytesPerSector, &FreeClusters, &Clusters)) { + /* KBytes available */ + sb->f_bavail = (unsigned int) + (((__int64)SectorsPerCluster * BytesPerSector * FreeClusters) / 1024); + + /* KBytes total */ + sb->f_blocks = (unsigned int) + (((__int64)SectorsPerCluster * BytesPerSector * Clusters) / 1024); + + /* inodes */ + sb->f_ffree = FreeClusters / 10; + sb->f_files = Clusters / 10; } } @@ -345,23 +374,36 @@ statvfs(const char *path, struct statvfs *vfs) // The memory allocated by getmntinfo() cannot be free'd by the application. */ -static unsigned drive_mask(char drive); -static struct statfs *enum_volumes(struct statfs *result, long resultsize, int *mnts, unsigned *drives); +struct StatBlock { + struct statfs *result; + size_t count; + size_t alloced; + uint32_t drives; +}; + +static void enum_volumes(struct StatBlock *sb); +static void enum_connections(struct StatBlock *sb); + +static int sfcreate(struct StatBlock *sb, size_t count); +static struct statfs *sfalloc(struct StatBlock *sb); +static uint32_t drive_mask(int drive); int getfsstat(struct statfs *buf, long bufsize, int mode) { - struct statfs *sb; int mnts = -1; // result. if (MNT_WAIT != mode && MNT_NOWAIT != mode) { errno = EINVAL; } else { - if (NULL != (sb = enum_volumes(buf, buf ? bufsize : 0, &mnts, NULL))) { - if (NULL == buf) { - free((void *)sb); // release temporary; only returning the count. - } + struct StatBlock sb = { NULL }; + + enum_volumes(&sb); + enum_connections(&sb); + if (sb.result) { + free((void *) sb.result); // release temporary; only returning the count. + mnts = (int)sb.count; } } return mnts; @@ -373,10 +415,10 @@ getmntinfo(struct statfs **psb, int flags) { static struct statfs *x_getmntinfo = NULL; // global instance - char szDrivesAvail[32 * 4], *p; - unsigned sbdrives = 0; - struct statfs *sb; - int ndrives, cnt = -1; + struct StatBlock sb = { NULL }; + wchar_t szDrivesAvail[32 * 4], *cursor; + size_t ndrives = 0; + int cnt = -1; if (! psb) { // invalid errno = EINVAL; @@ -390,92 +432,77 @@ getmntinfo(struct statfs **psb, int flags) *psb = NULL; if (x_getmntinfo) { // release previous result - free((void*) x_getmntinfo); + free((void *) x_getmntinfo); x_getmntinfo = NULL; } - (void) GetLogicalDriveStringsA(sizeof(szDrivesAvail), szDrivesAvail); - for (ndrives = 0, p = szDrivesAvail; *p; p += 4) { + (void) GetLogicalDriveStringsW(_countof(szDrivesAvail), szDrivesAvail); + for (cursor = szDrivesAvail; *cursor; cursor += 4) { ++ndrives; // A:\B:\C:\... } - if (ndrives > 0) { // by volumes - int t_cnt = -1; - - if (NULL != (sb = enum_volumes(NULL, ndrives, &t_cnt, &sbdrives))) { - cnt = t_cnt; - } + if (sfcreate(&sb, ndrives) == -1) { + return -1; } - if (ndrives > 0) { // by drives / network-mappings - if (NULL == sb && - NULL == (sb = (struct statfs *)calloc(ndrives, sizeof(struct statfs)))) { - cnt = -1; - - } else { - EMODEINIT() - EMODESUPPRESS() - - for (p = szDrivesAvail; *p && cnt < ndrives; p += 4) { - const unsigned mask = drive_mask(p[0]); + if (ndrives) { + enum_volumes(&sb); // by volumes + enum_connections(&sb); // by connections + } - if (mask == 0x01 || mask == 0x02) { - // if (DRIVE_REMOVABLE == GetDriveTypeA(p)) { - continue; // skip floppies/removable - // } - } + if (ndrives) { // by drives + for (cursor = szDrivesAvail; *cursor; cursor += 4) { + const uint32_t mask = drive_mask(cursor[0]); - if ((mask & sbdrives) == 0) { // not by volume - if (0 == statfs(p, sb + cnt)) { - sbdrives |= mask; - ++cnt; - } - } + if (mask == 0x01 || mask == 0x02) { + continue; // skip floppies/removable } - EMODERESTORE(); - - if (0 == cnt) { - free((void *)sb); - } else { - x_getmntinfo = sb; - *psb = sb; + if ((mask & sb.drives) == 0) { // not enumerated + struct statfs *sf; + + if (NULL != (sf = sfalloc(&sb))) { + /* + * if (0 == statfsW(cursor, sf)) { + * ++sb.count; + * continue; + * } + */ + + (void) memset(sf, 0, sizeof(*sf)); + sf->f_type = MOUNT_PC; + sf->f_mntonname[0] = (char) cursor[0]; + sf->f_mntonname[1] = ':'; + sf->f_mntonname[2] = '/'; + strncpy(sf->f_fstypename, "unknown", MFSNAMELEN); + strncpy(sf->f_mntfromname, "Networked", MNAMELEN); + sf->f_bsize = 1024; + ++sb.count; + } } } } - return cnt; -} - -static unsigned -drive_mask(char drive) -{ - if (drive >= 'A' && drive <= 'Z') { - return 1 << (drive - 'A'); - } else if (drive >= 'a' && drive <= 'z') { - return 1 << (drive - 'a'); + if ((cnt = (int)sb.count) != 0) { + x_getmntinfo = sb.result; + *psb = sb.result; + } else { + free((void *) sb.result); } - return 0; + + return cnt; } -static struct statfs * -enum_volumes(struct statfs *result, long resultsize, int *mnts, unsigned *drives) +static void +enum_volumes(struct StatBlock *sb) { - unsigned sballoc = (result ? resultsize / sizeof(struct statfs) : 0); - unsigned sbsize = 16, sbcnt = 0, sbdrives = 0; - struct statfs *sb = result; - WCHAR volume[WIN32_PATH_MAX] = {0}; HANDLE handle; BOOL ret; errno = 0; - if (result == NULL && (resultsize > (long)sbsize)) { - sbsize = resultsize; // allocation size - } - if (INVALID_HANDLE_VALUE != (handle = FindFirstVolumeW(volume, _countof(volume)))) { DWORD names_size = 1024 + 1; PWCHAR names = NULL; @@ -489,7 +516,6 @@ enum_volumes(struct statfs *result, long resultsize, int *mnts, unsigned *drives for (;;) { if (NULL == names && NULL == (names = (PWCHAR)calloc(names_size, sizeof(WCHAR)))) { - sballoc = -1; goto error; // allocation error. } @@ -510,29 +536,20 @@ enum_volumes(struct statfs *result, long resultsize, int *mnts, unsigned *drives names = NULL; } - if (names[0]) { // associated path(s) + if (names[0]) { // associated path(s); if mounted PWCHAR cursor, end; for (cursor = names, end = cursor + count; cursor < end && *cursor; ++cursor) { const unsigned len = (unsigned)wcslen(cursor); - struct statfs *csb; - - if (sbcnt >= sballoc) { - struct statfs *t_sb = - (NULL == result ? realloc(sb, (sballoc += sbsize) * sizeof(*sb)) : NULL); - if (NULL == t_sb) { - sballoc = -1; - goto error; // no-memory or overflow + struct statfs *sf; + + if (NULL != (sf = sfalloc(sb))) { + if (0 == statfsW(cursor, sf)) { + if (sf->f_mntonname[1] == ':') { + sb->drives |= drive_mask(sf->f_mntonname[0]); + } + ++sb->count; } - sb = t_sb; - } - - csb = sb + sbcnt; - if (0 == statfsW(cursor, csb)) { - if (csb->f_mntonname[1] == ':') { - sbdrives |= drive_mask(csb->f_mntonname[0]); - } - ++sbcnt; } cursor += len; } @@ -551,18 +568,112 @@ enum_volumes(struct statfs *result, long resultsize, int *mnts, unsigned *drives } } while (1); - error:; +error:; FindVolumeClose(handle); free((void*)names); } +} + + +static void +enum_connections(struct StatBlock *sb) +{ + DWORD cbBuffer = 16384; // buffer size + DWORD cEntries = (DWORD)-1; // enumerate all possible entries + LPNETRESOURCEW lpnrLocal = NULL; // pointer to enumerated structures + DWORD dwResultEnum, i; + HANDLE hEnum = NULL; + + // Enumerate all currently connected resources. + if (WNetOpenEnumW(RESOURCE_CONNECTED, RESOURCETYPE_DISK, 0, NULL, &hEnum) != NO_ERROR) { + return; + } + + if (NULL == (lpnrLocal = (LPNETRESOURCEW) GlobalAlloc(GPTR, cbBuffer))) { + (void) WNetCloseEnum(hEnum); + return; + } + + do { + ZeroMemory(lpnrLocal, cbBuffer); + dwResultEnum = WNetEnumResourceW(hEnum, &cEntries, lpnrLocal, &cbBuffer); + if (dwResultEnum == NO_ERROR) { + for (i = 0; i < cEntries; ++i) { + const LPNETRESOURCEW netResource = lpnrLocal + i; + + if (netResource->dwType == RESOURCETYPE_DISK && netResource->lpLocalName) { + const wchar_t disk = netResource->lpLocalName[0]; + + if (disk && netResource->lpLocalName[1] == ':') { + const uint32_t mask = drive_mask(disk); + + if ((mask & sb->drives) == 0) { + wchar_t drive[4] = { L"X:\\" }; + struct statfs *sf; + + drive[0] = disk; + if (NULL != (sf = sfalloc(sb))) { + if (0 == statfsW(drive, sf)) { + sb->drives |= mask; + ++sb->count; + } + } + } + } + } + } + + } else if (dwResultEnum != ERROR_NO_MORE_ITEMS) { + break; + } - if (sbcnt <= sballoc) { - if (mnts) *mnts = sbcnt; - if (drives) *drives = sbdrives; - return sb; + } while (dwResultEnum != ERROR_NO_MORE_ITEMS); + + GlobalFree((HGLOBAL)lpnrLocal); + (void) WNetCloseEnum(hEnum); +} + + +static int +sfcreate(struct StatBlock *sb, size_t count) +{ + (void) memset(sb, 0, sizeof(*sb)); + if (0 == count || + NULL != (sb->result = (struct statfs*) calloc(count, sizeof(struct statfs)))) { + sb->alloced = count; + return 0; } - if (NULL == result) free((void *)sb); - return NULL; + return -1; +} + + +static struct statfs * +sfalloc(struct StatBlock *sb) +{ + if (sb->count >= sb->alloced) { + struct statfs *result = + realloc(sb->result, (sb->alloced + 16) * sizeof(struct statfs)); + if (NULL == result) { + return NULL; // no-memory or overflow + } + sb->result = result; + sb->alloced += 16; + } + return (sb->result + sb->count); +} + + +static uint32_t +drive_mask(int drive) +{ + if (drive >= 'A' && drive <= 'Z') { + return 1U << (drive - 'A'); + + } else if (drive >= 'a' && drive <= 'z') { + return 1U << (drive - 'a'); + } + + return 0; } /*end*/ diff --git a/mcwin32/libw32/win32_misc.h b/mcwin32/libw32/win32_misc.h index 7afedeac..1d194324 100644 --- a/mcwin32/libw32/win32_misc.h +++ b/mcwin32/libw32/win32_misc.h @@ -1,7 +1,7 @@ #ifndef LIBW32_WIN32_MISC_H_INCLUDED #define LIBW32_WIN32_MISC_H_INCLUDED #include -__CIDENT_RCSID(gr_libw32_win32_misc_h,"$Id: win32_misc.h,v 1.20 2025/04/05 17:56:43 cvsuser Exp $") +__CIDENT_RCSID(gr_libw32_win32_misc_h,"$Id: win32_misc.h,v 1.21 2025/05/23 11:21:14 cvsuser Exp $") __CPRAGMA_ONCE /* -*- mode: c; indent-width: 4; -*- */ @@ -58,7 +58,9 @@ enum w32ostype { /* generalised machine types, ignoring server */ int w32_htof(HANDLE handle); HANDLE w32_ftoh(int pid); + HANDLE w32_osfhandle(int fildes); +int w32_osfdup(HANDLE osfhandle, int flags); LIBW32_API enum w32ostype w32_ostype(void); diff --git a/mcwin32/makelib.in b/mcwin32/makelib.in index d25612ea..d43208fc 100644 --- a/mcwin32/makelib.in +++ b/mcwin32/makelib.in @@ -1,5 +1,5 @@ #!/usr/bin/perl -# $Id: makelib.in,v 1.20 2025/04/08 10:28:51 cvsuser Exp $ +# $Id: makelib.in,v 1.21 2025/05/18 14:25:26 cvsuser Exp $ # -*- mode: perl; tabs: 8; indent-width: 4; -*- # makelib configuration # @@ -96,6 +96,7 @@ $PACKAGE_FILE = 'package.h'; 'libglib', 'src/kbtest', 'src/coninfo', + 'src/volinfo', 'diff', 'mandoc', 'apps', diff --git a/mcwin32/src/volinfo/.cvsignore b/mcwin32/src/volinfo/.cvsignore new file mode 100644 index 00000000..dcd29e7d --- /dev/null +++ b/mcwin32/src/volinfo/.cvsignore @@ -0,0 +1,3 @@ +Makefile +buildinfo.h + diff --git a/mcwin32/src/volinfo/.gitignore b/mcwin32/src/volinfo/.gitignore new file mode 100644 index 00000000..6af79096 --- /dev/null +++ b/mcwin32/src/volinfo/.gitignore @@ -0,0 +1,2 @@ +buildinfo.h + diff --git a/mcwin32/src/volinfo/Makefile.in b/mcwin32/src/volinfo/Makefile.in new file mode 100644 index 00000000..88ba8e6b --- /dev/null +++ b/mcwin32/src/volinfo/Makefile.in @@ -0,0 +1,207 @@ +# -*- mode: mak; indent-tabs-mode: t; tab-width: 8 -*- +# $Id: Makefile.in,v 1.2 2025/05/18 12:04:33 cvsuser Exp $ +# volinfo +# +# +# + +@SET_MAKE@ +ROOT= @abs_top_builddir@ +top_builddir= @top_builddir@ + +# Configuration + +PACKAGE= "mc" +PKG_NAME= "volinfo" +PKG_VERSION= 1.0.0 + +# File extensions + +E= +O= .o +A= .a +LP= lib + +CLEAN= *.bak *~ *.BAK *.swp *.tmp core *.core a.out +XCLEAN= + +# Compilers, programs + +CC= @CC@ +CXX= @CXX@ +AR= @AR@ +RANLIB= @RANLIB@ +RM= @RM@ +PERL= @PERL@ +LIBTOOL= @LIBTOOL@ +RC= @RC@ + +BUSYBOX= @BUSYBOX@ +ifeq ($(BUSYBOX),busybox) +BUSYBOX= $(shell which busybox 2>/dev/null) +endif + +BUILD_DATE= $(shell $(BUSYBOX) date +'%Y%m%d') +ifneq ($(APPVEYOR_BUILD_NUMBER),) +BUILD_NUMBER= $(APPVEYOR_BUILD_NUMBER) +else ifneq ($(GITHUB_RUN_NUMBER),) +BUILD_NUMBER= $(GITHUB_RUN_NUMBER) +else +BUILD_NUMBER= $(shell $(BUSYBOX) cat ../../BUILDNUMBER) +endif + +# Configuration + +ifeq ("$(BUILD_TYPE)","") #default +BUILD_TYPE= debug +MAKEFLAGS+= BUILD_TYPE=debug +endif +ifneq ("$(BUILD_TYPE)","release") +RTSUFFIX=d +endif + +QUIETOUT:= >/dev/null 2>&1 +ifeq ($(VERBOSE),1) +QUIETOUT:= +else ifeq ($(VERBOSE),0) +else ifeq ($(VERBOSE),) +else +$(error VERBOSE: unexpected value 0 or 1 required, $(VERBOSE) given])) +endif + +# Directories + +D_INC= $(ROOT)/include +D_BIN= $(ROOT)/bin@TOOLCHAINEXT@/$(BUILD_TYPE) +D_LIB= $(ROOT)/lib@TOOLCHAINEXT@/$(BUILD_TYPE) +D_OBJ= $(ROOT)/obj@TOOLCHAINEXT@/$(BUILD_TYPE)/volinfo + +# Common flags + +XFLAGS= +CFLAGS= @CFLAGS@ +CWARN= @CWARN@ $(CWALL) +ifeq ("gcc","@CC@") +#CWARN+= -Wno-unused-variable -Wno-unused-parameter -Wno-unused-parameter -Wno-unused-function +endif +CDEBUG= @CDEBUG@ +CRELEASE= @CRELEASE@ +CXXFLAGS= @CXXFLAGS@ +ifeq ("gcc","@CC@") +CXXFLAGS+= -fpermissive +endif +CXXDEBUG= @CXXDEBUG@ +ifeq ("$(CXXDEBUG)","") +CXXDEBUG= $(CDEBUG) +endif +CXXRELEASE= @CXXRELEASE@ +ifeq ("$(CXXRELEASE)","") +CXXRELEASE= $(CRELEASE) +endif +LDDEBUG= @LDDEBUG@ +LDRELEASE= @LDRELEASE@ + +CINCLUDE= -I$(D_INC) @CINCLUDE@ +CEXTRA= @DEFS@ @LIBCURL_CPPFLAGS@ -DLIBTRE_DLL +ifdef PURIFY +CEXTRA+= -DUSING_PURIFY +endif +ifeq ("win32","@build_os@") +CEXTRA+= -DWIN32 +endif +CDEPFLAGS= $(CEXTRA) $(CINCLUDE) + +ifeq ("$(BUILD_TYPE)","release") +CFLAGS+= $(CRELEASE) $(CWARN) $(CINCLUDE) $(CEXTRA) $(XFLAGS) +CXXFLAGS+= $(CXXRELEASE) $(CXXWARN) $(CINCLUDE) @CXXINCLUDE@ $(CEXTRA) $(XFLAGS) +LDFLAGS= $(LDRELEASE) @LDFLAGS@ +else +CFLAGS+= $(CDEBUG) $(CWARN) $(CINCLUDE) $(CEXTRA) $(XFLAGS) +CXXFLAGS+= $(CXXDEBUG) $(CXXWARN) $(CINCLUDE) @CXXINCLUDE@ $(CEXTRA) $(XFLAGS) +LDFLAGS= $(LDDEBUG) @LDFLAGS@ +endif +LDLIBS= -L$(D_LIB) @LIBS@ @EXTRALIBS@ + +YFLAGS= -d +ARFLAGS= rcv +RMFLAGS= -f +RMDFLAGS= -rf + + +######################################################################################### +# Targets + +CONINFOTSK= $(D_BIN)/volinfo$(E) +CONINFOSRC= . + +VPATH= $(CONINFOSRC) $(EXPATSRC) + +CONINFOOBJS=\ + $(D_OBJ)/volinfo$(O) \ + +ifeq (mingw,$(findstring mingw,"@TOOLCHAIN@")) +CONINFOOBJS+= $(D_OBJ)/volinfo_res.o +else +CONINFOOBJS+= $(D_OBJ)/volinfo.res +endif + +OBJS= $(CONINFOOBJS) +LIBS= +DLLS= +TSKS= $(CONINFOTSK) +INSTALLED= + + +######################################################################################### +# Rules + +.PHONY: build release debug +build: buildinfo $(LIBS) $(DLLS) $(TSKS) + +release: + $(MAKE) BUILD_TYPE=release $(filter-out release, $(MAKECMDGOALS)) +debug: + $(MAKE) BUILD_TYPE=debug $(filter-out debug, $(MAKECMDGOALS)) + +$(CONINFOTSK): CEXTRA += -I$(CONINFOSRC) +$(CONINFOTSK): CEXTRA += -DXML_STATIC -DLIBW32_DYNAMIC -DCOMPILED_FROM_DSP +$(CONINFOTSK): MAPFILE=$(basename $@).map +$(CONINFOTSK): $(D_OBJ)/.created $(CONINFOOBJS) + $(LIBTOOL) --mode=link $(CXX) $(LDFLAGS) -o $@ $(CONINFOOBJS) $(LDLIBS) @LDMAPFILE@ + +.PHONY: buildinfo +buildinfo.h: buildinfo +buildinfo: Makefile ../../buildinfo.pl + @echo updating buildinfo.h ... + -@$(PERL) ../..//buildinfo.pl -o buildinfo.h --package="$(PACKAGE)" --name="$(PKG_NAME)" --version="$(PKG_VERSION)" \ + --date="$(BUILD_DATE)" --build="$(BUILD_NUMBER)" --toolchain="@TOOLCHAINEXT@" + +%/.created: + -@mkdir $(@D) + @echo "do not delete, managed directory" > $@ + +clean: + @echo $(BUILD_TYPE) clean + -@$(LIBTOOL) --mode=clean $(RM) $(OBJS) $(DLLS) $(DLLOBJS) $(QUIETOUT) + -@$(RM) $(RMFLAGS) $(BAK) $(TSKS) $(INSTALLED) $(LIBS) $(CLEAN) $(XCLEAN) $(QUIETOUT) + -@$(RM) $(LIBOBJS) $(QUIETOUT) + +$(D_OBJ)/%$(O): %.cpp + $(CC) $(CXXFLAGS) -o $@ -c $< + +$(D_OBJ)/%$(O): %.c + $(CC) $(CFLAGS) -o $@ -c $< + +$(D_OBJ)/%.lo: %.cpp + $(LIBTOOL) --mode=compile $(CC) $(CXXFLAGS) -o $@ -c $< + +$(D_OBJ)/%.lo: %.c + $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -o $@ -c $< + +$(D_OBJ)/%.res: %.rc + $(RC) -fo $@ $(RCDEFS) $< + +$(D_OBJ)/%_res.o: %.rc + $(RC) -o $@ $(RCDEFS) $< + +#end diff --git a/mcwin32/src/volinfo/README.md b/mcwin32/src/volinfo/README.md new file mode 100644 index 00000000..52383d44 --- /dev/null +++ b/mcwin32/src/volinfo/README.md @@ -0,0 +1,34 @@ + +# volinfo - volume information + +A test framework which simulates the existing drive mapping emulation logic, analyses access times, and reports the corresponding delays. + +Facilitates the diagnosis and testing of the mounted system file interface, illuminating any access issues/bottle necks. + +## Usage + +``` +volinfo [options] [attributes] + + Query disk information using on the following operations: + +Operations: + volumes - Iterate by volume enumeration. + network - Iterate by network enumeration. + drives - Iterate published drive letters. + all - All available methods. + +Attributes: + --volumeinfo Volume information. + --drivetype Drive type. + --attributes Attributes. + --freespace Free space. + --statfs statfs, all attributes. + +Options: + --time Access timestamps. + --verbose Additional info. + --netconnected Network status (default: connected). + or --netremembered. + --help +``` diff --git a/mcwin32/src/volinfo/volinfo.c b/mcwin32/src/volinfo/volinfo.c new file mode 100644 index 00000000..3feb8640 --- /dev/null +++ b/mcwin32/src/volinfo/volinfo.c @@ -0,0 +1,748 @@ +/* + * Volume information. + * + * Build using: "cl volinfo.c" + * + * Copyright (c) 2025, Adam Young. + * All rights reserved. + */ + +#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x601) +#undef _WIN32_WINNT +#undef _WIN32_VER +#define _WIN32_WINNT 0x601 +#define _WIN32_VER 0x601 +#endif + +#include +#include +#include +#include +#include + +#define WINDOWS_MEAN_AND_LEAN +#include + +#if !defined(__GNUC__) +#pragma comment(lib, "mpr.lib") +#endif + +#ifndef _countof +#define _countof(__type) (sizeof(__type)/sizeof(__type[0])) +#endif + +static void EnumDriveTypes(void); +static void EnumVolumes(void); +static void EnumNetwork(void); +static void DisplayVolume(const wchar_t *name); +static void StatDrive(const wchar_t *drive); +static void HumanReadable(const char *label, uint64_t size); + +static void Tally(ULONGLONG start, const char *label); +static void Profile(void); + +static const char *IsOption(const char *argv, const char *option); +static void Usage(void); + +static void OutputA(const char *, ...); +static void OutputW(const wchar_t *, ...); + +static int otimestamp = 0; +static int overbose = 0; +static int onetconnected = 1; + +static int ovolumeinfo = 0; +static int odrivetype = 0; +static int oattributes = 0; +static int ofreespace = 0; +static int ostatfs = 0; + + +int +main(int argc, char *argv[]) +{ + int arg; + + if (argc <= 1) { + Usage(); + return EXIT_FAILURE; + } + + for (arg = 1; arg < argc; ++arg) { + const char *option = argv[arg], *val; + + if (option[0] != '-') { + break; + } + + if ((val = IsOption(option, "--time")) != NULL) { + otimestamp = 1; + } else if ((val = IsOption(option, "--verbose")) != NULL) { + overbose = 1; + + } else if ((val = IsOption(option, "--netconnected")) != NULL) { + onetconnected = 1; + } else if ((val = IsOption(option, "--netremembered")) != NULL) { + onetconnected = 0; + + } else if ((val = IsOption(option, "--volumeinfo")) != NULL) { + ovolumeinfo = 1; + } else if ((val = IsOption(option, "--drivetype")) != NULL) { + odrivetype = 1; + } else if ((val = IsOption(option, "--freespace")) != NULL) { + ofreespace = 1; + } else if ((val = IsOption(option, "--attributes")) != NULL) { + oattributes = 1; + } else if ((val = IsOption(option, "--statfs")) != NULL) { + ostatfs = 1; + + } else { + if ((val = IsOption(option, "--help")) == NULL) { + fprintf(stderr, "volinfo: invalid option <%s>\n\n", option); + } + Usage(); + return EXIT_FAILURE; + } + } + + argv += arg; + argc -= arg; + if (argc == 0) { + fprintf(stderr, "volinfo: expected an operation\n"); + return EXIT_FAILURE; + + } else if (argc != 1) { + if (argc > 2) { + fprintf(stderr, "volinfo: unexpected options <%s ...>\n", argv[1]); + } else { + fprintf(stderr, "volinfo: unexpected option <%s>\n", argv[1]); + } + return EXIT_FAILURE; + + } else { + const char *op = argv[0]; + unsigned operations = 0; + + if (0 == strcmp(op, "volumes")) { + operations = 0x04; + } else if (0 == strcmp(op, "network")) { + operations = 0x02; + } else if (0 == strcmp(op, "drives")) { + operations = 0x01; + } else if (0 == strcmp(op, "all")) { + operations = 0xff; + } else { + fprintf(stderr, "volinfo: invalid operation <%s>\n", op); + return EXIT_FAILURE; + } + + if (operations & 0x04) { + EnumVolumes(); + } + + if (operations & 0x02) { + EnumNetwork(); + } + + if (operations & 0x01) { + EnumDriveTypes(); + } + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Enumerate all drives in the system. +// + +static void +EnumDriveTypes(void) +{ + wchar_t buffer[1024]; + + OutputA("\nEnumDriveTypes:\n\n"); + + buffer[0] = 0; + if (GetLogicalDriveStringsW(_countof(buffer), buffer)) { + const wchar_t *drive = NULL; + + for (drive = buffer; drive[0] != L'\0'; drive += wcslen(drive) + 1) { + StatDrive(drive); + } + OutputA("\n"); + + } else { + OutputA("GetLogicalDriveStrings(): failure %u\n", (unsigned)GetLastError()); + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Enumerate all volumes in the system. +// + +static void +EnumVolumes(void) +{ + HANDLE findHandle = INVALID_HANDLE_VALUE; + WCHAR deviceName[MAX_PATH] = L""; + WCHAR volumeName[MAX_PATH] = L""; + DWORD charCount = 0; + + OutputA("\nEnumVolumes:\n\n"); + + findHandle = FindFirstVolumeW(volumeName, _countof(volumeName)); + if (findHandle == INVALID_HANDLE_VALUE) { + const DWORD rc = GetLastError(); + OutputA("FindFirstVolume(): failure %u\n", (unsigned)rc); + return; + } + + for (;;) { + // Skip the \\?\ prefix and remove the trailing backslash. + const size_t Index = wcslen(volumeName) - 1; + + if (volumeName[0] != L'\\' || volumeName[1] != L'\\' || volumeName[2] != L'?' || volumeName[3] != L'\\' || volumeName[Index] != L'\\') { + OutputW(L"FindFirstVolume/FindNextVolume(): bad path: <%ls>\n", volumeName); + break; + } + + volumeName[Index] = L'\0'; // temporarily removal of slash; QueryDosDeviceW requirement + charCount = QueryDosDeviceW(&volumeName[4], deviceName, _countof(deviceName)); + volumeName[Index] = L'\\'; + + if (charCount == 0) { + const DWORD rc = GetLastError(); + OutputW(L"QueryDosDevice(): failure %u\n", (unsigned)rc); + break; + } + + OutputW(L" Device: %ls\n", deviceName); + if (overbose) + OutputW(L" Volume: %ls\n", volumeName); + OutputW(L" Paths:\n"); + + DisplayVolume(volumeName); + + // Next volume. + if (! FindNextVolumeW(findHandle, volumeName, _countof(volumeName))) { + const DWORD rc = GetLastError(); + if (rc != ERROR_NO_MORE_FILES) { + OutputW(L"FindNextVolume(): failure %u\n", (unsigned)rc); + break; + } + break; + } + OutputW(L"\n"); + } + + FindVolumeClose(findHandle); +} + + +static void +DisplayVolume(const wchar_t *VolumeName) +{ + DWORD charCount = MAX_PATH + 1; + PWCHAR names = NULL; + BOOL success = FALSE; + + for (;;) { + // Allocate a buffer to hold the paths. + names = (PWCHAR) malloc(charCount * sizeof(WCHAR)); + if (NULL == names) { + OutputW(L"Memory error!\n"); + return; + } + + // Obtain for this volume. + success = GetVolumePathNamesForVolumeNameW(VolumeName, names, charCount, &charCount); + if (success) { + break; + } + + if (GetLastError() != ERROR_MORE_DATA) { + break; + } + + // Retry using new suggested size. + free((void *) names); + names = NULL; + } + + if (success && names[0]) { + // Display the various paths. + const wchar_t *drive = NULL; + + for (drive = names; drive[0] != L'\0'; drive += wcslen(drive) + 1) { + StatDrive(drive); + } + } + free((void *)names); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Enumerate network connections. +// + +static void NetworkDisplayStruct(unsigned i, LPNETRESOURCEW lpnrLocal); + +static BOOL WINAPI +EnumNetworkFunc(LPNETRESOURCEW lpnr) +{ + const DWORD dwScope = (onetconnected ? RESOURCE_CONNECTED : RESOURCE_REMEMBERED); + // CONNECTED - Open connections. + // REMEMBERED - PERSISTED, aka are connected at log-on. + + DWORD cbBuffer = 16384; // buffer size + DWORD dwResult, dwResultEnum; + DWORD cEntries = (DWORD)-1; // enumerate all possible entries + LPNETRESOURCEW lpnrLocal = NULL; // pointer to enumerated structures + HANDLE hEnum = NULL; + + // Enumerate all currently connected resources. + dwResult = WNetOpenEnumW(dwScope, RESOURCETYPE_DISK, 0, lpnr, &hEnum); + if (dwResult != NO_ERROR) { + OutputA("WnetOpenEnum: failure %u\n", (unsigned)dwResult); + return FALSE; + } + + if (NULL == (lpnrLocal = (LPNETRESOURCEW) GlobalAlloc(GPTR, cbBuffer))) { + OutputA("EnumNetworkFunc: memory error\n"); + (void) WNetCloseEnum(hEnum); + return FALSE; + } + + do { + ZeroMemory(lpnrLocal, cbBuffer); + dwResultEnum = WNetEnumResourceW(hEnum, &cEntries, lpnrLocal, &cbBuffer); + if (dwResultEnum == NO_ERROR) { + DWORD i; + + for (i = 0; i < cEntries; ++i) { + LPNETRESOURCEW netResource = lpnrLocal + i; + + NetworkDisplayStruct(i, netResource); + if (RESOURCEUSAGE_CONTAINER == (netResource->dwUsage & RESOURCEUSAGE_CONTAINER)) { + // If the NETRESOURCE structure represents a container resource, + // call the EnumerateFunc function recursively. + EnumNetworkFunc(netResource); + } + } + + } else if (dwResultEnum != ERROR_NO_MORE_ITEMS) { + OutputA("WNetEnumResource: failure %u\n", (unsigned)dwResultEnum); + break; + } + + } while (dwResultEnum != ERROR_NO_MORE_ITEMS); + + GlobalFree((HGLOBAL)lpnrLocal); + + dwResult = WNetCloseEnum(hEnum); + if (dwResult != NO_ERROR) { + OutputA("WNetCloseEnum: failure %u\n", (unsigned)dwResult); + return FALSE; + } + return TRUE; +} + + +static void +NetworkDisplayStruct(unsigned i, LPNETRESOURCEW lpnrLocal) +{ + OutputA(" NETRESOURCE[%u]:\n", i); + + OutputA(" Scope: "); + switch (lpnrLocal->dwScope) { + case (RESOURCE_CONNECTED): + OutputA("connected\n"); + break; + case (RESOURCE_GLOBALNET): + OutputA("all resources\n"); + break; + case (RESOURCE_REMEMBERED): + OutputA("remembered\n"); + break; + default: + OutputA("unknown scope %u\n", (unsigned)(lpnrLocal->dwScope)); + break; + } + + OutputA(" Type: "); + switch (lpnrLocal->dwType) { + case (RESOURCETYPE_ANY): + OutputA("any\n"); + break; + case (RESOURCETYPE_DISK): + OutputA("disk\n"); + break; + case (RESOURCETYPE_PRINT): + OutputA("print\n"); + break; + default: + OutputA("unknown type %u\n", (unsigned)(lpnrLocal->dwType)); + break; + } + + OutputA(" DisplayType: "); + switch (lpnrLocal->dwDisplayType) { + case (RESOURCEDISPLAYTYPE_GENERIC): + OutputA("generic\n"); + break; + case (RESOURCEDISPLAYTYPE_DOMAIN): + OutputA("domain\n"); + break; + case (RESOURCEDISPLAYTYPE_SERVER): + OutputA("server\n"); + break; + case (RESOURCEDISPLAYTYPE_SHARE): + OutputA("share\n"); + break; + case (RESOURCEDISPLAYTYPE_FILE): + OutputA("file\n"); + break; + case (RESOURCEDISPLAYTYPE_GROUP): + OutputA("group\n"); + break; + case (RESOURCEDISPLAYTYPE_NETWORK): + OutputA("network\n"); + break; + default: + OutputA("unknown display type %u\n", (unsigned)(lpnrLocal->dwDisplayType)); + break; + } + + OutputA(" Usage: 0x%x = ", (unsigned)(lpnrLocal->dwUsage)); + if (lpnrLocal->dwUsage & RESOURCEUSAGE_CONNECTABLE) + OutputA("connectable "); + if (lpnrLocal->dwUsage & RESOURCEUSAGE_CONTAINER) + OutputA("container "); + if (lpnrLocal->dwUsage & RESOURCEUSAGE_NOLOCALDEVICE) + OutputA("nonlocaldevice "); + if (lpnrLocal->dwUsage & RESOURCEUSAGE_ATTACHED) + OutputA("attached "); + OutputA("\n"); + + OutputW(L" LocalName: %s\n", lpnrLocal->lpLocalName ? lpnrLocal->lpLocalName : L""); + OutputW(L" RemoteName: %s\n", lpnrLocal->lpRemoteName ? lpnrLocal->lpRemoteName : L""); + OutputW(L" Comment: %s\n", lpnrLocal->lpComment ? lpnrLocal->lpComment : L""); + OutputW(L" Provider: %s\n", lpnrLocal->lpProvider ? lpnrLocal->lpProvider : L""); + + if (lpnrLocal->dwType == RESOURCETYPE_DISK && lpnrLocal->lpLocalName) { + if (lpnrLocal->lpLocalName[0] && lpnrLocal->lpLocalName[1] == ':') { + wchar_t drive[4] = { L"X:\\" }; + + drive[0] = lpnrLocal->lpLocalName[0]; + OutputA("\n"); + StatDrive(drive); + } + } + + OutputA("\n"); +} + + +static void +EnumNetwork(void) +{ + OutputA("\nEnumNetworks:\n\n"); + EnumNetworkFunc((LPNETRESOURCEW) NULL); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Publish drive information. +// + +static void +StatDrive(const wchar_t *drive) +{ +#define MNAMELEN 90 /* length of buffer for returned name */ +#define MFSNAMELEN 16 /* length of fs type name, including null */ + + wchar_t volName[MNAMELEN] = { 0 }, fsName[MFSNAMELEN] = { 0 }; + DWORD maxLength = 0, fsFlags = 0; + BOOL ready = 0, freespace = 0; + + if (otimestamp) { + const ULONGLONG ms = GetTickCount64(); + const time_t now = time(NULL); + char buffer[26]; + struct tm *tm; + + tm = localtime(&now); + strftime(buffer, sizeof(buffer), "%H:%M:%S", tm); + OutputA(" %s.%03u", buffer, ms % 1000); + } + + Tally(0, NULL); + OutputW(L" %s\t", drive); + + if (ovolumeinfo || ostatfs) { + const ULONGLONG start = GetTickCount64(); + + if (GetVolumeInformationW(drive, + volName, MNAMELEN, /* VolumeName and size */ + NULL, &maxLength, &fsFlags, fsName, MFSNAMELEN)) { + ready = TRUE; + } + + Tally(start, "GetVolumeInformation"); + OutputW(L" <%s>%*s | fsflags:0x%08x, maxln:%4u", + fsName, _countof(fsName) - wcslen(fsName), "", fsFlags, maxLength); + } + + if (ostatfs && overbose) { + const ULONGLONG start = GetTickCount64(); + wchar_t device[MAX_PATH], *ret = device; + wchar_t disk[3] = { L"X:" }; + + disk[0] = drive[0]; + if (QueryDosDeviceW(disk, device, _countof(device))) { + if (0 == wcsncmp(device, L"\\Device\\", 8)) { + ret = device + 8; + } + } else { + ret = L"NA"; + } + + Tally(start, "QueryDosDevice"); + OutputW(L", [%-16.16s]", ret); + } + + if (odrivetype || ostatfs) { + const ULONGLONG start = GetTickCount64(); + const UINT type = GetDriveTypeW(drive); + + Tally(start, "GetDriveType"); + OutputW(L", type:"); + + switch (type) { + case DRIVE_FIXED: + OutputW(L"Fixed, "); + freespace = ready; + break; + case DRIVE_REMOVABLE: + OutputW(L"Removable, "); + freespace = ready; + break; + case DRIVE_CDROM: + OutputW(L"CDROM, "); + freespace = ready; + break; + case DRIVE_REMOTE: + OutputW(L"REMOTE, "); + if (0 == wcscmp(fsName, L"9P")) { + freespace = ready; /* WSL2 */ + } + break; + case DRIVE_RAMDISK: + OutputW(L"RamDisk, "); + freespace = ready; + break; + case DRIVE_UNKNOWN: + OutputW(L"Unknown, "); + break; + case DRIVE_NO_ROOT_DIR: + OutputW(L"Invalid, "); + break; + default: + OutputW(L"NA, "); + break; + } + } + + if (oattributes || ostatfs) { + const ULONGLONG start = GetTickCount64(); + const DWORD Attributes = GetFileAttributesW(drive); + + Tally(start, "GetFileAttributes"); + if (Attributes == INVALID_FILE_ATTRIBUTES) { + OutputW(L"flags:none "); + } else if (Attributes & FILE_ATTRIBUTE_READONLY) { + OutputW(L"flags:rdonly "); + } else { + OutputW(L"flags:rdwr "); + } + } + + if (ofreespace || (ostatfs && freespace)) { + DWORD SectorsPerCluster = 0, BytesPerSector = 0, FreeClusters = 0, Clusters = 0; + const ULONGLONG start = GetTickCount64(); + BOOL success = GetDiskFreeSpaceW(drive, &SectorsPerCluster, &BytesPerSector, &FreeClusters, &Clusters); + + Tally(start, "GetDiskFreeSpace"); + OutputW(L" | "); + + if (success) { + /* available */ + HumanReadable("avail=", + (((uint64_t)SectorsPerCluster * BytesPerSector * FreeClusters) /*/ 1024*/)); + + /* total */ + HumanReadable(", blocks=", + (((uint64_t)SectorsPerCluster * BytesPerSector * Clusters) /*/ 1024*/)); + + /* clusters */ + HumanReadable(", free=", (size_t)(FreeClusters /*/ 10*/)); + HumanReadable(", files=", (size_t)(Clusters /*/ 10*/)); + } else { + OutputW(L" free:n/a"); + } + } + + Profile(); + OutputW(L"\n"); +} + + +static void +HumanReadable(const char* label, uint64_t size) +{ + const char *units[] = { "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + unsigned u = 0; + + while (size >= (1024 * 1024)) { + size /= 1024; + ++u; + } + + OutputA("%s%8.3f %s", label, ((double)size / 1024), units[u]); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Execution profile + +static unsigned tm_count; + +static struct TimestampEvent { + const char *label; + ULONGLONG start, end; +} tm_events[16] = {0}; + + +static void +Tally(ULONGLONG start, const char *label) +{ + if (0 == start) { // reset + tm_count = 0; + return; + } + + if (tm_count < _countof(tm_events)) { // push profile event + struct TimestampEvent *evt = tm_events + tm_count++; + + evt->label = label; + evt->start = start; + evt->end = GetTickCount64(); + } +} + + +static void +Profile(void) +{ + if (tm_count) { + const unsigned total = (unsigned)(tm_events[tm_count - 1].end - tm_events[0].start); + unsigned tm, count = 0; + + OutputA(" | total:%ums", total); + for (tm = 0; tm != tm_count; ++tm) { + const struct TimestampEvent *evt = tm_events + tm; + const unsigned slice = (unsigned)(evt->end - evt->start); + + if (slice >= 5) { + OutputA("%s%s:%ums", (0 == count ? " [ " : ", "), evt->label, slice); + ++count; + } + } + if (count) OutputA(" ]"); + } + tm_count = 0; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Command line support + +static const char * +IsOption(const char *argv, const char *option) +{ + const size_t olen = strlen(option); + if (strncmp(argv, option, olen) == 0) { + return argv + olen; + } + return NULL; +} + + +static void +Usage(void) +{ + fprintf(stderr, + "\n" \ + "volinfo [options] [attributes] \n" \ + "\n" \ + " Query disk information using on the following operations:\n" \ + "\n" \ + "Operations:\n" \ + " volumes - Iterate by volume enumeration.\n" \ + " network - Iterate by network enumeration.\n" \ + " drives - Iterate published drive letters.\n" \ + " all - All available methods.\n" \ + "\n" \ + "Attributes:\n" \ + " --volumeinfo Volume information.\n" \ + " --drivetype Drive type.\n" \ + " --attributes Attributes.\n" \ + " --freespace Free space.\n" \ + " --statfs statfs, all attributes.\n" \ + "\n" \ + "Options:\n" \ + " --time Access timestamps.\n" \ + " --verbose Additional info.\n" \ + " --netconnected Network status (default: connected).\n" \ + " or --netremembered.\n" \ + " --help\n" \ + "\n"); +} + + +static void +OutputA(const char *fmt, ...) +{ + HANDLE cout = GetStdHandle(STD_OUTPUT_HANDLE); + char out[512]; + va_list ap; + int len; + + va_start(ap, fmt); + if ((len = vsnprintf(out, _countof(out), fmt, ap)) > (int)_countof(out)) { + len = _countof(out); + } + WriteConsoleA(cout, out, len, NULL, NULL); + va_end(ap); +} + + +static void +OutputW(const wchar_t *fmt, ...) +{ + HANDLE cout = GetStdHandle(STD_OUTPUT_HANDLE); + wchar_t out[512]; + va_list ap; + int len; + + va_start(ap, fmt); + if ((len = vswprintf(out, _countof(out), fmt, ap)) > (int)_countof(out)) { + len = _countof(out); + } + WriteConsoleW(cout, out, len, NULL, NULL); + va_end(ap); +} + +//end diff --git a/mcwin32/src/volinfo/volinfo.rc b/mcwin32/src/volinfo/volinfo.rc new file mode 100644 index 00000000..f6eee42c --- /dev/null +++ b/mcwin32/src/volinfo/volinfo.rc @@ -0,0 +1,121 @@ +/* + * windows resource file + * + * This file is part of the Midnight Commander. + * + * The Midnight Commander is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * The Midnight Commander is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WINDRES +#include "windows.h" +#include "winver.h" +#endif + +#include "buildinfo.h" + +#define RC_PRODUCTVERSION VERSION_1 , VERSION_2 , VERSION_3 , VERSION_4 +#define RC_FILEVERSION VERSION_1 , VERSION_2 , VERSION_3 , VERSION_4 + +/* + * English (U.S.) resources + */ + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) + +#ifdef _WIN32 +#ifndef WINDRES +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#endif +#pragma code_page(1252) +#endif /* _WIN32 */ + +/* + * Manifest + */ +#ifndef RT_MANIFEST +#define RT_MANIFEST 24 +#endif +#ifndef CREATEPROCESS_MANIFEST_RESOURCE_ID +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 +#endif + +/* + * Updater Meta Data + * nameID typeID { raw data } + */ + +UPDATER HostURL { "\0" } +UPDATER Channel { "release\0" } + + +/* + * Version Information + */ + +VS_VERSION_INFO VERSIONINFO +#if defined(RC_FILEVERSION) + FILEVERSION RC_FILEVERSION + PRODUCTVERSION RC_PRODUCTVERSION +#endif + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS (VS_FF_SPECIALBUILD|VS_FF_DEBUG) +#else + FILEFLAGS (VS_FF_SPECIALBUILD) +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE VFT2_UNKNOWN + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "" + + VALUE "FileDescription", "VolInfo" + + VALUE "FileVersion", VERSION ", Build:" BUILD_DATE "-" BUILD_NUMBER /* match mc.rc */ + + VALUE "InternalName", "Volume information tester" + + VALUE "Copyright", + "Copyright (C) 2024-" BUILD_YEAR ", Adam Young. All rights reserved. \n" + "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. \n" + + VALUE "Maintainers", "https://github.com/adamyg/mcwin32" + + VALUE "LegalTrademarks", "see MC License" + + VALUE "OriginalFilename", "coninfo.exe" + + VALUE "ProductName", "VolInfo" /* match mc.rc */ + END + END + + /* The following line should only be modified for localized versions. */ + /* It consists of any number of WORD,WORD pairs, with each pair */ + /* describing a language,codepage combination supported by the file. */ + /* */ + /* For example, a file might have values "0x409,1252" indicating that it */ + /* supports English language (0x409) in the Windows ANSI codepage (1252). */ + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END + END + +#endif /* English (U.S.) resources */ + diff --git a/mcwin32/src/win32_argv.c b/mcwin32/src/win32_argv.c index d29b0de1..4508dd28 100644 --- a/mcwin32/src/win32_argv.c +++ b/mcwin32/src/win32_argv.c @@ -31,12 +31,241 @@ #define _WIN32_WINNT 0x501 #endif +#include + #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include #endif #include +#include + +#if !defined(_countof) +#define _countof(a) (sizeof(a)/sizeof(a[0])) +#endif + +static const struct { + const char *dispname; + const char *cilocale; + DWORD lcid; +} locales[] = { + { "Neutral", "neutral", MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) }, // (use built-in word breaking) + { "Userdefault", "user-default", MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) }, + { "FakeBibi", "fake-bidi", MAKELANGID(LANG_HEBREW, SUBLANG_NEUTRAL) }, + + { "Afrikaans", "af", MAKELANGID(LANG_AFRIKAANS, SUBLANG_DEFAULT) }, + { "Amharic", "am", MAKELANGID(LANG_AMHARIC, SUBLANG_DEFAULT) }, + { "Arabic", "ar", MAKELANGID(LANG_ARABIC, SUBLANG_DEFAULT) }, + { "Basque (Basque)", "eu", MAKELANGID(LANG_BASQUE, SUBLANG_DEFAULT) }, + { "Belarusian", "be", MAKELANGID(LANG_BELARUSIAN, SUBLANG_DEFAULT) }, + { "Bengali", "bn", MAKELANGID(LANG_BENGALI, SUBLANG_DEFAULT) }, + { "Bulgarian", "bg", MAKELANGID(LANG_BULGARIAN, SUBLANG_DEFAULT) }, + { "Catalan", "ca", MAKELANGID(LANG_CATALAN, SUBLANG_DEFAULT) }, + { "Chinese (China)", "zh,zh-CN", MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED) }, + { "Chinese (Hong Kong)", "zh-HK", MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_HONGKONG) }, + { "Chinese (Macau)", "zh-MO", MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_MACAU) }, + { "Chinese (Singapore)", "zh-SG", MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SINGAPORE) }, + { "Chinese (Taiwan)", "zh-TW", MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL) }, + { "Croatian", "hr", MAKELANGID(LANG_CROATIAN, SUBLANG_DEFAULT) }, + { "Czech", "cs", MAKELANGID(LANG_CZECH, SUBLANG_DEFAULT) }, + { "Danish", "da", MAKELANGID(LANG_DANISH, SUBLANG_DEFAULT) }, + { "Dutch", "nl", MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH) }, + { "English", "en", MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT) }, + { "English (Australian)", "en-AU", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_AUS) }, + { "English (Belize)", "en-BZ", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_BELIZE) }, + { "English (Canadian)", "en-CA", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CAN) }, + { "English (Caribbean)", "en-CB", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CARIBBEAN) }, + { "English (Eire)", "en-IE", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE) }, + { "English (Great Britain)", "en-GB", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK) }, + { "English (India)", "en-IN", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_INDIA) }, + { "English (Jamaica)", "en-JM", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_JAMAICA) }, + { "English (Malaysia)", "en-MY", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_MALAYSIA) }, + { "English (New Zealand)", "en-NZ", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_NZ) }, + { "English (Philippines)", "en-PH", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_PHILIPPINES) }, + { "English (Singapore)", "en-SG", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SINGAPORE) }, + { "English (South Africa)", "en-ZA", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SOUTH_AFRICA) }, + { "English (Trinidad & Tobago)", "en-TT", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_TRINIDAD) }, + { "English (United States)", "en-US", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) }, + { "English (Zimbabwe)", "en-ZW", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_ZIMBABWE) }, + { "Estonian", "et", MAKELANGID(LANG_ESTONIAN, SUBLANG_DEFAULT) }, + { "Faeroese", "fo", MAKELANGID(LANG_FAEROESE, SUBLANG_DEFAULT) }, + { "Finnish", "fi", MAKELANGID(LANG_FINNISH, SUBLANG_DEFAULT) }, + { "French", "fr,fr-FR", MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH) }, + { "French (Belgium)", "fr-BE", MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_BELGIAN) }, + { "French (Canadian)", "fr-CA", MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_CANADIAN) }, + { "French (Luxembourg)", "fr-LU", MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_LUXEMBOURG) }, + { "French (Monco)", "fr-MC", MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_MONACO) }, + { "French (Switzerland)", "fr-CH", MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_SWISS) }, + { "Galician", "gl", MAKELANGID(LANG_GALICIAN, SUBLANG_DEFAULT) }, + { "German", "de", MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN) }, + { "Greek", "el", MAKELANGID(LANG_GREEK, SUBLANG_DEFAULT) }, + { "Gujarati", "gu", MAKELANGID(LANG_GUJARATI, SUBLANG_DEFAULT) }, + { "Hebrew", "he,iw", MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT) }, + { "Hindi", "hi", MAKELANGID(LANG_HINDI, SUBLANG_DEFAULT) }, + { "Hungarian", "hu", MAKELANGID(LANG_HUNGARIAN, SUBLANG_DEFAULT) }, + { "Icelandic", "is", MAKELANGID(LANG_ICELANDIC, SUBLANG_DEFAULT) }, + { "Indonesian", "id,in", MAKELANGID(LANG_INDONESIAN, SUBLANG_DEFAULT) }, + { "Italian", "it", MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN) }, + { "Japanese", "ja", MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT) }, + { "Kannada", "kn", MAKELANGID(LANG_KANNADA, SUBLANG_DEFAULT) }, + { "Korean", "ko", MAKELANGID(LANG_KOREAN, SUBLANG_DEFAULT) }, + { "Latvian", "lv", MAKELANGID(LANG_LATVIAN, SUBLANG_DEFAULT) }, + { "Lithuanian", "lt", MAKELANGID(LANG_LITHUANIAN, SUBLANG_DEFAULT) }, + { "Lithuanian", "lt", MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN) }, + { "Malay", "ms", MAKELANGID(LANG_MALAY, SUBLANG_DEFAULT) }, + { "Malayalam", "ml", MAKELANGID(LANG_MALAYALAM, SUBLANG_DEFAULT) }, + { "Marathi", "mr", MAKELANGID(LANG_MARATHI, SUBLANG_DEFAULT) }, + { "Nepali", "ne", MAKELANGID(LANG_NEPALI, SUBLANG_NEPALI_NEPAL) }, + { "Norwegian (Nynorsk)", "nn", MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK) }, + { "Norwegian", "nb", MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL) }, + { "Norwegian", "no", MAKELANGID(LANG_NORWEGIAN, SUBLANG_DEFAULT) }, + { "Oriya", "or", MAKELANGID(LANG_ORIYA, SUBLANG_DEFAULT) }, + { "Persian", "fa", MAKELANGID(LANG_PERSIAN, SUBLANG_DEFAULT) }, + { "Polish", "pl", MAKELANGID(LANG_POLISH, SUBLANG_DEFAULT) }, + { "Portuguese (Brazil)", "pt-br", MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE_BRAZILIAN) }, + { "Portuguese", "pt", MAKELANGID(LANG_PORTUGUESE, SUBLANG_DEFAULT) }, + { "Punjabi", "pa", MAKELANGID(LANG_PUNJABI, SUBLANG_PUNJABI_INDIA) }, + { "Romanian", "ro", MAKELANGID(LANG_ROMANIAN, SUBLANG_DEFAULT) }, + { "Russian", "ru", MAKELANGID(LANG_RUSSIAN, SUBLANG_DEFAULT) }, + { "Sanskrit", "sa", MAKELANGID(LANG_SANSKRIT, SUBLANG_SANSKRIT_INDIA) }, + { "Sami (Northern)", "se", MAKELANGID(LANG_SAMI, SUBLANG_DEFAULT) }, + { "Sami (Inari) (Finland)", "se-FI", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_INARI_FINLAND) }, + { "Sami (Lule) (Norway)", "se-NO", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_LULE_NORWAY) }, + { "Sami (Lule) (Sweden)", "se-SE", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_LULE_SWEDEN) }, + { "Sami (Northern) (Finland)", "se-FI", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_FINLAND) }, + { "Sami (Northern) (Norway)", "se-NO", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_NORWAY) }, + { "Sami (Northern) (Sweden)", "se-SE", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_NORTHERN_SWEDEN) }, + { "Sami (Skolt) (Finland)", "se-FI", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SKOLT_FINLAND) }, + { "Sami (Southern) (Norway)", "se-NO", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_LULE_SWEDEN) }, + { "Sami (Southern) (Sweden)", "se-SE", MAKELANGID(LANG_SAMI, SUBLANG_SAMI_SOUTHERN_SWEDEN) }, + { "Serbian", "sr", MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT) }, + { "Sinhalese", "si", MAKELANGID(LANG_SINHALESE, SUBLANG_SINHALESE_SRI_LANKA) }, + { "Slovak", "sk", MAKELANGID(LANG_SLOVAK, SUBLANG_DEFAULT) }, + { "Slovenian", "sl", MAKELANGID(LANG_SLOVENIAN, SUBLANG_DEFAULT) }, + { "Spanish", "es,es-es", MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH) }, + { "Swahili", "sw", MAKELANGID(LANG_SWAHILI, SUBLANG_DEFAULT) }, + { "Swedish", "sv", MAKELANGID(LANG_SWEDISH, SUBLANG_DEFAULT) }, + { "Tamil", "ta", MAKELANGID(LANG_TAMIL, SUBLANG_DEFAULT) }, + { "Telugn", "te", MAKELANGID(LANG_TELUGU, SUBLANG_DEFAULT) }, + { "Thai", "th", MAKELANGID(LANG_THAI, SUBLANG_DEFAULT) }, + // { "Tigrigna", "ti", MAKELANGID(LANG_TIGRIGNA, SUBLANG_TIGRIGNA_ERITREA) }, + { "Turkish", "tr", MAKELANGID(LANG_TURKISH, SUBLANG_DEFAULT) }, + { "Ukrainian", "uk", MAKELANGID(LANG_UKRAINIAN, SUBLANG_DEFAULT) }, + { "Urdu", "ur", MAKELANGID(LANG_URDU, SUBLANG_DEFAULT) }, + { "Vietnamese", "vi", MAKELANGID(LANG_VIETNAMESE, SUBLANG_DEFAULT) }, + { "Zulu", "zu", MAKELANGID(LANG_ZULU, SUBLANG_DEFAULT) }, +}; + + +static int ArgumentSplit (char* cmd, char** argv, int cnt); +char ** GetUTF8Arguments (int* pargc); +char * GetUTF8Argument0 (void); + + +/** + * WIN32_Arguments --- + * Command line specialisation. + * + * Parameters: + * argv - Argument vector address, updated on exit. + * + * Returns: + * Argument count. + */ +int +WIN32_Arguments(int argc, char ***argv) +{ + unsigned i, o; + int nargc = 0; + char **nargv; + + nargv = GetUTF8Arguments (&nargc); // import utf8 command line + assert(nargv && nargc == argc); + +#define OPT_LOCALE "--locale=" + + nargc = 0; + for (i = 0, o = 0; nargv[i]; ++i) { + const char *arg = nargv[i]; + + if (arg[0] == '-' && arg[1] == '-') { // options "--" + + if (0 == strncmp (arg, OPT_LOCALE, sizeof(OPT_LOCALE) - 1)) { + const char *val = arg + (sizeof(OPT_LOCALE) - 1); + LCID lcid = 0; + unsigned l; + + if (isdigit (*val)) { + char *endptr = NULL; + unsigned long ul; + + errno = 0; + ul = strtoul(val, &endptr, 0); + if (0 == errno && endptr && *endptr == 0) { + lcid = (LCID) ul; + } + + } else if (0 == strcmp(val, "help")) { + char delimiter [60]; + + memset (delimiter, '-', sizeof(delimiter)); + tty_oprintf ("\t%-20s%s\n\t%.*s\n", "LCID", "Locale Name", sizeof(delimiter), delimiter); + for (l = 0; l < _countof(locales); ++l) { + tty_oprintf ("\t%-20s%s\n", locales[l].cilocale, locales[l].dispname); + } + tty_oprintf ("\t%.*s\n\n", sizeof(delimiter), delimiter); + nargv[nargc++] = "--help"; // suppress unknown option + continue; + + } else { + for (l = 0; l < _countof(locales); ++l) { + const char *cilocale = locales[l].cilocale, *part; + + while (NULL != (part = strchr (cilocale, ','))) { + if (0 == _strnicmp (val, cilocale, part - cilocale)) { + break; // example, "fr,fr-FR" + } + cilocale = part + 1; + } + + if (part || 0 == _stricmp (val, cilocale)) { + lcid = locales[l].lcid; + break; + } + } + } + + if (lcid) { + SetThreadLocale (lcid); + SetThreadUILanguage ((LANGID)lcid); + continue; // consume + } + } + } + + nargv[nargc++] = (char *) arg; // export + } + + nargv[nargc] = NULL; // terminate + + *argv = nargv; + return nargc; +} + + +/** + * ArgumentSplit --- + * Split the command line, handling quoting and escapes. + * + * Parameters: + * cmd - Command line buffer; shall be modified. + * argv - Argument vector to be populated, otherwise NULL; only count is returned. + * cnt - Argument limit, -1 unlimited. + * + * Returns: + * Argument count. + */ static int ArgumentSplit(char *cmd, char **argv, int cnt) @@ -127,7 +356,7 @@ ArgumentSplit(char *cmd, char **argv, int cnt) /** - * UTF8Arguments --- + * GetUTF8Arguments --- * Generate a UTF8-8 encoding argument vector from the wide-char command-line. * * Parameters: @@ -161,17 +390,24 @@ GetUTF8Arguments(int *pargc) if (NULL != (cmd = calloc(cmdsz + 1 /*NUL*/, sizeof(char)))) { char **argv = NULL; + char *t_cmd; int argc; WideCharToMultiByte(CP_UTF8, 0, wcmdline, (int)wcmdsz, cmd, cmdsz + 1, NULL, NULL); - if ((argc = ArgumentSplit(cmd, NULL, -1)) > 0) { // argument count - if (NULL != (argv = calloc(argc + 1, sizeof(char *)))) { - ArgumentSplit(cmd, argv, argc + 1); // populate arguments - if (pargc) *pargc = argc; - return argv; + + if (NULL != (t_cmd = _strdup(cmd))) { // temporary working copy + if ((argc = ArgumentSplit(t_cmd, NULL, -1)) > 0) { // argument count + if (NULL != (argv = calloc(argc + 1, sizeof(char*)))) { + ArgumentSplit(cmd, argv, argc + 1); // populate arguments + if (pargc) *pargc = argc; + free(t_cmd); + return argv; + } } + free(t_cmd); } } + free(cmd); } diff --git a/mcwin32/src/win32_pipe.c b/mcwin32/src/win32_pipe.c index 262fb866..3ff36934 100644 --- a/mcwin32/src/win32_pipe.c +++ b/mcwin32/src/win32_pipe.c @@ -100,7 +100,7 @@ mc_popen2 (const char *command, int *fds, GError **error) static mc_pipe_t * -pipe_open (const char *xcommand, gboolean fdout, gboolean fderr, gboolean fdin, GError ** error) +pipe_open (const char *ocommand, gboolean fdout, gboolean fderr, gboolean fdin, GError ** error) { win32_exec_t *args = NULL; const char *busybox = mc_BUSYBOX(); @@ -110,16 +110,16 @@ pipe_open (const char *xcommand, gboolean fdout, gboolean fderr, gboolean fdin, if (error) *error = NULL; - if (xcommand) { + if (ocommand) { char *command; - while (' ' == *xcommand) ++xcommand; // consume leading whitespace (if any). + while (' ' == *ocommand) ++ocommand; // consume leading whitespace (if any). /* whitespace within "#! xxx" shall be visible; confusing matching logic below */ - win32Trace(("mc_popen: in:<%s>", xcommand ? xcommand : "")) - // my_unquote_test(); + win32Trace(("mc_popen: in:<%s>", ocommand ? ocommand : "")) + //my_unquote_test(); - if (NULL == (command = my_unquote(xcommand, TRUE))) { + if (NULL == (command = my_unquote(ocommand, TRUE))) { goto error; } @@ -163,7 +163,7 @@ pipe_open (const char *xcommand, gboolean fdout, gboolean fderr, gboolean fdin, } else if (space) { /* - * If busybox , execute as <\"busybox\" sh -c `cmd` ...> + * If busybox , execute as <\"busybox\" sh -c "cmd" ...> */ unsigned i, count = 0; const char **busybox_cmds = mc_busybox_exts(&count); @@ -172,8 +172,8 @@ pipe_open (const char *xcommand, gboolean fdout, gboolean fderr, gboolean fdin, for (i = 0; i < count; ++i) { if (0 == strncmp(busybox_cmds[i], command, commandlen)) { char *t_cmd = NULL; - - if (NULL == (t_cmd = g_strconcat("\"", busybox, "\" sh -c \"", command, "\"", NULL))) { + // note: original command, busybox behaves as expected + if (NULL == (t_cmd = g_strconcat("\"", busybox, "\" sh -c \"", ocommand, "\"", NULL))) { free((void *)command); x_errno = ENOMEM; goto error; @@ -226,11 +226,11 @@ pipe_open (const char *xcommand, gboolean fdout, gboolean fderr, gboolean fdin, } p->out.len = MC_PIPE_BUFSIZE; // read buffer length. - p->out.null_term = FALSE; // whether buf is null-terminated or not. + p->out.null_term = FALSE; // whether buffer is null-terminated or not. p->out.buf[0] = '\0'; p->err.len = MC_PIPE_BUFSIZE; // read buffer length. - p->err.null_term = FALSE; // whether buf is null-terminated or not. + p->err.null_term = FALSE; // whether buffer is null-terminated or not. p->err.buf[0] = '\0'; return p; diff --git a/mcwin32/src/win32_tty.c b/mcwin32/src/win32_tty.c index ad2a59eb..0bba638b 100644 --- a/mcwin32/src/win32_tty.c +++ b/mcwin32/src/win32_tty.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,21 @@ #include "win32_key.h" +#ifndef VA_COPY +# if defined(HAVE_VA_COPY) || defined(va_copy) + /* ISO C99 and later */ +#define VA_COPY(__dst, __src) va_copy(__dst, __src) +# elif defined(HAVE___VA_COPY) || defined(__va_copy) + /* gnu */ +#define VA_COPY(__dst, __src) __va_copy(__dst, __src) +# elif defined(__WATCOMC__) + /* Older Watcom implementations */ +#define VA_COPY(__dst, __src) memcpy((__dst), (__src), sizeof (va_list)) +# else +#define VA_COPY(__dst, __src) (__dst) = (__src) +# endif +#endif /*VA_COPY*/ + static int tty_mouse_enabled = 0; /* mouse mode; true/false */ static wchar_t original_title[512]; /* original title */ @@ -105,14 +121,14 @@ tty_init (gboolean mouse_enable, gboolean is_xterm) exit (EXIT_FAILURE); } - SLsmg_touch_screen(); + SLsmg_touch_screen (); } void tty_shutdown (void) { - SLsmg_reset_smg(); + SLsmg_reset_smg (); } @@ -167,6 +183,97 @@ tty_reset_shell_mode (void) } +int +tty_utf8_mode (int state) +{ + const DWORD cp = GetConsoleOutputCP (); + + if (state == 1) { // enable, note a valid code-page + if (cp != CP_UTF8) { + (void) SetConsoleOutputCP (CP_UTF8); + } + + } else if (state > 1) { // restore + if (cp != (DWORD) state) { + (void) SetConsoleOutputCP (state); + } + } + + return (int) cp; +} + + +static void +uputs (HANDLE handle, const char *buffer, size_t size) +{ + const DWORD cp = GetConsoleOutputCP (); + + if (cp != CP_UTF8) { + (void) SetConsoleOutputCP (CP_UTF8); + } + WriteConsoleA (handle, buffer, size, NULL, NULL); + if (cp != CP_UTF8) { + (void) SetConsoleOutputCP (cp); + } +} + + +static void +uprintf (HANDLE handle, const char *fmt, va_list ap) +{ + va_list tap; + int size; + + VA_COPY (tap, ap); + size = vsnprintf (NULL, 0, fmt, tap); + if (size > 0) { + char *buffer; + + if (NULL != (buffer = (char*)malloc (size + 32))) { + size = vsprintf (buffer, fmt, ap); + uputs (handle, buffer, size); + free ((void*)buffer); + } + } +} + + +void +tty_oprintf (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + uprintf (GetStdHandle (STD_OUTPUT_HANDLE), fmt, ap); + va_end (ap); +} + + +void +tty_oputs (const char *str) +{ + uputs (GetStdHandle (STD_OUTPUT_HANDLE), str, (int) strlen (str)); +} + + +void +tty_eprintf (const char* fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + uprintf (GetStdHandle (STD_ERROR_HANDLE), fmt, ap); + va_end (ap); +} + + +void +tty_eputs (const char* str) +{ + uputs (GetStdHandle (STD_ERROR_HANDLE), str, (int) strlen (str)); +} + + void tty_raw_mode (void) { diff --git a/mcwin32/src/win32_utl.c b/mcwin32/src/win32_utl.c index 9c382a47..a09a3034 100644 --- a/mcwin32/src/win32_utl.c +++ b/mcwin32/src/win32_utl.c @@ -1429,7 +1429,9 @@ system_impl (int flags, const char *shell, const char *cmd) (argc = system_bustargs (cbuf, argv, MAX_ARGV)) <= MAX_ARGV && argc > 0) { if (0 == strcmp (argv[0], "set")) { - return system_SET (argc, argv); + if (0 == system_SET (argc, argv)) { + return 0; + } } else if (use_internal_busybox) { const size_t cmdlen = strlen (argv[0]); @@ -1533,6 +1535,10 @@ system_SET(int argc, const char **argv) // set or lookup const wchar_t *wname; + if (strpbrk (argv[1], "<>|") != NULL) { + return 1; // redirection or pipe, via shell + } + if (NULL != (wname = w32_utf2wca (argv[1], NULL))) { wchar_t *wvalue; @@ -1555,7 +1561,8 @@ system_SET(int argc, const char **argv) free ((void *)wname); } } - return 0; + + return 0; // complete } diff --git a/mcwin32/support/config_windows.pl b/mcwin32/support/config_windows.pl index ab7adbac..2b2dae90 100644 --- a/mcwin32/support/config_windows.pl +++ b/mcwin32/support/config_windows.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -*- mode: perl; -*- -# $Id: config_windows.pl,v 1.5 2025/04/09 11:53:01 cvsuser Exp $ +# $Id: config_windows.pl,v 1.6 2025/05/22 03:45:58 cvsuser Exp $ # Configure front-end for native windows targets. # @@ -109,6 +109,31 @@ return $default; } + +sub +SugguestCoreUtils # (app, path) +{ + my ($app, $path) = shift; + + $path = "" if (! $path); + + print "config_windows: CoreUtils=${app} missing\n\n"; + print "Possible solution using msys64:\n"; + + if ($app eq 'msgfmt') { + printf " ${path}pacman -S mingw-w64-i686-gettext-tools\n"; + + } elsif ($app eq 'zip' || $app eq 'unzip') { + printf " ${path}pacman -S ${app}\n"; + + } else { # coreutils etc + printf " ${path}pacman -S base-devl\n"; + } + + printf "\n"; +} + + sub ResolveCoreUtils # () { @@ -120,7 +145,7 @@ "c:/GnuWin32", # https://sourceforge.net/projects/getgnuwin32/files (legacy) "c:/Program Files (x86)/GnuWin32", # choco install gnuwin32-coreutils.install (legacy) ); - my @cmds = ("mkdir", "rmdir", "cp", "mv", "rm", "grep", "gzip", "tar", "unzip", "zip"); + my @cmds = ("mkdir", "rmdir", "cp", "mv", "rm", "grep", "gzip", "tar", "zip", "unzip", "msgfmt"); foreach my $path (@paths) { if (! $path) { # PATH @@ -130,10 +155,12 @@ my $resolved = which($app); Trace(" $app=%s", $resolved ? lc $resolved : "(unresolved)"); if (! $resolved) { + SugguestCoreUtils($app) + if ($success > 1); $success = 0; last; } - ++$success; + $success++; } if ($success) { print "config_windows: CoreUtils=PATH\n"; @@ -141,18 +168,20 @@ } } else { # explicit; test possible solutions - my $bin = "${path}/bin"; + my $bin = "${path}/bin/"; my $success = (-d $bin); if ($success) { Trace("checking CoreUtils against <${bin}>"); foreach my $app (@cmds) { - my $resolved = (-f "${bin}/${app}" || "${bin}/${app}.exe"); - Trace(" $app=%s", $resolved ? "${bin}/${app}" : "(unresolved)"); + my $resolved = (-f "${bin}${app}" || "${bin}${app}.exe"); + Trace(" $app=%s", $resolved ? "${bin}${app}" : "(unresolved)"); if (! $resolved) { + SugguestCoreUtils($app, $bin) + if ($success > 1); $success = 0; last; } - ++$success; + $success++; } } if ($success) { @@ -243,7 +272,7 @@ if (! defined $perlpath) { my $running = lc realpath($^X); - my $resolved = dirname(${running}); + my $resolved = dirname(${running}); my $perl = which("perl"); $perl = lc realpath($perl) @@ -275,7 +304,8 @@ push @options, "--binpath=\"${coreutils}/bin\""; } } else { - die "config_windows: coreutils detected yet not in PATH, add <$coreutils/bin> before proceeding.\n"; + die "config_windows: coreutils detected yet not in PATH,\n". + " either add <$coreutils/bin> before proceeding or add missing components.\n"; return; } } @@ -288,7 +318,7 @@ push @options, "--flex=\"${flex}\""; push @options, "--bison=\"${bison}\""; push @options, "--inno=\"${inno}\"" - if ($inno); + if ($inno); print "\n$^X ${script}\n => @options ${otarget}\n\n"; system "$^X ${script} @options ${otarget}"; diff --git a/mcwin32/win32_plugin/extfs.d/uzip.in b/mcwin32/win32_plugin/extfs.d/uzip.in index a189670d..72e1547d 100644 --- a/mcwin32/win32_plugin/extfs.d/uzip.in +++ b/mcwin32/win32_plugin/extfs.d/uzip.in @@ -63,6 +63,12 @@ my $cmd_list = ($op_has_zipinfo ? $cmd_list_zi : $cmd_list_nzi); ## my ($qarchive, $aqarchive) = map (quotemeta, $archive, $aarchive); my ($qarchive, $aqarchive) = ($archive, $aarchive); +$qarchive = '"'.$qarchive.'"' # WIN32: spaces, enclose file name. + if ($qarchive =~ /\s/); + +$aqarchive = '"'.$aqarchive.'"' # WIN32: spaces, enclose file name. + if ($aqarchive =~ /\s/); + # Strip all "." and ".." path components from a pathname. sub zipfs_canonicalize_pathname($) { my ($fname) = @_;