summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:22:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:22:03 +0000
commitffccd5b2b05243e7976db80f90f453dccfae9886 (patch)
tree39a43152d27f7390d8f7a6fb276fa6887f87c6e8 /lib
parentInitial commit. (diff)
downloadmc-ffccd5b2b05243e7976db80f90f453dccfae9886.tar.xz
mc-ffccd5b2b05243e7976db80f90f453dccfae9886.zip
Adding upstream version 3:4.8.30.upstream/3%4.8.30
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/Makefile.am75
-rw-r--r--lib/Makefile.in1013
-rw-r--r--lib/charsets.c527
-rw-r--r--lib/charsets.h113
-rw-r--r--lib/event-types.h84
-rw-r--r--lib/event.h48
-rw-r--r--lib/event/Makefile.am10
-rw-r--r--lib/event/Makefile.in745
-rw-r--r--lib/event/event.c135
-rw-r--r--lib/event/internal.h32
-rw-r--r--lib/event/manage.c216
-rw-r--r--lib/event/raise.c75
-rw-r--r--lib/file-entry.h49
-rw-r--r--lib/filehighlight.h34
-rw-r--r--lib/filehighlight/Makefile.am9
-rw-r--r--lib/filehighlight/Makefile.in746
-rw-r--r--lib/filehighlight/common.c129
-rw-r--r--lib/filehighlight/get-color.c313
-rw-r--r--lib/filehighlight/ini-file-read.c268
-rw-r--r--lib/filehighlight/internal.h61
-rw-r--r--lib/fileloc.h95
-rw-r--r--lib/fs.h124
-rw-r--r--lib/glibcompat.c210
-rw-r--r--lib/glibcompat.h40
-rw-r--r--lib/global.c137
-rw-r--r--lib/global.h292
-rw-r--r--lib/hook.c133
-rw-r--r--lib/hook.h34
-rw-r--r--lib/keybind.c509
-rw-r--r--lib/keybind.h367
-rw-r--r--lib/lock.c314
-rw-r--r--lib/lock.h29
-rw-r--r--lib/logging.c168
-rw-r--r--lib/logging.h32
-rw-r--r--lib/mcconfig.h116
-rw-r--r--lib/mcconfig/Makefile.am11
-rw-r--r--lib/mcconfig/Makefile.in754
-rw-r--r--lib/mcconfig/common.c287
-rw-r--r--lib/mcconfig/get.c214
-rw-r--r--lib/mcconfig/history.c219
-rw-r--r--lib/mcconfig/paths.c314
-rw-r--r--lib/mcconfig/set.c159
-rw-r--r--lib/search.h196
-rw-r--r--lib/search/Makefile.am12
-rw-r--r--lib/search/Makefile.in759
-rw-r--r--lib/search/glob.c207
-rw-r--r--lib/search/hex.c235
-rw-r--r--lib/search/internal.h86
-rw-r--r--lib/search/lib.c233
-rw-r--r--lib/search/normal.c108
-rw-r--r--lib/search/regex.c1121
-rw-r--r--lib/search/search.c521
-rw-r--r--lib/serialize.c351
-rw-r--r--lib/serialize.h27
-rw-r--r--lib/shell.c264
-rw-r--r--lib/shell.h43
-rw-r--r--lib/skin.h145
-rw-r--r--lib/skin/Makefile.am12
-rw-r--r--lib/skin/Makefile.in760
-rw-r--r--lib/skin/colors-old.c203
-rw-r--r--lib/skin/colors.c422
-rw-r--r--lib/skin/common.c209
-rw-r--r--lib/skin/hc-skins.c146
-rw-r--r--lib/skin/ini-file.c210
-rw-r--r--lib/skin/internal.h40
-rw-r--r--lib/skin/lines.c100
-rw-r--r--lib/stat-size.h99
-rw-r--r--lib/strescape.h33
-rw-r--r--lib/strutil.h661
-rw-r--r--lib/strutil/Makefile.am14
-rw-r--r--lib/strutil/Makefile.in773
-rw-r--r--lib/strutil/filevercmp.c267
-rw-r--r--lib/strutil/replace.c104
-rw-r--r--lib/strutil/strescape.c266
-rw-r--r--lib/strutil/strutil.c1026
-rw-r--r--lib/strutil/strutil8bit.c862
-rw-r--r--lib/strutil/strutilascii.c785
-rw-r--r--lib/strutil/strutilutf8.c1521
-rw-r--r--lib/strutil/strverscmp.c158
-rw-r--r--lib/strutil/xstrtol.c268
-rw-r--r--lib/timefmt.c156
-rw-r--r--lib/timefmt.h59
-rw-r--r--lib/tty/Makefile.am36
-rw-r--r--lib/tty/Makefile.in801
-rw-r--r--lib/tty/color-internal.c244
-rw-r--r--lib/tty/color-internal.h61
-rw-r--r--lib/tty/color-ncurses.c251
-rw-r--r--lib/tty/color-slang.c260
-rw-r--r--lib/tty/color-slang.h56
-rw-r--r--lib/tty/color.c244
-rw-r--r--lib/tty/color.h54
-rw-r--r--lib/tty/key.c2252
-rw-r--r--lib/tty/key.h121
-rw-r--r--lib/tty/keyxdef.c455
-rw-r--r--lib/tty/mouse.c216
-rw-r--r--lib/tty/mouse.h117
-rw-r--r--lib/tty/tty-internal.c110
-rw-r--r--lib/tty/tty-internal.h49
-rw-r--r--lib/tty/tty-ncurses.c772
-rw-r--r--lib/tty/tty-ncurses.h50
-rw-r--r--lib/tty/tty-slang.c781
-rw-r--r--lib/tty/tty-slang.h48
-rw-r--r--lib/tty/tty.c416
-rw-r--r--lib/tty/tty.h146
-rw-r--r--lib/tty/win.c168
-rw-r--r--lib/tty/win.h24
-rw-r--r--lib/tty/x11conn.c266
-rw-r--r--lib/tty/x11conn.h40
-rw-r--r--lib/unixcompat.h63
-rw-r--r--lib/util.c1538
-rw-r--r--lib/util.h297
-rw-r--r--lib/utilunix.c1293
-rw-r--r--lib/utilunix.h25
-rw-r--r--lib/vfs/HACKING104
-rw-r--r--lib/vfs/Makefile.am19
-rw-r--r--lib/vfs/Makefile.in767
-rw-r--r--lib/vfs/README70
-rw-r--r--lib/vfs/direntry.c1740
-rw-r--r--lib/vfs/gc.c335
-rw-r--r--lib/vfs/gc.h27
-rw-r--r--lib/vfs/interface.c875
-rw-r--r--lib/vfs/netutil.c83
-rw-r--r--lib/vfs/netutil.h26
-rw-r--r--lib/vfs/parse_ls_vga.c886
-rw-r--r--lib/vfs/path.c1683
-rw-r--r--lib/vfs/path.h149
-rw-r--r--lib/vfs/utilvfs.c374
-rw-r--r--lib/vfs/utilvfs.h64
-rw-r--r--lib/vfs/vfs.c775
-rw-r--r--lib/vfs/vfs.h343
-rw-r--r--lib/vfs/xdirentry.h205
-rw-r--r--lib/widget.h53
-rw-r--r--lib/widget/Makefile.am30
-rw-r--r--lib/widget/Makefile.in843
-rw-r--r--lib/widget/background.c126
-rw-r--r--lib/widget/background.h36
-rw-r--r--lib/widget/button.c284
-rw-r--r--lib/widget/button.h58
-rw-r--r--lib/widget/buttonbar.c290
-rw-r--r--lib/widget/buttonbar.h46
-rw-r--r--lib/widget/check.c182
-rw-r--r--lib/widget/check.h33
-rw-r--r--lib/widget/dialog-switch.c409
-rw-r--r--lib/widget/dialog-switch.h44
-rw-r--r--lib/widget/dialog.c626
-rw-r--r--lib/widget/dialog.h129
-rw-r--r--lib/widget/frame.c164
-rw-r--r--lib/widget/frame.h43
-rw-r--r--lib/widget/gauge.c178
-rw-r--r--lib/widget/gauge.h36
-rw-r--r--lib/widget/group.c970
-rw-r--r--lib/widget/group.h125
-rw-r--r--lib/widget/groupbox.c136
-rw-r--r--lib/widget/groupbox.h32
-rw-r--r--lib/widget/history.c302
-rw-r--r--lib/widget/history.h51
-rw-r--r--lib/widget/hline.c194
-rw-r--r--lib/widget/hline.h37
-rw-r--r--lib/widget/input.c1323
-rw-r--r--lib/widget/input.h155
-rw-r--r--lib/widget/input_complete.c1484
-rw-r--r--lib/widget/label.c201
-rw-r--r--lib/widget/label.h37
-rw-r--r--lib/widget/listbox-window.c176
-rw-r--r--lib/widget/listbox-window.h36
-rw-r--r--lib/widget/listbox.c832
-rw-r--r--lib/widget/listbox.h82
-rw-r--r--lib/widget/menu.c1092
-rw-r--r--lib/widget/menu.h63
-rw-r--r--lib/widget/mouse.c227
-rw-r--r--lib/widget/mouse.h65
-rw-r--r--lib/widget/quick.c626
-rw-r--r--lib/widget/quick.h354
-rw-r--r--lib/widget/radio.c251
-rw-r--r--lib/widget/radio.h38
-rw-r--r--lib/widget/rect.c253
-rw-r--r--lib/widget/rect.h45
-rw-r--r--lib/widget/widget-common.c905
-rw-r--r--lib/widget/widget-common.h462
-rw-r--r--lib/widget/wtools.c729
-rw-r--r--lib/widget/wtools.h100
181 files changed, 57474 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..a466599
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,75 @@
+SUBDIRS = event filehighlight mcconfig search skin tty vfs strutil widget .
+
+if ENABLE_MCLIB
+ LIB_VERSION=`echo $(LIBMC_VERSION) | \
+ tr '.' ' '| \
+ while read v1 v2 v3; do echo $$v2':'$$v3':'$$v1; done`
+
+ AM_LDFLAGS=-no-undefined -version-info $(LIB_VERSION) -release $(LIBMC_RELEASE)
+
+ lib_LTLIBRARIES = libmc.la
+else
+ noinst_LTLIBRARIES = libmc.la
+endif
+
+
+SUBLIB_includes = \
+ event.h event-types.h \
+ filehighlight.h \
+ mcconfig.h \
+ search.h \
+ skin.h \
+ strescape.h \
+ strutil.h \
+ widget.h
+
+SRC_mc_utils = \
+ utilunix.c utilunix.h \
+ unixcompat.h \
+ util.c util.h
+
+
+libmc_la_SOURCES = \
+ $(SUBLIB_includes) \
+ $(SRC_mc_utils) \
+ file-entry.h \
+ fileloc.h \
+ fs.h \
+ hook.c hook.h \
+ glibcompat.c glibcompat.h \
+ global.c global.h \
+ keybind.c keybind.h \
+ lock.c lock.h \
+ serialize.c serialize.h \
+ shell.c shell.h \
+ stat-size.h \
+ timefmt.c timefmt.h
+
+if USE_MAINTAINER_MODE
+libmc_la_SOURCES += logging.c logging.h
+endif
+
+if CHARSET
+libmc_la_SOURCES += charsets.c charsets.h
+endif
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+libmc_la_LIBADD = \
+ event/libmcevent.la \
+ filehighlight/libmcfilehighlight.la \
+ mcconfig/libmcconfig.la \
+ search/libsearch.la \
+ strutil/libmcstrutil.la \
+ skin/libmcskin.la \
+ tty/libmctty.la \
+ vfs/libmcvfs.la \
+ widget/libmcwidget.la
+
+libmc_la_LIBADD += $(MCLIBS) $(SLANGLIB)
+
+if HAVE_GMODULE
+ libmc_la_LIBADD += $(GMODULE_LIBS)
+else
+ libmc_la_LIBADD += $(GLIB_LIBS)
+endif
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 0000000..26a3fb1
--- /dev/null
+++ b/lib/Makefile.in
@@ -0,0 +1,1013 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@USE_MAINTAINER_MODE_TRUE@am__append_1 = logging.c logging.h
+@CHARSET_TRUE@am__append_2 = charsets.c charsets.h
+@HAVE_GMODULE_TRUE@am__append_3 = $(GMODULE_LIBS)
+@HAVE_GMODULE_FALSE@am__append_4 = $(GLIB_LIBS)
+subdir = lib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)"
+LTLIBRARIES = $(lib_LTLIBRARIES) $(noinst_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_GMODULE_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
+@HAVE_GMODULE_FALSE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1)
+libmc_la_DEPENDENCIES = event/libmcevent.la \
+ filehighlight/libmcfilehighlight.la mcconfig/libmcconfig.la \
+ search/libsearch.la strutil/libmcstrutil.la skin/libmcskin.la \
+ tty/libmctty.la vfs/libmcvfs.la widget/libmcwidget.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_3)
+am__libmc_la_SOURCES_DIST = event.h event-types.h filehighlight.h \
+ mcconfig.h search.h skin.h strescape.h strutil.h widget.h \
+ utilunix.c utilunix.h unixcompat.h util.c util.h file-entry.h \
+ fileloc.h fs.h hook.c hook.h glibcompat.c glibcompat.h \
+ global.c global.h keybind.c keybind.h lock.c lock.h \
+ serialize.c serialize.h shell.c shell.h stat-size.h timefmt.c \
+ timefmt.h logging.c logging.h charsets.c charsets.h
+am__objects_1 =
+am__objects_2 = utilunix.lo util.lo
+@USE_MAINTAINER_MODE_TRUE@am__objects_3 = logging.lo
+@CHARSET_TRUE@am__objects_4 = charsets.lo
+am_libmc_la_OBJECTS = $(am__objects_1) $(am__objects_2) hook.lo \
+ glibcompat.lo global.lo keybind.lo lock.lo serialize.lo \
+ shell.lo timefmt.lo $(am__objects_3) $(am__objects_4)
+libmc_la_OBJECTS = $(am_libmc_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+@ENABLE_MCLIB_FALSE@am_libmc_la_rpath =
+@ENABLE_MCLIB_TRUE@am_libmc_la_rpath = -rpath $(libdir)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/charsets.Plo \
+ ./$(DEPDIR)/glibcompat.Plo ./$(DEPDIR)/global.Plo \
+ ./$(DEPDIR)/hook.Plo ./$(DEPDIR)/keybind.Plo \
+ ./$(DEPDIR)/lock.Plo ./$(DEPDIR)/logging.Plo \
+ ./$(DEPDIR)/serialize.Plo ./$(DEPDIR)/shell.Plo \
+ ./$(DEPDIR)/timefmt.Plo ./$(DEPDIR)/util.Plo \
+ ./$(DEPDIR)/utilunix.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmc_la_SOURCES)
+DIST_SOURCES = $(am__libmc_la_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = event filehighlight mcconfig search skin tty vfs strutil widget .
+@ENABLE_MCLIB_TRUE@LIB_VERSION = `echo $(LIBMC_VERSION) | \
+@ENABLE_MCLIB_TRUE@ tr '.' ' '| \
+@ENABLE_MCLIB_TRUE@ while read v1 v2 v3; do echo $$v2':'$$v3':'$$v1; done`
+
+@ENABLE_MCLIB_TRUE@AM_LDFLAGS = -no-undefined -version-info $(LIB_VERSION) -release $(LIBMC_RELEASE)
+@ENABLE_MCLIB_TRUE@lib_LTLIBRARIES = libmc.la
+@ENABLE_MCLIB_FALSE@noinst_LTLIBRARIES = libmc.la
+SUBLIB_includes = \
+ event.h event-types.h \
+ filehighlight.h \
+ mcconfig.h \
+ search.h \
+ skin.h \
+ strescape.h \
+ strutil.h \
+ widget.h
+
+SRC_mc_utils = \
+ utilunix.c utilunix.h \
+ unixcompat.h \
+ util.c util.h
+
+libmc_la_SOURCES = $(SUBLIB_includes) $(SRC_mc_utils) file-entry.h \
+ fileloc.h fs.h hook.c hook.h glibcompat.c glibcompat.h \
+ global.c global.h keybind.c keybind.h lock.c lock.h \
+ serialize.c serialize.h shell.c shell.h stat-size.h timefmt.c \
+ timefmt.h $(am__append_1) $(am__append_2)
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+libmc_la_LIBADD = event/libmcevent.la \
+ filehighlight/libmcfilehighlight.la mcconfig/libmcconfig.la \
+ search/libsearch.la strutil/libmcstrutil.la skin/libmcskin.la \
+ tty/libmctty.la vfs/libmcvfs.la widget/libmcwidget.la \
+ $(MCLIBS) $(SLANGLIB) $(am__append_3) $(am__append_4)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmc.la: $(libmc_la_OBJECTS) $(libmc_la_DEPENDENCIES) $(EXTRA_libmc_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(am_libmc_la_rpath) $(libmc_la_OBJECTS) $(libmc_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/charsets.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/glibcompat.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/global.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hook.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keybind.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lock.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logging.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/serialize.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/shell.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/timefmt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utilunix.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ clean-noinstLTLIBRARIES mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/charsets.Plo
+ -rm -f ./$(DEPDIR)/glibcompat.Plo
+ -rm -f ./$(DEPDIR)/global.Plo
+ -rm -f ./$(DEPDIR)/hook.Plo
+ -rm -f ./$(DEPDIR)/keybind.Plo
+ -rm -f ./$(DEPDIR)/lock.Plo
+ -rm -f ./$(DEPDIR)/logging.Plo
+ -rm -f ./$(DEPDIR)/serialize.Plo
+ -rm -f ./$(DEPDIR)/shell.Plo
+ -rm -f ./$(DEPDIR)/timefmt.Plo
+ -rm -f ./$(DEPDIR)/util.Plo
+ -rm -f ./$(DEPDIR)/utilunix.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/charsets.Plo
+ -rm -f ./$(DEPDIR)/glibcompat.Plo
+ -rm -f ./$(DEPDIR)/global.Plo
+ -rm -f ./$(DEPDIR)/hook.Plo
+ -rm -f ./$(DEPDIR)/keybind.Plo
+ -rm -f ./$(DEPDIR)/lock.Plo
+ -rm -f ./$(DEPDIR)/logging.Plo
+ -rm -f ./$(DEPDIR)/serialize.Plo
+ -rm -f ./$(DEPDIR)/shell.Plo
+ -rm -f ./$(DEPDIR)/timefmt.Plo
+ -rm -f ./$(DEPDIR)/util.Plo
+ -rm -f ./$(DEPDIR)/utilunix.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/charsets.c b/lib/charsets.c
new file mode 100644
index 0000000..c97a3cf
--- /dev/null
+++ b/lib/charsets.c
@@ -0,0 +1,527 @@
+/*
+ Text conversion from one charset to another.
+
+ Copyright (C) 2001-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Walery Studennikov <despair@sama.ru>
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file charsets.c
+ * \brief Source: Text conversion from one charset to another
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h" /* utf-8 functions */
+#include "lib/fileloc.h"
+#include "lib/util.h" /* whitespace() */
+
+#include "lib/charsets.h"
+
+/*** global variables ****************************************************************************/
+
+GPtrArray *codepages = NULL;
+
+unsigned char conv_displ[256];
+unsigned char conv_input[256];
+
+const char *cp_display = NULL;
+const char *cp_source = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define UNKNCHAR '\001'
+
+#define OTHER_8BIT "Other_8_bit"
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static codepage_desc *
+new_codepage_desc (const char *id, const char *name)
+{
+ codepage_desc *desc;
+
+ desc = g_new (codepage_desc, 1);
+ desc->id = g_strdup (id);
+ desc->name = g_strdup (name);
+
+ return desc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+free_codepage_desc (gpointer data)
+{
+ codepage_desc *desc = (codepage_desc *) data;
+
+ g_free (desc->id);
+ g_free (desc->name);
+ g_free (desc);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* returns display codepage */
+
+static void
+load_codepages_list_from_file (GPtrArray ** list, const char *fname)
+{
+ FILE *f;
+ char buf[BUF_MEDIUM];
+ char *default_codepage = NULL;
+
+ f = fopen (fname, "r");
+ if (f == NULL)
+ return;
+
+ while (fgets (buf, sizeof buf, f) != NULL)
+ {
+ /* split string into id and cpname */
+ char *p = buf;
+ size_t buflen;
+
+ if (*p == '\n' || *p == '\0' || *p == '#')
+ continue;
+
+ buflen = strlen (buf);
+
+ if (buflen != 0 && buf[buflen - 1] == '\n')
+ buf[buflen - 1] = '\0';
+ while (*p != '\0' && !whitespace (*p))
+ ++p;
+ if (*p == '\0')
+ goto fail;
+
+ *p++ = '\0';
+ g_strstrip (p);
+ if (*p == '\0')
+ goto fail;
+
+ if (strcmp (buf, "default") == 0)
+ default_codepage = g_strdup (p);
+ else
+ {
+ const char *id = buf;
+
+ if (*list == NULL)
+ {
+ *list = g_ptr_array_sized_new (16);
+ g_ptr_array_add (*list, new_codepage_desc (id, p));
+ }
+ else
+ {
+ unsigned int i;
+
+ /* whether id is already present in list */
+ /* if yes, overwrite description */
+ for (i = 0; i < (*list)->len; i++)
+ {
+ codepage_desc *desc;
+
+ desc = (codepage_desc *) g_ptr_array_index (*list, i);
+
+ if (strcmp (id, desc->id) == 0)
+ {
+ /* found */
+ g_free (desc->name);
+ desc->name = g_strdup (p);
+ break;
+ }
+ }
+
+ /* not found */
+ if (i == (*list)->len)
+ g_ptr_array_add (*list, new_codepage_desc (id, p));
+ }
+ }
+ }
+
+ if (default_codepage != NULL)
+ {
+ mc_global.display_codepage = get_codepage_index (default_codepage);
+ g_free (default_codepage);
+ }
+
+ fail:
+ fclose (f);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char
+translate_character (GIConv cd, char c)
+{
+ gchar *tmp_buff = NULL;
+ gsize bytes_read, bytes_written = 0;
+ const char *ibuf = &c;
+ char ch = UNKNCHAR;
+ int ibuflen = 1;
+
+ tmp_buff = g_convert_with_iconv (ibuf, ibuflen, cd, &bytes_read, &bytes_written, NULL);
+ if (tmp_buff != NULL)
+ ch = tmp_buff[0];
+ g_free (tmp_buff);
+ return ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+load_codepages_list (void)
+{
+ char *fname;
+
+ /* 1: try load /usr/share/mc/mc.charsets */
+ fname = g_build_filename (mc_global.share_data_dir, CHARSETS_LIST, (char *) NULL);
+ load_codepages_list_from_file (&codepages, fname);
+ g_free (fname);
+
+ /* 2: try load /etc/mc/mc.charsets */
+ fname = g_build_filename (mc_global.sysconfig_dir, CHARSETS_LIST, (char *) NULL);
+ load_codepages_list_from_file (&codepages, fname);
+ g_free (fname);
+
+ if (codepages == NULL)
+ {
+ /* files are not found, add default codepage */
+ fprintf (stderr, "%s\n", _("Warning: cannot load codepages list"));
+
+ codepages = g_ptr_array_new_with_free_func (free_codepage_desc);
+ g_ptr_array_add (codepages, new_codepage_desc (DEFAULT_CHARSET, _("7-bit ASCII")));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+free_codepages_list (void)
+{
+ g_ptr_array_free (codepages, TRUE);
+ /* NULL-ize pointer to make unit tests happy */
+ codepages = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+get_codepage_id (const int n)
+{
+ return (n < 0) ? OTHER_8BIT : ((codepage_desc *) g_ptr_array_index (codepages, n))->id;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+get_codepage_index (const char *id)
+{
+ size_t i;
+
+ if (codepages == NULL)
+ return -1;
+ if (strcmp (id, OTHER_8BIT) == 0)
+ return -1;
+ for (i = 0; i < codepages->len; i++)
+ if (strcmp (id, ((codepage_desc *) g_ptr_array_index (codepages, i))->id) == 0)
+ return i;
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Check if specified encoding can be used in mc.
+ * @param encoding name of encoding
+ * @return TRUE if encoding is supported by mc, FALSE otherwise
+ */
+
+gboolean
+is_supported_encoding (const char *encoding)
+{
+ gboolean result = FALSE;
+ guint t;
+
+ for (t = 0; t < codepages->len; t++)
+ {
+ const char *id;
+
+ id = ((codepage_desc *) g_ptr_array_index (codepages, t))->id;
+ result |= (g_ascii_strncasecmp (encoding, id, strlen (id)) == 0);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+init_translation_table (int cpsource, int cpdisplay)
+{
+ int i;
+ GIConv cd;
+
+ /* Fill inpit <-> display tables */
+
+ if (cpsource < 0 || cpdisplay < 0 || cpsource == cpdisplay)
+ {
+ for (i = 0; i <= 255; ++i)
+ {
+ conv_displ[i] = i;
+ conv_input[i] = i;
+ }
+ cp_source = cp_display;
+ return NULL;
+ }
+
+ for (i = 0; i <= 127; ++i)
+ {
+ conv_displ[i] = i;
+ conv_input[i] = i;
+ }
+ cp_source = ((codepage_desc *) g_ptr_array_index (codepages, cpsource))->id;
+ cp_display = ((codepage_desc *) g_ptr_array_index (codepages, cpdisplay))->id;
+
+ /* display <- inpit table */
+
+ cd = g_iconv_open (cp_display, cp_source);
+ if (cd == INVALID_CONV)
+ return g_strdup_printf (_("Cannot translate from %s to %s"), cp_source, cp_display);
+
+ for (i = 128; i <= 255; ++i)
+ conv_displ[i] = translate_character (cd, i);
+
+ g_iconv_close (cd);
+
+ /* inpit <- display table */
+
+ cd = g_iconv_open (cp_source, cp_display);
+ if (cd == INVALID_CONV)
+ return g_strdup_printf (_("Cannot translate from %s to %s"), cp_display, cp_source);
+
+ for (i = 128; i <= 255; ++i)
+ {
+ unsigned char ch;
+ ch = translate_character (cd, i);
+ conv_input[i] = (ch == UNKNCHAR) ? i : ch;
+ }
+
+ g_iconv_close (cd);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+convert_to_display (char *str)
+{
+ if (str != NULL)
+ for (; *str != '\0'; str++)
+ *str = conv_displ[(unsigned char) *str];
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+str_nconvert_to_display (const char *str, int len)
+{
+ GString *buff;
+ GIConv conv;
+
+ if (str == NULL)
+ return NULL;
+
+ if (cp_display == cp_source)
+ return g_string_new (str);
+
+ conv = str_crt_conv_from (cp_source);
+
+ buff = g_string_new ("");
+ str_nconvert (conv, str, len, buff);
+ str_close_conv (conv);
+ return buff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+convert_from_input (char *str)
+{
+ if (str != NULL)
+ for (; *str != '\0'; str++)
+ *str = conv_input[(unsigned char) *str];
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+str_nconvert_to_input (const char *str, int len)
+{
+ GString *buff;
+ GIConv conv;
+
+ if (str == NULL)
+ return NULL;
+
+ if (cp_display == cp_source)
+ return g_string_new (str);
+
+ conv = str_crt_conv_to (cp_source);
+
+ buff = g_string_new ("");
+ str_nconvert (conv, str, len, buff);
+ str_close_conv (conv);
+ return buff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+unsigned char
+convert_from_utf_to_current (const char *str)
+{
+ unsigned char buf_ch[UTF8_CHAR_LEN + 1];
+ unsigned char ch = '.';
+ GIConv conv;
+ const char *cp_to;
+
+ if (str == NULL)
+ return '.';
+
+ cp_to = get_codepage_id (mc_global.source_codepage);
+ conv = str_crt_conv_to (cp_to);
+
+ if (conv != INVALID_CONV)
+ {
+ switch (str_translate_char (conv, str, -1, (char *) buf_ch, sizeof (buf_ch)))
+ {
+ case ESTR_SUCCESS:
+ ch = buf_ch[0];
+ break;
+ case ESTR_PROBLEM:
+ case ESTR_FAILURE:
+ ch = '.';
+ break;
+ default:
+ break;
+ }
+ str_close_conv (conv);
+ }
+
+ return ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+unsigned char
+convert_from_utf_to_current_c (int input_char, GIConv conv)
+{
+ unsigned char str[UTF8_CHAR_LEN + 1];
+ unsigned char buf_ch[UTF8_CHAR_LEN + 1];
+ unsigned char ch = '.';
+ int res;
+
+ res = g_unichar_to_utf8 (input_char, (char *) str);
+ if (res == 0)
+ return ch;
+
+ str[res] = '\0';
+
+ switch (str_translate_char (conv, (char *) str, -1, (char *) buf_ch, sizeof (buf_ch)))
+ {
+ case ESTR_SUCCESS:
+ ch = buf_ch[0];
+ break;
+ case ESTR_PROBLEM:
+ case ESTR_FAILURE:
+ ch = '.';
+ break;
+ default:
+ break;
+ }
+
+ return ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+convert_from_8bit_to_utf_c (char input_char, GIConv conv)
+{
+ unsigned char str[2];
+ unsigned char buf_ch[UTF8_CHAR_LEN + 1];
+ int ch;
+
+ str[0] = (unsigned char) input_char;
+ str[1] = '\0';
+
+ switch (str_translate_char (conv, (char *) str, -1, (char *) buf_ch, sizeof (buf_ch)))
+ {
+ case ESTR_SUCCESS:
+ {
+ int res;
+
+ res = g_utf8_get_char_validated ((char *) buf_ch, -1);
+ ch = res >= 0 ? res : buf_ch[0];
+ break;
+ }
+ case ESTR_PROBLEM:
+ case ESTR_FAILURE:
+ default:
+ ch = '.';
+ break;
+ }
+
+ return ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+convert_from_8bit_to_utf_c2 (char input_char)
+{
+ int ch = '.';
+ GIConv conv;
+ const char *cp_from;
+
+ cp_from = get_codepage_id (mc_global.source_codepage);
+
+ conv = str_crt_conv_to (cp_from);
+ if (conv != INVALID_CONV)
+ {
+ ch = convert_from_8bit_to_utf_c (input_char, conv);
+ str_close_conv (conv);
+ }
+
+ return ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/charsets.h b/lib/charsets.h
new file mode 100644
index 0000000..a7c1bb7
--- /dev/null
+++ b/lib/charsets.h
@@ -0,0 +1,113 @@
+/** \file charsets.h
+ * \brief Header: Text conversion from one charset to another
+ */
+
+#ifndef MC__CHARSETS_H
+#define MC__CHARSETS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ char *id;
+ char *name;
+} codepage_desc;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern unsigned char conv_displ[256];
+extern unsigned char conv_input[256];
+
+extern const char *cp_display;
+extern const char *cp_source;
+extern GPtrArray *codepages;
+
+/*** declarations of public functions ************************************************************/
+
+const char *get_codepage_id (const int n);
+int get_codepage_index (const char *id);
+void load_codepages_list (void);
+void free_codepages_list (void);
+gboolean is_supported_encoding (const char *encoding);
+char *init_translation_table (int cpsource, int cpdisplay);
+void convert_to_display (char *str);
+void convert_from_input (char *str);
+void convert_string (unsigned char *str);
+
+/*
+ * Converter from utf to selected codepage
+ * param str, utf char
+ * return char in needle codepage (by global int mc_global.source_codepage)
+ */
+unsigned char convert_from_utf_to_current (const char *str);
+
+/*
+ * Converter from utf to selected codepage
+ * param input_char, gunichar
+ * return char in needle codepage (by global int mc_global.source_codepage)
+ */
+unsigned char convert_from_utf_to_current_c (int input_char, GIConv conv);
+
+/*
+ * Converter from selected codepage 8-bit
+ * param char input_char, GIConv converter
+ * return int utf char
+ */
+int convert_from_8bit_to_utf_c (char input_char, GIConv conv);
+
+/*
+ * Converter from display codepage 8-bit to utf-8
+ * param char input_char, GIConv converter
+ * return int utf char
+ */
+int convert_from_8bit_to_utf_c2 (char input_char);
+
+GString *str_nconvert_to_input (const char *str, int len);
+GString *str_nconvert_to_display (const char *str, int len);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Convert single characters */
+static inline int
+convert_to_display_c (int c)
+{
+ if (c < 0 || c >= 256)
+ return c;
+ return (int) conv_displ[c];
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+convert_from_input_c (int c)
+{
+ if (c < 0 || c >= 256)
+ return c;
+ return (int) conv_input[c];
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline GString *
+str_convert_to_input (const char *str)
+{
+ return str_nconvert_to_input (str, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline GString *
+str_convert_to_display (const char *str)
+{
+ return str_nconvert_to_display (str, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__CHARSETS_H */
diff --git a/lib/event-types.h b/lib/event-types.h
new file mode 100644
index 0000000..2625c36
--- /dev/null
+++ b/lib/event-types.h
@@ -0,0 +1,84 @@
+#ifndef MC__EVENT_TYPES_H
+#define MC__EVENT_TYPES_H
+
+#include <stdarg.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* Event groups for main modules */
+#define MCEVENT_GROUP_CORE "Core"
+#define MCEVENT_GROUP_DIALOG "Dialog"
+#define MCEVENT_GROUP_DIFFVIEWER "DiffViewer"
+#define MCEVENT_GROUP_EDITOR "Editor"
+#define MCEVENT_GROUP_FILEMANAGER "FileManager"
+#define MCEVENT_GROUP_VIEWER "Viewer"
+
+/* Events */
+#define MCEVENT_HISTORY_LOAD "history_load"
+#define MCEVENT_HISTORY_SAVE "history_save"
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* MCEVENT_GROUP_CORE:vfs_timestamp */
+struct vfs_class;
+typedef struct
+{
+ struct vfs_class *vclass;
+ gpointer id;
+ gboolean ret;
+} ev_vfs_stamp_create_t;
+
+/* MCEVENT_GROUP_CORE:vfs_print_message */
+typedef struct
+{
+ char *msg;
+} ev_vfs_print_message_t;
+
+/* MCEVENT_GROUP_CORE:clipboard_text_from_file */
+typedef struct
+{
+ char **text;
+ gboolean ret;
+} ev_clipboard_text_from_file_t;
+
+/* MCEVENT_GROUP_CORE:help */
+typedef struct
+{
+ const char *filename;
+ const char *node;
+} ev_help_t;
+
+/* MCEVENT_GROUP_CORE:background_parent_call */
+/* MCEVENT_GROUP_CORE:background_parent_call_string */
+typedef struct
+{
+ void *routine;
+ gpointer *ctx;
+ int argc;
+ va_list ap;
+ union
+ {
+ int i;
+ char *s;
+ } ret;
+} ev_background_parent_call_t;
+
+/* MCEVENT_GROUP_DIALOG:history_load */
+/* MCEVENT_GROUP_DIALOG:history_save */
+struct mc_config_t;
+struct Widget;
+typedef struct
+{
+ struct mc_config_t *cfg;
+ struct Widget *receiver; /* NULL means broadcast message */
+} ev_history_load_save_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EVENT_TYPES_H */
diff --git a/lib/event.h b/lib/event.h
new file mode 100644
index 0000000..6661639
--- /dev/null
+++ b/lib/event.h
@@ -0,0 +1,48 @@
+#ifndef MC__EVENT_H
+#define MC__EVENT_H
+
+#include "event-types.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef gboolean (*mc_event_callback_func_t) (const gchar *, const gchar *, gpointer, gpointer);
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ const char *event_group_name;
+ const char *event_name;
+ mc_event_callback_func_t cb;
+ gpointer init_data;
+} event_init_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* event.c: */
+gboolean mc_event_init (GError ** mcerror);
+gboolean mc_event_deinit (GError ** mcerror);
+
+
+/* manage.c: */
+gboolean mc_event_add (const gchar * event_group_name, const gchar * event_name,
+ mc_event_callback_func_t event_callback, gpointer event_init_data,
+ GError ** mcerror);
+void mc_event_del (const gchar * event_group_name, const gchar * event_name,
+ mc_event_callback_func_t event_callback, gpointer event_init_data);
+void mc_event_destroy (const gchar * event_group_name, const gchar * event_name);
+void mc_event_group_del (const gchar * event_group_name);
+gboolean mc_event_present (const gchar * event_group_name, const gchar * event_name);
+gboolean mc_event_mass_add (const event_init_t * events, GError ** mcerror);
+
+/* raise.c: */
+gboolean mc_event_raise (const gchar *, const gchar *, gpointer);
+
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EVENT_H */
diff --git a/lib/event/Makefile.am b/lib/event/Makefile.am
new file mode 100644
index 0000000..407bead
--- /dev/null
+++ b/lib/event/Makefile.am
@@ -0,0 +1,10 @@
+
+noinst_LTLIBRARIES = libmcevent.la
+
+libmcevent_la_SOURCES = \
+ event.c \
+ internal.h \
+ manage.c \
+ raise.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/lib/event/Makefile.in b/lib/event/Makefile.in
new file mode 100644
index 0000000..298c4e4
--- /dev/null
+++ b/lib/event/Makefile.in
@@ -0,0 +1,745 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lib/event
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcevent_la_LIBADD =
+am_libmcevent_la_OBJECTS = event.lo manage.lo raise.lo
+libmcevent_la_OBJECTS = $(am_libmcevent_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/event.Plo ./$(DEPDIR)/manage.Plo \
+ ./$(DEPDIR)/raise.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcevent_la_SOURCES)
+DIST_SOURCES = $(libmcevent_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcevent.la
+libmcevent_la_SOURCES = \
+ event.c \
+ internal.h \
+ manage.c \
+ raise.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/event/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/event/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcevent.la: $(libmcevent_la_OBJECTS) $(libmcevent_la_DEPENDENCIES) $(EXTRA_libmcevent_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcevent_la_OBJECTS) $(libmcevent_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/manage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/raise.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/event.Plo
+ -rm -f ./$(DEPDIR)/manage.Plo
+ -rm -f ./$(DEPDIR)/raise.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/event.Plo
+ -rm -f ./$(DEPDIR)/manage.Plo
+ -rm -f ./$(DEPDIR)/raise.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/event/event.c b/lib/event/event.c
new file mode 100644
index 0000000..10df96d
--- /dev/null
+++ b/lib/event/event.c
@@ -0,0 +1,135 @@
+/*
+ Handle events in application.
+ Interface functions: init/deinit; start/stop
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/event.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+GTree *mc_event_grouplist = NULL;
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_event_init (GError ** mcerror)
+{
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (mc_event_grouplist != NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Event system already initialized"));
+ return FALSE;
+ }
+
+ mc_event_grouplist =
+ g_tree_new_full ((GCompareDataFunc) g_ascii_strcasecmp,
+ NULL, (GDestroyNotify) g_free, (GDestroyNotify) g_tree_destroy);
+
+ if (mc_event_grouplist == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Failed to initialize event system"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_event_deinit (GError ** mcerror)
+{
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (mc_event_grouplist == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Event system not initialized"));
+ return FALSE;
+ }
+
+ g_tree_destroy (mc_event_grouplist);
+ mc_event_grouplist = NULL;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_event_mass_add (const event_init_t * events, GError ** mcerror)
+{
+ size_t array_index;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ for (array_index = 0; events[array_index].event_group_name != NULL; array_index++)
+ {
+ if (!mc_event_add (events[array_index].event_group_name,
+ events[array_index].event_name,
+ events[array_index].cb, events[array_index].init_data, mcerror))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_event_present (const gchar * event_group_name, const gchar * event_name)
+{
+ GTree *event_group;
+ GPtrArray *callbacks;
+
+ if (mc_event_grouplist == NULL || event_group_name == NULL || event_name == NULL)
+ return FALSE;
+
+ event_group = mc_event_get_event_group_by_name (event_group_name, FALSE, NULL);
+ if (event_group == NULL)
+ return FALSE;
+
+ callbacks = mc_event_get_event_by_name (event_group, event_name, FALSE, NULL);
+ if (callbacks == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/event/internal.h b/lib/event/internal.h
new file mode 100644
index 0000000..f82ac3a
--- /dev/null
+++ b/lib/event/internal.h
@@ -0,0 +1,32 @@
+#ifndef MC_EVENT_INTERNAL_H
+#define MC_EVENT_INTERNAL_H
+
+/*** typedefs(not structures) and defined constants ********************/
+
+/*** enums *************************************************************/
+
+/*** structures declarations (and typedefs of structures)***************/
+
+typedef struct mc_event_callback_struct
+{
+ gpointer init_data;
+ mc_event_callback_func_t callback;
+} mc_event_callback_t;
+
+
+/*** global variables defined in .c file *******************************/
+
+extern GTree *mc_event_grouplist;
+
+/*** declarations of public functions **********************************/
+
+GTree *mc_event_get_event_group_by_name (const gchar * event_group_name, gboolean create_new,
+ GError ** mcerror);
+GPtrArray *mc_event_get_event_by_name (GTree * event_group, const gchar * event_name,
+ gboolean create_new, GError ** mcerror);
+mc_event_callback_t *mc_event_is_callback_in_array (GPtrArray * callbacks,
+ mc_event_callback_func_t event_callback,
+ gpointer event_init_data);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_EVENT_INTERNAL_H */
diff --git a/lib/event/manage.c b/lib/event/manage.c
new file mode 100644
index 0000000..b8f9733
--- /dev/null
+++ b/lib/event/manage.c
@@ -0,0 +1,216 @@
+/*
+ Handle any events in application.
+ Manage events: add, delete, destroy, search
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/event.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_event_group_destroy_value (gpointer data)
+{
+ g_ptr_array_free ((GPtrArray *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_event_add (const gchar * event_group_name, const gchar * event_name,
+ mc_event_callback_func_t event_callback, gpointer event_init_data, GError ** mcerror)
+{
+ GTree *event_group;
+ GPtrArray *callbacks;
+ mc_event_callback_t *cb;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (mc_event_grouplist == NULL || event_group_name == NULL || event_name == NULL
+ || event_callback == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Check input data! Some of parameters are NULL!"));
+ return FALSE;
+ }
+
+ event_group = mc_event_get_event_group_by_name (event_group_name, TRUE, mcerror);
+ if (event_group == NULL)
+ return FALSE;
+
+ callbacks = mc_event_get_event_by_name (event_group, event_name, TRUE, mcerror);
+ if (callbacks == NULL)
+ return FALSE;
+
+ cb = mc_event_is_callback_in_array (callbacks, event_callback, event_init_data);
+ if (cb == NULL)
+ {
+ cb = g_new0 (mc_event_callback_t, 1);
+ cb->callback = event_callback;
+ g_ptr_array_add (callbacks, (gpointer) cb);
+ }
+ cb->init_data = event_init_data;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_event_del (const gchar * event_group_name, const gchar * event_name,
+ mc_event_callback_func_t event_callback, gpointer event_init_data)
+{
+ GTree *event_group;
+ GPtrArray *callbacks;
+ mc_event_callback_t *cb;
+
+ if (mc_event_grouplist == NULL || event_group_name == NULL || event_name == NULL
+ || event_callback == NULL)
+ return;
+
+ event_group = mc_event_get_event_group_by_name (event_group_name, FALSE, NULL);
+ if (event_group == NULL)
+ return;
+
+ callbacks = mc_event_get_event_by_name (event_group, event_name, FALSE, NULL);
+ if (callbacks == NULL)
+ return;
+
+ cb = mc_event_is_callback_in_array (callbacks, event_callback, event_init_data);
+ if (cb != NULL)
+ g_ptr_array_remove (callbacks, (gpointer) cb);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_event_destroy (const gchar * event_group_name, const gchar * event_name)
+{
+ GTree *event_group;
+
+ if (mc_event_grouplist == NULL || event_group_name == NULL || event_name == NULL)
+ return;
+
+ event_group = mc_event_get_event_group_by_name (event_group_name, FALSE, NULL);
+ g_tree_remove (event_group, (gconstpointer) event_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_event_group_del (const gchar * event_group_name)
+{
+
+ if (mc_event_grouplist != NULL && event_group_name != NULL)
+ g_tree_remove (mc_event_grouplist, (gconstpointer) event_group_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GTree *
+mc_event_get_event_group_by_name (const gchar * event_group_name, gboolean create_new,
+ GError ** mcerror)
+{
+ GTree *event_group;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ event_group = (GTree *) g_tree_lookup (mc_event_grouplist, (gconstpointer) event_group_name);
+ if (event_group == NULL && create_new)
+ {
+ event_group =
+ g_tree_new_full ((GCompareDataFunc) g_ascii_strcasecmp,
+ NULL,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) mc_event_group_destroy_value);
+ if (event_group == NULL)
+ {
+ mc_propagate_error (mcerror, 0, _("Unable to create group '%s' for events!"),
+ event_group_name);
+ return NULL;
+ }
+ g_tree_insert (mc_event_grouplist, g_strdup (event_group_name), (gpointer) event_group);
+ }
+ return event_group;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GPtrArray *
+mc_event_get_event_by_name (GTree * event_group, const gchar * event_name, gboolean create_new,
+ GError ** mcerror)
+{
+ GPtrArray *callbacks;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ callbacks = (GPtrArray *) g_tree_lookup (event_group, (gconstpointer) event_name);
+ if (callbacks == NULL && create_new)
+ {
+ callbacks = g_ptr_array_new_with_free_func (g_free);
+ if (callbacks == NULL)
+ {
+ mc_propagate_error (mcerror, 0, _("Unable to create event '%s'!"), event_name);
+ return NULL;
+ }
+ g_tree_insert (event_group, g_strdup (event_name), (gpointer) callbacks);
+ }
+ return callbacks;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_event_callback_t *
+mc_event_is_callback_in_array (GPtrArray * callbacks, mc_event_callback_func_t event_callback,
+ gpointer event_init_data)
+{
+ guint array_index;
+
+ for (array_index = 0; array_index < callbacks->len; array_index++)
+ {
+ mc_event_callback_t *cb = g_ptr_array_index (callbacks, array_index);
+ if (cb->callback == event_callback && cb->init_data == event_init_data)
+ return cb;
+ }
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/event/raise.c b/lib/event/raise.c
new file mode 100644
index 0000000..37cad46
--- /dev/null
+++ b/lib/event/raise.c
@@ -0,0 +1,75 @@
+/*
+ Handle any events in application.
+ Raise events.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/event.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_event_raise (const gchar * event_group_name, const gchar * event_name, gpointer event_data)
+{
+ GTree *event_group;
+ GPtrArray *callbacks;
+ guint array_index;
+
+ if (mc_event_grouplist == NULL || event_group_name == NULL || event_name == NULL)
+ return FALSE;
+
+ event_group = mc_event_get_event_group_by_name (event_group_name, FALSE, NULL);
+ if (event_group == NULL)
+ return FALSE;
+
+ callbacks = mc_event_get_event_by_name (event_group, event_name, FALSE, NULL);
+ if (callbacks == NULL)
+ return FALSE;
+
+ for (array_index = callbacks->len; array_index > 0; array_index--)
+ {
+ mc_event_callback_t *cb = g_ptr_array_index (callbacks, array_index - 1);
+ if (!(*cb->callback) (event_group_name, event_name, cb->init_data, event_data))
+ break;
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/file-entry.h b/lib/file-entry.h
new file mode 100644
index 0000000..2d9cfc4
--- /dev/null
+++ b/lib/file-entry.h
@@ -0,0 +1,49 @@
+/** \file lib/file-entry.h
+ * \brief Header: file entry definition
+ */
+
+#ifndef MC__ILE_ENTRY_H
+#define MC__ILE_ENTRY_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h" /* include <glib.h> */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* keys are set only during sorting */
+typedef struct
+{
+ /* File name */
+ GString *fname;
+ /* File attributes */
+ struct stat st;
+ /* Key used for comparing names */
+ char *name_sort_key;
+ /* Key used for comparing extensions */
+ char *extension_sort_key;
+
+ /* Flags */
+ struct
+ {
+ unsigned int marked:1; /* File marked in pane window */
+ unsigned int link_to_dir:1; /* If this is a link, does it point to directory? */
+ unsigned int stale_link:1; /* If this is a symlink and points to Charon's land */
+ unsigned int dir_size_computed:1; /* Size of directory was computed with dirsizes_cmd */
+ } f;
+} file_entry_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__FILE_ENTRY_H */
diff --git a/lib/filehighlight.h b/lib/filehighlight.h
new file mode 100644
index 0000000..fd2c976
--- /dev/null
+++ b/lib/filehighlight.h
@@ -0,0 +1,34 @@
+#ifndef MC__FILEHIGHLIGHT_H
+#define MC__FILEHIGHLIGHT_H
+
+#include "lib/mcconfig.h" /* mc_config_t */
+#include "lib/file-entry.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_fhl_struct
+{
+ mc_config_t *config;
+ GPtrArray *filters;
+} mc_fhl_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+mc_fhl_t *mc_fhl_new (gboolean need_auto_fill);
+void mc_fhl_free (mc_fhl_t ** fhl);
+
+int mc_fhl_get_color (const mc_fhl_t * fhl, const file_entry_t * fe);
+
+gboolean mc_fhl_read_ini_file (mc_fhl_t * fhl, const gchar * filename);
+gboolean mc_fhl_parse_ini_file (mc_fhl_t * fhl);
+void mc_fhl_clear (mc_fhl_t * fhl);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__FILEHIGHLIGHT_H */
diff --git a/lib/filehighlight/Makefile.am b/lib/filehighlight/Makefile.am
new file mode 100644
index 0000000..dee24e2
--- /dev/null
+++ b/lib/filehighlight/Makefile.am
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libmcfilehighlight.la
+
+libmcfilehighlight_la_SOURCES = \
+ common.c \
+ get-color.c \
+ ini-file-read.c \
+ internal.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/lib/filehighlight/Makefile.in b/lib/filehighlight/Makefile.in
new file mode 100644
index 0000000..7ad3212
--- /dev/null
+++ b/lib/filehighlight/Makefile.in
@@ -0,0 +1,746 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lib/filehighlight
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcfilehighlight_la_LIBADD =
+am_libmcfilehighlight_la_OBJECTS = common.lo get-color.lo \
+ ini-file-read.lo
+libmcfilehighlight_la_OBJECTS = $(am_libmcfilehighlight_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/common.Plo ./$(DEPDIR)/get-color.Plo \
+ ./$(DEPDIR)/ini-file-read.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcfilehighlight_la_SOURCES)
+DIST_SOURCES = $(libmcfilehighlight_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcfilehighlight.la
+libmcfilehighlight_la_SOURCES = \
+ common.c \
+ get-color.c \
+ ini-file-read.c \
+ internal.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/filehighlight/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/filehighlight/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcfilehighlight.la: $(libmcfilehighlight_la_OBJECTS) $(libmcfilehighlight_la_DEPENDENCIES) $(EXTRA_libmcfilehighlight_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcfilehighlight_la_OBJECTS) $(libmcfilehighlight_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/get-color.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ini-file-read.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/get-color.Plo
+ -rm -f ./$(DEPDIR)/ini-file-read.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/get-color.Plo
+ -rm -f ./$(DEPDIR)/ini-file-read.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/filehighlight/common.c b/lib/filehighlight/common.c
new file mode 100644
index 0000000..d519744
--- /dev/null
+++ b/lib/filehighlight/common.c
@@ -0,0 +1,129 @@
+/*
+ File highlight plugin.
+ Interface functions
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "lib/filehighlight.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_fhl_filter_free (gpointer data)
+{
+ mc_fhl_filter_t *filter = (mc_fhl_filter_t *) data;
+
+ g_free (filter->fgcolor);
+ g_free (filter->bgcolor);
+ mc_search_free (filter->search_condition);
+ g_free (filter);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_fhl_array_free (mc_fhl_t * fhl)
+{
+ if (fhl->filters != NULL)
+ {
+ g_ptr_array_free (fhl->filters, TRUE);
+ fhl->filters = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_fhl_t *
+mc_fhl_new (gboolean need_auto_fill)
+{
+ mc_fhl_t *fhl;
+
+ fhl = g_try_new0 (mc_fhl_t, 1);
+ if (fhl == NULL)
+ return NULL;
+
+ if (!need_auto_fill)
+ return fhl;
+
+ if (!mc_fhl_init_from_standard_files (fhl))
+ {
+ g_free (fhl);
+ return NULL;
+ }
+
+ if (!mc_fhl_parse_ini_file (fhl))
+ {
+ mc_fhl_free (&fhl);
+ return NULL;
+ }
+
+ return fhl;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_fhl_free (mc_fhl_t ** fhl)
+{
+ if (fhl == NULL || *fhl == NULL)
+ return;
+
+ mc_fhl_clear (*fhl);
+
+ MC_PTR_FREE (*fhl);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_fhl_clear (mc_fhl_t * fhl)
+{
+ if (fhl != NULL)
+ {
+ mc_config_deinit (fhl->config);
+ mc_fhl_array_free (fhl);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/filehighlight/get-color.c b/lib/filehighlight/get-color.c
new file mode 100644
index 0000000..cdca7e4
--- /dev/null
+++ b/lib/filehighlight/get-color.c
@@ -0,0 +1,313 @@
+/*
+ File highlight plugin.
+ Interface functions. get color pair index for highlighted file.
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/skin.h"
+#include "lib/util.h" /* is_exe() */
+#include "lib/filehighlight.h"
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/*inline functions */
+inline static gboolean
+mc_fhl_is_file (const file_entry_t * fe)
+{
+#if HAVE_S_ISREG == 0
+ (void) fe;
+#endif
+ return S_ISREG (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_file_exec (const file_entry_t * fe)
+{
+ return is_exe (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_dir (const file_entry_t * fe)
+{
+#if HAVE_S_ISDIR == 0
+ (void) fe;
+#endif
+ return S_ISDIR (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_link (const file_entry_t * fe)
+{
+#if HAVE_S_ISLNK == 0
+ (void) fe;
+#endif
+ return S_ISLNK (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_hlink (const file_entry_t * fe)
+{
+ return (fe->st.st_nlink > 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_link_to_dir (const file_entry_t * fe)
+{
+ return mc_fhl_is_link (fe) && fe->f.link_to_dir != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_stale_link (const file_entry_t * fe)
+{
+ return mc_fhl_is_link (fe) ? (fe->f.stale_link != 0) : !mc_fhl_is_file (fe);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_device_char (const file_entry_t * fe)
+{
+#if HAVE_S_ISCHR == 0
+ (void) fe;
+#endif
+ return S_ISCHR (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_device_block (const file_entry_t * fe)
+{
+#if HAVE_S_ISBLK == 0
+ (void) fe;
+#endif
+ return S_ISBLK (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_special_socket (const file_entry_t * fe)
+{
+#if HAVE_S_ISSOCK == 0
+ (void) fe;
+#endif
+ return S_ISSOCK (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_special_fifo (const file_entry_t * fe)
+{
+#if HAVE_S_ISFIFO == 0
+ (void) fe;
+#endif
+ return S_ISFIFO (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_special_door (const file_entry_t * fe)
+{
+#if HAVE_S_ISDOOR == 0
+ (void) fe;
+#endif
+ return S_ISDOOR (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static gboolean
+mc_fhl_is_special (const file_entry_t * fe)
+{
+ return
+ (mc_fhl_is_special_socket (fe) || mc_fhl_is_special_fifo (fe)
+ || mc_fhl_is_special_door (fe));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mc_fhl_get_color_filetype (const mc_fhl_filter_t * mc_filter, const mc_fhl_t * fhl,
+ const file_entry_t * fe)
+{
+ gboolean my_color = FALSE;
+
+ (void) fhl;
+
+ switch (mc_filter->file_type)
+ {
+ case MC_FLHGH_FTYPE_T_FILE:
+ if (mc_fhl_is_file (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_FILE_EXE:
+ if (mc_fhl_is_file (fe) && mc_fhl_is_file_exec (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_DIR:
+ if (mc_fhl_is_dir (fe) || mc_fhl_is_link_to_dir (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_LINK_DIR:
+ if (mc_fhl_is_link_to_dir (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_LINK:
+ if (mc_fhl_is_link (fe) || mc_fhl_is_hlink (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_HARDLINK:
+ if (mc_fhl_is_hlink (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_SYMLINK:
+ if (mc_fhl_is_link (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_STALE_LINK:
+ if (mc_fhl_is_stale_link (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_DEVICE:
+ if (mc_fhl_is_device_char (fe) || mc_fhl_is_device_block (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_DEVICE_BLOCK:
+ if (mc_fhl_is_device_block (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_DEVICE_CHAR:
+ if (mc_fhl_is_device_char (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_SPECIAL:
+ if (mc_fhl_is_special (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_SPECIAL_SOCKET:
+ if (mc_fhl_is_special_socket (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_SPECIAL_FIFO:
+ if (mc_fhl_is_special_fifo (fe))
+ my_color = TRUE;
+ break;
+ case MC_FLHGH_FTYPE_T_SPECIAL_DOOR:
+ if (mc_fhl_is_special_door (fe))
+ my_color = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ return my_color ? mc_filter->color_pair_index : -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mc_fhl_get_color_regexp (const mc_fhl_filter_t * mc_filter, const mc_fhl_t * fhl,
+ const file_entry_t * fe)
+{
+ (void) fhl;
+
+ if (mc_filter->search_condition == NULL)
+ return -1;
+
+ if (mc_search_run (mc_filter->search_condition, fe->fname->str, 0, fe->fname->len, NULL))
+ return mc_filter->color_pair_index;
+
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_fhl_get_color (const mc_fhl_t * fhl, const file_entry_t * fe)
+{
+ guint i;
+ int ret;
+
+ if (fhl == NULL)
+ return NORMAL_COLOR;
+
+ for (i = 0; i < fhl->filters->len; i++)
+ {
+ mc_fhl_filter_t *mc_filter;
+
+ mc_filter = (mc_fhl_filter_t *) g_ptr_array_index (fhl->filters, i);
+ switch (mc_filter->type)
+ {
+ case MC_FLHGH_T_FTYPE:
+ ret = mc_fhl_get_color_filetype (mc_filter, fhl, fe);
+ if (ret > 0)
+ return -ret;
+ break;
+ case MC_FLHGH_T_EXT:
+ case MC_FLHGH_T_FREGEXP:
+ ret = mc_fhl_get_color_regexp (mc_filter, fhl, fe);
+ if (ret > 0)
+ return -ret;
+ break;
+ default:
+ break;
+ }
+ }
+ return NORMAL_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/filehighlight/ini-file-read.c b/lib/filehighlight/ini-file-read.c
new file mode 100644
index 0000000..bf12b9b
--- /dev/null
+++ b/lib/filehighlight/ini-file-read.c
@@ -0,0 +1,268 @@
+/*
+ File highlight plugin.
+ Reading and parse rules from ini-files
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/strescape.h"
+#include "lib/skin.h"
+#include "lib/util.h" /* exist_file() */
+
+#include "lib/filehighlight.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_fhl_parse_fill_color_info (mc_fhl_filter_t * mc_filter, mc_fhl_t * fhl, const gchar * group_name)
+{
+ (void) fhl;
+
+ mc_filter->color_pair_index = mc_skin_color_get ("filehighlight", group_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_fhl_parse_get_file_type_id (mc_fhl_t * fhl, const gchar * group_name)
+{
+ mc_fhl_filter_t *mc_filter;
+
+ const gchar *types[] = {
+ "FILE", "FILE_EXE",
+ "DIR", "LINK_DIR",
+ "LINK", "HARDLINK", "SYMLINK",
+ "STALE_LINK",
+ "DEVICE", "DEVICE_BLOCK", "DEVICE_CHAR",
+ "SPECIAL", "SPECIAL_SOCKET", "SPECIAL_FIFO", "SPECIAL_DOOR",
+ NULL
+ };
+ int i;
+ gchar *param_type;
+
+ param_type = mc_config_get_string (fhl->config, group_name, "type", "");
+ if (*param_type == '\0')
+ {
+ g_free (param_type);
+ return FALSE;
+ }
+
+ for (i = 0; types[i] != NULL; i++)
+ if (strcmp (types[i], param_type) == 0)
+ break;
+
+ g_free (param_type);
+
+ if (types[i] == NULL)
+ return FALSE;
+
+ mc_filter = g_new0 (mc_fhl_filter_t, 1);
+ mc_filter->type = MC_FLHGH_T_FTYPE;
+ mc_filter->file_type = (mc_flhgh_ftype_type) i;
+ mc_fhl_parse_fill_color_info (mc_filter, fhl, group_name);
+
+ g_ptr_array_add (fhl->filters, (gpointer) mc_filter);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_fhl_parse_get_regexp (mc_fhl_t * fhl, const gchar * group_name)
+{
+ mc_fhl_filter_t *mc_filter;
+ gchar *regexp;
+
+ regexp = mc_config_get_string (fhl->config, group_name, "regexp", "");
+ if (*regexp == '\0')
+ {
+ g_free (regexp);
+ return FALSE;
+ }
+
+ mc_filter = g_new0 (mc_fhl_filter_t, 1);
+ mc_filter->type = MC_FLHGH_T_FREGEXP;
+ mc_filter->search_condition = mc_search_new (regexp, DEFAULT_CHARSET);
+ mc_filter->search_condition->is_case_sensitive = TRUE;
+ mc_filter->search_condition->search_type = MC_SEARCH_T_REGEX;
+
+ mc_fhl_parse_fill_color_info (mc_filter, fhl, group_name);
+ g_ptr_array_add (fhl->filters, (gpointer) mc_filter);
+ g_free (regexp);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_fhl_parse_get_extensions (mc_fhl_t * fhl, const gchar * group_name)
+{
+ mc_fhl_filter_t *mc_filter;
+ gchar **exts, **exts_orig;
+ GString *buf;
+
+ exts_orig = mc_config_get_string_list (fhl->config, group_name, "extensions", NULL);
+ if (exts_orig == NULL || exts_orig[0] == NULL)
+ {
+ g_strfreev (exts_orig);
+ return FALSE;
+ }
+
+ buf = g_string_sized_new (64);
+
+ for (exts = exts_orig; *exts != NULL; exts++)
+ {
+ char *esc_ext;
+
+ esc_ext = strutils_regex_escape (*exts);
+ if (buf->len != 0)
+ g_string_append_c (buf, '|');
+ g_string_append (buf, esc_ext);
+ g_free (esc_ext);
+ }
+
+ g_strfreev (exts_orig);
+
+ g_string_prepend (buf, ".*\\.(");
+ g_string_append (buf, ")$");
+
+ mc_filter = g_new0 (mc_fhl_filter_t, 1);
+ mc_filter->type = MC_FLHGH_T_FREGEXP;
+ mc_filter->search_condition = mc_search_new_len (buf->str, buf->len, DEFAULT_CHARSET);
+ mc_filter->search_condition->is_case_sensitive =
+ mc_config_get_bool (fhl->config, group_name, "extensions_case", FALSE);
+ mc_filter->search_condition->search_type = MC_SEARCH_T_REGEX;
+
+ mc_fhl_parse_fill_color_info (mc_filter, fhl, group_name);
+ g_ptr_array_add (fhl->filters, (gpointer) mc_filter);
+ g_string_free (buf, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_fhl_read_ini_file (mc_fhl_t * fhl, const gchar * filename)
+{
+ if (fhl == NULL || filename == NULL || !exist_file (filename))
+ return FALSE;
+
+ if (fhl->config != NULL)
+ return mc_config_read_file (fhl->config, filename, TRUE, FALSE);
+
+ fhl->config = mc_config_init (filename, TRUE);
+
+ return (fhl->config != NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_fhl_init_from_standard_files (mc_fhl_t * fhl)
+{
+ gchar *name;
+ gboolean ok;
+
+ /* ${XDG_CONFIG_HOME}/mc/filehighlight.ini */
+ name = mc_config_get_full_path (MC_FHL_INI_FILE);
+ ok = mc_fhl_read_ini_file (fhl, name);
+ g_free (name);
+ if (ok)
+ return TRUE;
+
+ /* ${sysconfdir}/mc/filehighlight.ini */
+ name = g_build_filename (mc_global.sysconfig_dir, MC_FHL_INI_FILE, (char *) NULL);
+ ok = mc_fhl_read_ini_file (fhl, name);
+ g_free (name);
+ if (ok)
+ return TRUE;
+
+ /* ${datadir}/mc/filehighlight.ini */
+ name = g_build_filename (mc_global.share_data_dir, MC_FHL_INI_FILE, (char *) NULL);
+ ok = mc_fhl_read_ini_file (fhl, name);
+ g_free (name);
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_fhl_parse_ini_file (mc_fhl_t * fhl)
+{
+ gchar **group_names, **orig_group_names;
+ gboolean ok;
+
+ mc_fhl_array_free (fhl);
+ fhl->filters = g_ptr_array_new_with_free_func (mc_fhl_filter_free);
+
+ orig_group_names = mc_config_get_groups (fhl->config, NULL);
+ ok = (*orig_group_names != NULL);
+
+ for (group_names = orig_group_names; *group_names != NULL; group_names++)
+ {
+ if (mc_config_has_param (fhl->config, *group_names, "type"))
+ {
+ /* parse filetype filter */
+ mc_fhl_parse_get_file_type_id (fhl, *group_names);
+ }
+ if (mc_config_has_param (fhl->config, *group_names, "regexp"))
+ {
+ /* parse regexp filter */
+ mc_fhl_parse_get_regexp (fhl, *group_names);
+ }
+ if (mc_config_has_param (fhl->config, *group_names, "extensions"))
+ {
+ /* parse extensions filter */
+ mc_fhl_parse_get_extensions (fhl, *group_names);
+ }
+ }
+
+ g_strfreev (orig_group_names);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/filehighlight/internal.h b/lib/filehighlight/internal.h
new file mode 100644
index 0000000..3cf718d
--- /dev/null
+++ b/lib/filehighlight/internal.h
@@ -0,0 +1,61 @@
+#ifndef MC__FILEHIGHLIGHT_INTERNAL_H
+#define MC__FILEHIGHLIGHT_INTERNAL_H
+
+#include "lib/search.h" /* mc_search_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ MC_FLHGH_T_FTYPE,
+ MC_FLHGH_T_EXT,
+ MC_FLHGH_T_FREGEXP
+} mc_flhgh_filter_type;
+
+typedef enum
+{
+ MC_FLHGH_FTYPE_T_FILE,
+ MC_FLHGH_FTYPE_T_FILE_EXE,
+ MC_FLHGH_FTYPE_T_DIR,
+ MC_FLHGH_FTYPE_T_LINK_DIR,
+ MC_FLHGH_FTYPE_T_LINK,
+ MC_FLHGH_FTYPE_T_HARDLINK,
+ MC_FLHGH_FTYPE_T_SYMLINK,
+ MC_FLHGH_FTYPE_T_STALE_LINK,
+ MC_FLHGH_FTYPE_T_DEVICE,
+ MC_FLHGH_FTYPE_T_DEVICE_BLOCK,
+ MC_FLHGH_FTYPE_T_DEVICE_CHAR,
+ MC_FLHGH_FTYPE_T_SPECIAL,
+ MC_FLHGH_FTYPE_T_SPECIAL_SOCKET,
+ MC_FLHGH_FTYPE_T_SPECIAL_FIFO,
+ MC_FLHGH_FTYPE_T_SPECIAL_DOOR,
+} mc_flhgh_ftype_type;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_fhl_filter_struct
+{
+
+ int color_pair_index;
+ gchar *fgcolor;
+ gchar *bgcolor;
+ mc_flhgh_filter_type type;
+ mc_search_t *search_condition;
+ mc_flhgh_ftype_type file_type;
+
+} mc_fhl_filter_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void mc_fhl_filter_free (gpointer data);
+void mc_fhl_array_free (mc_fhl_t * fhl);
+
+gboolean mc_fhl_init_from_standard_files (mc_fhl_t * fhl);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__FILEHIGHLIGHT_INTERNAL_H */
diff --git a/lib/fileloc.h b/lib/fileloc.h
new file mode 100644
index 0000000..ebcfe9d
--- /dev/null
+++ b/lib/fileloc.h
@@ -0,0 +1,95 @@
+/** \file fileloc.h
+ * \brief Header: config files list
+ *
+ * This file defines the locations of the various user specific
+ * configuration files of the Midnight Commander. Historically the
+ * system wide and the user specific file names have not always been
+ * the same, so don't use these names for finding system wide
+ * configuration files.
+ *
+ * \todo This inconsistency should disappear in the one of the next versions (5.0?)
+ */
+
+#ifndef MC_FILELOC_H
+#define MC_FILELOC_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#ifndef MC_USERCONF_DIR
+#define MC_USERCONF_DIR "mc"
+#endif
+
+#define TAGS_NAME "TAGS"
+
+#define MC_GLOBAL_CONFIG_FILE "mc.lib"
+#define MC_GLOBAL_MENU "mc.menu"
+#define MC_LOCAL_MENU ".mc.menu"
+#define MC_HINT "hints" PATH_SEP_STR "mc.hint"
+#define MC_HELP "help" PATH_SEP_STR "mc.hlp"
+#define GLOBAL_KEYMAP_FILE "mc.keymap"
+#define CHARSETS_LIST "mc.charsets"
+#define MC_MACRO_FILE "mc.macros"
+
+#define FISH_PREFIX "fish"
+
+#define FISH_LS_FILE "ls"
+#define FISH_EXISTS_FILE "fexists"
+#define FISH_MKDIR_FILE "mkdir"
+#define FISH_UNLINK_FILE "unlink"
+#define FISH_CHOWN_FILE "chown"
+#define FISH_CHMOD_FILE "chmod"
+#define FISH_UTIME_FILE "utime"
+#define FISH_RMDIR_FILE "rmdir"
+#define FISH_LN_FILE "ln"
+#define FISH_MV_FILE "mv"
+#define FISH_HARDLINK_FILE "hardlink"
+#define FISH_GET_FILE "get"
+#define FISH_SEND_FILE "send"
+#define FISH_APPEND_FILE "append"
+#define FISH_INFO_FILE "info"
+
+#define MC_EXTFS_DIR "extfs.d"
+
+#define MC_BASHRC_FILE "bashrc"
+#define MC_ZSHRC_FILE ".zshrc"
+#define MC_ASHRC_FILE "ashrc"
+#define MC_INPUTRC_FILE "inputrc"
+#define MC_CONFIG_FILE "ini"
+#define MC_EXT_FILE "mc.ext.ini"
+#define MC_EXT_OLD_FILE "mc.ext"
+#define MC_FILEPOS_FILE "filepos"
+#define MC_HISTORY_FILE "history"
+#define MC_HOTLIST_FILE "hotlist"
+#define MC_USERMENU_FILE "menu"
+#define MC_TREESTORE_FILE "Tree"
+#define MC_PANELS_FILE "panels.ini"
+#define MC_FHL_INI_FILE "filehighlight.ini"
+
+#define MC_SKINS_DIR "skins"
+
+/* editor home directory */
+#define EDIT_HOME_DIR "mcedit"
+
+/* file names */
+#define EDIT_HOME_MACRO_FILE EDIT_HOME_DIR PATH_SEP_STR "macros.d" PATH_SEP_STR "macro"
+#define EDIT_HOME_CLIP_FILE EDIT_HOME_DIR PATH_SEP_STR "mcedit.clip"
+#define EDIT_HOME_BLOCK_FILE EDIT_HOME_DIR PATH_SEP_STR "mcedit.block"
+#define EDIT_HOME_TEMP_FILE EDIT_HOME_DIR PATH_SEP_STR "mcedit.temp"
+#define EDIT_SYNTAX_DIR "syntax"
+#define EDIT_SYNTAX_FILE EDIT_SYNTAX_DIR PATH_SEP_STR "Syntax"
+
+#define EDIT_GLOBAL_MENU "mcedit.menu"
+#define EDIT_LOCAL_MENU ".cedit.menu"
+#define EDIT_HOME_MENU EDIT_HOME_DIR PATH_SEP_STR "menu"
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/lib/fs.h b/lib/fs.h
new file mode 100644
index 0000000..40d29f4
--- /dev/null
+++ b/lib/fs.h
@@ -0,0 +1,124 @@
+/** \file fs.h
+ * \brief Header: fs compatibility definitions
+ */
+
+/* Include file to use opendir/closedir/readdir */
+
+#ifndef MC_FS_H
+#define MC_FS_H
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#ifdef S_ISREG
+#define HAVE_S_ISREG 1
+#else
+#define HAVE_S_ISREG 0
+#define S_ISREG(x) 0
+#endif
+
+#ifdef S_ISDIR
+#define HAVE_S_ISDIR 1
+#else
+#define HAVE_S_ISDIR 0
+#define S_ISDIR(x) 0
+#endif
+
+/* Replacement for permission bits missing in sys/stat.h */
+#ifdef S_ISLNK
+#define HAVE_S_ISLNK 1
+#else
+#define HAVE_S_ISLNK 0
+#define S_ISLNK(x) 0
+#endif
+
+#ifdef S_ISSOCK
+#define HAVE_S_ISSOCK 1
+#else
+#define HAVE_S_ISSOCK 0
+#define S_ISSOCK(x) 0
+#endif
+
+#ifdef S_ISFIFO
+#define HAVE_S_ISFIFO 1
+#else
+#define HAVE_S_ISFIFO 0
+#define S_ISFIFO(x) 0
+#endif
+
+#ifdef S_ISCHR
+#define HAVE_S_ISCHR 1
+#else
+#define HAVE_S_ISCHR 0
+#define S_ISCHR(x) 0
+#endif
+
+#ifdef S_ISBLK
+#define HAVE_S_ISBLK 1
+#else
+#define HAVE_S_ISBLK 0
+#define S_ISBLK(x) 0
+#endif
+
+/* Door is something that only exists on Solaris */
+#ifdef S_ISDOOR
+#define HAVE_S_ISDOOR 1
+#else
+#define HAVE_S_ISDOOR 0
+#define S_ISDOOR(x) 0
+#endif
+
+/* Special named files are widely used in QNX6 */
+#ifdef S_ISNAM
+#define HAVE_S_ISNAM 1
+#else
+#define HAVE_S_ISNAM 0
+#define S_ISNAM(x) 0
+#endif
+
+#ifndef PATH_MAX
+#ifdef _POSIX_VERSION
+#define PATH_MAX _POSIX_PATH_MAX
+#else
+#ifdef MAXPATHLEN
+#define PATH_MAX MAXPATHLEN
+#else
+#define PATH_MAX 1024
+#endif
+#endif
+#endif
+
+#ifndef MAXPATHLEN
+#define MC_MAXPATHLEN 4096
+#else
+#define MC_MAXPATHLEN MAXPATHLEN
+#endif
+
+/* unistd.h defines _POSIX_VERSION on POSIX.1 systems. */
+#define NLENGTH(dirent) (strlen ((dirent)->d_name))
+
+/* DragonFlyBSD doesn't provide MAXNAMLEN macro */
+#ifndef MAXNAMLEN
+#define MAXNAMLEN NAME_MAX
+#endif
+
+#define MC_MAXFILENAMELEN MAXNAMLEN
+
+#define DIR_IS_DOT(x) ((x)[0] == '.' && (x)[1] == '\0')
+#define DIR_IS_DOTDOT(x) ((x)[0] == '.' && (x)[1] == '.' && (x)[2] == '\0')
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/lib/glibcompat.c b/lib/glibcompat.c
new file mode 100644
index 0000000..0522c0f
--- /dev/null
+++ b/lib/glibcompat.c
@@ -0,0 +1,210 @@
+/*
+ GLIB - Library of useful routines for C programming
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009, 2013.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file glibcompat.c
+ * \brief Source: compatibility with older versions of glib
+ *
+ * Following code was copied from glib to GNU Midnight Commander to
+ * provide compatibility with older versions of glib.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "global.h"
+#include "glibcompat.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#if ! GLIB_CHECK_VERSION (2, 63, 3)
+/**
+ * g_clear_slist: (skip)
+ * @slist_ptr: (not nullable): a #GSList return location
+ * @destroy: (nullable): the function to pass to g_slist_free_full() or NULL to not free elements
+ *
+ * Clears a pointer to a #GSList, freeing it and, optionally, freeing its elements using @destroy.
+ *
+ * @slist_ptr must be a valid pointer. If @slist_ptr points to a null #GSList, this does nothing.
+ *
+ * Since: 2.64
+ */
+void
+g_clear_slist (GSList ** slist_ptr, GDestroyNotify destroy)
+{
+ GSList *slist;
+
+ slist = *slist_ptr;
+
+ if (slist != NULL)
+ {
+ *slist_ptr = NULL;
+
+ if (destroy != NULL)
+ g_slist_free_full (slist, destroy);
+ else
+ g_slist_free (slist);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * g_clear_list:
+ * @list_ptr: (not nullable): a #GList return location
+ * @destroy: (nullable): the function to pass to g_list_free_full() or NULL to not free elements
+ *
+ * Clears a pointer to a #GList, freeing it and, optionally, freeing its elements using @destroy.
+ *
+ * @list_ptr must be a valid pointer. If @list_ptr points to a null #GList, this does nothing.
+ *
+ * Since: 2.64
+ */
+void
+g_clear_list (GList ** list_ptr, GDestroyNotify destroy)
+{
+ GList *list;
+
+ list = *list_ptr;
+
+ if (list != NULL)
+ {
+ *list_ptr = NULL;
+
+ if (destroy != NULL)
+ g_list_free_full (list, destroy);
+ else
+ g_list_free (list);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* ! GLIB_CHECK_VERSION (2, 63, 3) */
+
+#if ! GLIB_CHECK_VERSION (2, 32, 0)
+/**
+ * g_queue_free_full:
+ * @queue: a pointer to a #GQueue
+ * @free_func: the function to be called to free each element's data
+ *
+ * Convenience method, which frees all the memory used by a #GQueue,
+ * and calls the specified destroy function on every element's data.
+ *
+ * Since: 2.32
+ */
+void
+g_queue_free_full (GQueue * queue, GDestroyNotify free_func)
+{
+ g_queue_foreach (queue, (GFunc) free_func, NULL);
+ g_queue_free (queue);
+}
+#endif /* ! GLIB_CHECK_VERSION (2, 32, 0) */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if ! GLIB_CHECK_VERSION (2, 60, 0)
+/**
+ * g_queue_clear_full:
+ * @queue: a pointer to a #GQueue
+ * @free_func: (nullable): the function to be called to free memory allocated
+ *
+ * Convenience method, which frees all the memory used by a #GQueue,
+ * and calls the provided @free_func on each item in the #GQueue.
+ *
+ * Since: 2.60
+ */
+void
+g_queue_clear_full (GQueue * queue, GDestroyNotify free_func)
+{
+ g_return_if_fail (queue != NULL);
+
+ if (free_func != NULL)
+ g_queue_foreach (queue, (GFunc) free_func, NULL);
+
+ g_queue_clear (queue);
+}
+#endif /* ! GLIB_CHECK_VERSION (2, 60, 0) */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * mc_g_string_copy:
+ * @dest: (not nullable): the destination #GString. Its current contents are destroyed
+ * @src: (not nullable): the source #GString
+ * @return: @dest
+ *
+ * Copies the bytes from a #GString into a #GString, destroying any previous contents.
+ * It is rather like the standard strcpy() function, except that you do not have to worry about
+ * having enough space to copy the string.
+ *
+ * There is no such API in GLib2.
+ */
+GString *
+mc_g_string_copy (GString * dest, const GString * src)
+{
+ g_return_val_if_fail (src != NULL, NULL);
+ g_return_val_if_fail (dest != NULL, NULL);
+
+ g_string_set_size (dest, 0);
+ g_string_append_len (dest, src->str, src->len);
+
+ return dest;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * mc_g_string_dup:
+ * @s: (nullable): the source #GString
+ * @return: @copy of @s
+ *
+ * Copies the bytes from one #GString to another.
+ *
+ * There is no such API in GLib2.
+ */
+GString *
+mc_g_string_dup (const GString * s)
+{
+ GString *ret = NULL;
+
+ if (s != NULL)
+ ret = g_string_new_len (s->str, s->len);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/glibcompat.h b/lib/glibcompat.h
new file mode 100644
index 0000000..cb40966
--- /dev/null
+++ b/lib/glibcompat.h
@@ -0,0 +1,40 @@
+#ifndef MC_GLIBCOMPAT_H
+#define MC_GLIBCOMPAT_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#ifndef G_OPTION_ENTRY_NULL
+#define G_OPTION_ENTRY_NULL \
+ { NULL, '\0', 0, 0, NULL, NULL, NULL }
+#endif /* G_OPTION_ENTRY_NULL */
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+#if ! GLIB_CHECK_VERSION (2, 63, 3)
+void g_clear_slist (GSList ** slist_ptr, GDestroyNotify destroy);
+void g_clear_list (GList ** list_ptr, GDestroyNotify destroy);
+#endif /* ! GLIB_CHECK_VERSION (2, 63, 3) */
+
+#if ! GLIB_CHECK_VERSION (2, 32, 0)
+void g_queue_free_full (GQueue * queue, GDestroyNotify free_func);
+#endif /* ! GLIB_CHECK_VERSION (2, 32, 0) */
+
+#if ! GLIB_CHECK_VERSION (2, 60, 0)
+void g_queue_clear_full (GQueue * queue, GDestroyNotify free_func);
+#endif /* ! GLIB_CHECK_VERSION (2, 60, 0) */
+
+/* There is no such API in GLib2 */
+GString *mc_g_string_copy (GString * dest, const GString * src);
+
+/* There is no such API in GLib2 */
+GString *mc_g_string_dup (const GString * s);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_GLIBCOMPAT_H */
diff --git a/lib/global.c b/lib/global.c
new file mode 100644
index 0000000..0b1639d
--- /dev/null
+++ b/lib/global.c
@@ -0,0 +1,137 @@
+/*
+ Global structure for some library-related variables
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file glibcompat.c
+ * \brief Source: global structure for some library-related variables
+ *
+ */
+
+#include <config.h>
+
+#include "mc-version.h"
+
+#include "global.h"
+
+/* *INDENT-OFF* */
+#ifdef ENABLE_SUBSHELL
+# ifdef SUBSHELL_OPTIONAL
+# define SUBSHELL_USE FALSE
+# else /* SUBSHELL_OPTIONAL */
+# define SUBSHELL_USE TRUE
+# endif /* SUBSHELL_OPTIONAL */
+#else /* !ENABLE_SUBSHELL */
+# define SUBSHELL_USE FALSE
+#endif /* !ENABLE_SUBSHELL */
+/* *INDENT-ON* */
+
+/*** global variables ****************************************************************************/
+
+/* *INDENT-OFF* */
+mc_global_t mc_global =
+{
+ .mc_version = MC_CURRENT_VERSION,
+
+ .mc_run_mode = MC_RUN_FULL,
+ .run_from_parent_mc = FALSE,
+ .midnight_shutdown = FALSE,
+
+ .sysconfig_dir = NULL,
+ .share_data_dir = NULL,
+
+ .profile_name = NULL,
+
+#ifdef HAVE_CHARSET
+ .source_codepage = -1,
+ .display_codepage = -1,
+#else
+ .eight_bit_clean = TRUE,
+ .full_eight_bits = FALSE,
+#endif /* !HAVE_CHARSET */
+ .utf8_display = FALSE,
+
+ .message_visible = TRUE,
+ .keybar_visible = TRUE,
+
+#ifdef ENABLE_BACKGROUND
+ .we_are_background = FALSE,
+#endif /* ENABLE_BACKGROUND */
+
+ .widget =
+ {
+ .confirm_history_cleanup = TRUE,
+ .show_all_if_ambiguous = FALSE,
+ .is_right = FALSE
+ },
+
+ .shell = NULL,
+
+ .tty =
+ {
+ .skin = NULL,
+ .shadows = TRUE,
+ .setup_color_string = NULL,
+ .term_color_string = NULL,
+ .color_terminal_string = NULL,
+ .command_line_colors = NULL,
+#ifndef LINUX_CONS_SAVER_C
+ .console_flag = '\0',
+#endif /* !LINUX_CONS_SAVER_C */
+
+ .use_subshell = SUBSHELL_USE,
+
+#ifdef ENABLE_SUBSHELL
+ .subshell_pty = 0,
+#endif /* !ENABLE_SUBSHELL */
+
+ .xterm_flag = FALSE,
+ .disable_x11 = FALSE,
+ .slow_terminal = FALSE,
+ .disable_colors = FALSE,
+ .ugly_line_drawing = FALSE,
+ .old_mouse = FALSE,
+ .alternate_plus_minus = FALSE
+ },
+
+ .vfs =
+ {
+ .cd_symlinks = TRUE,
+ .preallocate_space = FALSE,
+ }
+
+};
+/* *INDENT-ON* */
+
+#undef SUBSHELL_USE
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/global.h b/lib/global.h
new file mode 100644
index 0000000..9b24537
--- /dev/null
+++ b/lib/global.h
@@ -0,0 +1,292 @@
+/** \file global.h
+ * \brief Header: %global definitions for compatibility
+ *
+ * This file should be included after all system includes and before all local includes.
+ */
+
+#ifndef MC_GLOBAL_H
+#define MC_GLOBAL_H
+
+#if defined(HAVE_STRING_H)
+#include <string.h>
+ /* An ANSI string.h and pre-ANSI memory.h might conflict */
+#elif defined(HAVE_MEMORY_H)
+#include <memory.h>
+#else
+#include <strings.h>
+ /* memory and strings.h conflict on other systems */
+#endif /* !STDC_HEADERS & !HAVE_STRING_H */
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+/* for O_* macros */
+#include <fcntl.h>
+
+/* for sig_atomic_t */
+#include <signal.h>
+
+#ifdef HAVE_FUNC_ATTRIBUTE_FALLTHROUGH
+#define MC_FALLTHROUGH __attribute__((fallthrough))
+#else
+#define MC_FALLTHROUGH
+#endif
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* The O_BINARY definition was taken from gettext */
+#if !defined O_BINARY && defined _O_BINARY
+ /* For MSC-compatible compilers. */
+#define O_BINARY _O_BINARY
+#endif
+#ifdef __BEOS__
+ /* BeOS 5 has O_BINARY, but is has no effect. */
+#undef O_BINARY
+#endif
+/* On reasonable systems, binary I/O is the default. */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+/* Replacement for O_NONBLOCK */
+#ifndef O_NONBLOCK
+#ifdef O_NDELAY /* SYSV */
+#define O_NONBLOCK O_NDELAY
+#else /* BSD */
+#define O_NONBLOCK FNDELAY
+#endif /* !O_NDELAY */
+#endif /* !O_NONBLOCK */
+
+#if defined(__QNX__) && !defined(__QNXNTO__)
+/* exec*() from <process.h> */
+#include <unix.h>
+#endif
+
+#include <glib.h>
+#include "glibcompat.h"
+
+/* Solaris9 doesn't have PRIXMAX */
+#ifndef PRIXMAX
+#define PRIXMAX PRIxMAX
+#endif
+
+#ifdef ENABLE_NLS
+#include <libintl.h>
+#define _(String) gettext (String)
+#ifdef gettext_noop
+#define N_(String) gettext_noop (String)
+#else
+#define N_(String) (String)
+#endif
+#else /* Stubs that do something close enough. */
+#define textdomain(String) 1
+#define gettext(String) (String)
+#define ngettext(String1,String2,Num) (((Num) == 1) ? (String1) : (String2))
+#define dgettext(Domain,Message) (Message)
+#define dcgettext(Domain,Message,Type) (Message)
+#define bindtextdomain(Domain,Directory) 1
+#define _(String) (String)
+#define N_(String) (String)
+#endif /* !ENABLE_NLS */
+
+#include "fs.h"
+#include "shell.h"
+#include "mcconfig.h"
+
+#ifdef USE_MAINTAINER_MODE
+#include "lib/logging.h"
+#endif
+
+/* Just for keeping Your's brains from invention a proper size of the buffer :-) */
+#define BUF_10K 10240L
+#define BUF_8K 8192L
+#define BUF_4K 4096L
+#define BUF_1K 1024L
+
+#define BUF_LARGE BUF_1K
+#define BUF_MEDIUM 512
+#define BUF_SMALL 128
+#define BUF_TINY 64
+
+/* ESC_CHAR is defined in /usr/include/langinfo.h in some systems */
+#ifdef ESC_CHAR
+#undef ESC_CHAR
+#endif
+/* AIX compiler doesn't understand '\e' */
+#define ESC_CHAR '\033'
+#define ESC_STR "\033"
+
+/* OS specific defines */
+#define PATH_SEP '/'
+#define PATH_SEP_STR "/"
+#define IS_PATH_SEP(c) ((c) == PATH_SEP)
+#define PATH_ENV_SEP ':'
+#define TMPDIR_DEFAULT "/tmp"
+#define SCRIPT_SUFFIX ""
+#define get_default_editor() "vi"
+#define OS_SORT_CASE_SENSITIVE_DEFAULT TRUE
+#define UTF8_CHAR_LEN 6
+
+/* struct stat members */
+#ifdef __APPLE__
+#define st_atim st_atimespec
+#define st_ctim st_ctimespec
+#define st_mtim st_mtimespec
+#endif
+
+/* Used to distinguish between a normal MC termination and */
+/* one caused by typing 'exit' or 'logout' in the subshell */
+#define SUBSHELL_EXIT 128
+
+#define MC_ERROR g_quark_from_static_string (PACKAGE)
+
+#define DEFAULT_CHARSET "ASCII"
+
+/*** enums ***************************************************************************************/
+
+/* run mode and params */
+typedef enum
+{
+ MC_RUN_FULL = 0,
+ MC_RUN_EDITOR,
+ MC_RUN_VIEWER,
+ MC_RUN_DIFFVIEWER
+} mc_run_mode_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ const char *mc_version;
+
+ mc_run_mode_t mc_run_mode;
+ gboolean run_from_parent_mc;
+ /* Used so that widgets know if they are being destroyed or shut down */
+ gboolean midnight_shutdown;
+
+ /* sysconfig_dir: Area for default settings from maintainers of distributuves
+ default is /etc/mc or may be defined by MC_DATADIR */
+ char *sysconfig_dir;
+ /* share_data_dir: Area for default settings from developers */
+ char *share_data_dir;
+
+ char *profile_name;
+
+ mc_config_t *main_config;
+ mc_config_t *panels_config;
+
+#ifdef HAVE_CHARSET
+ /* Numbers of (file I/O) and (input/display) codepages. -1 if not selected */
+ int source_codepage;
+ int display_codepage;
+#else
+ /* If true, allow characters in the range 160-255 */
+ gboolean eight_bit_clean;
+ /*
+ * If true, also allow characters in the range 128-159.
+ * This is reported to break on many terminals (xterm, qansi-m).
+ */
+ gboolean full_eight_bits;
+#endif /* !HAVE_CHARSET */
+ /*
+ * If utf-8 terminal utf8_display = TRUE
+ * Display bits set UTF-8
+ */
+ gboolean utf8_display;
+
+ /* Set if the nice message (hint) bar is visible */
+ gboolean message_visible;
+ /* Set if the nice and useful keybar is visible */
+ gboolean keybar_visible;
+
+#ifdef ENABLE_BACKGROUND
+ /* If true, this is a background process */
+ gboolean we_are_background;
+#endif /* ENABLE_BACKGROUND */
+
+ struct
+ {
+ /* Asks for confirmation before clean up of history */
+ gboolean confirm_history_cleanup;
+
+ /* Set if you want the possible completions dialog for the first time */
+ gboolean show_all_if_ambiguous;
+
+ /* Ugly hack in order to distinguish between left and right panel in menubar */
+ /* Set if the command is being run from the "Right" menu */
+ gboolean is_right; /* If the selected menu was the right */
+ } widget;
+
+ /* The user's shell */
+ mc_shell_t *shell;
+
+ struct
+ {
+ /* Use the specified skin */
+ char *skin;
+ /* Dialog window and drop down menu have a shadow */
+ gboolean shadows;
+
+ char *setup_color_string;
+ char *term_color_string;
+ char *color_terminal_string;
+ /* colors specified on the command line: they override any other setting */
+ char *command_line_colors;
+
+#ifndef LINUX_CONS_SAVER_C
+ /* Used only in mc, not in cons.saver */
+ char console_flag;
+#endif /* !LINUX_CONS_SAVER_C */
+ /* If using a subshell for evaluating commands this is true */
+ gboolean use_subshell;
+
+#ifdef ENABLE_SUBSHELL
+ /* File descriptors of the pseudoterminal used by the subshell */
+ int subshell_pty;
+#endif /* !ENABLE_SUBSHELL */
+
+ /* This flag is set by xterm detection routine in function main() */
+ /* It is used by function toggle_subshell() */
+ gboolean xterm_flag;
+
+ /* disable x11 support */
+ gboolean disable_x11;
+
+ /* For slow terminals */
+ /* If true lines are shown by spaces */
+ gboolean slow_terminal;
+
+ /* Set to force black and white display at program startup */
+ gboolean disable_colors;
+
+ /* If true use +, -, | for line drawing */
+ gboolean ugly_line_drawing;
+
+ /* Tries to use old highlight mouse tracking */
+ gboolean old_mouse;
+
+ /* If true, use + and \ keys normally and select/unselect do if M-+ / M-\.
+ and M-- and keypad + / - */
+ gboolean alternate_plus_minus;
+ } tty;
+
+ struct
+ {
+ /* Set when cd symlink following is desirable (bash mode) */
+ gboolean cd_symlinks;
+
+ /* Preallocate space before file copying */
+ gboolean preallocate_space;
+
+ } vfs;
+} mc_global_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern mc_global_t mc_global;
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/lib/hook.c b/lib/hook.c
new file mode 100644
index 0000000..c67d75c
--- /dev/null
+++ b/lib/hook.c
@@ -0,0 +1,133 @@
+/*
+ Hooks.
+
+ Slavaz: Warning! this file is deprecated and should be replaced
+ by mcevents functional.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996
+ Janne Kukonlehto, 1994, 1995, 1996
+ Dugan Porter, 1994, 1995, 1996
+ Jakub Jelinek, 1994, 1995, 1996
+ Mauricio Plaza, 1994, 1995, 1996
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: hooks
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/hook.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+add_hook (hook_t ** hook_list, void (*hook_fn) (void *), void *data)
+{
+ hook_t *new_hook = g_new (hook_t, 1);
+
+ new_hook->hook_fn = hook_fn;
+ new_hook->next = *hook_list;
+ new_hook->hook_data = data;
+
+ *hook_list = new_hook;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+execute_hooks (hook_t * hook_list)
+{
+ hook_t *new_hook = NULL;
+ hook_t *p;
+
+ /* We copy the hook list first so tahat we let the hook
+ * function call delete_hook
+ */
+
+ while (hook_list != NULL)
+ {
+ add_hook (&new_hook, hook_list->hook_fn, hook_list->hook_data);
+ hook_list = hook_list->next;
+ }
+ p = new_hook;
+
+ while (new_hook != NULL)
+ {
+ new_hook->hook_fn (new_hook->hook_data);
+ new_hook = new_hook->next;
+ }
+
+ for (hook_list = p; hook_list != NULL;)
+ {
+ p = hook_list;
+ hook_list = hook_list->next;
+ g_free (p);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+delete_hook (hook_t ** hook_list, void (*hook_fn) (void *))
+{
+ hook_t *new_list = NULL;
+ hook_t *current, *next;
+
+ for (current = *hook_list; current != NULL; current = next)
+ {
+ next = current->next;
+ if (current->hook_fn == hook_fn)
+ g_free (current);
+ else
+ add_hook (&new_list, current->hook_fn, current->hook_data);
+ }
+ *hook_list = new_list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+hook_present (hook_t * hook_list, void (*hook_fn) (void *))
+{
+ hook_t *p;
+
+ for (p = hook_list; p != NULL; p = p->next)
+ if (p->hook_fn == hook_fn)
+ return TRUE;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/hook.h b/lib/hook.h
new file mode 100644
index 0000000..0cd3f46
--- /dev/null
+++ b/lib/hook.h
@@ -0,0 +1,34 @@
+/** \file lib/hook.h
+ * \brief Header: hooks
+ */
+
+#ifndef MC_HOOK_H
+#define MC_HOOK_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct hook_t
+{
+ void (*hook_fn) (void *);
+ void *hook_data;
+ struct hook_t *next;
+} hook_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void add_hook (hook_t ** hook_list, void (*hook_fn) (void *), void *data);
+void execute_hooks (hook_t * hook_list);
+void delete_hook (hook_t ** hook_list, void (*hook_fn) (void *));
+gboolean hook_present (hook_t * hook_list, void (*hook_fn) (void *));
+
+/*** inline functions **************************************************/
+
+#endif /* MC_HOOK_H */
diff --git a/lib/keybind.c b/lib/keybind.c
new file mode 100644
index 0000000..ebbc82e
--- /dev/null
+++ b/lib/keybind.c
@@ -0,0 +1,509 @@
+/*
+ Definitions of key bindings.
+
+ Copyright (C) 2005-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Vitja Makarov, 2005
+ Ilia Maslakov <il.smind@gmail.com>, 2009, 2012
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2020
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/tty/key.h" /* KEY_M_ */
+#include "lib/keybind.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define ADD_KEYMAP_NAME(name) \
+ { #name, CK_##name }
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct name_keymap_t
+{
+ const char *name;
+ long val;
+} name_keymap_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static name_keymap_t command_names[] = {
+ /* common */
+ ADD_KEYMAP_NAME (InsertChar),
+ ADD_KEYMAP_NAME (Enter),
+ ADD_KEYMAP_NAME (ChangePanel),
+ ADD_KEYMAP_NAME (Up),
+ ADD_KEYMAP_NAME (Down),
+ ADD_KEYMAP_NAME (Left),
+ ADD_KEYMAP_NAME (Right),
+ ADD_KEYMAP_NAME (LeftQuick),
+ ADD_KEYMAP_NAME (RightQuick),
+ ADD_KEYMAP_NAME (Home),
+ ADD_KEYMAP_NAME (End),
+ ADD_KEYMAP_NAME (PageUp),
+ ADD_KEYMAP_NAME (PageDown),
+ ADD_KEYMAP_NAME (HalfPageUp),
+ ADD_KEYMAP_NAME (HalfPageDown),
+ ADD_KEYMAP_NAME (Top),
+ ADD_KEYMAP_NAME (Bottom),
+ ADD_KEYMAP_NAME (TopOnScreen),
+ ADD_KEYMAP_NAME (MiddleOnScreen),
+ ADD_KEYMAP_NAME (BottomOnScreen),
+ ADD_KEYMAP_NAME (WordLeft),
+ ADD_KEYMAP_NAME (WordRight),
+ ADD_KEYMAP_NAME (Copy),
+ ADD_KEYMAP_NAME (Move),
+ ADD_KEYMAP_NAME (Delete),
+ ADD_KEYMAP_NAME (MakeDir),
+ ADD_KEYMAP_NAME (ChangeMode),
+ ADD_KEYMAP_NAME (ChangeOwn),
+ ADD_KEYMAP_NAME (ChangeOwnAdvanced),
+#ifdef ENABLE_EXT2FS_ATTR
+ ADD_KEYMAP_NAME (ChangeAttributes),
+#endif
+ ADD_KEYMAP_NAME (Remove),
+ ADD_KEYMAP_NAME (BackSpace),
+ ADD_KEYMAP_NAME (Redo),
+ ADD_KEYMAP_NAME (Clear),
+ ADD_KEYMAP_NAME (Menu),
+ ADD_KEYMAP_NAME (MenuLastSelected),
+ ADD_KEYMAP_NAME (UserMenu),
+ ADD_KEYMAP_NAME (EditUserMenu),
+ ADD_KEYMAP_NAME (Search),
+ ADD_KEYMAP_NAME (SearchContinue),
+ ADD_KEYMAP_NAME (Replace),
+ ADD_KEYMAP_NAME (ReplaceContinue),
+ ADD_KEYMAP_NAME (Help),
+ ADD_KEYMAP_NAME (Shell),
+ ADD_KEYMAP_NAME (Edit),
+ ADD_KEYMAP_NAME (EditNew),
+#ifdef HAVE_CHARSET
+ ADD_KEYMAP_NAME (SelectCodepage),
+#endif
+ ADD_KEYMAP_NAME (EditorViewerHistory),
+ ADD_KEYMAP_NAME (History),
+ ADD_KEYMAP_NAME (HistoryNext),
+ ADD_KEYMAP_NAME (HistoryPrev),
+ ADD_KEYMAP_NAME (Complete),
+ ADD_KEYMAP_NAME (Save),
+ ADD_KEYMAP_NAME (SaveAs),
+ ADD_KEYMAP_NAME (Goto),
+ ADD_KEYMAP_NAME (Reread),
+ ADD_KEYMAP_NAME (Refresh),
+ ADD_KEYMAP_NAME (Suspend),
+ ADD_KEYMAP_NAME (Swap),
+ ADD_KEYMAP_NAME (HotList),
+ ADD_KEYMAP_NAME (SelectInvert),
+ ADD_KEYMAP_NAME (ScreenList),
+ ADD_KEYMAP_NAME (ScreenNext),
+ ADD_KEYMAP_NAME (ScreenPrev),
+ ADD_KEYMAP_NAME (FileNext),
+ ADD_KEYMAP_NAME (FilePrev),
+ ADD_KEYMAP_NAME (DeleteToHome),
+ ADD_KEYMAP_NAME (DeleteToEnd),
+ ADD_KEYMAP_NAME (DeleteToWordBegin),
+ ADD_KEYMAP_NAME (DeleteToWordEnd),
+ ADD_KEYMAP_NAME (Cut),
+ ADD_KEYMAP_NAME (Store),
+ ADD_KEYMAP_NAME (Paste),
+ ADD_KEYMAP_NAME (Mark),
+ ADD_KEYMAP_NAME (MarkLeft),
+ ADD_KEYMAP_NAME (MarkRight),
+ ADD_KEYMAP_NAME (MarkUp),
+ ADD_KEYMAP_NAME (MarkDown),
+ ADD_KEYMAP_NAME (MarkToWordBegin),
+ ADD_KEYMAP_NAME (MarkToWordEnd),
+ ADD_KEYMAP_NAME (MarkToHome),
+ ADD_KEYMAP_NAME (MarkToEnd),
+ ADD_KEYMAP_NAME (ToggleNavigation),
+ ADD_KEYMAP_NAME (Sort),
+ ADD_KEYMAP_NAME (Options),
+ ADD_KEYMAP_NAME (LearnKeys),
+ ADD_KEYMAP_NAME (Bookmark),
+ ADD_KEYMAP_NAME (Quit),
+ ADD_KEYMAP_NAME (QuitQuiet),
+ ADD_KEYMAP_NAME (ExtendedKeyMap),
+
+ /* main commands */
+#ifdef USE_INTERNAL_EDIT
+ ADD_KEYMAP_NAME (EditForceInternal),
+#endif
+ ADD_KEYMAP_NAME (View),
+ ADD_KEYMAP_NAME (ViewRaw),
+ ADD_KEYMAP_NAME (ViewFile),
+ ADD_KEYMAP_NAME (ViewFiltered),
+ ADD_KEYMAP_NAME (Find),
+ ADD_KEYMAP_NAME (DirSize),
+ ADD_KEYMAP_NAME (CompareDirs),
+#ifdef USE_DIFF_VIEW
+ ADD_KEYMAP_NAME (CompareFiles),
+#endif
+ ADD_KEYMAP_NAME (OptionsVfs),
+ ADD_KEYMAP_NAME (OptionsConfirm),
+ ADD_KEYMAP_NAME (OptionsDisplayBits),
+ ADD_KEYMAP_NAME (EditExtensionsFile),
+ ADD_KEYMAP_NAME (EditFileHighlightFile),
+ ADD_KEYMAP_NAME (LinkSymbolicEdit),
+ ADD_KEYMAP_NAME (ExternalPanelize),
+ ADD_KEYMAP_NAME (Filter),
+#ifdef ENABLE_VFS_FISH
+ ADD_KEYMAP_NAME (ConnectFish),
+#endif
+#ifdef ENABLE_VFS_FTP
+ ADD_KEYMAP_NAME (ConnectFtp),
+#endif
+#ifdef ENABLE_VFS_SFTP
+ ADD_KEYMAP_NAME (ConnectSftp),
+#endif
+ ADD_KEYMAP_NAME (PanelInfo),
+#ifdef ENABLE_BACKGROUND
+ ADD_KEYMAP_NAME (Jobs),
+#endif
+ ADD_KEYMAP_NAME (OptionsLayout),
+ ADD_KEYMAP_NAME (OptionsAppearance),
+ ADD_KEYMAP_NAME (Link),
+ ADD_KEYMAP_NAME (SetupListingFormat),
+ ADD_KEYMAP_NAME (PanelListing),
+#ifdef LISTMODE_EDITOR
+ ADD_KEYMAP_NAME (ListMode),
+#endif
+ ADD_KEYMAP_NAME (OptionsPanel),
+ ADD_KEYMAP_NAME (CdQuick),
+ ADD_KEYMAP_NAME (PanelQuickView),
+ ADD_KEYMAP_NAME (LinkSymbolicRelative),
+ ADD_KEYMAP_NAME (VfsList),
+ ADD_KEYMAP_NAME (SaveSetup),
+ ADD_KEYMAP_NAME (LinkSymbolic),
+ ADD_KEYMAP_NAME (PanelTree),
+ ADD_KEYMAP_NAME (Tree),
+#ifdef ENABLE_VFS_UNDELFS
+ ADD_KEYMAP_NAME (Undelete),
+#endif
+ ADD_KEYMAP_NAME (PutCurrentLink),
+ ADD_KEYMAP_NAME (PutOtherLink),
+ ADD_KEYMAP_NAME (HotListAdd),
+ ADD_KEYMAP_NAME (ShowHidden),
+ ADD_KEYMAP_NAME (SplitVertHoriz),
+ ADD_KEYMAP_NAME (SplitEqual),
+ ADD_KEYMAP_NAME (SplitMore),
+ ADD_KEYMAP_NAME (SplitLess),
+ ADD_KEYMAP_NAME (PutCurrentPath),
+ ADD_KEYMAP_NAME (PutOtherPath),
+ ADD_KEYMAP_NAME (PutCurrentSelected),
+ ADD_KEYMAP_NAME (PutCurrentFullSelected),
+ ADD_KEYMAP_NAME (PutCurrentTagged),
+ ADD_KEYMAP_NAME (PutOtherTagged),
+ ADD_KEYMAP_NAME (Select),
+ ADD_KEYMAP_NAME (Unselect),
+
+ /* panel */
+ ADD_KEYMAP_NAME (SelectExt),
+ ADD_KEYMAP_NAME (ScrollLeft),
+ ADD_KEYMAP_NAME (ScrollRight),
+ ADD_KEYMAP_NAME (PanelOtherCd),
+ ADD_KEYMAP_NAME (PanelOtherCdLink),
+ ADD_KEYMAP_NAME (CopySingle),
+ ADD_KEYMAP_NAME (MoveSingle),
+ ADD_KEYMAP_NAME (DeleteSingle),
+ ADD_KEYMAP_NAME (CdParent),
+ ADD_KEYMAP_NAME (CdChild),
+ ADD_KEYMAP_NAME (Panelize),
+ ADD_KEYMAP_NAME (PanelOtherSync),
+ ADD_KEYMAP_NAME (SortNext),
+ ADD_KEYMAP_NAME (SortPrev),
+ ADD_KEYMAP_NAME (SortReverse),
+ ADD_KEYMAP_NAME (SortByName),
+ ADD_KEYMAP_NAME (SortByExt),
+ ADD_KEYMAP_NAME (SortBySize),
+ ADD_KEYMAP_NAME (SortByMTime),
+ ADD_KEYMAP_NAME (CdParentSmart),
+ ADD_KEYMAP_NAME (CycleListingFormat),
+
+ /* dialog */
+ ADD_KEYMAP_NAME (Ok),
+ ADD_KEYMAP_NAME (Cancel),
+
+ /* input line */
+ ADD_KEYMAP_NAME (Yank),
+
+ /* help */
+ ADD_KEYMAP_NAME (Index),
+ ADD_KEYMAP_NAME (Back),
+ ADD_KEYMAP_NAME (LinkNext),
+ ADD_KEYMAP_NAME (LinkPrev),
+ ADD_KEYMAP_NAME (NodeNext),
+ ADD_KEYMAP_NAME (NodePrev),
+
+ /* tree */
+ ADD_KEYMAP_NAME (Forget),
+
+#if defined (USE_INTERNAL_EDIT) || defined (USE_DIFF_VIEW)
+ ADD_KEYMAP_NAME (ShowNumbers),
+#endif
+
+ /* chattr dialog */
+ ADD_KEYMAP_NAME (MarkAndDown),
+
+#ifdef USE_INTERNAL_EDIT
+ ADD_KEYMAP_NAME (Close),
+ ADD_KEYMAP_NAME (Tab),
+ ADD_KEYMAP_NAME (Undo),
+ ADD_KEYMAP_NAME (ScrollUp),
+ ADD_KEYMAP_NAME (ScrollDown),
+ ADD_KEYMAP_NAME (Return),
+ ADD_KEYMAP_NAME (ParagraphUp),
+ ADD_KEYMAP_NAME (ParagraphDown),
+ ADD_KEYMAP_NAME (EditFile),
+ ADD_KEYMAP_NAME (MarkWord),
+ ADD_KEYMAP_NAME (MarkLine),
+ ADD_KEYMAP_NAME (MarkAll),
+ ADD_KEYMAP_NAME (Unmark),
+ ADD_KEYMAP_NAME (MarkColumn),
+ ADD_KEYMAP_NAME (BlockSave),
+ ADD_KEYMAP_NAME (InsertFile),
+ ADD_KEYMAP_NAME (InsertOverwrite),
+ ADD_KEYMAP_NAME (Date),
+ ADD_KEYMAP_NAME (DeleteLine),
+ ADD_KEYMAP_NAME (EditMail),
+ ADD_KEYMAP_NAME (ParagraphFormat),
+ ADD_KEYMAP_NAME (MatchBracket),
+ ADD_KEYMAP_NAME (ExternalCommand),
+ ADD_KEYMAP_NAME (MacroStartRecord),
+ ADD_KEYMAP_NAME (MacroStopRecord),
+ ADD_KEYMAP_NAME (MacroStartStopRecord),
+ ADD_KEYMAP_NAME (MacroDelete),
+ ADD_KEYMAP_NAME (RepeatStartStopRecord),
+#ifdef HAVE_ASPELL
+ ADD_KEYMAP_NAME (SpellCheck),
+ ADD_KEYMAP_NAME (SpellCheckCurrentWord),
+ ADD_KEYMAP_NAME (SpellCheckSelectLang),
+#endif /* HAVE_ASPELL */
+ ADD_KEYMAP_NAME (BookmarkFlush),
+ ADD_KEYMAP_NAME (BookmarkNext),
+ ADD_KEYMAP_NAME (BookmarkPrev),
+ ADD_KEYMAP_NAME (MarkPageUp),
+ ADD_KEYMAP_NAME (MarkPageDown),
+ ADD_KEYMAP_NAME (MarkToFileBegin),
+ ADD_KEYMAP_NAME (MarkToFileEnd),
+ ADD_KEYMAP_NAME (MarkToPageBegin),
+ ADD_KEYMAP_NAME (MarkToPageEnd),
+ ADD_KEYMAP_NAME (MarkScrollUp),
+ ADD_KEYMAP_NAME (MarkScrollDown),
+ ADD_KEYMAP_NAME (MarkParagraphUp),
+ ADD_KEYMAP_NAME (MarkParagraphDown),
+ ADD_KEYMAP_NAME (MarkColumnPageUp),
+ ADD_KEYMAP_NAME (MarkColumnPageDown),
+ ADD_KEYMAP_NAME (MarkColumnLeft),
+ ADD_KEYMAP_NAME (MarkColumnRight),
+ ADD_KEYMAP_NAME (MarkColumnUp),
+ ADD_KEYMAP_NAME (MarkColumnDown),
+ ADD_KEYMAP_NAME (MarkColumnScrollUp),
+ ADD_KEYMAP_NAME (MarkColumnScrollDown),
+ ADD_KEYMAP_NAME (MarkColumnParagraphUp),
+ ADD_KEYMAP_NAME (MarkColumnParagraphDown),
+ ADD_KEYMAP_NAME (BlockShiftLeft),
+ ADD_KEYMAP_NAME (BlockShiftRight),
+ ADD_KEYMAP_NAME (InsertLiteral),
+ ADD_KEYMAP_NAME (ShowTabTws),
+ ADD_KEYMAP_NAME (SyntaxOnOff),
+ ADD_KEYMAP_NAME (SyntaxChoose),
+ ADD_KEYMAP_NAME (ShowMargin),
+ ADD_KEYMAP_NAME (OptionsSaveMode),
+ ADD_KEYMAP_NAME (About),
+ /* An action to run external script from macro */
+ {"ExecuteScript", CK_PipeBlock (0)},
+ ADD_KEYMAP_NAME (WindowMove),
+ ADD_KEYMAP_NAME (WindowResize),
+ ADD_KEYMAP_NAME (WindowFullscreen),
+ ADD_KEYMAP_NAME (WindowList),
+ ADD_KEYMAP_NAME (WindowNext),
+ ADD_KEYMAP_NAME (WindowPrev),
+#endif /* USE_INTERNAL_EDIT */
+
+ /* viewer */
+ ADD_KEYMAP_NAME (WrapMode),
+ ADD_KEYMAP_NAME (HexEditMode),
+ ADD_KEYMAP_NAME (HexMode),
+ ADD_KEYMAP_NAME (MagicMode),
+ ADD_KEYMAP_NAME (NroffMode),
+ ADD_KEYMAP_NAME (BookmarkGoto),
+ ADD_KEYMAP_NAME (Ruler),
+ ADD_KEYMAP_NAME (SearchForward),
+ ADD_KEYMAP_NAME (SearchBackward),
+ ADD_KEYMAP_NAME (SearchForwardContinue),
+ ADD_KEYMAP_NAME (SearchBackwardContinue),
+ ADD_KEYMAP_NAME (SearchOppositeContinue),
+
+#ifdef USE_DIFF_VIEW
+ /* diff viewer */
+ ADD_KEYMAP_NAME (ShowSymbols),
+ ADD_KEYMAP_NAME (SplitFull),
+ ADD_KEYMAP_NAME (Tab2),
+ ADD_KEYMAP_NAME (Tab3),
+ ADD_KEYMAP_NAME (Tab4),
+ ADD_KEYMAP_NAME (Tab8),
+ ADD_KEYMAP_NAME (HunkNext),
+ ADD_KEYMAP_NAME (HunkPrev),
+ ADD_KEYMAP_NAME (EditOther),
+ ADD_KEYMAP_NAME (Merge),
+ ADD_KEYMAP_NAME (MergeOther),
+#endif /* USE_DIFF_VIEW */
+
+ {NULL, CK_IgnoreKey}
+};
+
+/* *INDENT-OFF* */
+static const size_t num_command_names = G_N_ELEMENTS (command_names) - 1;
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+name_keymap_comparator (const void *p1, const void *p2)
+{
+ const name_keymap_t *m1 = (const name_keymap_t *) p1;
+ const name_keymap_t *m2 = (const name_keymap_t *) p2;
+
+ return g_ascii_strcasecmp (m1->name, m2->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+sort_command_names (void)
+{
+ static gboolean has_been_sorted = FALSE;
+
+ if (!has_been_sorted)
+ {
+ qsort (command_names, num_command_names,
+ sizeof (command_names[0]), &name_keymap_comparator);
+ has_been_sorted = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+keymap_add (GArray * keymap, long key, long cmd, const char *caption)
+{
+ if (key != 0 && cmd != CK_IgnoreKey)
+ {
+ global_keymap_t new_bind;
+
+ new_bind.key = key;
+ new_bind.command = cmd;
+ g_snprintf (new_bind.caption, sizeof (new_bind.caption), "%s", caption);
+ g_array_append_val (keymap, new_bind);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+keybind_cmd_bind (GArray * keymap, const char *keybind, long action)
+{
+ char *caption = NULL;
+ long key;
+
+ key = tty_keyname_to_keycode (keybind, &caption);
+ keymap_add (keymap, key, action, caption);
+ g_free (caption);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+long
+keybind_lookup_action (const char *name)
+{
+ const name_keymap_t key = { name, 0 };
+ name_keymap_t *res;
+
+ sort_command_names ();
+
+ res = bsearch (&key, command_names, num_command_names,
+ sizeof (command_names[0]), name_keymap_comparator);
+
+ return (res != NULL) ? res->val : CK_IgnoreKey;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+keybind_lookup_actionname (long action)
+{
+ size_t i;
+
+ for (i = 0; command_names[i].name != NULL; i++)
+ if (command_names[i].val == action)
+ return command_names[i].name;
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+keybind_lookup_keymap_shortcut (const global_keymap_t * keymap, long action)
+{
+ if (keymap != NULL)
+ {
+ size_t i;
+
+ for (i = 0; keymap[i].key != 0; i++)
+ if (keymap[i].command == action)
+ return (keymap[i].caption[0] != '\0') ? keymap[i].caption : NULL;
+ }
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+long
+keybind_lookup_keymap_command (const global_keymap_t * keymap, long key)
+{
+ if (keymap != NULL)
+ {
+ size_t i;
+
+ for (i = 0; keymap[i].key != 0; i++)
+ if (keymap[i].key == key)
+ return keymap[i].command;
+ }
+
+ return CK_IgnoreKey;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/keybind.h b/lib/keybind.h
new file mode 100644
index 0000000..9c0fe98
--- /dev/null
+++ b/lib/keybind.h
@@ -0,0 +1,367 @@
+#ifndef MC__KEYBIND_H
+#define MC__KEYBIND_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* keymap sections */
+#define KEYMAP_SECTION_FILEMANAGER "filemanager"
+#define KEYMAP_SECTION_FILEMANAGER_EXT "filemanager:xmap"
+#define KEYMAP_SECTION_PANEL "panel"
+#define KEYMAP_SECTION_DIALOG "dialog"
+#define KEYMAP_SECTION_MENU "menu"
+#define KEYMAP_SECTION_INPUT "input"
+#define KEYMAP_SECTION_LISTBOX "listbox"
+#define KEYMAP_SECTION_RADIO "radio"
+#define KEYMAP_SECTION_TREE "tree"
+#define KEYMAP_SECTION_HELP "help"
+#define KEYMAP_SECTION_CHATTR "chattr"
+#define KEYMAP_SECTION_EDITOR "editor"
+#define KEYMAP_SECTION_EDITOR_EXT "editor:xmap"
+#define KEYMAP_SECTION_VIEWER "viewer"
+#define KEYMAP_SECTION_VIEWER_HEX "viewer:hex"
+#define KEYMAP_SECTION_DIFFVIEWER "diffviewer"
+
+#define KEYMAP_SHORTCUT_LENGTH 32 /* FIXME: is 32 bytes enough for shortcut? */
+
+#define CK_PipeBlock(i) (10000+(i))
+#define CK_Macro(i) (20000+(i))
+#define CK_MacroLast CK_Macro(0x7FFF)
+
+/*** enums ***************************************************************************************/
+
+enum
+{
+ /* special commands */
+ CK_InsertChar = -1L,
+ CK_IgnoreKey = 0L,
+
+ /* common */
+ CK_Enter = 1L,
+ CK_ChangePanel,
+ CK_Up,
+ CK_Down,
+ CK_Left,
+ CK_Right,
+ CK_Home,
+ CK_End,
+ CK_LeftQuick,
+ CK_RightQuick,
+ CK_PageUp,
+ CK_PageDown,
+ CK_HalfPageUp,
+ CK_HalfPageDown,
+ CK_Top,
+ CK_Bottom,
+ CK_TopOnScreen,
+ CK_MiddleOnScreen,
+ CK_BottomOnScreen,
+ CK_WordLeft,
+ CK_WordRight,
+ CK_Copy,
+ CK_Move,
+ CK_Delete,
+ CK_MakeDir,
+ CK_ChangeMode,
+ CK_ChangeOwn,
+ CK_ChangeOwnAdvanced,
+ CK_ChangeAttributes,
+ CK_Remove,
+ CK_BackSpace,
+ CK_Redo,
+ CK_Clear,
+ CK_Menu,
+ CK_MenuLastSelected,
+ CK_UserMenu,
+ CK_EditUserMenu,
+ CK_Search,
+ CK_SearchContinue,
+ CK_Replace,
+ CK_ReplaceContinue,
+ CK_SearchStop,
+ CK_Help,
+ CK_Edit,
+ CK_EditNew,
+ CK_Shell,
+ CK_SelectCodepage,
+ CK_EditorViewerHistory,
+ CK_History,
+ CK_HistoryNext,
+ CK_HistoryPrev,
+ CK_Complete,
+ CK_Save,
+ CK_SaveAs,
+ CK_Goto,
+ CK_Reread,
+ CK_Refresh,
+ CK_Suspend,
+ CK_Swap,
+ CK_Mark,
+ CK_HotList,
+ CK_ScreenList,
+ CK_ScreenNext,
+ CK_ScreenPrev,
+ CK_FilePrev,
+ CK_FileNext,
+ CK_DeleteToHome,
+ CK_DeleteToEnd,
+ CK_DeleteToWordBegin,
+ CK_DeleteToWordEnd,
+ CK_ShowNumbers,
+ CK_Store,
+ CK_Cut,
+ CK_Paste,
+ CK_MarkLeft,
+ CK_MarkRight,
+ CK_MarkUp,
+ CK_MarkDown,
+ CK_MarkToWordBegin,
+ CK_MarkToWordEnd,
+ CK_MarkToHome,
+ CK_MarkToEnd,
+ CK_ToggleNavigation,
+ CK_Sort,
+ CK_Options,
+ CK_LearnKeys,
+ CK_Bookmark,
+ CK_Quit,
+ CK_QuitQuiet,
+ /* C-x or similar */
+ CK_ExtendedKeyMap,
+
+ /* main commands */
+ CK_EditForceInternal = 100L,
+ CK_View,
+ CK_ViewRaw,
+ CK_ViewFile,
+ CK_ViewFiltered,
+ CK_Find,
+ CK_DirSize,
+ CK_HotListAdd,
+ CK_SetupListingFormat,
+ CK_CompareDirs,
+ CK_OptionsVfs,
+ CK_OptionsConfirm,
+ CK_PutCurrentLink,
+ CK_PutOtherLink,
+ CK_OptionsDisplayBits,
+ CK_EditExtensionsFile,
+ CK_EditFileHighlightFile,
+ CK_LinkSymbolicEdit,
+ CK_ExternalPanelize,
+ CK_Filter,
+ CK_ConnectFish,
+ CK_ConnectFtp,
+ CK_ConnectSftp,
+ CK_PanelInfo,
+ CK_Jobs,
+ CK_OptionsLayout,
+ CK_OptionsAppearance,
+ CK_Link,
+ CK_PanelListing,
+ CK_ListMode,
+ CK_CdQuick,
+ CK_PanelQuickView,
+ CK_VfsList,
+ CK_SaveSetup,
+ CK_LinkSymbolic,
+ CK_ShowHidden,
+ CK_PanelTree,
+ CK_Tree,
+ CK_Undelete,
+ CK_SplitVertHoriz,
+ CK_SplitEqual,
+ CK_SplitMore,
+ CK_SplitLess,
+ CK_CompareFiles,
+ CK_OptionsPanel,
+ CK_LinkSymbolicRelative,
+ CK_PutCurrentPath,
+ CK_PutOtherPath,
+ CK_PutCurrentSelected,
+ CK_PutCurrentFullSelected,
+ CK_PutCurrentTagged,
+ CK_PutOtherTagged,
+ CK_Select,
+ CK_Unselect,
+ CK_SelectExt,
+ CK_SelectInvert,
+
+ /* panels */
+ CK_PanelOtherCd = 200L,
+ CK_PanelOtherCdLink,
+ CK_Panelize,
+ CK_CopySingle,
+ CK_MoveSingle,
+ CK_DeleteSingle,
+ CK_CdChild,
+ CK_CdParent,
+ CK_CdParentSmart,
+ CK_PanelOtherSync,
+ CK_SortNext,
+ CK_SortPrev,
+ CK_SortReverse,
+ CK_SortByName,
+ CK_SortByExt,
+ CK_SortBySize,
+ CK_SortByMTime,
+ CK_ScrollLeft,
+ CK_ScrollRight,
+ CK_CycleListingFormat,
+
+ /* dialog */
+ CK_Ok = 300L,
+ CK_Cancel,
+
+ /* input */
+ CK_Yank = 350L,
+
+ /* help */
+ CK_Index = 400L,
+ CK_Back,
+ CK_LinkNext,
+ CK_LinkPrev,
+ CK_NodeNext,
+ CK_NodePrev,
+
+ /* tree */
+ CK_Forget = 450L,
+
+ /* chattr dialog */
+ CK_MarkAndDown = 480L,
+
+ /* editor */
+ /* cursor movements */
+ CK_Tab = 500L,
+ CK_Undo,
+ CK_ScrollUp,
+ CK_ScrollDown,
+ CK_Return,
+ CK_ParagraphUp,
+ CK_ParagraphDown,
+ /* file commands */
+ CK_EditFile,
+ CK_InsertFile,
+ CK_EditSyntaxFile,
+ CK_Close,
+ /* block commands */
+ CK_BlockSave,
+ CK_BlockShiftLeft,
+ CK_BlockShiftRight,
+ CK_DeleteLine,
+ /* bookmarks */
+ CK_BookmarkFlush,
+ CK_BookmarkNext,
+ CK_BookmarkPrev,
+ /* mark commands */
+ CK_MarkColumn,
+ CK_MarkWord,
+ CK_MarkLine,
+ CK_MarkAll,
+ CK_Unmark,
+ CK_MarkPageUp,
+ CK_MarkPageDown,
+ CK_MarkToFileBegin,
+ CK_MarkToFileEnd,
+ CK_MarkToPageBegin,
+ CK_MarkToPageEnd,
+ CK_MarkScrollUp,
+ CK_MarkScrollDown,
+ CK_MarkParagraphUp,
+ CK_MarkParagraphDown,
+ /* column mark commands */
+ CK_MarkColumnPageUp,
+ CK_MarkColumnPageDown,
+ CK_MarkColumnLeft,
+ CK_MarkColumnRight,
+ CK_MarkColumnUp,
+ CK_MarkColumnDown,
+ CK_MarkColumnScrollUp,
+ CK_MarkColumnScrollDown,
+ CK_MarkColumnParagraphUp,
+ CK_MarkColumnParagraphDown,
+ /* macros */
+ CK_MacroStartRecord,
+ CK_MacroStopRecord,
+ CK_MacroStartStopRecord,
+ CK_MacroDelete,
+ CK_RepeatStartRecord,
+ CK_RepeatStopRecord,
+ CK_RepeatStartStopRecord,
+ /* window commands */
+ CK_WindowMove,
+ CK_WindowResize,
+ CK_WindowFullscreen,
+ CK_WindowList,
+ CK_WindowNext,
+ CK_WindowPrev,
+ /* misc commands */
+ CK_SpellCheck,
+ CK_SpellCheckCurrentWord,
+ CK_SpellCheckSelectLang,
+ CK_InsertOverwrite,
+ CK_ParagraphFormat,
+ CK_MatchBracket,
+ CK_OptionsSaveMode,
+ CK_About,
+ CK_ShowMargin,
+ CK_ShowTabTws,
+ CK_SyntaxOnOff,
+ CK_SyntaxChoose,
+ CK_InsertLiteral,
+ CK_ExternalCommand,
+ CK_Date,
+ CK_EditMail,
+
+ /* viewer */
+ CK_WrapMode = 600L,
+ CK_MagicMode,
+ CK_NroffMode,
+ CK_HexMode,
+ CK_HexEditMode,
+ CK_BookmarkGoto,
+ CK_Ruler,
+ CK_SearchForward,
+ CK_SearchBackward,
+ CK_SearchForwardContinue,
+ CK_SearchBackwardContinue,
+ CK_SearchOppositeContinue,
+
+ /* diff viewer */
+ CK_ShowSymbols = 700L,
+ CK_SplitFull,
+ CK_Tab2,
+ CK_Tab3,
+ CK_Tab4,
+ CK_Tab8,
+ CK_HunkNext,
+ CK_HunkPrev,
+ CK_EditOther,
+ CK_Merge,
+ CK_MergeOther
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* The global keymaps are of this type */
+typedef struct global_keymap_t
+{
+ long key;
+ long command;
+ char caption[KEYMAP_SHORTCUT_LENGTH];
+} global_keymap_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void keybind_cmd_bind (GArray * keymap, const char *keybind, long action);
+long keybind_lookup_action (const char *name);
+const char *keybind_lookup_actionname (long action);
+const char *keybind_lookup_keymap_shortcut (const global_keymap_t * keymap, long action);
+long keybind_lookup_keymap_command (const global_keymap_t * keymap, long key);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__KEYBIND_H */
diff --git a/lib/lock.c b/lib/lock.c
new file mode 100644
index 0000000..bae7093
--- /dev/null
+++ b/lib/lock.c
@@ -0,0 +1,314 @@
+/*
+ File locking
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Adam Byrtek, 2003
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: file locking
+ * \author Adam Byrtek
+ * \date 2003
+ *
+ * Locking scheme is based on a documentation found
+ * in JED editor sources. Abstract from lock.c file (by John E. Davis):
+ *
+ * The basic idea here is quite simple. Whenever a buffer is attached to
+ * a file, and that buffer is modified, then attempt to lock the
+ * file. Moreover, before writing to a file for any reason, lock the
+ * file. The lock is really a protocol respected and not a real lock.
+ * The protocol is this: If in the directory of the file is a
+ * symbolic link with name ".#FILE", the FILE is considered to be locked
+ * by the process specified by the link.
+ */
+
+#include <config.h>
+
+#include <signal.h> /* kill() */
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/lock.h"
+#include "lib/widget.h" /* query_dialog() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define BUF_SIZE 255
+#define PID_BUF_SIZE 10
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ char *who;
+ pid_t pid;
+} lock_s;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** \fn static char * lock_build_name (void)
+ * \brief builds user@host.domain.pid string (need to be freed)
+ * \return a pointer to lock filename
+ */
+
+static char *
+lock_build_name (void)
+{
+ char host[BUF_SIZE];
+ const char *user = NULL;
+ struct passwd *pw;
+
+ pw = getpwuid (getuid ());
+ if (pw != NULL)
+ user = pw->pw_name;
+ if (user == NULL)
+ user = getenv ("USER");
+ if (user == NULL)
+ user = getenv ("USERNAME");
+ if (user == NULL)
+ user = getenv ("LOGNAME");
+ if (user == NULL)
+ user = "";
+
+ /** \todo Use FQDN, no clean interface, so requires lot of code */
+ if (gethostname (host, sizeof (host) - 1) == -1)
+ *host = '\0';
+
+ return g_strdup_printf ("%s@%s.%d", user, host, (int) getpid ());
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+lock_build_symlink_name (const vfs_path_t * fname_vpath)
+{
+ const char *elpath;
+ char *str_filename, *str_dirname, *symlink_name;
+
+ /* get first path piece */
+ elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
+
+ str_filename = g_path_get_basename (elpath);
+ str_dirname = g_path_get_dirname (elpath);
+ symlink_name = g_strconcat (str_dirname, PATH_SEP_STR ".#", str_filename, (char *) NULL);
+ g_free (str_dirname);
+ g_free (str_filename);
+
+ return symlink_name;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Extract pid from user@host.domain.pid string
+ */
+
+static lock_s *
+lock_extract_info (const char *str)
+{
+ size_t i, len;
+ const char *p, *s;
+ static char pid[PID_BUF_SIZE], who[BUF_SIZE];
+ static lock_s lock;
+
+ len = strlen (str);
+
+ for (p = str + len - 1; p >= str && *p != '.'; p--)
+ ;
+
+ /* Everything before last '.' is user@host */
+ for (i = 0, s = str; i < sizeof (who) && s < p; i++, s++)
+ who[i] = *s;
+ if (i == sizeof (who))
+ i--;
+ who[i] = '\0';
+
+ /* Treat text between '.' and ':' or '\0' as pid */
+ for (i = 0, p++, s = str + len; i < sizeof (pid) && p < s && *p != ':'; i++, p++)
+ pid[i] = *p;
+ if (i == sizeof (pid))
+ i--;
+ pid[i] = '\0';
+
+ lock.pid = (pid_t) atol (pid);
+ lock.who = who;
+ return &lock;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Extract user@host.domain.pid from lock file (static string)
+ */
+
+static const char *
+lock_get_info (const char *lockfname)
+{
+ ssize_t cnt;
+ static char buf[BUF_SIZE];
+
+ cnt = readlink (lockfname, buf, sizeof (buf) - 1);
+ if (cnt == -1 || *buf == '\0')
+ return NULL;
+ buf[cnt] = '\0';
+ return buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Tries to raise file lock
+ Returns 1 on success, 0 on failure, -1 if abort
+ Warning: Might do screen refresh and lose edit->force */
+
+int
+lock_file (const vfs_path_t * fname_vpath)
+{
+ char *lockfname = NULL, *newlock, *msg;
+ struct stat statbuf;
+ lock_s *lockinfo;
+ gboolean is_local;
+ gboolean symlink_ok = FALSE;
+ const char *elpath;
+
+ if (fname_vpath == NULL)
+ return 0;
+
+ elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
+ /* Just to be sure (and don't lock new file) */
+ if (*elpath == '\0')
+ return 0;
+
+ /* Locking on VFS is not supported */
+ is_local = vfs_file_is_local (fname_vpath);
+ if (is_local)
+ {
+ /* Check if already locked */
+ lockfname = lock_build_symlink_name (fname_vpath);
+ }
+
+ if (!is_local || lockfname == NULL)
+ return 0;
+
+ if (lstat (lockfname, &statbuf) == 0)
+ {
+ const char *lock;
+
+ lock = lock_get_info (lockfname);
+ if (lock == NULL)
+ goto ret;
+ lockinfo = lock_extract_info (lock);
+
+ /* Check if locking process alive, ask user if required */
+ if (lockinfo->pid == 0 || !(kill (lockinfo->pid, 0) == -1 && errno == ESRCH))
+ {
+ msg =
+ g_strdup_printf (_
+ ("File \"%s\" is already being edited.\n"
+ "User: %s\nProcess ID: %d"), x_basename (lockfname) + 2,
+ lockinfo->who, (int) lockinfo->pid);
+ /* TODO: Implement "Abort" - needs to rewind undo stack */
+ switch (query_dialog
+ (_("File locked"), msg, D_NORMAL, 2, _("&Grab lock"), _("&Ignore lock")))
+ {
+ case 0:
+ break;
+ case 1:
+ case -1:
+ default: /* Esc Esc */
+ g_free (msg);
+ goto ret;
+ }
+ g_free (msg);
+ }
+ unlink (lockfname);
+ }
+
+ /* Create lock symlink */
+ newlock = lock_build_name ();
+ symlink_ok = (symlink (newlock, lockfname) != -1);
+ g_free (newlock);
+
+ ret:
+ g_free (lockfname);
+ return symlink_ok ? 1 : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Lowers file lock if possible
+ * @return Always 0
+ */
+
+int
+unlock_file (const vfs_path_t * fname_vpath)
+{
+ char *lockfname;
+ const char *elpath;
+
+ if (fname_vpath == NULL)
+ return 0;
+
+ elpath = vfs_path_get_by_index (fname_vpath, 0)->path;
+ /* Just to be sure (and don't lock new file) */
+ if (*elpath == '\0')
+ return 0;
+
+ lockfname = lock_build_symlink_name (fname_vpath);
+ if (lockfname != NULL)
+ {
+ struct stat statbuf;
+
+ /* Check if lock exists */
+ if (lstat (lockfname, &statbuf) != -1)
+ {
+ const char *lock;
+
+ lock = lock_get_info (lockfname);
+ /* Don't touch if lock is not ours */
+ if (lock == NULL || lock_extract_info (lock)->pid == getpid ())
+ unlink (lockfname);
+ }
+
+ g_free (lockfname);
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/lock.h b/lib/lock.h
new file mode 100644
index 0000000..7cca65f
--- /dev/null
+++ b/lib/lock.h
@@ -0,0 +1,29 @@
+
+/** \file
+ * \brief Header: file locking
+ * \author Adam Byrtek
+ * \date 2003
+ * Look at lock.c for more details
+ */
+
+#ifndef MC_LOCK_H
+#define MC_LOCK_H
+
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int lock_file (const vfs_path_t * fname_vpath);
+int unlock_file (const vfs_path_t * fname_vpath);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_LOCK_H */
diff --git a/lib/logging.c b/lib/logging.c
new file mode 100644
index 0000000..6dd0731
--- /dev/null
+++ b/lib/logging.c
@@ -0,0 +1,168 @@
+/*
+ Provides a log file to ease tracing the program.
+
+ Copyright (C) 2006-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Roland Illig <roland.illig@gmx.de>, 2006
+ Slava Zanko <slavazanko@gmail.com>, 2009, 2011
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file logging.c
+ * \brief Source: provides a log file to ease tracing the program
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/fileloc.h"
+
+#include "logging.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define CONFIG_GROUP_NAME "Development"
+#define CONFIG_KEY_NAME "logging"
+#define CONFIG_KEY_NAME_FILE "logfile"
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static gboolean logging_initialized = FALSE;
+static gboolean logging_enabled = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_logging_enabled_from_env (void)
+{
+ const char *env_is_enabled;
+
+ env_is_enabled = g_getenv ("MC_LOG_ENABLE");
+ if (env_is_enabled == NULL)
+ return FALSE;
+
+ logging_enabled = (*env_is_enabled == '1' || g_ascii_strcasecmp (env_is_enabled, "true") == 0);
+ logging_initialized = TRUE;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_logging_enabled (void)
+{
+
+ if (logging_initialized)
+ return logging_enabled;
+
+ if (is_logging_enabled_from_env ())
+ return logging_enabled;
+
+ logging_enabled =
+ mc_config_get_bool (mc_global.main_config, CONFIG_GROUP_NAME, CONFIG_KEY_NAME, FALSE);
+ logging_initialized = TRUE;
+
+ return logging_enabled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+get_log_filename (void)
+{
+ const char *env_filename;
+
+ env_filename = g_getenv ("MC_LOG_FILE");
+ if (env_filename != NULL)
+ return g_strdup (env_filename);
+
+ if (mc_config_has_param (mc_global.main_config, CONFIG_GROUP_NAME, CONFIG_KEY_NAME_FILE))
+ return mc_config_get_string (mc_global.main_config, CONFIG_GROUP_NAME, CONFIG_KEY_NAME_FILE,
+ NULL);
+
+ return mc_config_get_full_path ("mc.log");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+G_GNUC_PRINTF (1, 0)
+mc_va_log (const char *fmt, va_list args)
+{
+ char *logfilename;
+
+ logfilename = get_log_filename ();
+
+ if (logfilename != NULL)
+ {
+ FILE *f;
+
+ f = fopen (logfilename, "a");
+ if (f != NULL)
+ {
+ (void) vfprintf (f, fmt, args);
+ (void) fclose (f);
+ }
+ g_free (logfilename);
+ }
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_log (const char *fmt, ...)
+{
+ va_list args;
+
+ if (!is_logging_enabled ())
+ return;
+
+ va_start (args, fmt);
+ mc_va_log (fmt, args);
+ va_end (args);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_always_log (const char *fmt, ...)
+{
+ va_list args;
+
+ va_start (args, fmt);
+ mc_va_log (fmt, args);
+ va_end (args);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/logging.h b/lib/logging.h
new file mode 100644
index 0000000..effdfb4
--- /dev/null
+++ b/lib/logging.h
@@ -0,0 +1,32 @@
+/** \file logging.h
+ * \brief Header: provides a log file to ease tracing the program
+ */
+
+#ifndef MC_LOGGING_H
+#define MC_LOGGING_H
+
+/*
+ This file provides an easy-to-use function for writing all kinds of
+ events into a central log file that can be used for debugging.
+ */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define mc_log_mark() mc_log("%s:%d\n",__FILE__,__LINE__)
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* *INDENT-OFF* */
+void mc_log (const char *fmt, ...) G_GNUC_PRINTF (1, 2);
+void mc_always_log (const char *fmt, ...) G_GNUC_PRINTF (1, 2);
+/* *INDENT-ON* */
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/lib/mcconfig.h b/lib/mcconfig.h
new file mode 100644
index 0000000..0c3ab3b
--- /dev/null
+++ b/lib/mcconfig.h
@@ -0,0 +1,116 @@
+#ifndef MC__CONFIG_H
+#define MC__CONFIG_H
+
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define CONFIG_APP_SECTION "Midnight-Commander"
+#define CONFIG_PANELS_SECTION "Panels"
+#define CONFIG_LAYOUT_SECTION "Layout"
+#define CONFIG_MISC_SECTION "Misc"
+#define CONFIG_EXT_EDITOR_VIEWER_SECTION "External editor or viewer parameters"
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_config_t
+{
+ GKeyFile *handle;
+ gchar *ini_path;
+} mc_config_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int num_history_items_recorded;
+
+/*** declarations of public functions ************************************************************/
+
+/* mcconfig/common.c: */
+
+mc_config_t *mc_config_init (const gchar * ini_path, gboolean read_only);
+void mc_config_deinit (mc_config_t * mc_config);
+
+gboolean mc_config_has_param (const mc_config_t * mc_config, const char *group,
+ const gchar * param);
+gboolean mc_config_has_group (mc_config_t * mc_config, const char *group);
+
+gboolean mc_config_del_key (mc_config_t * mc_config, const char *group, const gchar * param);
+gboolean mc_config_del_group (mc_config_t * mc_config, const char *group);
+
+gboolean mc_config_read_file (mc_config_t * mc_config, const gchar * ini_path, gboolean read_only,
+ gboolean remove_empty);
+gboolean mc_config_save_file (mc_config_t * config, GError ** mcerror);
+gboolean mc_config_save_to_file (mc_config_t * mc_config, const gchar * ini_path,
+ GError ** mcerror);
+
+
+/* mcconfig/get.c: */
+
+gchar **mc_config_get_groups (const mc_config_t * mc_config, gsize * len);
+gchar **mc_config_get_keys (const mc_config_t * mc_config, const gchar * group, gsize * len);
+
+gchar *mc_config_get_string (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ const gchar * def);
+gchar *mc_config_get_string_raw (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ const gchar * def);
+gboolean mc_config_get_bool (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ gboolean def);
+int mc_config_get_int (mc_config_t * mc_config, const gchar * group, const gchar * param, int def);
+
+gchar **mc_config_get_string_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, gsize * length);
+gboolean *mc_config_get_bool_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, gsize * length);
+int *mc_config_get_int_list (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ gsize * length);
+
+
+/* mcconfig/set.c: */
+
+void mc_config_set_string_raw (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ const gchar * value);
+void mc_config_set_string_raw_value (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, const gchar * value);
+void mc_config_set_string (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ const gchar * value);
+void mc_config_set_bool (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ gboolean value);
+void mc_config_set_int (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ int value);
+
+void
+mc_config_set_string_list (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ const gchar * const value[], gsize length);
+void mc_config_set_bool_list (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ gboolean value[], gsize length);
+void mc_config_set_int_list (mc_config_t * mc_config, const gchar * group, const gchar * param,
+ int value[], gsize length);
+
+
+/* mcconfig/paths.c: */
+
+void mc_config_init_config_paths (GError ** error);
+void mc_config_deinit_config_paths (void);
+
+const char *mc_config_get_data_path (void);
+const char *mc_config_get_cache_path (void);
+const char *mc_config_get_home_dir (void);
+const char *mc_config_get_path (void);
+char *mc_config_get_full_path (const char *config_name);
+vfs_path_t *mc_config_get_full_vpath (const char *config_name);
+
+/* mcconfig/history.h */
+
+/* read history to the mc_config, but don't save config to file */
+GList *mc_config_history_get (const char *name);
+/* load history from the mc_config */
+GList *mc_config_history_load (mc_config_t * cfg, const char *name);
+/* save history to the mc_config, but don't save config to file */
+void mc_config_history_save (mc_config_t * cfg, const char *name, GList * h);
+
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__CONFIG_H */
diff --git a/lib/mcconfig/Makefile.am b/lib/mcconfig/Makefile.am
new file mode 100644
index 0000000..ad62b91
--- /dev/null
+++ b/lib/mcconfig/Makefile.am
@@ -0,0 +1,11 @@
+
+noinst_LTLIBRARIES = libmcconfig.la
+
+libmcconfig_la_SOURCES = \
+ common.c \
+ get.c \
+ history.c \
+ set.c \
+ paths.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/lib/mcconfig/Makefile.in b/lib/mcconfig/Makefile.in
new file mode 100644
index 0000000..ae9c856
--- /dev/null
+++ b/lib/mcconfig/Makefile.in
@@ -0,0 +1,754 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lib/mcconfig
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcconfig_la_LIBADD =
+am_libmcconfig_la_OBJECTS = common.lo get.lo history.lo set.lo \
+ paths.lo
+libmcconfig_la_OBJECTS = $(am_libmcconfig_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/common.Plo ./$(DEPDIR)/get.Plo \
+ ./$(DEPDIR)/history.Plo ./$(DEPDIR)/paths.Plo \
+ ./$(DEPDIR)/set.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcconfig_la_SOURCES)
+DIST_SOURCES = $(libmcconfig_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcconfig.la
+libmcconfig_la_SOURCES = \
+ common.c \
+ get.c \
+ history.c \
+ set.c \
+ paths.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/mcconfig/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/mcconfig/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcconfig.la: $(libmcconfig_la_OBJECTS) $(libmcconfig_la_DEPENDENCIES) $(EXTRA_libmcconfig_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcconfig_la_OBJECTS) $(libmcconfig_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/get.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/history.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paths.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/set.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/get.Plo
+ -rm -f ./$(DEPDIR)/history.Plo
+ -rm -f ./$(DEPDIR)/paths.Plo
+ -rm -f ./$(DEPDIR)/set.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/get.Plo
+ -rm -f ./$(DEPDIR)/history.Plo
+ -rm -f ./$(DEPDIR)/paths.Plo
+ -rm -f ./$(DEPDIR)/set.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/mcconfig/common.c b/lib/mcconfig/common.c
new file mode 100644
index 0000000..75979be
--- /dev/null
+++ b/lib/mcconfig/common.c
@@ -0,0 +1,287 @@
+/*
+ Configure module for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h> /* extern int errno */
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h" /* mc_stat */
+#include "lib/util.h"
+
+#include "lib/mcconfig.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_config_new_or_override_file (mc_config_t * mc_config, const gchar * ini_path, GError ** mcerror)
+{
+ gchar *data, *written_data;
+ gsize len, total_written;
+ gboolean ret;
+ int fd;
+ ssize_t cur_written;
+ vfs_path_t *ini_vpath;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ data = g_key_file_to_data (mc_config->handle, &len, NULL);
+ if (!exist_file (ini_path))
+ {
+ ret = g_file_set_contents (ini_path, data, len, mcerror);
+ g_free (data);
+ return ret;
+ }
+
+ mc_util_make_backup_if_possible (ini_path, "~");
+
+ ini_vpath = vfs_path_from_str (ini_path);
+ fd = mc_open (ini_vpath, O_WRONLY | O_TRUNC, 0);
+ vfs_path_free (ini_vpath, TRUE);
+
+ if (fd == -1)
+ {
+ mc_propagate_error (mcerror, 0, "%s", unix_error_string (errno));
+ g_free (data);
+ return FALSE;
+ }
+
+ for (written_data = data, total_written = len;
+ (cur_written = mc_write (fd, (const void *) written_data, total_written)) > 0;
+ written_data += cur_written, total_written -= cur_written)
+ ;
+
+ mc_close (fd);
+ g_free (data);
+
+ if (cur_written == -1)
+ {
+ mc_util_restore_from_backup_if_possible (ini_path, "~");
+ mc_propagate_error (mcerror, 0, "%s", unix_error_string (errno));
+ return FALSE;
+ }
+
+ mc_util_unlink_backup_if_possible (ini_path, "~");
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+mc_config_t *
+mc_config_init (const gchar * ini_path, gboolean read_only)
+{
+ mc_config_t *mc_config;
+ struct stat st;
+
+ mc_config = g_try_malloc0 (sizeof (mc_config_t));
+ if (mc_config == NULL)
+ return NULL;
+
+ mc_config->handle = g_key_file_new ();
+ if (mc_config->handle == NULL)
+ {
+ g_free (mc_config);
+ return NULL;
+ }
+
+ if (ini_path == NULL)
+ return mc_config;
+
+ if (exist_file (ini_path))
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (ini_path);
+ if (mc_stat (vpath, &st) == 0 && st.st_size != 0)
+ {
+ GKeyFileFlags flags = G_KEY_FILE_NONE;
+
+ if (!read_only)
+ flags |= G_KEY_FILE_KEEP_COMMENTS;
+
+ /* file exists and not empty */
+ g_key_file_load_from_file (mc_config->handle, ini_path, flags, NULL);
+ }
+ vfs_path_free (vpath, TRUE);
+ }
+
+ mc_config->ini_path = g_strdup (ini_path);
+ return mc_config;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_deinit (mc_config_t * mc_config)
+{
+ if (mc_config != NULL)
+ {
+ g_free (mc_config->ini_path);
+ g_key_file_free (mc_config->handle);
+ g_free (mc_config);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_has_param (const mc_config_t * mc_config, const char *group, const gchar * param)
+{
+ char *value;
+ gboolean ret;
+
+ g_return_val_if_fail (mc_config != NULL, FALSE);
+
+ value = g_key_file_get_value (mc_config->handle, group, param, NULL);
+ ret = value != NULL;
+ g_free (value);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_has_group (mc_config_t * mc_config, const char *group)
+{
+ if (mc_config == NULL || group == NULL)
+ return FALSE;
+
+ return g_key_file_has_group (mc_config->handle, group);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_del_key (mc_config_t * mc_config, const char *group, const gchar * param)
+{
+ if (mc_config == NULL || group == NULL || param == NULL)
+ return FALSE;
+
+ return g_key_file_remove_key (mc_config->handle, group, param, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_del_group (mc_config_t * mc_config, const char *group)
+{
+ if (mc_config == NULL || group == NULL)
+ return FALSE;
+
+ return g_key_file_remove_group (mc_config->handle, group, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_read_file (mc_config_t * mc_config, const gchar * ini_path, gboolean read_only,
+ gboolean remove_empty)
+{
+ mc_config_t *tmp_config;
+ gchar **groups, **curr_grp;
+ gchar *value;
+ gboolean ok;
+
+ if (mc_config == NULL)
+ return FALSE;
+
+ tmp_config = mc_config_init (ini_path, read_only);
+ if (tmp_config == NULL)
+ return FALSE;
+
+ groups = mc_config_get_groups (tmp_config, NULL);
+ ok = (*groups != NULL);
+
+ for (curr_grp = groups; *curr_grp != NULL; curr_grp++)
+ {
+ gchar **keys, **curr_key;
+
+ keys = mc_config_get_keys (tmp_config, *curr_grp, NULL);
+
+ for (curr_key = keys; *curr_key != NULL; curr_key++)
+ {
+ value = g_key_file_get_value (tmp_config->handle, *curr_grp, *curr_key, NULL);
+ if (value != NULL)
+ {
+ if (*value == '\0' && remove_empty)
+ g_key_file_remove_key (mc_config->handle, *curr_grp, *curr_key, NULL);
+ else
+ g_key_file_set_value (mc_config->handle, *curr_grp, *curr_key, value);
+ g_free (value);
+ }
+ else if (remove_empty)
+ g_key_file_remove_key (mc_config->handle, *curr_grp, *curr_key, NULL);
+ }
+ g_strfreev (keys);
+ }
+
+ g_strfreev (groups);
+ mc_config_deinit (tmp_config);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_save_file (mc_config_t * mc_config, GError ** mcerror)
+{
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (mc_config == NULL || mc_config->ini_path == NULL)
+ return FALSE;
+
+ return mc_config_new_or_override_file (mc_config, mc_config->ini_path, mcerror);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_save_to_file (mc_config_t * mc_config, const gchar * ini_path, GError ** mcerror)
+{
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (mc_config == NULL)
+ return FALSE;
+
+ return mc_config_new_or_override_file (mc_config, ini_path, mcerror);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/mcconfig/get.c b/lib/mcconfig/get.c
new file mode 100644
index 0000000..f8ecfb1
--- /dev/null
+++ b/lib/mcconfig/get.c
@@ -0,0 +1,214 @@
+/*
+ Configure module for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+
+#include "lib/mcconfig.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gchar **
+mc_config_get_groups (const mc_config_t * mc_config, gsize * len)
+{
+ gchar **ret = NULL;
+
+ if (mc_config != NULL)
+ ret = g_key_file_get_groups (mc_config->handle, len);
+
+ if (ret == NULL)
+ {
+ ret = g_try_malloc0 (sizeof (gchar **));
+ if (len != NULL)
+ *len = 0;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gchar **
+mc_config_get_keys (const mc_config_t * mc_config, const gchar * group, gsize * len)
+{
+ gchar **ret = NULL;
+
+ if (mc_config != NULL && group != NULL)
+ ret = g_key_file_get_keys (mc_config->handle, group, len, NULL);
+
+ if (ret == NULL)
+ {
+ ret = g_try_malloc0 (sizeof (gchar **));
+ if (len != NULL)
+ *len = 0;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gchar *
+mc_config_get_string (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, const gchar * def)
+{
+ GIConv conv;
+ GString *buffer;
+ gchar *ret;
+ estr_t conv_res;
+
+ ret = mc_config_get_string_raw (mc_config, group, param, def);
+
+ if (mc_global.utf8_display)
+ return ret;
+
+ conv = str_crt_conv_from ("UTF-8");
+ if (conv == INVALID_CONV)
+ return ret;
+
+ buffer = g_string_new ("");
+ conv_res = str_convert (conv, ret, buffer);
+ str_close_conv (conv);
+
+ if (conv_res == ESTR_FAILURE)
+ {
+ g_string_free (buffer, TRUE);
+ return ret;
+ }
+
+ g_free (ret);
+
+ return g_string_free (buffer, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gchar *
+mc_config_get_string_raw (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, const gchar * def)
+{
+ gchar *ret;
+
+ if (mc_config == NULL || group == NULL || param == NULL)
+ return g_strdup (def);
+
+ if (!mc_config_has_param (mc_config, group, param))
+ {
+ if (def != NULL)
+ mc_config_set_string (mc_config, group, param, def);
+ return g_strdup (def);
+ }
+
+ ret = g_key_file_get_string (mc_config->handle, group, param, NULL);
+
+ return ret != NULL ? ret : g_strdup (def);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_config_get_bool (mc_config_t * mc_config, const gchar * group, const gchar * param, gboolean def)
+{
+ if (mc_config == NULL || group == NULL || param == NULL)
+ return def;
+
+ if (!mc_config_has_param (mc_config, group, param))
+ {
+ mc_config_set_bool (mc_config, group, param, def);
+ return def;
+ }
+
+ return g_key_file_get_boolean (mc_config->handle, group, param, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_config_get_int (mc_config_t * mc_config, const gchar * group, const gchar * param, int def)
+{
+ if (mc_config == NULL || group == NULL || param == NULL)
+ return def;
+
+ if (!mc_config_has_param (mc_config, group, param))
+ {
+ mc_config_set_int (mc_config, group, param, def);
+ return def;
+ }
+
+ return g_key_file_get_integer (mc_config->handle, group, param, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gchar **
+mc_config_get_string_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, gsize * length)
+{
+ if (mc_config == NULL || group == NULL || param == NULL)
+ return NULL;
+
+ return g_key_file_get_string_list (mc_config->handle, group, param, length, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean *
+mc_config_get_bool_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, gsize * length)
+{
+ if (mc_config == NULL || group == NULL || param == NULL)
+ return NULL;
+
+ return g_key_file_get_boolean_list (mc_config->handle, group, param, length, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int *
+mc_config_get_int_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, gsize * length)
+{
+ if (mc_config == NULL || group == NULL || param == NULL)
+ return NULL;
+
+ return g_key_file_get_integer_list (mc_config->handle, group, param, length, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/mcconfig/history.c b/lib/mcconfig/history.c
new file mode 100644
index 0000000..bfbdf0a
--- /dev/null
+++ b/lib/mcconfig/history.c
@@ -0,0 +1,219 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2019
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file history.c
+ * \brief Source: save and load history
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/fileloc.h" /* MC_HISTORY_FILE */
+#include "lib/strutil.h"
+#include "lib/util.h" /* list_append_unique */
+
+#include "lib/mcconfig.h"
+
+/*** global variables ****************************************************************************/
+
+/* how much history items are used */
+int num_history_items_recorded = 60;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Load the history from the ${XDG_CACHE_HOME}/mc/history file.
+ * It is called with the widgets history name and returns the GList list.
+ */
+
+GList *
+mc_config_history_get (const char *name)
+{
+ GList *hist = NULL;
+ char *profile;
+ mc_config_t *cfg;
+
+ if (num_history_items_recorded == 0) /* this is how to disable */
+ return NULL;
+ if (name == NULL || *name == '\0')
+ return NULL;
+
+ profile = mc_config_get_full_path (MC_HISTORY_FILE);
+ cfg = mc_config_init (profile, TRUE);
+
+ hist = mc_config_history_load (cfg, name);
+
+ mc_config_deinit (cfg);
+ g_free (profile);
+
+ return hist;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Load history from the mc_config
+ */
+GList *
+mc_config_history_load (mc_config_t * cfg, const char *name)
+{
+ size_t i;
+ GList *hist = NULL;
+ char **keys;
+ size_t keys_num = 0;
+ GIConv conv = INVALID_CONV;
+ GString *buffer;
+
+ if (name == NULL || *name == '\0')
+ return NULL;
+
+ /* get number of keys */
+ keys = mc_config_get_keys (cfg, name, &keys_num);
+ g_strfreev (keys);
+
+ /* create charset conversion handler to convert strings
+ from utf-8 to system codepage */
+ if (!mc_global.utf8_display)
+ conv = str_crt_conv_from ("UTF-8");
+
+ buffer = g_string_sized_new (64);
+
+ for (i = 0; i < keys_num; i++)
+ {
+ char key[BUF_TINY];
+ char *this_entry;
+
+ g_snprintf (key, sizeof (key), "%lu", (unsigned long) i);
+ this_entry = mc_config_get_string_raw (cfg, name, key, "");
+
+ if (this_entry == NULL)
+ continue;
+
+ if (conv == INVALID_CONV)
+ hist = list_append_unique (hist, this_entry);
+ else
+ {
+ g_string_set_size (buffer, 0);
+ if (str_convert (conv, this_entry, buffer) == ESTR_FAILURE)
+ hist = list_append_unique (hist, this_entry);
+ else
+ {
+ hist = list_append_unique (hist, g_strndup (buffer->str, buffer->len));
+ g_free (this_entry);
+ }
+ }
+ }
+
+ g_string_free (buffer, TRUE);
+ if (conv != INVALID_CONV)
+ str_close_conv (conv);
+
+ /* return pointer to the last entry in the list */
+ return g_list_last (hist);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Save history to the mc_config, but don't save config to file
+ */
+void
+mc_config_history_save (mc_config_t * cfg, const char *name, GList * h)
+{
+ GIConv conv = INVALID_CONV;
+ GString *buffer;
+ int i;
+
+ if (name == NULL || *name == '\0' || h == NULL)
+ return;
+
+ /* go to end of list */
+ h = g_list_last (h);
+
+ /* go back 60 places */
+ for (i = 0; (i < num_history_items_recorded - 1) && (h->prev != NULL); i++)
+ h = g_list_previous (h);
+
+ if (name != NULL)
+ mc_config_del_group (cfg, name);
+
+ /* create charset conversion handler to convert strings
+ from system codepage to UTF-8 */
+ if (!mc_global.utf8_display)
+ conv = str_crt_conv_to ("UTF-8");
+
+ buffer = g_string_sized_new (64);
+
+ /* dump history into profile */
+ for (i = 0; h != NULL; h = g_list_next (h))
+ {
+ char key[BUF_TINY];
+ char *text = (char *) h->data;
+
+ /* We shouldn't have null entries, but let's be sure */
+ if (text == NULL)
+ continue;
+
+ g_snprintf (key, sizeof (key), "%d", i++);
+
+ if (conv == INVALID_CONV)
+ mc_config_set_string_raw (cfg, name, key, text);
+ else
+ {
+ g_string_set_size (buffer, 0);
+ if (str_convert (conv, text, buffer) == ESTR_FAILURE)
+ mc_config_set_string_raw (cfg, name, key, text);
+ else
+ mc_config_set_string_raw (cfg, name, key, buffer->str);
+ }
+ }
+
+ g_string_free (buffer, TRUE);
+ if (conv != INVALID_CONV)
+ str_close_conv (conv);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/mcconfig/paths.c b/lib/mcconfig/paths.c
new file mode 100644
index 0000000..46ec14d
--- /dev/null
+++ b/lib/mcconfig/paths.c
@@ -0,0 +1,314 @@
+/*
+ paths to configuration files
+
+ Copyright (C) 2010-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2010.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h" /* unix_error_string() */
+
+#include "lib/mcconfig.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static gboolean xdg_vars_initialized = FALSE;
+static char *mc_config_str = NULL;
+static char *mc_cache_str = NULL;
+static char *mc_data_str = NULL;
+
+static gboolean config_dir_present = FALSE;
+
+static const struct
+{
+ char **basedir;
+ const char *filename;
+} mc_config_files_reference[] =
+{
+ /* *INDENT-OFF* */
+ /* config */
+ { &mc_config_str, MC_CONFIG_FILE },
+ { &mc_config_str, MC_FHL_INI_FILE },
+ { &mc_config_str, MC_HOTLIST_FILE },
+ { &mc_config_str, GLOBAL_KEYMAP_FILE },
+ { &mc_config_str, MC_USERMENU_FILE },
+ { &mc_config_str, EDIT_HOME_MENU },
+ { &mc_config_str, MC_PANELS_FILE },
+
+ /* User should move this file with applying some changes in file */
+ { &mc_config_str, MC_EXT_FILE },
+ { &mc_config_str, MC_EXT_OLD_FILE },
+
+ /* data */
+ { &mc_data_str, MC_SKINS_DIR },
+ { &mc_data_str, FISH_PREFIX },
+ { &mc_data_str, MC_ASHRC_FILE },
+ { &mc_data_str, MC_BASHRC_FILE },
+ { &mc_data_str, MC_INPUTRC_FILE },
+ { &mc_data_str, MC_ZSHRC_FILE },
+ { &mc_data_str, MC_EXTFS_DIR },
+ { &mc_data_str, MC_HISTORY_FILE },
+ { &mc_data_str, MC_FILEPOS_FILE },
+ { &mc_data_str, EDIT_SYNTAX_FILE },
+ { &mc_data_str, EDIT_HOME_CLIP_FILE },
+ { &mc_data_str, MC_MACRO_FILE },
+
+ /* cache */
+ { &mc_cache_str, "mc.log" },
+ { &mc_cache_str, MC_TREESTORE_FILE },
+ { &mc_cache_str, EDIT_HOME_TEMP_FILE },
+ { &mc_cache_str, EDIT_HOME_BLOCK_FILE },
+
+ { NULL, NULL }
+ /* *INDENT-ON* */
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions *********************************************************************** */
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_config_mkdir (const char *directory_name, GError ** mcerror)
+{
+ mc_return_if_error (mcerror);
+
+ if ((!g_file_test (directory_name, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) &&
+ (g_mkdir_with_parents (directory_name, 0700) != 0))
+ mc_propagate_error (mcerror, 0, _("Cannot create %s directory"), directory_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+mc_config_init_one_config_path (const char *path_base, const char *subdir, GError ** mcerror)
+{
+ char *full_path;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ full_path = g_build_filename (path_base, subdir, (char *) NULL);
+
+ if (g_file_test (full_path, G_FILE_TEST_EXISTS))
+ {
+ if (g_file_test (full_path, G_FILE_TEST_IS_DIR))
+ config_dir_present = TRUE;
+ else
+ {
+ fprintf (stderr, "%s %s\n", _("FATAL: not a directory:"), full_path);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ mc_config_mkdir (full_path, mcerror);
+ if (mcerror != NULL && *mcerror != NULL)
+ MC_PTR_FREE (full_path);
+
+ return full_path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_init_config_paths (GError ** mcerror)
+{
+ const char *profile_root;
+ char *dir;
+
+ mc_return_if_error (mcerror);
+
+ if (xdg_vars_initialized)
+ return;
+
+ profile_root = mc_get_profile_root ();
+
+ if (strcmp (profile_root, mc_config_get_home_dir ()) != 0)
+ {
+ /*
+ * The user overrode the default profile root.
+ *
+ * In this case we can't use GLib's g_get_user_{config,cache,data}_dir()
+ * as these functions use the user's home dir as the root.
+ */
+
+ dir = g_build_filename (profile_root, ".config", (char *) NULL);
+ mc_config_str = mc_config_init_one_config_path (dir, MC_USERCONF_DIR, mcerror);
+ g_free (dir);
+
+ dir = g_build_filename (profile_root, ".cache", (char *) NULL);
+ mc_cache_str = mc_config_init_one_config_path (dir, MC_USERCONF_DIR, mcerror);
+ g_free (dir);
+
+ dir = g_build_filename (profile_root, ".local", "share", (char *) NULL);
+ mc_data_str = mc_config_init_one_config_path (dir, MC_USERCONF_DIR, mcerror);
+ g_free (dir);
+ }
+ else
+ {
+ mc_config_str =
+ mc_config_init_one_config_path (g_get_user_config_dir (), MC_USERCONF_DIR, mcerror);
+ mc_cache_str =
+ mc_config_init_one_config_path (g_get_user_cache_dir (), MC_USERCONF_DIR, mcerror);
+ mc_data_str =
+ mc_config_init_one_config_path (g_get_user_data_dir (), MC_USERCONF_DIR, mcerror);
+ }
+
+ xdg_vars_initialized = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_deinit_config_paths (void)
+{
+ if (!xdg_vars_initialized)
+ return;
+
+ g_free (mc_config_str);
+ g_free (mc_cache_str);
+ g_free (mc_data_str);
+
+ g_free (mc_global.share_data_dir);
+ g_free (mc_global.sysconfig_dir);
+
+ xdg_vars_initialized = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+mc_config_get_data_path (void)
+{
+ if (!xdg_vars_initialized)
+ mc_config_init_config_paths (NULL);
+
+ return (const char *) mc_data_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+mc_config_get_cache_path (void)
+{
+ if (!xdg_vars_initialized)
+ mc_config_init_config_paths (NULL);
+
+ return (const char *) mc_cache_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+mc_config_get_home_dir (void)
+{
+ static const char *homedir = NULL;
+
+ if (homedir == NULL)
+ {
+ /* Prior to GLib 2.36, g_get_home_dir() ignores $HOME, which is why
+ * we read it ourselves. As that function's documentation explains,
+ * using $HOME is good for compatibility with other programs and
+ * for running from test frameworks. */
+ homedir = g_getenv ("HOME");
+ if (homedir == NULL || *homedir == '\0')
+ homedir = g_get_home_dir ();
+ }
+
+ return homedir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+mc_config_get_path (void)
+{
+ if (!xdg_vars_initialized)
+ mc_config_init_config_paths (NULL);
+
+ return (const char *) mc_config_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get full path to config file by short name.
+ *
+ * @param config_name short name
+ * @return full path to config file
+ */
+
+char *
+mc_config_get_full_path (const char *config_name)
+{
+ size_t rule_index;
+
+ if (config_name == NULL)
+ return NULL;
+
+ if (!xdg_vars_initialized)
+ mc_config_init_config_paths (NULL);
+
+ for (rule_index = 0; mc_config_files_reference[rule_index].filename != NULL; rule_index++)
+ if (strcmp (config_name, mc_config_files_reference[rule_index].filename) == 0)
+ return g_build_filename (*mc_config_files_reference[rule_index].basedir,
+ mc_config_files_reference[rule_index].filename, (char *) NULL);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get full path to config file by short name.
+ *
+ * @param config_name short name
+ * @return object with full path to config file
+ */
+
+vfs_path_t *
+mc_config_get_full_vpath (const char *config_name)
+{
+ vfs_path_t *ret_vpath;
+ char *str_path;
+
+ str_path = mc_config_get_full_path (config_name);
+
+ ret_vpath = vfs_path_from_str (str_path);
+ g_free (str_path);
+
+ return ret_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/mcconfig/set.c b/lib/mcconfig/set.c
new file mode 100644
index 0000000..961435f
--- /dev/null
+++ b/lib/mcconfig/set.c
@@ -0,0 +1,159 @@
+/*
+ Configure module for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+
+#include "lib/mcconfig.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gchar *
+mc_config_normalize_before_save (const gchar * value)
+{
+ GIConv conv;
+ GString *buffer;
+ gboolean ok;
+
+ if (mc_global.utf8_display)
+ return g_strdup (value);
+
+ conv = str_crt_conv_to ("UTF-8");
+ if (conv == INVALID_CONV)
+ return g_strdup (value);
+
+ buffer = g_string_new ("");
+
+ ok = (str_convert (conv, value, buffer) != ESTR_FAILURE);
+ str_close_conv (conv);
+
+ if (!ok)
+ {
+ g_string_free (buffer, TRUE);
+ return g_strdup (value);
+ }
+
+ return g_string_free (buffer, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_string_raw (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, const gchar * value)
+{
+ if (mc_config != NULL && group != NULL && param != NULL && value != NULL)
+ g_key_file_set_string (mc_config->handle, group, param, value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_string_raw_value (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, const gchar * value)
+{
+ if (mc_config != NULL && group != NULL && param != NULL && value != NULL)
+ g_key_file_set_value (mc_config->handle, group, param, value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_string (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, const gchar * value)
+{
+ if (mc_config != NULL && group != NULL && param != NULL && value != NULL)
+ {
+ gchar *buffer;
+
+ buffer = mc_config_normalize_before_save (value);
+ g_key_file_set_string (mc_config->handle, group, param, buffer);
+ g_free (buffer);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_bool (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, gboolean value)
+{
+ if (mc_config != NULL && group != NULL && param != NULL)
+ g_key_file_set_boolean (mc_config->handle, group, param, value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_int (mc_config_t * mc_config, const gchar * group, const gchar * param, int value)
+{
+ if (mc_config != NULL && group != NULL && param != NULL)
+ g_key_file_set_integer (mc_config->handle, group, param, value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_string_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, const gchar * const value[], gsize length)
+{
+ if (mc_config != NULL && group != NULL && param != NULL && value != NULL && length != 0)
+ g_key_file_set_string_list (mc_config->handle, group, param, value, length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_bool_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, gboolean value[], gsize length)
+{
+ if (mc_config != NULL && group != NULL && param != NULL && value != NULL && length != 0)
+ g_key_file_set_boolean_list (mc_config->handle, group, param, value, length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_config_set_int_list (mc_config_t * mc_config, const gchar * group,
+ const gchar * param, int value[], gsize length)
+{
+ if (mc_config != NULL && group != NULL && param != NULL && value != NULL && length != 0)
+ g_key_file_set_integer_list (mc_config->handle, group, param, value, length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/search.h b/lib/search.h
new file mode 100644
index 0000000..07372c2
--- /dev/null
+++ b/lib/search.h
@@ -0,0 +1,196 @@
+#ifndef MC__SEARCH_H
+#define MC__SEARCH_H
+
+#include <config.h>
+
+#include "lib/global.h" /* <glib.h> */
+
+#include <sys/types.h>
+
+#ifdef SEARCH_TYPE_PCRE
+#ifdef HAVE_PCRE2
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
+#else
+#include <pcre.h>
+#endif
+#endif /* SEARCH_TYPE_PCRE */
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef enum mc_search_cbret_t mc_search_cbret_t;
+
+typedef mc_search_cbret_t (*mc_search_fn) (const void *user_data, gsize char_offset,
+ int *current_char);
+typedef mc_search_cbret_t (*mc_update_fn) (const void *user_data, gsize char_offset);
+
+#define MC_SEARCH__NUM_REPLACE_ARGS 64
+
+#ifdef SEARCH_TYPE_GLIB
+#define mc_search_matchinfo_t GMatchInfo
+#else
+#ifdef HAVE_PCRE2
+/* no pcre_extra in PCRE2. pcre2_jit_compile (equivalent of pcre_study) handles
+ * all of this internally. but we can use this to hold the pcre2_matches data
+ * until the search is complete */
+#define mc_search_matchinfo_t pcre2_match_data
+#else
+#define mc_search_matchinfo_t pcre_extra
+#endif
+#endif
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ MC_SEARCH_E_OK = 0,
+ MC_SEARCH_E_INPUT,
+ MC_SEARCH_E_REGEX_COMPILE,
+ MC_SEARCH_E_REGEX,
+ MC_SEARCH_E_REGEX_REPLACE,
+ MC_SEARCH_E_NOTFOUND,
+ MC_SEARCH_E_ABORT
+} mc_search_error_t;
+
+typedef enum
+{
+ MC_SEARCH_T_INVALID = -1,
+ MC_SEARCH_T_NORMAL,
+ MC_SEARCH_T_REGEX,
+ MC_SEARCH_T_HEX,
+ MC_SEARCH_T_GLOB
+} mc_search_type_t;
+
+enum mc_search_cbret_t
+{
+ MC_SEARCH_CB_OK = 0,
+ MC_SEARCH_CB_INVALID = -1,
+ MC_SEARCH_CB_ABORT = -2,
+ MC_SEARCH_CB_SKIP = -3,
+ MC_SEARCH_CB_NOTFOUND = -4
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_search_struct
+{
+ /* public input data */
+
+#ifdef HAVE_CHARSET
+ /* search in all charsets */
+ gboolean is_all_charsets;
+#endif
+
+ /* case sensitive search */
+ gboolean is_case_sensitive;
+
+ /* search only once. Is this for replace? */
+ gboolean is_once_only;
+
+ /* search only whole words (from begin to end). Used only with NORMAL search type */
+ gboolean whole_words;
+
+ /* search entire string (from begin to end). Used only with GLOB search type */
+ gboolean is_entire_line;
+
+ /* function, used for getting data. NULL if not used */
+ mc_search_fn search_fn;
+
+ /* function, used for updatin current search status. NULL if not used */
+ mc_update_fn update_fn;
+
+ /* type of search */
+ mc_search_type_t search_type;
+
+ /* public output data */
+
+ /* some data for normal */
+ off_t normal_offset;
+
+ off_t start_buffer;
+ /* some data for regexp */
+ int num_results;
+ gboolean is_utf8;
+ mc_search_matchinfo_t *regex_match_info;
+ GString *regex_buffer;
+#ifdef SEARCH_TYPE_PCRE
+#ifdef HAVE_PCRE2
+ /* pcre2 will provide a pointer to a match_data structure that can be manipulated like an iovector */
+ size_t *iovector;
+#else
+ int iovector[MC_SEARCH__NUM_REPLACE_ARGS * 2];
+#endif
+#endif /* SEARCH_TYPE_PCRE */
+
+ /* private data */
+
+ struct
+ {
+ GPtrArray *conditions;
+ gboolean result;
+ } prepared;
+
+ /* original search string */
+ struct
+ {
+ GString *str;
+#ifdef HAVE_CHARSET
+ gchar *charset;
+#endif
+ } original;
+
+ /* error code after search */
+ mc_search_error_t error;
+ gchar *error_str;
+} mc_search_t;
+
+typedef struct mc_search_type_str_struct
+{
+ const char *str;
+ mc_search_type_t type;
+} mc_search_type_str_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/* Error messages */
+extern const char *STR_E_NOTFOUND;
+extern const char *STR_E_UNKNOWN_TYPE;
+extern const char *STR_E_RPL_NOT_EQ_TO_FOUND;
+extern const char *STR_E_RPL_INVALID_TOKEN;
+
+/*** declarations of public functions ************************************************************/
+
+mc_search_t *mc_search_new (const gchar * original, const gchar * original_charset);
+
+mc_search_t *mc_search_new_len (const gchar * original, gsize original_len,
+ const gchar * original_charset);
+
+void mc_search_free (mc_search_t * lc_mc_search);
+
+gboolean mc_search_prepare (mc_search_t * mc_search);
+
+gboolean mc_search_run (mc_search_t * mc_search, const void *user_data, gsize start_search,
+ gsize end_search, gsize * found_len);
+
+gboolean mc_search_is_type_avail (mc_search_type_t search_type);
+
+const mc_search_type_str_t *mc_search_types_list_get (size_t * num);
+
+GString *mc_search_prepare_replace_str (mc_search_t * mc_search, GString * replace_str);
+char *mc_search_prepare_replace_str2 (mc_search_t * lc_mc_search, const char *replace_str);
+
+gboolean mc_search_is_fixed_search_str (const mc_search_t * lc_mc_search);
+
+gchar **mc_search_get_types_strings_array (size_t * num);
+
+gboolean mc_search (const gchar * pattern, const gchar * pattern_charset, const gchar * str,
+ mc_search_type_t type);
+
+int mc_search_getstart_result_by_num (mc_search_t * lc_mc_search, int lc_index);
+int mc_search_getend_result_by_num (mc_search_t * lc_mc_search, int lc_index);
+
+/* *INDENT-OFF* */
+void mc_search_set_error (mc_search_t * lc_mc_search, mc_search_error_t code, const gchar * format, ...)
+ G_GNUC_PRINTF (3, 4);
+/* *INDENT-ON* */
+
+#endif /* MC__SEARCH_H */
diff --git a/lib/search/Makefile.am b/lib/search/Makefile.am
new file mode 100644
index 0000000..48774a5
--- /dev/null
+++ b/lib/search/Makefile.am
@@ -0,0 +1,12 @@
+noinst_LTLIBRARIES = libsearch.la
+
+libsearch_la_SOURCES = \
+ search.c \
+ internal.h \
+ lib.c \
+ normal.c \
+ regex.c \
+ glob.c \
+ hex.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/lib/search/Makefile.in b/lib/search/Makefile.in
new file mode 100644
index 0000000..b587d13
--- /dev/null
+++ b/lib/search/Makefile.in
@@ -0,0 +1,759 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lib/search
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsearch_la_LIBADD =
+am_libsearch_la_OBJECTS = search.lo lib.lo normal.lo regex.lo glob.lo \
+ hex.lo
+libsearch_la_OBJECTS = $(am_libsearch_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/glob.Plo ./$(DEPDIR)/hex.Plo \
+ ./$(DEPDIR)/lib.Plo ./$(DEPDIR)/normal.Plo \
+ ./$(DEPDIR)/regex.Plo ./$(DEPDIR)/search.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsearch_la_SOURCES)
+DIST_SOURCES = $(libsearch_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsearch.la
+libsearch_la_SOURCES = \
+ search.c \
+ internal.h \
+ lib.c \
+ normal.c \
+ regex.c \
+ glob.c \
+ hex.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/search/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/search/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsearch.la: $(libsearch_la_OBJECTS) $(libsearch_la_DEPENDENCIES) $(EXTRA_libsearch_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsearch_la_OBJECTS) $(libsearch_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/glob.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/normal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/regex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/glob.Plo
+ -rm -f ./$(DEPDIR)/hex.Plo
+ -rm -f ./$(DEPDIR)/lib.Plo
+ -rm -f ./$(DEPDIR)/normal.Plo
+ -rm -f ./$(DEPDIR)/regex.Plo
+ -rm -f ./$(DEPDIR)/search.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/glob.Plo
+ -rm -f ./$(DEPDIR)/hex.Plo
+ -rm -f ./$(DEPDIR)/lib.Plo
+ -rm -f ./$(DEPDIR)/normal.Plo
+ -rm -f ./$(DEPDIR)/regex.Plo
+ -rm -f ./$(DEPDIR)/search.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/search/glob.c b/lib/search/glob.c
new file mode 100644
index 0000000..5874aba
--- /dev/null
+++ b/lib/search/glob.c
@@ -0,0 +1,207 @@
+/*
+ Search text engine.
+ Glob-style pattern matching
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/search.h"
+#include "lib/strescape.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+mc_search__glob_translate_to_regex (const GString * astr)
+{
+ const char *str = astr->str;
+ GString *buff;
+ gsize loop;
+ gboolean inside_group = FALSE;
+
+ buff = g_string_sized_new (32);
+
+ for (loop = 0; loop < astr->len; loop++)
+ {
+ switch (str[loop])
+ {
+ case '*':
+ if (!strutils_is_char_escaped (str, &(str[loop])))
+ {
+ g_string_append (buff, inside_group ? ".*" : "(.*)");
+ continue;
+ }
+ break;
+ case '?':
+ if (!strutils_is_char_escaped (str, &(str[loop])))
+ {
+ g_string_append (buff, inside_group ? "." : "(.)");
+ continue;
+ }
+ break;
+ case ',':
+ if (!strutils_is_char_escaped (str, &(str[loop])))
+ {
+ g_string_append_c (buff, inside_group ? '|' : ',');
+ continue;
+ }
+ break;
+ case '{':
+ if (!strutils_is_char_escaped (str, &(str[loop])))
+ {
+ g_string_append_c (buff, '(');
+ inside_group = TRUE;
+ continue;
+ }
+ break;
+ case '}':
+ if (!strutils_is_char_escaped (str, &(str[loop])))
+ {
+ g_string_append_c (buff, ')');
+ inside_group = FALSE;
+ continue;
+ }
+ break;
+ case '+':
+ case '.':
+ case '$':
+ case '(':
+ case ')':
+ case '^':
+ g_string_append_c (buff, '\\');
+ break;
+ default:
+ break;
+ }
+ g_string_append_c (buff, str[loop]);
+ }
+ return buff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+mc_search__translate_replace_glob_to_regex (const char *str)
+{
+ GString *buff;
+ char cnt = '0';
+ gboolean escaped_mode = FALSE;
+
+ buff = g_string_sized_new (32);
+
+ while (*str != '\0')
+ {
+ char c = *str++;
+
+ switch (c)
+ {
+ case '\\':
+ if (!escaped_mode)
+ {
+ escaped_mode = TRUE;
+ g_string_append_c (buff, '\\');
+ continue;
+ }
+ break;
+ case '*':
+ case '?':
+ if (!escaped_mode)
+ {
+ g_string_append_c (buff, '\\');
+ c = ++cnt;
+ }
+ break;
+ case '&':
+ if (!escaped_mode)
+ g_string_append_c (buff, '\\');
+ break;
+ default:
+ break;
+ }
+ g_string_append_c (buff, c);
+ escaped_mode = FALSE;
+ }
+ return buff;
+}
+
+/*** public functions ****************************************************************************/
+
+void
+mc_search__cond_struct_new_init_glob (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond)
+{
+ GString *tmp;
+
+ tmp = mc_search__glob_translate_to_regex (mc_search_cond->str);
+ g_string_free (mc_search_cond->str, TRUE);
+
+ if (lc_mc_search->is_entire_line)
+ {
+ g_string_prepend_c (tmp, '^');
+ g_string_append_c (tmp, '$');
+ }
+ mc_search_cond->str = tmp;
+
+ mc_search__cond_struct_new_init_regex (charset, lc_mc_search, mc_search_cond);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_search__run_glob (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len)
+{
+ return mc_search__run_regex (lc_mc_search, user_data, start_search, end_search, found_len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search_glob_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str)
+{
+ GString *repl, *res;
+
+ repl = mc_search__translate_replace_glob_to_regex (replace_str->str);
+ res = mc_search_regex_prepare_replace_str (lc_mc_search, repl);
+ g_string_free (repl, TRUE);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/search/hex.c b/lib/search/hex.c
new file mode 100644
index 0000000..50af6fb
--- /dev/null
+++ b/lib/search/hex.c
@@ -0,0 +1,235 @@
+/*
+ Search text engine.
+ HEX-style pattern matching
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/search.h"
+#include "lib/strescape.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+typedef enum
+{
+ MC_SEARCH_HEX_E_OK,
+ MC_SEARCH_HEX_E_NUM_OUT_OF_RANGE,
+ MC_SEARCH_HEX_E_INVALID_CHARACTER,
+ MC_SEARCH_HEX_E_UNMATCHED_QUOTES
+} mc_search_hex_parse_error_t;
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+mc_search__hex_translate_to_regex (const GString * astr, mc_search_hex_parse_error_t * error_ptr,
+ int *error_pos_ptr)
+{
+ GString *buff;
+ const char *str;
+ gsize str_len;
+ gsize loop = 0;
+ mc_search_hex_parse_error_t error = MC_SEARCH_HEX_E_OK;
+
+ buff = g_string_sized_new (64);
+ str = astr->str;
+ str_len = astr->len;
+
+ while (loop < str_len && error == MC_SEARCH_HEX_E_OK)
+ {
+ unsigned int val;
+ int ptr;
+
+ if (g_ascii_isspace (str[loop]))
+ {
+ /* Eat-up whitespace between tokens. */
+ while (g_ascii_isspace (str[loop]))
+ loop++;
+ }
+ /* cppcheck-suppress invalidscanf */
+ else if (sscanf (str + loop, "%x%n", &val, &ptr) == 1)
+ {
+ if (val > 255)
+ error = MC_SEARCH_HEX_E_NUM_OUT_OF_RANGE;
+ else
+ {
+ g_string_append_printf (buff, "\\x%02X", val);
+ loop += ptr;
+ }
+ }
+ else if (str[loop] == '"')
+ {
+ gsize loop2;
+
+ loop2 = loop + 1;
+
+ while (loop2 < str_len)
+ {
+ if (str[loop2] == '"')
+ break;
+ if (str[loop2] == '\\' && loop2 + 1 < str_len)
+ loop2++;
+ g_string_append_c (buff, str[loop2]);
+ loop2++;
+ }
+
+ if (str[loop2] == '\0')
+ error = MC_SEARCH_HEX_E_UNMATCHED_QUOTES;
+ else
+ loop = loop2 + 1;
+ }
+ else
+ error = MC_SEARCH_HEX_E_INVALID_CHARACTER;
+ }
+
+ if (error != MC_SEARCH_HEX_E_OK)
+ {
+ g_string_free (buff, TRUE);
+ if (error_ptr != NULL)
+ *error_ptr = error;
+ if (error_pos_ptr != NULL)
+ *error_pos_ptr = loop;
+ return NULL;
+ }
+
+ return buff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_search__cond_struct_new_init_hex (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond)
+{
+ GString *tmp;
+ mc_search_hex_parse_error_t error = MC_SEARCH_HEX_E_OK;
+ int error_pos = 0;
+
+ /*
+ * We may be searching in binary data, which is often invalid UTF-8.
+ *
+ * We have to create a non UTF-8 regex (that is, G_REGEX_RAW) or else, as
+ * the data is invalid UTF-8, both GLib's PCRE and our
+ * mc_search__g_regex_match_full_safe() are going to fail us. The former by
+ * not finding all bytes, the latter by overwriting the supposedly invalid
+ * UTF-8 with NULs.
+ *
+ * To do this, we specify "ASCII" as the charset.
+ *
+ * In fact, we can specify any charset other than "UTF-8": any such charset
+ * will trigger G_REGEX_RAW (see [1]). The output of [2] will be the same
+ * for all charsets because it skips the \xXX symbols
+ * mc_search__hex_translate_to_regex() outputs.
+ *
+ * But "ASCII" is the best choice because a hex pattern may contain a
+ * quoted string: this way we know [2] will ignore any characters outside
+ * ASCII letters range (these ignored chars will be copied verbatim to the
+ * output and will match as-is; in other words, in a case-sensitive manner;
+ * If the user is interested in case-insensitive searches of international
+ * text, he shouldn't be using hex search in the first place.)
+ *
+ * Switching out of UTF-8 has another advantage:
+ *
+ * When doing case-insensitive searches, GLib treats \xXX symbols as normal
+ * letters and therefore matches both "a" and "A" for the hex pattern
+ * "0x61". When we switch out of UTF-8, we're switching to using [2], which
+ * doesn't have this issue.
+ *
+ * [1] mc_search__cond_struct_new_init_regex
+ * [2] mc_search__cond_struct_new_regex_ci_str
+ */
+ if (str_isutf8 (charset))
+ charset = "ASCII";
+
+ tmp = mc_search__hex_translate_to_regex (mc_search_cond->str, &error, &error_pos);
+ if (tmp != NULL)
+ {
+ g_string_free (mc_search_cond->str, TRUE);
+ mc_search_cond->str = tmp;
+ mc_search__cond_struct_new_init_regex (charset, lc_mc_search, mc_search_cond);
+ }
+ else
+ {
+ const char *desc;
+
+ switch (error)
+ {
+ case MC_SEARCH_HEX_E_NUM_OUT_OF_RANGE:
+ desc =
+ _
+ ("Number out of range (should be in byte range, 0 <= n <= 0xFF, expressed in hex)");
+ break;
+ case MC_SEARCH_HEX_E_INVALID_CHARACTER:
+ desc = _("Invalid character");
+ break;
+ case MC_SEARCH_HEX_E_UNMATCHED_QUOTES:
+ desc = _("Unmatched quotes character");
+ break;
+ default:
+ desc = "";
+ }
+
+ lc_mc_search->error = MC_SEARCH_E_INPUT;
+ lc_mc_search->error_str =
+ g_strdup_printf (_("Hex pattern error at position %d:\n%s."), error_pos + 1, desc);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_search__run_hex (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len)
+{
+ return mc_search__run_regex (lc_mc_search, user_data, start_search, end_search, found_len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search_hex_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str)
+{
+ (void) lc_mc_search;
+
+ return mc_g_string_dup (replace_str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/search/internal.h b/lib/search/internal.h
new file mode 100644
index 0000000..08cb019
--- /dev/null
+++ b/lib/search/internal.h
@@ -0,0 +1,86 @@
+#ifndef MC__SEARCH_INTERNAL_H
+#define MC__SEARCH_INTERNAL_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#ifdef SEARCH_TYPE_GLIB
+#define mc_search_regex_t GRegex
+#else
+#ifdef HAVE_PCRE2
+#define mc_search_regex_t pcre2_code
+#else
+#define mc_search_regex_t pcre
+#endif
+#endif
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ COND__NOT_FOUND,
+ COND__NOT_ALL_FOUND,
+ COND__FOUND_CHAR,
+ COND__FOUND_CHAR_LAST,
+ COND__FOUND_OK,
+ COND__FOUND_ERROR
+} mc_search__found_cond_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_search_cond_struct
+{
+ GString *str;
+ GString *upper;
+ GString *lower;
+ mc_search_regex_t *regex_handle;
+ gchar *charset;
+} mc_search_cond_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* search/lib.c : */
+
+GString *mc_search__recode_str (const char *str, gsize str_len, const char *charset_from,
+ const char *charset_to);
+GString *mc_search__get_one_symbol (const char *charset, const char *str, gsize str_len,
+ gboolean * just_letters);
+GString *mc_search__tolower_case_str (const char *charset, const GString * str);
+GString *mc_search__toupper_case_str (const char *charset, const GString * str);
+
+/* search/regex.c : */
+
+void mc_search__cond_struct_new_init_regex (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond);
+gboolean mc_search__run_regex (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len);
+GString *mc_search_regex_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str);
+
+/* search/normal.c : */
+
+void mc_search__cond_struct_new_init_normal (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond);
+gboolean mc_search__run_normal (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len);
+GString *mc_search_normal_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str);
+
+/* search/glob.c : */
+
+void mc_search__cond_struct_new_init_glob (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond);
+gboolean mc_search__run_glob (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len);
+GString *mc_search_glob_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str);
+
+/* search/hex.c : */
+
+void mc_search__cond_struct_new_init_hex (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond);
+gboolean mc_search__run_hex (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len);
+GString *mc_search_hex_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__SEARCH_INTERNAL_H */
diff --git a/lib/search/lib.c b/lib/search/lib.c
new file mode 100644
index 0000000..2c22504
--- /dev/null
+++ b/lib/search/lib.c
@@ -0,0 +1,233 @@
+/*
+ Search text engine.
+ Common share code for module.
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009, 2011
+ Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/search.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/* *INDENT-OFF* */
+const char *STR_E_NOTFOUND = N_("Search string not found");
+const char *STR_E_UNKNOWN_TYPE = N_("Not implemented yet");
+const char *STR_E_RPL_NOT_EQ_TO_FOUND =
+ N_("Num of replace tokens not equal to num of found tokens");
+const char *STR_E_RPL_INVALID_TOKEN = N_("Invalid token number %d");
+/* *INDENT-ON* */
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef gboolean (*case_conv_fn) (const char *ch, char **out, size_t * remain);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+mc_search__change_case_str (const char *charset, const GString * str, case_conv_fn case_conv)
+{
+ GString *ret;
+ const char *src_ptr;
+ gchar *dst_str;
+ gchar *dst_ptr;
+ gsize dst_len;
+#ifdef HAVE_CHARSET
+ GString *converted_str;
+
+ if (charset == NULL)
+ charset = cp_source;
+
+ converted_str = mc_search__recode_str (str->str, str->len, charset, cp_display);
+
+ dst_len = converted_str->len + 1; /* +1 is required for str_toupper/str_tolower */
+ dst_str = g_malloc (dst_len);
+
+ for (src_ptr = converted_str->str, dst_ptr = dst_str;
+ case_conv (src_ptr, &dst_ptr, &dst_len); src_ptr += str_length_char (src_ptr))
+ ;
+ *dst_ptr = '\0';
+
+ dst_len = converted_str->len;
+ g_string_free (converted_str, TRUE);
+
+ ret = mc_search__recode_str (dst_str, dst_len, cp_display, charset);
+ g_free (dst_str);
+#else
+ (void) charset;
+
+ dst_len = str->len + 1; /* +1 is required for str_toupper/str_tolower */
+ dst_str = g_malloc (dst_len);
+
+ for (src_ptr = str->str, dst_ptr = dst_str;
+ case_conv (src_ptr, &dst_ptr, &dst_len); src_ptr += str_length_char (src_ptr))
+ ;
+ *dst_ptr = '\0';
+
+ ret = g_string_new_len (dst_str, dst_len);
+ g_free (dst_str);
+#endif
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search__recode_str (const char *str, gsize str_len, const char *charset_from,
+ const char *charset_to)
+{
+ GString *ret = NULL;
+
+ if (charset_from != NULL && charset_to != NULL
+ && g_ascii_strcasecmp (charset_to, charset_from) != 0)
+ {
+ GIConv conv;
+
+ conv = g_iconv_open (charset_to, charset_from);
+ if (conv != INVALID_CONV)
+ {
+ gchar *val;
+ gsize bytes_read = 0;
+ gsize bytes_written = 0;
+
+ val = g_convert_with_iconv (str, str_len, conv, &bytes_read, &bytes_written, NULL);
+
+ g_iconv_close (conv);
+
+ if (val != NULL)
+ {
+ ret = g_string_new_len (val, bytes_written);
+ g_free (val);
+ }
+ }
+ }
+
+ if (ret == NULL)
+ ret = g_string_new_len (str, str_len);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search__get_one_symbol (const char *charset, const char *str, gsize str_len,
+ gboolean * just_letters)
+{
+ GString *converted_str;
+ const gchar *next_char;
+
+#ifdef HAVE_CHARSET
+ GString *converted_str2;
+
+ if (charset == NULL)
+ charset = cp_source;
+
+ converted_str = mc_search__recode_str (str, str_len, charset, cp_display);
+#else
+ (void) charset;
+
+ converted_str = g_string_new_len (str, str_len);
+#endif
+
+ next_char = str_cget_next_char (converted_str->str);
+ g_string_set_size (converted_str, (gsize) (next_char - converted_str->str));
+
+#ifdef HAVE_CHARSET
+ converted_str2 =
+ mc_search__recode_str (converted_str->str, converted_str->len, cp_display, charset);
+#endif
+ if (just_letters != NULL)
+ *just_letters = str_isalnum (converted_str->str) && !str_isdigit (converted_str->str);
+#ifdef HAVE_CHARSET
+ g_string_free (converted_str, TRUE);
+ return converted_str2;
+#else
+ return converted_str;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search__tolower_case_str (const char *charset, const GString * str)
+{
+ return mc_search__change_case_str (charset, str, str_tolower);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search__toupper_case_str (const char *charset, const GString * str)
+{
+ return mc_search__change_case_str (charset, str, str_toupper);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gchar **
+mc_search_get_types_strings_array (size_t * num)
+{
+ gchar **ret;
+ int lc_index;
+ size_t n;
+
+ const mc_search_type_str_t *type_str;
+ const mc_search_type_str_t *types_str = mc_search_types_list_get (&n);
+
+ ret = g_try_new0 (char *, n + 1);
+ if (ret == NULL)
+ return NULL;
+
+ for (lc_index = 0, type_str = types_str; type_str->str != NULL; type_str++, lc_index++)
+ ret[lc_index] = g_strdup (type_str->str);
+
+ /* don't count last NULL item */
+ if (num != NULL)
+ *num = (size_t) lc_index;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/search/normal.c b/lib/search/normal.c
new file mode 100644
index 0000000..9042bfc
--- /dev/null
+++ b/lib/search/normal.c
@@ -0,0 +1,108 @@
+/*
+ Search text engine.
+ Plain search
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/search.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_search__normal_translate_to_regex (GString * str)
+{
+ gsize loop;
+
+ for (loop = 0; loop < str->len; loop++)
+ switch (str->str[loop])
+ {
+ case '*':
+ case '?':
+ case ',':
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case '\\':
+ case '+':
+ case '.':
+ case '$':
+ case '(':
+ case ')':
+ case '^':
+ case '-':
+ case '|':
+ g_string_insert_c (str, loop, '\\');
+ loop++;
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_search__cond_struct_new_init_normal (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond)
+{
+ mc_search__normal_translate_to_regex (mc_search_cond->str);
+ mc_search__cond_struct_new_init_regex (charset, lc_mc_search, mc_search_cond);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_search__run_normal (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len)
+{
+ return mc_search__run_regex (lc_mc_search, user_data, start_search, end_search, found_len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+GString *
+mc_search_normal_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str)
+{
+ (void) lc_mc_search;
+
+ return mc_g_string_dup (replace_str);
+}
diff --git a/lib/search/regex.c b/lib/search/regex.c
new file mode 100644
index 0000000..d24cf48
--- /dev/null
+++ b/lib/search/regex.c
@@ -0,0 +1,1121 @@
+/*
+ Search text engine.
+ Regex search
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009, 2010, 2011, 2013
+ Vitaliy Filippov <vitalif@yourcmc.ru>, 2011
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2015
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/search.h"
+#include "lib/strescape.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define REPLACE_PREPARE_T_NOTHING_SPECIAL -1
+#define REPLACE_PREPARE_T_REPLACE_FLAG -2
+#define REPLACE_PREPARE_T_ESCAPE_SEQ -3
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ REPLACE_T_NO_TRANSFORM = 0,
+ REPLACE_T_UPP_TRANSFORM_CHAR = 1,
+ REPLACE_T_LOW_TRANSFORM_CHAR = 2,
+ REPLACE_T_UPP_TRANSFORM = 4,
+ REPLACE_T_LOW_TRANSFORM = 8
+} replace_transform_type_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_search__regex_str_append_if_special (GString * copy_to, const GString * regex_str,
+ gsize * offset)
+{
+ const char *special_chars[] = {
+ "\\s", "\\S",
+ "\\d", "\\D",
+ "\\b", "\\B",
+ "\\w", "\\W",
+ "\\t", "\\n",
+ "\\r", "\\f",
+ "\\a", "\\e",
+ "\\x", "\\X",
+ "\\c", "\\C",
+ "\\l", "\\L",
+ "\\u", "\\U",
+ "\\E", "\\Q",
+ NULL
+ };
+
+ char *tmp_regex_str;
+ const char **spec_chr;
+
+ tmp_regex_str = &(regex_str->str[*offset]);
+
+ for (spec_chr = special_chars; *spec_chr != NULL; spec_chr++)
+ {
+ gsize spec_chr_len;
+
+ spec_chr_len = strlen (*spec_chr);
+
+ if (strncmp (tmp_regex_str, *spec_chr, spec_chr_len) == 0
+ && !strutils_is_char_escaped (regex_str->str, tmp_regex_str))
+ {
+ if (strncmp ("\\x", *spec_chr, spec_chr_len) == 0)
+ {
+ if (tmp_regex_str[spec_chr_len] != '{')
+ spec_chr_len += 2;
+ else
+ {
+ while ((spec_chr_len < regex_str->len - *offset)
+ && tmp_regex_str[spec_chr_len] != '}')
+ spec_chr_len++;
+ if (tmp_regex_str[spec_chr_len] == '}')
+ spec_chr_len++;
+ }
+ }
+ g_string_append_len (copy_to, tmp_regex_str, spec_chr_len);
+ *offset += spec_chr_len;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_search__cond_struct_new_regex_hex_add (const char *charset, GString * str_to,
+ const GString * one_char)
+{
+ GString *upp, *low;
+ gsize loop;
+
+ upp = mc_search__toupper_case_str (charset, one_char);
+ low = mc_search__tolower_case_str (charset, one_char);
+
+ for (loop = 0; loop < upp->len; loop++)
+ {
+ gchar tmp_str[10 + 1]; /* longest content is "[\\x%02X\\x%02X]" */
+ gint tmp_len;
+
+ if (loop >= low->len || upp->str[loop] == low->str[loop])
+ tmp_len =
+ g_snprintf (tmp_str, sizeof (tmp_str), "\\x%02X", (unsigned char) upp->str[loop]);
+ else
+ tmp_len =
+ g_snprintf (tmp_str, sizeof (tmp_str), "[\\x%02X\\x%02X]",
+ (unsigned char) upp->str[loop], (unsigned char) low->str[loop]);
+
+ g_string_append_len (str_to, tmp_str, tmp_len);
+ }
+
+ g_string_free (upp, TRUE);
+ g_string_free (low, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_search__cond_struct_new_regex_accum_append (const char *charset, GString * str_to,
+ GString * str_from)
+{
+ GString *recoded_part;
+ gsize loop = 0;
+
+ recoded_part = g_string_sized_new (32);
+
+ while (loop < str_from->len)
+ {
+ GString *one_char;
+ gboolean just_letters;
+
+ one_char =
+ mc_search__get_one_symbol (charset, str_from->str + loop,
+ MIN (str_from->len - loop, 6), &just_letters);
+
+ if (one_char->len == 0)
+ loop++;
+ else
+ {
+ loop += one_char->len;
+
+ if (just_letters)
+ mc_search__cond_struct_new_regex_hex_add (charset, recoded_part, one_char);
+ else
+ g_string_append_len (recoded_part, one_char->str, one_char->len);
+ }
+
+ g_string_free (one_char, TRUE);
+ }
+
+ g_string_append_len (str_to, recoded_part->str, recoded_part->len);
+ g_string_free (recoded_part, TRUE);
+ g_string_set_size (str_from, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Creates a case-insensitive version of a regex pattern.
+ *
+ * For example (assuming ASCII charset): given "\\bHello!\\xAB", returns
+ * "\\b[Hh][Ee][Ll][Ll][Oo]!\\xAB" (this example is for easier reading; in
+ * reality hex codes are used instead of letters).
+ *
+ * This function knows not to ruin special regex symbols.
+ *
+ * This function is used when working with non-UTF-8 charsets: GLib's
+ * regex engine doesn't understand such charsets and therefore can't do
+ * this job itself.
+ */
+static GString *
+mc_search__cond_struct_new_regex_ci_str (const char *charset, const GString * astr)
+{
+ GString *accumulator, *spec_char, *ret_str;
+ gsize loop;
+
+ ret_str = g_string_sized_new (64);
+ accumulator = g_string_sized_new (64);
+ spec_char = g_string_sized_new (64);
+ loop = 0;
+
+ while (loop < astr->len)
+ {
+ if (mc_search__regex_str_append_if_special (spec_char, astr, &loop))
+ {
+ mc_search__cond_struct_new_regex_accum_append (charset, ret_str, accumulator);
+ g_string_append_len (ret_str, spec_char->str, spec_char->len);
+ g_string_set_size (spec_char, 0);
+ continue;
+ }
+
+ if (astr->str[loop] == '[' && !strutils_is_char_escaped (astr->str, &(astr->str[loop])))
+ {
+ mc_search__cond_struct_new_regex_accum_append (charset, ret_str, accumulator);
+
+ while (loop < astr->len && !(astr->str[loop] == ']'
+ && !strutils_is_char_escaped (astr->str,
+ &(astr->str[loop]))))
+ {
+ g_string_append_c (ret_str, astr->str[loop]);
+ loop++;
+ }
+
+ g_string_append_c (ret_str, astr->str[loop]);
+ loop++;
+ continue;
+ }
+ /*
+ TODO: handle [ and ]
+ */
+ g_string_append_c (accumulator, astr->str[loop]);
+ loop++;
+ }
+ mc_search__cond_struct_new_regex_accum_append (charset, ret_str, accumulator);
+
+ g_string_free (accumulator, TRUE);
+ g_string_free (spec_char, TRUE);
+
+ return ret_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef SEARCH_TYPE_GLIB
+/* A thin wrapper above g_regex_match_full that makes sure the string passed
+ * to it is valid UTF-8 (unless G_REGEX_RAW compile flag was set), as it is a
+ * requirement by glib and it might crash otherwise. See: mc ticket 3449.
+ * Be careful: there might be embedded NULs in the strings. */
+static gboolean
+mc_search__g_regex_match_full_safe (const GRegex * regex,
+ const gchar * string,
+ gssize string_len,
+ gint start_position,
+ GRegexMatchFlags match_options,
+ GMatchInfo ** match_info, GError ** error)
+{
+ char *string_safe, *p, *end;
+ gboolean ret;
+
+ if (string_len < 0)
+ string_len = strlen (string);
+
+ if ((g_regex_get_compile_flags (regex) & G_REGEX_RAW)
+ || g_utf8_validate (string, string_len, NULL))
+ {
+ return g_regex_match_full (regex, string, string_len, start_position, match_options,
+ match_info, error);
+ }
+
+ /* Correctly handle embedded NULs while copying */
+ p = string_safe = g_malloc (string_len + 1);
+ memcpy (string_safe, string, string_len);
+ string_safe[string_len] = '\0';
+ end = p + string_len;
+
+ while (p < end)
+ {
+ gunichar c = g_utf8_get_char_validated (p, -1);
+ if (c != (gunichar) (-1) && c != (gunichar) (-2))
+ {
+ p = g_utf8_next_char (p);
+ }
+ else
+ {
+ /* U+FFFD would be the proper choice, but then we'd have to
+ maintain mapping between old and new offsets.
+ So rather do a byte by byte replacement. */
+ *p++ = '\0';
+ }
+ }
+
+ ret =
+ g_regex_match_full (regex, string_safe, string_len, start_position, match_options,
+ match_info, error);
+ g_free (string_safe);
+ return ret;
+}
+#endif /* SEARCH_TYPE_GLIB */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_search__found_cond_t
+mc_search__regex_found_cond_one (mc_search_t * lc_mc_search, mc_search_regex_t * regex,
+ GString * search_str)
+{
+#ifdef SEARCH_TYPE_GLIB
+ GError *mcerror = NULL;
+
+ if (!mc_search__g_regex_match_full_safe
+ (regex, search_str->str, search_str->len, 0, G_REGEX_MATCH_NEWLINE_ANY,
+ &lc_mc_search->regex_match_info, &mcerror))
+ {
+ g_match_info_free (lc_mc_search->regex_match_info);
+ lc_mc_search->regex_match_info = NULL;
+ if (mcerror != NULL)
+ {
+ lc_mc_search->error = MC_SEARCH_E_REGEX;
+ g_free (lc_mc_search->error_str);
+ lc_mc_search->error_str =
+ str_conv_gerror_message (mcerror, _("Regular expression error"));
+ g_error_free (mcerror);
+ return COND__FOUND_ERROR;
+ }
+ return COND__NOT_FOUND;
+ }
+ lc_mc_search->num_results = g_match_info_get_match_count (lc_mc_search->regex_match_info);
+#else /* SEARCH_TYPE_GLIB */
+
+ lc_mc_search->num_results =
+#ifdef HAVE_PCRE2
+ pcre2_match (regex, (unsigned char *) search_str->str, search_str->len, 0, 0,
+ lc_mc_search->regex_match_info, NULL);
+#else
+ pcre_exec (regex, lc_mc_search->regex_match_info, search_str->str, search_str->len, 0, 0,
+ lc_mc_search->iovector, MC_SEARCH__NUM_REPLACE_ARGS);
+#endif
+ if (lc_mc_search->num_results < 0)
+ {
+ return COND__NOT_FOUND;
+ }
+#endif /* SEARCH_TYPE_GLIB */
+ return COND__FOUND_OK;
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_search__found_cond_t
+mc_search__regex_found_cond (mc_search_t * lc_mc_search, GString * search_str)
+{
+ gsize loop1;
+
+ for (loop1 = 0; loop1 < lc_mc_search->prepared.conditions->len; loop1++)
+ {
+ mc_search_cond_t *mc_search_cond;
+ mc_search__found_cond_t ret;
+
+ mc_search_cond =
+ (mc_search_cond_t *) g_ptr_array_index (lc_mc_search->prepared.conditions, loop1);
+
+ if (!mc_search_cond->regex_handle)
+ continue;
+
+ ret =
+ mc_search__regex_found_cond_one (lc_mc_search, mc_search_cond->regex_handle,
+ search_str);
+ if (ret != COND__NOT_FOUND)
+ return ret;
+ }
+ return COND__NOT_ALL_FOUND;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mc_search_regex__get_max_num_of_replace_tokens (const gchar * str, gsize len)
+{
+ int max_token = 0;
+ gsize loop;
+ for (loop = 0; loop < len - 1; loop++)
+ {
+ if (str[loop] == '\\' && g_ascii_isdigit (str[loop + 1]))
+ {
+ if (strutils_is_char_escaped (str, &str[loop]))
+ continue;
+ if (max_token < str[loop + 1] - '0')
+ max_token = str[loop + 1] - '0';
+ continue;
+ }
+ if (str[loop] == '$' && str[loop + 1] == '{')
+ {
+ gsize tmp_len;
+
+ if (strutils_is_char_escaped (str, &str[loop]))
+ continue;
+
+ for (tmp_len = 0;
+ loop + tmp_len + 2 < len && (str[loop + 2 + tmp_len] & (char) 0xf0) == 0x30;
+ tmp_len++);
+
+ if (str[loop + 2 + tmp_len] == '}')
+ {
+ int tmp_token;
+ char *tmp_str;
+
+ tmp_str = g_strndup (&str[loop + 2], tmp_len);
+ tmp_token = atoi (tmp_str);
+ if (max_token < tmp_token)
+ max_token = tmp_token;
+ g_free (tmp_str);
+ }
+ }
+ }
+ return max_token;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+mc_search_regex__get_token_by_num (const mc_search_t * lc_mc_search, gsize lc_index)
+{
+ int fnd_start = 0, fnd_end = 0;
+
+#ifdef SEARCH_TYPE_GLIB
+ g_match_info_fetch_pos (lc_mc_search->regex_match_info, lc_index, &fnd_start, &fnd_end);
+#else /* SEARCH_TYPE_GLIB */
+ fnd_start = lc_mc_search->iovector[lc_index * 2 + 0];
+ fnd_end = lc_mc_search->iovector[lc_index * 2 + 1];
+#endif /* SEARCH_TYPE_GLIB */
+
+ if (fnd_end == fnd_start)
+ return g_strdup ("");
+
+ return g_strndup (lc_mc_search->regex_buffer->str + fnd_start, fnd_end - fnd_start);
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_search_regex__replace_handle_esc_seq (const GString * replace_str, const gsize current_pos,
+ gsize * skip_len, int *ret)
+{
+ char *curr_str = &(replace_str->str[current_pos]);
+ char c = curr_str[1];
+
+ if (replace_str->len > current_pos + 2)
+ {
+ if (c == '{')
+ {
+ for (*skip_len = 2; /* \{ */
+ current_pos + *skip_len < replace_str->len && curr_str[*skip_len] >= '0'
+ && curr_str[*skip_len] <= '7'; (*skip_len)++)
+ ;
+
+ if (current_pos + *skip_len < replace_str->len && curr_str[*skip_len] == '}')
+ {
+ (*skip_len)++;
+ *ret = REPLACE_PREPARE_T_ESCAPE_SEQ;
+ return FALSE;
+ }
+ else
+ {
+ *ret = REPLACE_PREPARE_T_NOTHING_SPECIAL;
+ return TRUE;
+ }
+ }
+
+ if (c == 'x')
+ {
+ *skip_len = 2; /* \x */
+ c = curr_str[2];
+ if (c == '{')
+ {
+ for (*skip_len = 3; /* \x{ */
+ current_pos + *skip_len < replace_str->len
+ && g_ascii_isxdigit ((guchar) curr_str[*skip_len]); (*skip_len)++)
+ ;
+
+ if (current_pos + *skip_len < replace_str->len && curr_str[*skip_len] == '}')
+ {
+ (*skip_len)++;
+ *ret = REPLACE_PREPARE_T_ESCAPE_SEQ;
+ return FALSE;
+ }
+ else
+ {
+ *ret = REPLACE_PREPARE_T_NOTHING_SPECIAL;
+ return TRUE;
+ }
+ }
+ else if (!g_ascii_isxdigit ((guchar) c))
+ {
+ *skip_len = 2; /* \x without number behind */
+ *ret = REPLACE_PREPARE_T_NOTHING_SPECIAL;
+ return FALSE;
+ }
+ else
+ {
+ c = curr_str[3];
+ if (!g_ascii_isxdigit ((guchar) c))
+ *skip_len = 3; /* \xH */
+ else
+ *skip_len = 4; /* \xHH */
+ *ret = REPLACE_PREPARE_T_ESCAPE_SEQ;
+ return FALSE;
+ }
+ }
+ }
+
+ if (strchr ("ntvbrfa", c) != NULL)
+ {
+ *skip_len = 2;
+ *ret = REPLACE_PREPARE_T_ESCAPE_SEQ;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mc_search_regex__process_replace_str (const GString * replace_str, const gsize current_pos,
+ gsize * skip_len, replace_transform_type_t * replace_flags)
+{
+ int ret = -1;
+ const char *curr_str = &(replace_str->str[current_pos]);
+
+ if (current_pos > replace_str->len)
+ return REPLACE_PREPARE_T_NOTHING_SPECIAL;
+
+ *skip_len = 0;
+
+ if (replace_str->len > current_pos + 2 && curr_str[0] == '$' && curr_str[1] == '{'
+ && (curr_str[2] & (char) 0xf0) == 0x30)
+ {
+ char *tmp_str;
+
+ if (strutils_is_char_escaped (replace_str->str, curr_str))
+ {
+ *skip_len = 1;
+ return REPLACE_PREPARE_T_NOTHING_SPECIAL;
+ }
+
+ for (*skip_len = 0;
+ current_pos + *skip_len + 2 < replace_str->len
+ && (curr_str[2 + *skip_len] & (char) 0xf0) == 0x30; (*skip_len)++)
+ ;
+
+ if (curr_str[2 + *skip_len] != '}')
+ return REPLACE_PREPARE_T_NOTHING_SPECIAL;
+
+ tmp_str = g_strndup (curr_str + 2, *skip_len);
+ if (tmp_str == NULL)
+ return REPLACE_PREPARE_T_NOTHING_SPECIAL;
+
+ ret = atoi (tmp_str);
+ g_free (tmp_str);
+
+ *skip_len += 3; /* ${} */
+ return ret; /* capture buffer index >= 0 */
+ }
+
+ if (curr_str[0] == '\\' && replace_str->len > current_pos + 1)
+ {
+ if (strutils_is_char_escaped (replace_str->str, curr_str))
+ {
+ *skip_len = 1;
+ return REPLACE_PREPARE_T_NOTHING_SPECIAL;
+ }
+
+ if (g_ascii_isdigit (curr_str[1]))
+ {
+ ret = g_ascii_digit_value (curr_str[1]); /* capture buffer index >= 0 */
+ *skip_len = 2; /* \\ and one digit */
+ return ret;
+ }
+
+ if (!mc_search_regex__replace_handle_esc_seq (replace_str, current_pos, skip_len, &ret))
+ return ret;
+
+ ret = REPLACE_PREPARE_T_REPLACE_FLAG;
+ *skip_len += 2;
+
+ switch (curr_str[1])
+ {
+ case 'U':
+ *replace_flags |= REPLACE_T_UPP_TRANSFORM;
+ *replace_flags &= ~REPLACE_T_LOW_TRANSFORM;
+ break;
+ case 'u':
+ *replace_flags |= REPLACE_T_UPP_TRANSFORM_CHAR;
+ break;
+ case 'L':
+ *replace_flags |= REPLACE_T_LOW_TRANSFORM;
+ *replace_flags &= ~REPLACE_T_UPP_TRANSFORM;
+ break;
+ case 'l':
+ *replace_flags |= REPLACE_T_LOW_TRANSFORM_CHAR;
+ break;
+ case 'E':
+ *replace_flags = REPLACE_T_NO_TRANSFORM;
+ break;
+ default:
+ ret = REPLACE_PREPARE_T_NOTHING_SPECIAL;
+ break;
+ }
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_search_regex__process_append_str (GString * dest_str, const char *from, gsize len,
+ replace_transform_type_t * replace_flags)
+{
+ gsize loop;
+ gsize char_len;
+
+ if (len == (gsize) (-1))
+ len = strlen (from);
+
+ if (*replace_flags == REPLACE_T_NO_TRANSFORM)
+ {
+ g_string_append_len (dest_str, from, len);
+ return;
+ }
+
+ for (loop = 0; loop < len; loop += char_len)
+ {
+ GString *tmp_string = NULL;
+ GString *s;
+
+ s = mc_search__get_one_symbol (NULL, from + loop, len - loop, NULL);
+ char_len = s->len;
+
+ if ((*replace_flags & REPLACE_T_UPP_TRANSFORM_CHAR) != 0)
+ {
+ *replace_flags &= ~REPLACE_T_UPP_TRANSFORM_CHAR;
+ tmp_string = mc_search__toupper_case_str (NULL, s);
+ g_string_append_len (dest_str, tmp_string->str, tmp_string->len);
+ }
+ else if ((*replace_flags & REPLACE_T_LOW_TRANSFORM_CHAR) != 0)
+ {
+ *replace_flags &= ~REPLACE_T_LOW_TRANSFORM_CHAR;
+ tmp_string = mc_search__tolower_case_str (NULL, s);
+ g_string_append_len (dest_str, tmp_string->str, tmp_string->len);
+ }
+ else if ((*replace_flags & REPLACE_T_UPP_TRANSFORM) != 0)
+ {
+ tmp_string = mc_search__toupper_case_str (NULL, s);
+ g_string_append_len (dest_str, tmp_string->str, tmp_string->len);
+ }
+ else if ((*replace_flags & REPLACE_T_LOW_TRANSFORM) != 0)
+ {
+ tmp_string = mc_search__tolower_case_str (NULL, s);
+ g_string_append_len (dest_str, tmp_string->str, tmp_string->len);
+ }
+
+ g_string_free (s, TRUE);
+ if (tmp_string != NULL)
+ g_string_free (tmp_string, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_search_regex__process_escape_sequence (GString * dest_str, const char *from, gsize len,
+ replace_transform_type_t * replace_flags,
+ gboolean is_utf8)
+{
+ gsize i = 0;
+ unsigned int c = 0;
+ char b;
+
+ if (len == (gsize) (-1))
+ len = strlen (from);
+ if (len == 0)
+ return;
+
+ if (from[i] == '{')
+ i++;
+ if (i >= len)
+ return;
+
+ if (from[i] == 'x')
+ {
+ i++;
+ if (i < len && from[i] == '{')
+ i++;
+ for (; i < len; i++)
+ {
+ if (from[i] >= '0' && from[i] <= '9')
+ c = c * 16 + from[i] - '0';
+ else if (from[i] >= 'a' && from[i] <= 'f')
+ c = c * 16 + 10 + from[i] - 'a';
+ else if (from[i] >= 'A' && from[i] <= 'F')
+ c = c * 16 + 10 + from[i] - 'A';
+ else
+ break;
+ }
+ }
+ else if (from[i] >= '0' && from[i] <= '7')
+ for (; i < len && from[i] >= '0' && from[i] <= '7'; i++)
+ c = c * 8 + from[i] - '0';
+ else
+ {
+ switch (from[i])
+ {
+ case 'n':
+ c = '\n';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'v':
+ c = '\v';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'a':
+ c = '\a';
+ break;
+ default:
+ mc_search_regex__process_append_str (dest_str, from, len, replace_flags);
+ return;
+ }
+ }
+
+ if (c < 0x80 || !is_utf8)
+ g_string_append_c (dest_str, (char) c);
+ else if (c < 0x800)
+ {
+ b = 0xC0 | (c >> 6);
+ g_string_append_c (dest_str, b);
+ b = 0x80 | (c & 0x3F);
+ g_string_append_c (dest_str, b);
+ }
+ else if (c < 0x10000)
+ {
+ b = 0xE0 | (c >> 12);
+ g_string_append_c (dest_str, b);
+ b = 0x80 | ((c >> 6) & 0x3F);
+ g_string_append_c (dest_str, b);
+ b = 0x80 | (c & 0x3F);
+ g_string_append_c (dest_str, b);
+ }
+ else if (c < 0x10FFFF)
+ {
+ b = 0xF0 | (c >> 16);
+ g_string_append_c (dest_str, b);
+ b = 0x80 | ((c >> 12) & 0x3F);
+ g_string_append_c (dest_str, b);
+ b = 0x80 | ((c >> 6) & 0x3F);
+ g_string_append_c (dest_str, b);
+ b = 0x80 | (c & 0x3F);
+ g_string_append_c (dest_str, b);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_search__cond_struct_new_init_regex (const char *charset, mc_search_t * lc_mc_search,
+ mc_search_cond_t * mc_search_cond)
+{
+ if (lc_mc_search->whole_words && !lc_mc_search->is_entire_line)
+ {
+ /* NOTE: \b as word boundary doesn't allow search
+ * whole words with non-ASCII symbols.
+ * Update: Is it still true nowadays? Probably not. #2396, #3524 */
+ g_string_prepend (mc_search_cond->str, "(?<![\\p{L}\\p{N}_])");
+ g_string_append (mc_search_cond->str, "(?![\\p{L}\\p{N}_])");
+ }
+
+ {
+#ifdef SEARCH_TYPE_GLIB
+ GError *mcerror = NULL;
+ GRegexCompileFlags g_regex_options = G_REGEX_OPTIMIZE | G_REGEX_DOTALL;
+
+ if (str_isutf8 (charset) && mc_global.utf8_display)
+ {
+ if (!lc_mc_search->is_case_sensitive)
+ g_regex_options |= G_REGEX_CASELESS;
+ }
+ else
+ {
+ g_regex_options |= G_REGEX_RAW;
+
+ if (!lc_mc_search->is_case_sensitive)
+ {
+ GString *tmp;
+
+ tmp = mc_search_cond->str;
+ mc_search_cond->str = mc_search__cond_struct_new_regex_ci_str (charset, tmp);
+ g_string_free (tmp, TRUE);
+ }
+ }
+
+ mc_search_cond->regex_handle =
+ g_regex_new (mc_search_cond->str->str, g_regex_options, 0, &mcerror);
+
+ if (mcerror != NULL)
+ {
+ lc_mc_search->error = MC_SEARCH_E_REGEX_COMPILE;
+ g_free (lc_mc_search->error_str);
+ lc_mc_search->error_str =
+ str_conv_gerror_message (mcerror, _("Regular expression error"));
+ g_error_free (mcerror);
+ return;
+ }
+#else /* SEARCH_TYPE_GLIB */
+
+#ifdef HAVE_PCRE2
+ int errcode;
+ char error[BUF_SMALL];
+ size_t erroffset;
+ int pcre_options = PCRE2_MULTILINE;
+#else
+ const char *error;
+ int erroffset;
+ int pcre_options = PCRE_EXTRA | PCRE_MULTILINE;
+#endif
+
+ if (str_isutf8 (charset) && mc_global.utf8_display)
+ {
+#ifdef HAVE_PCRE2
+ pcre_options |= PCRE2_UTF;
+ if (!lc_mc_search->is_case_sensitive)
+ pcre_options |= PCRE2_CASELESS;
+#else
+ pcre_options |= PCRE_UTF8;
+ if (!lc_mc_search->is_case_sensitive)
+ pcre_options |= PCRE_CASELESS;
+#endif
+ }
+ else if (!lc_mc_search->is_case_sensitive)
+ {
+ GString *tmp;
+
+ tmp = mc_search_cond->str;
+ mc_search_cond->str = mc_search__cond_struct_new_regex_ci_str (charset, tmp);
+ g_string_free (tmp, TRUE);
+ }
+
+ mc_search_cond->regex_handle =
+#ifdef HAVE_PCRE2
+ pcre2_compile ((unsigned char *) mc_search_cond->str->str, PCRE2_ZERO_TERMINATED,
+ pcre_options, &errcode, &erroffset, NULL);
+#else
+ pcre_compile (mc_search_cond->str->str, pcre_options, &error, &erroffset, NULL);
+#endif
+ if (mc_search_cond->regex_handle == NULL)
+ {
+#ifdef HAVE_PCRE2
+ pcre2_get_error_message (errcode, (unsigned char *) error, sizeof (error));
+#endif
+ mc_search_set_error (lc_mc_search, MC_SEARCH_E_REGEX_COMPILE, "%s", error);
+ return;
+ }
+#ifdef HAVE_PCRE2
+ if (pcre2_jit_compile (mc_search_cond->regex_handle, PCRE2_JIT_COMPLETE) && *error != '\0')
+#else
+ lc_mc_search->regex_match_info = pcre_study (mc_search_cond->regex_handle, 0, &error);
+ if (lc_mc_search->regex_match_info == NULL && error != NULL)
+#endif
+ {
+ mc_search_set_error (lc_mc_search, MC_SEARCH_E_REGEX_COMPILE, "%s", error);
+ MC_PTR_FREE (mc_search_cond->regex_handle);
+ return;
+ }
+#endif /* SEARCH_TYPE_GLIB */
+ }
+
+ lc_mc_search->is_utf8 = str_isutf8 (charset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_search__run_regex (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len)
+{
+ mc_search_cbret_t ret = MC_SEARCH_CB_NOTFOUND;
+ gsize current_pos, virtual_pos;
+ gint start_pos;
+ gint end_pos;
+
+ if (lc_mc_search->regex_buffer != NULL)
+ g_string_set_size (lc_mc_search->regex_buffer, 0);
+ else
+ lc_mc_search->regex_buffer = g_string_sized_new (64);
+
+ virtual_pos = current_pos = start_search;
+ while (virtual_pos <= end_search)
+ {
+ g_string_set_size (lc_mc_search->regex_buffer, 0);
+ lc_mc_search->start_buffer = current_pos;
+
+ if (lc_mc_search->search_fn != NULL)
+ {
+ while (TRUE)
+ {
+ int current_chr = '\n'; /* stop search symbol */
+
+ ret = lc_mc_search->search_fn (user_data, current_pos, &current_chr);
+
+ if (ret == MC_SEARCH_CB_ABORT)
+ break;
+
+ if (ret == MC_SEARCH_CB_INVALID)
+ continue;
+
+ current_pos++;
+
+ if (ret == MC_SEARCH_CB_SKIP)
+ continue;
+
+ virtual_pos++;
+
+ g_string_append_c (lc_mc_search->regex_buffer, (char) current_chr);
+
+ if ((char) current_chr == '\n' || virtual_pos > end_search)
+ break;
+ }
+ }
+ else
+ {
+ /* optimization for standard case (for search from file manager)
+ * where there is no MC_SEARCH_CB_INVALID or MC_SEARCH_CB_SKIP
+ * return codes, so we can copy line at regex buffer all at once
+ */
+ while (TRUE)
+ {
+ const char current_chr = ((const char *) user_data)[current_pos];
+
+ if (current_chr == '\0')
+ break;
+
+ current_pos++;
+
+ if (current_chr == '\n' || current_pos > end_search)
+ break;
+ }
+
+ /* use virtual_pos as index of start of current chunk */
+ g_string_append_len (lc_mc_search->regex_buffer, (const char *) user_data + virtual_pos,
+ current_pos - virtual_pos);
+ virtual_pos = current_pos;
+ }
+
+ switch (mc_search__regex_found_cond (lc_mc_search, lc_mc_search->regex_buffer))
+ {
+ case COND__FOUND_OK:
+#ifdef SEARCH_TYPE_GLIB
+ g_match_info_fetch_pos (lc_mc_search->regex_match_info, 0, &start_pos, &end_pos);
+#else /* SEARCH_TYPE_GLIB */
+ start_pos = lc_mc_search->iovector[0];
+ end_pos = lc_mc_search->iovector[1];
+#endif /* SEARCH_TYPE_GLIB */
+ if (found_len != NULL)
+ *found_len = end_pos - start_pos;
+ lc_mc_search->normal_offset = lc_mc_search->start_buffer + start_pos;
+ return TRUE;
+ case COND__NOT_ALL_FOUND:
+ break;
+ default:
+ g_string_free (lc_mc_search->regex_buffer, TRUE);
+ lc_mc_search->regex_buffer = NULL;
+ return FALSE;
+ }
+
+ if ((lc_mc_search->update_fn != NULL) &&
+ ((lc_mc_search->update_fn) (user_data, current_pos) == MC_SEARCH_CB_ABORT))
+ ret = MC_SEARCH_CB_ABORT;
+
+ if (ret == MC_SEARCH_CB_ABORT || ret == MC_SEARCH_CB_NOTFOUND)
+ break;
+ }
+
+ g_string_free (lc_mc_search->regex_buffer, TRUE);
+ lc_mc_search->regex_buffer = NULL;
+
+ MC_PTR_FREE (lc_mc_search->error_str);
+ lc_mc_search->error = ret == MC_SEARCH_CB_ABORT ? MC_SEARCH_E_ABORT : MC_SEARCH_E_NOTFOUND;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search_regex_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str)
+{
+ GString *ret;
+
+ int num_replace_tokens;
+ gsize loop;
+ gsize prev = 0;
+ replace_transform_type_t replace_flags = REPLACE_T_NO_TRANSFORM;
+
+ num_replace_tokens =
+ mc_search_regex__get_max_num_of_replace_tokens (replace_str->str, replace_str->len);
+
+ if (lc_mc_search->num_results < 0)
+ return mc_g_string_dup (replace_str);
+
+ if (num_replace_tokens > lc_mc_search->num_results - 1
+ || num_replace_tokens > MC_SEARCH__NUM_REPLACE_ARGS)
+ {
+ mc_search_set_error (lc_mc_search, MC_SEARCH_E_REGEX_REPLACE, "%s",
+ _(STR_E_RPL_NOT_EQ_TO_FOUND));
+ return NULL;
+ }
+
+ ret = g_string_sized_new (64);
+
+ for (loop = 0; loop < replace_str->len - 1; loop++)
+ {
+ int lc_index;
+ gchar *tmp_str;
+ gsize len = 0;
+
+ lc_index = mc_search_regex__process_replace_str (replace_str, loop, &len, &replace_flags);
+
+ if (lc_index == REPLACE_PREPARE_T_NOTHING_SPECIAL)
+ {
+ if (len != 0)
+ {
+ mc_search_regex__process_append_str (ret, replace_str->str + prev, loop - prev,
+ &replace_flags);
+ mc_search_regex__process_append_str (ret, replace_str->str + loop + 1, len - 1,
+ &replace_flags);
+ prev = loop + len;
+ loop = prev - 1; /* prepare to loop++ */
+ }
+
+ continue;
+ }
+
+ if (lc_index == REPLACE_PREPARE_T_REPLACE_FLAG)
+ {
+ if (loop != 0)
+ mc_search_regex__process_append_str (ret, replace_str->str + prev, loop - prev,
+ &replace_flags);
+ prev = loop + len;
+ loop = prev - 1; /* prepare to loop++ */
+ continue;
+ }
+
+ /* escape sequence */
+ if (lc_index == REPLACE_PREPARE_T_ESCAPE_SEQ)
+ {
+ mc_search_regex__process_append_str (ret, replace_str->str + prev, loop - prev,
+ &replace_flags);
+ /* call process_escape_sequence without starting '\\' */
+ mc_search_regex__process_escape_sequence (ret, replace_str->str + loop + 1, len - 1,
+ &replace_flags, lc_mc_search->is_utf8);
+ prev = loop + len;
+ loop = prev - 1; /* prepare to loop++ */
+ continue;
+ }
+
+ /* invalid capture buffer number */
+ if (lc_index > lc_mc_search->num_results)
+ {
+ g_string_free (ret, TRUE);
+ mc_search_set_error (lc_mc_search, MC_SEARCH_E_REGEX_REPLACE,
+ _(STR_E_RPL_INVALID_TOKEN), lc_index);
+ return NULL;
+ }
+
+ tmp_str = mc_search_regex__get_token_by_num (lc_mc_search, lc_index);
+
+ if (loop != 0)
+ mc_search_regex__process_append_str (ret, replace_str->str + prev, loop - prev,
+ &replace_flags);
+
+ mc_search_regex__process_append_str (ret, tmp_str, -1, &replace_flags);
+ g_free (tmp_str);
+
+ prev = loop + len;
+ loop = prev - 1; /* prepare to loop++ */
+ }
+
+ mc_search_regex__process_append_str (ret, replace_str->str + prev, replace_str->len - prev,
+ &replace_flags);
+
+ return ret;
+}
diff --git a/lib/search/search.c b/lib/search/search.c
new file mode 100644
index 0000000..8ccb65f
--- /dev/null
+++ b/lib/search/search.c
@@ -0,0 +1,521 @@
+/*
+ Search text engine.
+ Interface functions
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/search.h"
+#include "lib/util.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const mc_search_type_str_t mc_search__list_types[] = {
+ {N_("No&rmal"), MC_SEARCH_T_NORMAL},
+ {N_("Re&gular expression"), MC_SEARCH_T_REGEX},
+ {N_("He&xadecimal"), MC_SEARCH_T_HEX},
+ {N_("Wil&dcard search"), MC_SEARCH_T_GLOB},
+ {NULL, MC_SEARCH_T_INVALID}
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_search_cond_t *
+mc_search__cond_struct_new (mc_search_t * lc_mc_search, const GString * str, const char *charset)
+{
+ mc_search_cond_t *mc_search_cond;
+
+ mc_search_cond = g_malloc0 (sizeof (mc_search_cond_t));
+ mc_search_cond->str = mc_g_string_dup (str);
+ mc_search_cond->charset = g_strdup (charset);
+#ifdef HAVE_PCRE2
+ lc_mc_search->regex_match_info = pcre2_match_data_create (MC_SEARCH__NUM_REPLACE_ARGS, NULL);
+ lc_mc_search->iovector = pcre2_get_ovector_pointer (lc_mc_search->regex_match_info);
+#endif
+ switch (lc_mc_search->search_type)
+ {
+ case MC_SEARCH_T_GLOB:
+ mc_search__cond_struct_new_init_glob (charset, lc_mc_search, mc_search_cond);
+ break;
+ case MC_SEARCH_T_NORMAL:
+ mc_search__cond_struct_new_init_normal (charset, lc_mc_search, mc_search_cond);
+ break;
+ case MC_SEARCH_T_REGEX:
+ mc_search__cond_struct_new_init_regex (charset, lc_mc_search, mc_search_cond);
+ break;
+ case MC_SEARCH_T_HEX:
+ mc_search__cond_struct_new_init_hex (charset, lc_mc_search, mc_search_cond);
+ break;
+ default:
+ break;
+ }
+ return mc_search_cond;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_search__cond_struct_free (gpointer data)
+{
+ mc_search_cond_t *mc_search_cond = (mc_search_cond_t *) data;
+
+ if (mc_search_cond->upper != NULL)
+ g_string_free (mc_search_cond->upper, TRUE);
+
+ if (mc_search_cond->lower != NULL)
+ g_string_free (mc_search_cond->lower, TRUE);
+
+ g_string_free (mc_search_cond->str, TRUE);
+ g_free (mc_search_cond->charset);
+
+#ifdef SEARCH_TYPE_GLIB
+ if (mc_search_cond->regex_handle != NULL)
+ g_regex_unref (mc_search_cond->regex_handle);
+#else /* SEARCH_TYPE_GLIB */
+ g_free (mc_search_cond->regex_handle);
+#endif /* SEARCH_TYPE_GLIB */
+
+ g_free (mc_search_cond);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/* Init search descriptor.
+ *
+ * @param original pattern to search
+ * @param original_charset charset of #original. If NULL then cp_display will be used
+ *
+ * @return new mc_search_t object. Use #mc_search_free() to free it.
+ */
+
+mc_search_t *
+mc_search_new (const gchar * original, const gchar * original_charset)
+{
+ if (original == NULL)
+ return NULL;
+
+ return mc_search_new_len (original, strlen (original), original_charset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Init search descriptor.
+ *
+ * @param original pattern to search
+ * @param original_len length of #original or -1 if #original is NULL-terminated
+ * @param original_charset charset of #original. If NULL then cp_display will be used
+ *
+ * @return new mc_search_t object. Use #mc_search_free() to free it.
+ */
+
+mc_search_t *
+mc_search_new_len (const gchar * original, gsize original_len, const gchar * original_charset)
+{
+ mc_search_t *lc_mc_search;
+
+ if (original == NULL || original_len == 0)
+ return NULL;
+
+ lc_mc_search = g_new0 (mc_search_t, 1);
+ lc_mc_search->original.str = g_string_new_len (original, original_len);
+#ifdef HAVE_CHARSET
+ lc_mc_search->original.charset =
+ g_strdup (original_charset != NULL
+ && *original_charset != '\0' ? original_charset : cp_display);
+#else
+ (void) original_charset;
+#endif
+
+ return lc_mc_search;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_search_free (mc_search_t * lc_mc_search)
+{
+ if (lc_mc_search == NULL)
+ return;
+
+ g_string_free (lc_mc_search->original.str, TRUE);
+#ifdef HAVE_CHARSET
+ g_free (lc_mc_search->original.charset);
+#endif
+ g_free (lc_mc_search->error_str);
+
+ if (lc_mc_search->prepared.conditions != NULL)
+ g_ptr_array_free (lc_mc_search->prepared.conditions, TRUE);
+
+#ifdef SEARCH_TYPE_GLIB
+ if (lc_mc_search->regex_match_info != NULL)
+ g_match_info_free (lc_mc_search->regex_match_info);
+#else /* SEARCH_TYPE_GLIB */
+ g_free (lc_mc_search->regex_match_info);
+#endif /* SEARCH_TYPE_GLIB */
+
+ if (lc_mc_search->regex_buffer != NULL)
+ g_string_free (lc_mc_search->regex_buffer, TRUE);
+
+ g_free (lc_mc_search);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_search_prepare (mc_search_t * lc_mc_search)
+{
+ GPtrArray *ret;
+
+ if (lc_mc_search->prepared.conditions != NULL)
+ return lc_mc_search->prepared.result;
+
+ ret = g_ptr_array_new_with_free_func (mc_search__cond_struct_free);
+#ifdef HAVE_CHARSET
+ if (!lc_mc_search->is_all_charsets)
+ g_ptr_array_add (ret,
+ mc_search__cond_struct_new (lc_mc_search, lc_mc_search->original.str,
+ lc_mc_search->original.charset));
+ else
+ {
+ gsize loop1;
+
+ for (loop1 = 0; loop1 < codepages->len; loop1++)
+ {
+ const char *id;
+
+ id = ((codepage_desc *) g_ptr_array_index (codepages, loop1))->id;
+ if (g_ascii_strcasecmp (id, lc_mc_search->original.charset) == 0)
+ g_ptr_array_add (ret,
+ mc_search__cond_struct_new (lc_mc_search,
+ lc_mc_search->original.str,
+ lc_mc_search->original.charset));
+ else
+ {
+ GString *buffer;
+
+ buffer =
+ mc_search__recode_str (lc_mc_search->original.str->str,
+ lc_mc_search->original.str->len,
+ lc_mc_search->original.charset, id);
+ g_ptr_array_add (ret, mc_search__cond_struct_new (lc_mc_search, buffer, id));
+ g_string_free (buffer, TRUE);
+ }
+ }
+ }
+#else
+ g_ptr_array_add (ret,
+ mc_search__cond_struct_new (lc_mc_search, lc_mc_search->original.str,
+ str_detect_termencoding ()));
+#endif
+ lc_mc_search->prepared.conditions = ret;
+ lc_mc_search->prepared.result = (lc_mc_search->error == MC_SEARCH_E_OK);
+
+ return lc_mc_search->prepared.result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Carries out the search.
+ *
+ * Returns TRUE if found.
+ *
+ * Returns FALSE if not found. In this case, lc_mc_search->error reveals
+ * the reason:
+ *
+ * - MC_SEARCH_E_NOTFOUND: the pattern isn't in the subject string.
+ * - MC_SEARCH_E_ABORT: the user aborted the search.
+ * - For any other reason (but not for the above two!): the description
+ * is in lc_mc_search->error_str.
+ */
+gboolean
+mc_search_run (mc_search_t * lc_mc_search, const void *user_data,
+ gsize start_search, gsize end_search, gsize * found_len)
+{
+ gboolean ret = FALSE;
+
+ if (lc_mc_search == NULL || user_data == NULL)
+ return FALSE;
+ if (!mc_search_is_type_avail (lc_mc_search->search_type))
+ {
+ mc_search_set_error (lc_mc_search, MC_SEARCH_E_INPUT, "%s", _(STR_E_UNKNOWN_TYPE));
+ return FALSE;
+ }
+#ifdef SEARCH_TYPE_GLIB
+ if (lc_mc_search->regex_match_info != NULL)
+ {
+ g_match_info_free (lc_mc_search->regex_match_info);
+ lc_mc_search->regex_match_info = NULL;
+ }
+#endif /* SEARCH_TYPE_GLIB */
+
+ mc_search_set_error (lc_mc_search, MC_SEARCH_E_OK, NULL);
+
+ if (!mc_search_prepare (lc_mc_search))
+ return FALSE;
+
+ switch (lc_mc_search->search_type)
+ {
+ case MC_SEARCH_T_NORMAL:
+ ret = mc_search__run_normal (lc_mc_search, user_data, start_search, end_search, found_len);
+ break;
+ case MC_SEARCH_T_REGEX:
+ ret = mc_search__run_regex (lc_mc_search, user_data, start_search, end_search, found_len);
+ break;
+ case MC_SEARCH_T_GLOB:
+ ret = mc_search__run_glob (lc_mc_search, user_data, start_search, end_search, found_len);
+ break;
+ case MC_SEARCH_T_HEX:
+ ret = mc_search__run_hex (lc_mc_search, user_data, start_search, end_search, found_len);
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_search_is_type_avail (mc_search_type_t search_type)
+{
+ switch (search_type)
+ {
+ case MC_SEARCH_T_GLOB:
+ case MC_SEARCH_T_NORMAL:
+ case MC_SEARCH_T_REGEX:
+ case MC_SEARCH_T_HEX:
+ return TRUE;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const mc_search_type_str_t *
+mc_search_types_list_get (size_t * num)
+{
+ /* don't count last NULL item */
+ if (num != NULL)
+ *num = G_N_ELEMENTS (mc_search__list_types) - 1;
+
+ return mc_search__list_types;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GString *
+mc_search_prepare_replace_str (mc_search_t * lc_mc_search, GString * replace_str)
+{
+ GString *ret;
+
+ if (replace_str == NULL || replace_str->len == 0)
+ return g_string_new ("");
+
+ if (lc_mc_search == NULL)
+ return mc_g_string_dup (replace_str);
+
+ switch (lc_mc_search->search_type)
+ {
+ case MC_SEARCH_T_REGEX:
+ ret = mc_search_regex_prepare_replace_str (lc_mc_search, replace_str);
+ break;
+ case MC_SEARCH_T_GLOB:
+ ret = mc_search_glob_prepare_replace_str (lc_mc_search, replace_str);
+ break;
+ case MC_SEARCH_T_NORMAL:
+ ret = mc_search_normal_prepare_replace_str (lc_mc_search, replace_str);
+ break;
+ case MC_SEARCH_T_HEX:
+ ret = mc_search_hex_prepare_replace_str (lc_mc_search, replace_str);
+ break;
+ default:
+ ret = mc_g_string_dup (replace_str);
+ break;
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mc_search_prepare_replace_str2 (mc_search_t * lc_mc_search, const char *replace_str)
+{
+ GString *ret;
+ GString *replace_str2;
+
+ replace_str2 = g_string_new (replace_str);
+ ret = mc_search_prepare_replace_str (lc_mc_search, replace_str2);
+ g_string_free (replace_str2, TRUE);
+ return (ret != NULL) ? g_string_free (ret, FALSE) : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_search_is_fixed_search_str (const mc_search_t * lc_mc_search)
+{
+ if (lc_mc_search == NULL)
+ return FALSE;
+ switch (lc_mc_search->search_type)
+ {
+ case MC_SEARCH_T_REGEX:
+ case MC_SEARCH_T_GLOB:
+ return FALSE;
+ default:
+ return TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Search specified pattern in specified string.
+ *
+ * @param pattern string to search
+ * @param pattern_charset charset of #pattern. If NULL then cp_display will be used
+ * @param str string where search #pattern
+ * @param search type (normal, regex, hex or glob)
+ *
+ * @return TRUE if found is successful, FALSE otherwise.
+ */
+
+gboolean
+mc_search (const gchar * pattern, const gchar * pattern_charset, const gchar * str,
+ mc_search_type_t type)
+{
+ gboolean ret;
+ mc_search_t *search;
+
+ if (str == NULL)
+ return FALSE;
+
+ search = mc_search_new (pattern, pattern_charset);
+ if (search == NULL)
+ return FALSE;
+
+ search->search_type = type;
+ search->is_case_sensitive = TRUE;
+
+ if (type == MC_SEARCH_T_GLOB)
+ search->is_entire_line = TRUE;
+
+ ret = mc_search_run (search, str, 0, strlen (str), NULL);
+ mc_search_free (search);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_search_getstart_result_by_num (mc_search_t * lc_mc_search, int lc_index)
+{
+ if (lc_mc_search == NULL)
+ return 0;
+ if (lc_mc_search->search_type == MC_SEARCH_T_NORMAL)
+ return 0;
+#ifdef SEARCH_TYPE_GLIB
+ {
+ gint start_pos;
+ gint end_pos;
+
+ g_match_info_fetch_pos (lc_mc_search->regex_match_info, lc_index, &start_pos, &end_pos);
+ return (int) start_pos;
+ }
+#else /* SEARCH_TYPE_GLIB */
+ return lc_mc_search->iovector[lc_index * 2];
+#endif /* SEARCH_TYPE_GLIB */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_search_getend_result_by_num (mc_search_t * lc_mc_search, int lc_index)
+{
+ if (lc_mc_search == NULL)
+ return 0;
+ if (lc_mc_search->search_type == MC_SEARCH_T_NORMAL)
+ return 0;
+#ifdef SEARCH_TYPE_GLIB
+ {
+ gint start_pos;
+ gint end_pos;
+
+ g_match_info_fetch_pos (lc_mc_search->regex_match_info, lc_index, &start_pos, &end_pos);
+ return (int) end_pos;
+ }
+#else /* SEARCH_TYPE_GLIB */
+ return lc_mc_search->iovector[lc_index * 2 + 1];
+#endif /* SEARCH_TYPE_GLIB */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Replace an old error code and message of an mc_search_t object.
+ *
+ * @param mc_search mc_search_t object
+ * @param code error code, one of mc_search_error_t values
+ * @param format format of error message. If NULL, the old error string is free'd and become NULL
+ */
+
+void
+mc_search_set_error (mc_search_t * lc_mc_search, mc_search_error_t code, const gchar * format, ...)
+{
+ lc_mc_search->error = code;
+
+ MC_PTR_FREE (lc_mc_search->error_str);
+
+ if (format != NULL)
+ {
+ va_list args;
+
+ va_start (args, format);
+ lc_mc_search->error_str = g_strdup_vprintf (format, args);
+ va_end (args);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/serialize.c b/lib/serialize.c
new file mode 100644
index 0000000..1db0a9c
--- /dev/null
+++ b/lib/serialize.c
@@ -0,0 +1,351 @@
+/*
+ Provides a serialize/unserialize functionality for INI-like formats.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file lib/serialize.c
+ * \brief Source: serialize/unserialize functionality for INI-like formats.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "lib/global.h"
+
+#include "lib/serialize.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define SRLZ_DELIM_C ':'
+#define SRLZ_DELIM_S ":"
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+G_GNUC_PRINTF (2, 3)
+prepend_error_message (GError ** error, const char *format, ...)
+{
+ char *prepend_str;
+ char *split_str;
+ va_list ap;
+
+ if ((error == NULL) || (*error == NULL))
+ return;
+
+ va_start (ap, format);
+ prepend_str = g_strdup_vprintf (format, ap);
+ va_end (ap);
+
+ split_str = g_strdup_printf ("%s: %s", prepend_str, (*error)->message);
+ g_free (prepend_str);
+ g_free ((*error)->message);
+ (*error)->message = split_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+go_to_end_of_serialized_string (const char *non_serialized_data,
+ const char *already_serialized_part, size_t * offset)
+{
+ size_t calculated_offset;
+ const char *semi_ptr = strchr (non_serialized_data + 1, SRLZ_DELIM_C);
+
+ calculated_offset = (semi_ptr - non_serialized_data) + 1 + strlen (already_serialized_part);
+ if (calculated_offset >= strlen (non_serialized_data))
+ return NULL;
+
+ non_serialized_data += calculated_offset;
+ *offset += calculated_offset;
+
+ return non_serialized_data;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Serialize some string object to string
+ *
+ * @param prefix prefix for serialization
+ * @param data data for serialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return serialized data as newly allocated string
+ */
+
+char *
+mc_serialize_str (const char prefix, const char *data, GError ** error)
+{
+ if (data == NULL)
+ {
+ g_set_error (error, MC_ERROR, 0, "mc_serialize_str(): Input data is NULL.");
+ return NULL;
+ }
+ return g_strdup_printf ("%c%zu" SRLZ_DELIM_S "%s", prefix, strlen (data), data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deserialize string to string object
+ *
+ * @param prefix prefix for deserailization
+ * @param data data for deserialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return newly allocated string
+ */
+
+#define FUNC_NAME "mc_serialize_str()"
+char *
+mc_deserialize_str (const char prefix, const char *data, GError ** error)
+{
+ size_t data_len;
+
+ if ((data == NULL) || (*data == '\0'))
+ {
+ g_set_error (error, MC_ERROR, 0, FUNC_NAME ": Input data is NULL or empty.");
+ return NULL;
+ }
+
+ if (*data != prefix)
+ {
+ g_set_error (error, MC_ERROR, 0, FUNC_NAME ": String prefix doesn't equal to '%c'", prefix);
+ return NULL;
+ }
+
+ {
+ char buffer[BUF_TINY];
+ char *semi_ptr;
+ size_t semi_offset;
+
+ semi_ptr = strchr (data + 1, SRLZ_DELIM_C);
+ if (semi_ptr == NULL)
+ {
+ g_set_error (error, MC_ERROR, 0,
+ FUNC_NAME ": Length delimiter '%c' doesn't exists", SRLZ_DELIM_C);
+ return NULL;
+ }
+ semi_offset = semi_ptr - (data + 1);
+ if (semi_offset >= BUF_TINY)
+ {
+ g_set_error (error, MC_ERROR, 0, FUNC_NAME ": Too big string length");
+ return NULL;
+ }
+ strncpy (buffer, data + 1, semi_offset);
+ buffer[semi_offset] = '\0';
+ data_len = atol (buffer);
+ data += semi_offset + 2;
+ }
+
+ if (data_len > strlen (data))
+ {
+ g_set_error (error, MC_ERROR, 0,
+ FUNC_NAME
+ ": Specified data length (%zu) is greater than actual data length (%zu)",
+ data_len, strlen (data));
+ return NULL;
+ }
+ return g_strndup (data, data_len);
+}
+
+#undef FUNC_NAME
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Serialize mc_config_t object to string
+ *
+ * @param data data for serialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return serialized data as newly allocated string
+ */
+
+char *
+mc_serialize_config (mc_config_t * data, GError ** error)
+{
+ gchar **groups, **group_iterator;
+ GString *buffer;
+
+ buffer = g_string_new ("");
+ groups = mc_config_get_groups (data, NULL);
+
+ for (group_iterator = groups; *group_iterator != NULL; group_iterator++)
+ {
+ char *serialized_str;
+ gchar **params, **param_iterator;
+
+ serialized_str = mc_serialize_str ('g', *group_iterator, error);
+ if (serialized_str == NULL)
+ {
+ g_string_free (buffer, TRUE);
+ g_strfreev (groups);
+ return NULL;
+ }
+ g_string_append (buffer, serialized_str);
+ g_free (serialized_str);
+
+ params = mc_config_get_keys (data, *group_iterator, NULL);
+
+ for (param_iterator = params; *param_iterator != NULL; param_iterator++)
+ {
+ char *value;
+
+ serialized_str = mc_serialize_str ('p', *param_iterator, error);
+ if (serialized_str == NULL)
+ {
+ g_string_free (buffer, TRUE);
+ g_strfreev (params);
+ g_strfreev (groups);
+ return NULL;
+ }
+ g_string_append (buffer, serialized_str);
+ g_free (serialized_str);
+
+ value = mc_config_get_string_raw (data, *group_iterator, *param_iterator, "");
+ serialized_str = mc_serialize_str ('v', value, error);
+ g_free (value);
+
+ if (serialized_str == NULL)
+ {
+ g_string_free (buffer, TRUE);
+ g_strfreev (params);
+ g_strfreev (groups);
+ return NULL;
+ }
+
+ g_string_append (buffer, serialized_str);
+ g_free (serialized_str);
+ }
+
+ g_strfreev (params);
+ }
+
+ g_strfreev (groups);
+
+ return g_string_free (buffer, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deserialize string to mc_config_t object
+ *
+ * @param data data for serialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return newly allocated mc_config_t object
+ */
+
+#define FUNC_NAME "mc_deserialize_config()"
+#define prepend_error_and_exit() { \
+ prepend_error_message (error, FUNC_NAME " at %zu", current_position + 1); \
+ mc_config_deinit (ret_data); \
+ return NULL; \
+}
+
+mc_config_t *
+mc_deserialize_config (const char *data, GError ** error)
+{
+ char *current_group = NULL, *current_param = NULL, *current_value = NULL;
+ size_t current_position = 0;
+ mc_config_t *ret_data;
+ enum automat_status
+ {
+ WAIT_GROUP,
+ WAIT_PARAM,
+ WAIT_VALUE
+ } current_status = WAIT_GROUP;
+
+ ret_data = mc_config_init (NULL, FALSE);
+
+ while (data != NULL)
+ {
+ if ((current_status == WAIT_GROUP) && (*data == 'p') && (current_group != NULL))
+ current_status = WAIT_PARAM;
+
+ switch (current_status)
+ {
+ case WAIT_GROUP:
+ g_free (current_group);
+
+ current_group = mc_deserialize_str ('g', data, error);
+ if (current_group == NULL)
+ prepend_error_and_exit ();
+
+ data = go_to_end_of_serialized_string (data, current_group, &current_position);
+ current_status = WAIT_PARAM;
+ break;
+ case WAIT_PARAM:
+ g_free (current_param);
+
+ current_param = mc_deserialize_str ('p', data, error);
+ if (current_param == NULL)
+ {
+ g_free (current_group);
+ prepend_error_and_exit ();
+ }
+
+ data = go_to_end_of_serialized_string (data, current_param, &current_position);
+ current_status = WAIT_VALUE;
+ break;
+ case WAIT_VALUE:
+ current_value = mc_deserialize_str ('v', data, error);
+ if (current_value == NULL)
+ {
+ g_free (current_group);
+ g_free (current_param);
+ prepend_error_and_exit ();
+ }
+ mc_config_set_string (ret_data, current_group, current_param, current_value);
+
+ data = go_to_end_of_serialized_string (data, current_value, &current_position);
+ g_free (current_value);
+ current_status = WAIT_GROUP;
+ break;
+ default:
+ break;
+ }
+ }
+ g_free (current_group);
+ g_free (current_param);
+
+ return ret_data;
+}
+
+#undef FUNC_NAME
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/serialize.h b/lib/serialize.h
new file mode 100644
index 0000000..ba24a80
--- /dev/null
+++ b/lib/serialize.h
@@ -0,0 +1,27 @@
+#ifndef MC__SERIALIZE_H
+#define MC__SERIALIZE_H
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+char *mc_serialize_str (const char prefix, const char *data, GError ** error);
+char *mc_deserialize_str (const char prefix, const char *data, GError ** error);
+
+char *mc_serialize_config (mc_config_t * data, GError ** error);
+mc_config_t *mc_deserialize_config (const char *data, GError ** error);
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/lib/shell.c b/lib/shell.c
new file mode 100644
index 0000000..0646171
--- /dev/null
+++ b/lib/shell.c
@@ -0,0 +1,264 @@
+/*
+ Provides a functions for working with shell.
+
+ Copyright (C) 2006-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2015.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file shell.c
+ * \brief Source: provides a functions for working with shell.
+ */
+
+#include <config.h>
+
+#include <pwd.h> /* for username in xterm title */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "global.h"
+#include "util.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char rp_shell[PATH_MAX];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get a system shell.
+ *
+ * @return newly allocated mc_shell_t object with shell name
+ */
+
+static mc_shell_t *
+mc_shell_get_installed_in_system (void)
+{
+ mc_shell_t *mc_shell;
+
+ mc_shell = g_new0 (mc_shell_t, 1);
+
+ /* 3rd choice: look for existing shells supported as MC subshells. */
+ if (access ("/bin/bash", X_OK) == 0)
+ mc_shell->path = g_strdup ("/bin/bash");
+ else if (access ("/bin/ash", X_OK) == 0)
+ mc_shell->path = g_strdup ("/bin/ash");
+ else if (access ("/bin/dash", X_OK) == 0)
+ mc_shell->path = g_strdup ("/bin/dash");
+ else if (access ("/bin/busybox", X_OK) == 0)
+ mc_shell->path = g_strdup ("/bin/busybox");
+ else if (access ("/bin/zsh", X_OK) == 0)
+ mc_shell->path = g_strdup ("/bin/zsh");
+ else if (access ("/bin/tcsh", X_OK) == 0)
+ mc_shell->path = g_strdup ("/bin/tcsh");
+ else if (access ("/bin/csh", X_OK) == 0)
+ mc_shell->path = g_strdup ("/bin/csh");
+ /* No fish as fallback because it is so much different from other shells and
+ * in a way exotic (even though user-friendly by name) that we should not
+ * present it as a subshell without the user's explicit intention. We rather
+ * will not use a subshell but just a command line.
+ * else if (access("/bin/fish", X_OK) == 0)
+ * mc_global.tty.shell = g_strdup ("/bin/fish");
+ */
+ else
+ /* Fallback and last resort: system default shell */
+ mc_shell->path = g_strdup ("/bin/sh");
+
+ return mc_shell;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+mc_shell_get_name_env (void)
+{
+ const char *shell_env;
+ char *shell_name = NULL;
+
+ shell_env = g_getenv ("SHELL");
+ if ((shell_env == NULL) || (shell_env[0] == '\0'))
+ {
+ /* 2nd choice: user login shell */
+ struct passwd *pwd;
+
+ pwd = getpwuid (geteuid ());
+ if (pwd != NULL)
+ shell_name = g_strdup (pwd->pw_shell);
+ }
+ else
+ /* 1st choice: SHELL environment variable */
+ shell_name = g_strdup (shell_env);
+
+ return shell_name;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_shell_t *
+mc_shell_get_from_env (void)
+{
+ mc_shell_t *mc_shell = NULL;
+
+ char *shell_name;
+
+ shell_name = mc_shell_get_name_env ();
+
+ if (shell_name != NULL)
+ {
+ mc_shell = g_new0 (mc_shell_t, 1);
+ mc_shell->path = shell_name;
+ }
+
+ return mc_shell;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_shell_recognize_real_path (mc_shell_t * mc_shell)
+{
+ if (strstr (mc_shell->path, "/zsh") != NULL || strstr (mc_shell->real_path, "/zsh") != NULL
+ || getenv ("ZSH_VERSION") != NULL)
+ {
+ /* Also detects ksh symlinked to zsh */
+ mc_shell->type = SHELL_ZSH;
+ mc_shell->name = "zsh";
+ }
+ else if (strstr (mc_shell->path, "/tcsh") != NULL
+ || strstr (mc_shell->real_path, "/tcsh") != NULL)
+ {
+ /* Also detects csh symlinked to tcsh */
+ mc_shell->type = SHELL_TCSH;
+ mc_shell->name = "tcsh";
+ }
+ else if (strstr (mc_shell->path, "/csh") != NULL
+ || strstr (mc_shell->real_path, "/csh") != NULL)
+ {
+ mc_shell->type = SHELL_TCSH;
+ mc_shell->name = "csh";
+ }
+ else if (strstr (mc_shell->path, "/fish") != NULL
+ || strstr (mc_shell->real_path, "/fish") != NULL)
+ {
+ mc_shell->type = SHELL_FISH;
+ mc_shell->name = "fish";
+ }
+ else if (strstr (mc_shell->path, "/dash") != NULL
+ || strstr (mc_shell->real_path, "/dash") != NULL)
+ {
+ /* Debian ash (also found if symlinked to by ash/sh) */
+ mc_shell->type = SHELL_DASH;
+ mc_shell->name = "dash";
+ }
+ else if (strstr (mc_shell->real_path, "/busybox") != NULL)
+ {
+ /* If shell is symlinked to busybox, assume it is an ash, even though theoretically
+ * it could also be a hush (a mini shell for non-MMU systems deactivated by default).
+ * For simplicity's sake we assume that busybox always contains an ash, not a hush.
+ * On embedded platforms or on server systems, /bin/sh often points to busybox.
+ * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option),
+ * so we need to check busybox symlinks *before* checking for the name "bash"
+ * in order to avoid that case. */
+ mc_shell->type = SHELL_ASH_BUSYBOX;
+ mc_shell->name = mc_shell->path;
+ }
+ else
+ mc_shell->type = SHELL_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_shell_recognize_path (mc_shell_t * mc_shell)
+{
+ /* If shell is not symlinked to busybox, it is safe to assume it is a real shell */
+ if (strstr (mc_shell->path, "/bash") != NULL || getenv ("BASH") != NULL)
+ {
+ mc_shell->type = SHELL_BASH;
+ mc_shell->name = "bash";
+ }
+ else if (strstr (mc_shell->path, "/sh") != NULL || getenv ("SH") != NULL)
+ {
+ mc_shell->type = SHELL_SH;
+ mc_shell->name = "sh";
+ }
+ else if (strstr (mc_shell->path, "/ash") != NULL || getenv ("ASH") != NULL)
+ {
+ mc_shell->type = SHELL_ASH_BUSYBOX;
+ mc_shell->name = "ash";
+ }
+ else
+ mc_shell->type = SHELL_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_shell_init (void)
+{
+ mc_shell_t *mc_shell;
+
+ mc_shell = mc_shell_get_from_env ();
+
+ if (mc_shell == NULL)
+ mc_shell = mc_shell_get_installed_in_system ();
+
+ mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell);
+
+ /* Find out what type of shell we have. Also consider real paths (resolved symlinks)
+ * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */
+
+ if (mc_shell->real_path != NULL)
+ mc_shell_recognize_real_path (mc_shell);
+
+ if (mc_shell->type == SHELL_NONE)
+ mc_shell_recognize_path (mc_shell);
+
+ if (mc_shell->type == SHELL_NONE)
+ mc_global.tty.use_subshell = FALSE;
+
+ mc_global.shell = mc_shell;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_shell_deinit (void)
+{
+ if (mc_global.shell != NULL)
+ {
+ g_free (mc_global.shell->path);
+ MC_PTR_FREE (mc_global.shell);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/shell.h b/lib/shell.h
new file mode 100644
index 0000000..7ba82dc
--- /dev/null
+++ b/lib/shell.h
@@ -0,0 +1,43 @@
+/** \file shell.h
+ * \brief Header: shell structure
+ */
+
+#ifndef MC_SHELL_H
+#define MC_SHELL_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ SHELL_NONE,
+ SHELL_SH,
+ SHELL_BASH,
+ SHELL_ASH_BUSYBOX, /* BusyBox default shell (ash) */
+ SHELL_DASH, /* Debian variant of ash */
+ SHELL_TCSH,
+ SHELL_ZSH,
+ SHELL_FISH
+} shell_type_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ shell_type_t type;
+ const char *name;
+ char *path;
+ char *real_path;
+} mc_shell_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void mc_shell_init (void);
+void mc_shell_deinit (void);
+
+/*** inline functions **************************************************/
+
+#endif /* MC_SHELL_H */
diff --git a/lib/skin.h b/lib/skin.h
new file mode 100644
index 0000000..024e0c5
--- /dev/null
+++ b/lib/skin.h
@@ -0,0 +1,145 @@
+#ifndef MC_SKIN_H
+#define MC_SKIN_H
+
+#include "lib/global.h"
+
+#include "lib/mcconfig.h"
+
+#include "lib/tty/color.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* Beware! When using Slang with color, not all the indexes are free.
+ See color-slang.h (A_*) */
+
+/* cache often used colors */
+#define DEFAULT_COLOR mc_skin_color__cache[0]
+#define NORMAL_COLOR mc_skin_color__cache[1]
+#define MARKED_COLOR mc_skin_color__cache[2]
+#define SELECTED_COLOR mc_skin_color__cache[3]
+#define MARKED_SELECTED_COLOR mc_skin_color__cache[4]
+#define DISABLED_COLOR mc_skin_color__cache[5]
+#define REVERSE_COLOR mc_skin_color__cache[6]
+#define COMMAND_MARK_COLOR mc_skin_color__cache[7]
+#define HEADER_COLOR mc_skin_color__cache[8]
+#define SHADOW_COLOR mc_skin_color__cache[9]
+
+/* Dialog colors */
+#define COLOR_NORMAL mc_skin_color__cache[10]
+#define COLOR_FOCUS mc_skin_color__cache[11]
+#define COLOR_HOT_NORMAL mc_skin_color__cache[12]
+#define COLOR_HOT_FOCUS mc_skin_color__cache[13]
+#define COLOR_TITLE mc_skin_color__cache[14]
+
+/* Error dialog colors */
+#define ERROR_COLOR mc_skin_color__cache[15]
+#define ERROR_FOCUS mc_skin_color__cache[16]
+#define ERROR_HOT_NORMAL mc_skin_color__cache[17]
+#define ERROR_HOT_FOCUS mc_skin_color__cache[18]
+#define ERROR_TITLE mc_skin_color__cache[19]
+
+/* Menu colors */
+#define MENU_ENTRY_COLOR mc_skin_color__cache[20]
+#define MENU_SELECTED_COLOR mc_skin_color__cache[21]
+#define MENU_HOT_COLOR mc_skin_color__cache[22]
+#define MENU_HOTSEL_COLOR mc_skin_color__cache[23]
+#define MENU_INACTIVE_COLOR mc_skin_color__cache[24]
+
+/* Popup menu colors */
+#define PMENU_ENTRY_COLOR mc_skin_color__cache[25]
+#define PMENU_SELECTED_COLOR mc_skin_color__cache[26]
+#define PMENU_HOT_COLOR mc_skin_color__cache[27] /* unused: not implemented yet */
+#define PMENU_HOTSEL_COLOR mc_skin_color__cache[28] /* unused: not implemented yet */
+#define PMENU_TITLE_COLOR mc_skin_color__cache[29]
+
+#define BUTTONBAR_HOTKEY_COLOR mc_skin_color__cache[30]
+#define BUTTONBAR_BUTTON_COLOR mc_skin_color__cache[31]
+
+#define STATUSBAR_COLOR mc_skin_color__cache[32]
+
+/*
+ * This should be selectable independently. Default has to be black background
+ * foreground does not matter at all.
+ */
+#define GAUGE_COLOR mc_skin_color__cache[33]
+#define INPUT_COLOR mc_skin_color__cache[34]
+#define INPUT_UNCHANGED_COLOR mc_skin_color__cache[35]
+#define INPUT_MARK_COLOR mc_skin_color__cache[36]
+#define INPUT_HISTORY_COLOR mc_skin_color__cache[37]
+#define COMMAND_HISTORY_COLOR mc_skin_color__cache[38]
+
+#define HELP_NORMAL_COLOR mc_skin_color__cache[39]
+#define HELP_ITALIC_COLOR mc_skin_color__cache[40]
+#define HELP_BOLD_COLOR mc_skin_color__cache[41]
+#define HELP_LINK_COLOR mc_skin_color__cache[42]
+#define HELP_SLINK_COLOR mc_skin_color__cache[43]
+#define HELP_TITLE_COLOR mc_skin_color__cache[44]
+
+
+#define VIEW_NORMAL_COLOR mc_skin_color__cache[45]
+#define VIEW_BOLD_COLOR mc_skin_color__cache[46]
+#define VIEW_UNDERLINED_COLOR mc_skin_color__cache[47]
+#define VIEW_SELECTED_COLOR mc_skin_color__cache[48]
+
+/*
+ * editor colors - only 4 for normal, search->found, select, and whitespace
+ * respectively
+ * Last is defined to view color.
+ */
+#define EDITOR_NORMAL_COLOR mc_skin_color__cache[49]
+#define EDITOR_BOLD_COLOR mc_skin_color__cache[50]
+#define EDITOR_MARKED_COLOR mc_skin_color__cache[51]
+#define EDITOR_WHITESPACE_COLOR mc_skin_color__cache[52]
+#define EDITOR_RIGHT_MARGIN_COLOR mc_skin_color__cache[53]
+#define EDITOR_BACKGROUND mc_skin_color__cache[54]
+#define EDITOR_FRAME mc_skin_color__cache[55]
+#define EDITOR_FRAME_ACTIVE mc_skin_color__cache[56]
+#define EDITOR_FRAME_DRAG mc_skin_color__cache[57]
+/* color of left 8 char status per line */
+#define LINE_STATE_COLOR mc_skin_color__cache[58]
+#define BOOK_MARK_COLOR mc_skin_color__cache[59]
+#define BOOK_MARK_FOUND_COLOR mc_skin_color__cache[60]
+
+/* Diff colors */
+#define DFF_ADD_COLOR mc_skin_color__cache[61]
+#define DFF_CHG_COLOR mc_skin_color__cache[62]
+#define DFF_CHH_COLOR mc_skin_color__cache[63]
+#define DFF_CHD_COLOR mc_skin_color__cache[64]
+#define DFF_DEL_COLOR mc_skin_color__cache[65]
+#define DFF_ERROR_COLOR mc_skin_color__cache[66]
+
+#define MC_SKIN_COLOR_CACHE_COUNT 67
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_skin_struct
+{
+ gchar *name;
+ gchar *description;
+ mc_config_t *config;
+ GHashTable *colors;
+ gboolean have_256_colors;
+ gboolean have_true_colors;
+} mc_skin_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int mc_skin_color__cache[];
+extern mc_skin_t mc_skin__default;
+
+/*** declarations of public functions ************************************************************/
+
+gboolean mc_skin_init (const gchar * skin_override, GError ** error);
+void mc_skin_deinit (void);
+
+int mc_skin_color_get (const gchar * group, const gchar * name);
+
+void mc_skin_lines_parse_ini_file (mc_skin_t * mc_skin);
+
+gchar *mc_skin_get (const gchar * group, const gchar * key, const gchar * default_value);
+
+GPtrArray *mc_skin_list (void);
+
+#endif /* MC_SKIN_H */
diff --git a/lib/skin/Makefile.am b/lib/skin/Makefile.am
new file mode 100644
index 0000000..04f12ed
--- /dev/null
+++ b/lib/skin/Makefile.am
@@ -0,0 +1,12 @@
+noinst_LTLIBRARIES = libmcskin.la
+
+libmcskin_la_SOURCES = \
+ colors.c \
+ colors-old.c \
+ common.c \
+ hc-skins.c \
+ ini-file.c \
+ lines.c \
+ internal.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/lib/skin/Makefile.in b/lib/skin/Makefile.in
new file mode 100644
index 0000000..62f642b
--- /dev/null
+++ b/lib/skin/Makefile.in
@@ -0,0 +1,760 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lib/skin
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcskin_la_LIBADD =
+am_libmcskin_la_OBJECTS = colors.lo colors-old.lo common.lo \
+ hc-skins.lo ini-file.lo lines.lo
+libmcskin_la_OBJECTS = $(am_libmcskin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/colors-old.Plo \
+ ./$(DEPDIR)/colors.Plo ./$(DEPDIR)/common.Plo \
+ ./$(DEPDIR)/hc-skins.Plo ./$(DEPDIR)/ini-file.Plo \
+ ./$(DEPDIR)/lines.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcskin_la_SOURCES)
+DIST_SOURCES = $(libmcskin_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcskin.la
+libmcskin_la_SOURCES = \
+ colors.c \
+ colors-old.c \
+ common.c \
+ hc-skins.c \
+ ini-file.c \
+ lines.c \
+ internal.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/skin/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/skin/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcskin.la: $(libmcskin_la_OBJECTS) $(libmcskin_la_DEPENDENCIES) $(EXTRA_libmcskin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcskin_la_OBJECTS) $(libmcskin_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colors-old.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colors.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hc-skins.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ini-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lines.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/colors-old.Plo
+ -rm -f ./$(DEPDIR)/colors.Plo
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/hc-skins.Plo
+ -rm -f ./$(DEPDIR)/ini-file.Plo
+ -rm -f ./$(DEPDIR)/lines.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/colors-old.Plo
+ -rm -f ./$(DEPDIR)/colors.Plo
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/hc-skins.Plo
+ -rm -f ./$(DEPDIR)/ini-file.Plo
+ -rm -f ./$(DEPDIR)/lines.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/skin/colors-old.c b/lib/skin/colors-old.c
new file mode 100644
index 0000000..628b2aa
--- /dev/null
+++ b/lib/skin/colors-old.c
@@ -0,0 +1,203 @@
+/*
+ Skins engine.
+ Work with colors - backward compatibility
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Egmont Koblinger <egmont@gmail.com>, 2010
+ Andrew Borodin <aborodin@vmail.ru>, 2012
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h> /* strcmp() */
+#include <sys/types.h> /* size_t */
+
+#include "internal.h"
+
+#include "lib/tty/color.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct mc_skin_colors_old_struct
+{
+ const char *old_color;
+ const char *group;
+ const char *key;
+} mc_skin_colors_old_t;
+
+/*** file scope variables ************************************************************************/
+
+/* keep this table alphabetically sorted */
+static const mc_skin_colors_old_t old_colors[] = {
+ {"bbarbutton", "buttonbar", "button"},
+ {"bbarhotkey", "buttonbar", "hotkey"},
+ {"commandlinemark", "core", "commandlinemark"},
+ {"dfocus", "dialog", "dfocus"},
+ {"dhotfocus", "dialog", "dhotfocus"},
+ {"dhotnormal", "dialog", "dhotnormal"},
+ {"disabled", "core", "disabled"},
+ {"dnormal", "dialog", "_default_"},
+ {"editbg", "editor", "editbg"},
+ {"editbold", "editor", "editbold"},
+ {"editframe", "editor", "editframe"},
+ {"editframeactive", "editor", "editframeactive"},
+ {"editframedrag", "editor", "editframedrag"},
+ {"editlinestate", "editor", "editlinestate"},
+ {"editmarked", "editor", "editmarked"},
+ {"editnormal", "editor", "_default_"},
+ {"editwhitespace", "editor", "editwhitespace"},
+ {"errdhotfocus", "error", "errdhotfocus"},
+ {"errdhotnormal", "error", "errdhotnormal"},
+ {"errors", "error", "_default_"},
+ {"gauge", "core", "gauge"},
+ {"header", "core", "header"},
+ {"helpbold", "help", "helpbold"},
+ {"helpitalic", "help", "helpitalic"},
+ {"helplink", "help", "helplink"},
+ {"helpnormal", "help", "_default_"},
+ {"helpslink", "help", "helpslink"},
+ {"input", "core", "input"},
+ {"inputmark", "core", "inputmark"},
+ {"inputunchanged", "core", "inputunchanged"},
+ {"marked", "core", "marked"},
+ {"markselect", "core", "markselect"},
+ {"menuhot", "menu", "menuhot"},
+ {"menuhotsel", "menu", "menuhotsel"},
+ {"menuinactive", "menu", "menuinactive"},
+ {"menunormal", "menu", "_default_"},
+ {"menusel", "menu", "menusel"},
+ {"normal", "core", "_default_"},
+ {"pmenunormal", "popupmenu", "_default_"},
+ {"pmenusel", "popupmenu", "menusel"},
+ {"pmenutitle", "popupmenu", "menutitle"},
+ {"reverse", "core", "reverse"},
+ {"selected", "core", "selected"},
+ {"statusbar", "statusbar", "_default_"},
+ {"viewbold", "viewer", "viewbold"},
+ {"viewnormal", "viewer", "_default_"},
+ {"viewselected", "viewer", "viewselected"},
+ {"viewunderline", "viewer", "viewunderline"}
+};
+
+static const size_t num_old_colors = G_N_ELEMENTS (old_colors);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+old_color_comparator (const void *p1, const void *p2)
+{
+ const mc_skin_colors_old_t *m1 = (const mc_skin_colors_old_t *) p1;
+ const mc_skin_colors_old_t *m2 = (const mc_skin_colors_old_t *) p2;
+
+ return strcmp (m1->old_color, m2->old_color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_skin_colors_old_transform (const char *old_color, const char **group, const char **key)
+{
+ const mc_skin_colors_old_t oc = { old_color, NULL, NULL };
+ mc_skin_colors_old_t *res;
+
+ if (old_color == NULL)
+ return FALSE;
+
+ res = (mc_skin_colors_old_t *) bsearch (&oc, old_colors, num_old_colors,
+ sizeof (old_colors[0]), old_color_comparator);
+
+ if (res == NULL)
+ return FALSE;
+
+ if (group != NULL)
+ *group = res->group;
+ if (key != NULL)
+ *key = res->key;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_colors_old_configure_one (mc_skin_t * mc_skin, const char *the_color_string)
+{
+ gchar **colors, **orig_colors;
+
+ if (the_color_string == NULL)
+ return;
+
+ orig_colors = g_strsplit (the_color_string, ":", -1);
+ if (orig_colors == NULL)
+ return;
+
+ for (colors = orig_colors; *colors != NULL; colors++)
+ {
+ gchar **key_val;
+ const gchar *skin_group, *skin_key;
+
+ key_val = g_strsplit_set (*colors, "=,", 4);
+
+ if (key_val == NULL)
+ continue;
+
+ if (key_val[1] != NULL && mc_skin_colors_old_transform (key_val[0], &skin_group, &skin_key))
+ {
+ gchar *skin_val;
+
+ if (key_val[2] == NULL)
+ skin_val = g_strdup_printf ("%s;", key_val[1]);
+ else if (key_val[3] == NULL)
+ skin_val = g_strdup_printf ("%s;%s", key_val[1], key_val[2]);
+ else
+ skin_val = g_strdup_printf ("%s;%s;%s", key_val[1], key_val[2], key_val[3]);
+
+ mc_config_set_string (mc_skin->config, skin_group, skin_key, skin_val);
+ g_free (skin_val);
+ }
+
+ g_strfreev (key_val);
+ }
+ g_strfreev (orig_colors);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_skin_colors_old_configure (mc_skin_t * mc_skin)
+{
+ mc_skin_colors_old_configure_one (mc_skin, mc_global.tty.setup_color_string);
+ mc_skin_colors_old_configure_one (mc_skin, mc_global.tty.term_color_string);
+ mc_skin_colors_old_configure_one (mc_skin, getenv ("MC_COLOR_TABLE"));
+ mc_skin_colors_old_configure_one (mc_skin, mc_global.tty.command_line_colors);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/skin/colors.c b/lib/skin/colors.c
new file mode 100644
index 0000000..87e9442
--- /dev/null
+++ b/lib/skin/colors.c
@@ -0,0 +1,422 @@
+/*
+ Skins engine.
+ Work with colors
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Egmont Koblinger <egmont@gmail.com>, 2010
+ Andrew Borodin <aborodin@vmail.ru>, 2012
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "internal.h"
+
+#include "lib/tty/color.h"
+
+/*** global variables ****************************************************************************/
+
+int mc_skin_color__cache[MC_SKIN_COLOR_CACHE_COUNT];
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_skin_color_t *
+mc_skin_color_get_from_hash (mc_skin_t * mc_skin, const gchar * group, const gchar * key)
+{
+ gchar kname[BUF_TINY];
+ mc_skin_color_t *mc_skin_color;
+
+ if (group == NULL || key == NULL)
+ return NULL;
+
+ if (mc_skin == NULL)
+ mc_skin = &mc_skin__default;
+
+ g_snprintf (kname, sizeof (kname), "%s.%s", group, key);
+ mc_skin_color = (mc_skin_color_t *) g_hash_table_lookup (mc_skin->colors, (gpointer) kname);
+
+ return mc_skin_color;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static void
+mc_skin_color_remove_from_hash (mc_skin_t * mc_skin, const gchar * group, const gchar * key)
+{
+ gchar kname[BUF_TINY];
+ if (group == NULL || key == NULL)
+ return;
+
+ if (mc_skin == NULL)
+ mc_skin = &mc_skin__default;
+
+ g_snprintf (kname, sizeof (kname), "%s.%s", group, key);
+ g_hash_table_remove (mc_skin->colors, (gpointer) kname);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_color_add_to_hash (mc_skin_t * mc_skin, const gchar * group, const gchar * key,
+ mc_skin_color_t * mc_skin_color)
+{
+ gchar *kname;
+
+ kname = g_strdup_printf ("%s.%s", group, key);
+ if (kname != NULL)
+ {
+ if (g_hash_table_lookup (mc_skin->colors, (gpointer) kname) != NULL)
+ g_hash_table_remove (mc_skin->colors, (gpointer) kname);
+
+ g_hash_table_insert (mc_skin->colors, (gpointer) kname, (gpointer) mc_skin_color);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_skin_color_t *
+mc_skin_color_get_with_defaults (const gchar * group, const gchar * name)
+{
+ mc_skin_color_t *mc_skin_color;
+
+ mc_skin_color = mc_skin_color_get_from_hash (NULL, group, name);
+ if (mc_skin_color != NULL)
+ return mc_skin_color;
+
+ mc_skin_color = mc_skin_color_get_from_hash (NULL, group, "_default_");
+ if (mc_skin_color != NULL)
+ return mc_skin_color;
+
+ mc_skin_color = mc_skin_color_get_from_hash (NULL, "core", "_default_");
+ return mc_skin_color;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* If an alias is found, alloc a new string for the resolved value and free the input parameter.
+ Otherwise it's a no-op returning the original string. */
+static gchar *
+mc_skin_color_look_up_alias (mc_skin_t * mc_skin, gchar * str)
+{
+ gchar *orig, *str2;
+ int hop = 0;
+
+ orig = g_strdup (str);
+ str2 = g_strdup (str);
+
+ while (TRUE)
+ {
+ gchar **values;
+ gsize items_count;
+
+ values = mc_config_get_string_list (mc_skin->config, "aliases", str, &items_count);
+ if (items_count != 1)
+ {
+ /* No such alias declaration found, that is, we've got the resolved value. */
+ g_strfreev (values);
+ g_free (str2);
+ g_free (orig);
+ return str;
+ }
+
+ g_free (str);
+ str = g_strdup (values[0]);
+ g_strfreev (values);
+
+ /* str2 resolves at half speed than str. This is used for loop detection. */
+ if (hop++ % 2 != 0)
+ {
+ values = mc_config_get_string_list (mc_skin->config, "aliases", str2, &items_count);
+ g_assert (items_count == 1);
+ g_free (str2);
+ str2 = g_strdup (values[0]);
+ g_strfreev (values);
+
+ if (strcmp (str, str2) == 0)
+ {
+ /* Loop detected. */
+ fprintf (stderr,
+ "Loop detected while trying to resolve alias \"%s\" in skin \"%s\"\n",
+ orig, mc_skin->name);
+ g_free (str);
+ g_free (str2);
+ return orig;
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_skin_color_t *
+mc_skin_color_get_from_ini_file (mc_skin_t * mc_skin, const gchar * group, const gchar * key)
+{
+ gsize items_count;
+ gchar **values;
+ mc_skin_color_t *mc_skin_color, *tmp;
+
+ values = mc_config_get_string_list (mc_skin->config, group, key, &items_count);
+ if (values == NULL || values[0] == NULL)
+ {
+ g_strfreev (values);
+ return NULL;
+ }
+
+ mc_skin_color = g_try_new0 (mc_skin_color_t, 1);
+ if (mc_skin_color == NULL)
+ {
+ g_strfreev (values);
+ return NULL;
+ }
+
+ tmp = mc_skin_color_get_with_defaults (group, "_default_");
+ mc_skin_color->fgcolor = (items_count > 0 && values[0][0]) ?
+ mc_skin_color_look_up_alias (mc_skin, g_strstrip (g_strdup (values[0]))) :
+ (tmp != NULL) ? g_strdup (tmp->fgcolor) : NULL;
+ mc_skin_color->bgcolor = (items_count > 1 && values[1][0]) ?
+ mc_skin_color_look_up_alias (mc_skin, g_strstrip (g_strdup (values[1]))) :
+ (tmp != NULL) ? g_strdup (tmp->bgcolor) : NULL;
+ mc_skin_color->attrs = (items_count > 2 && values[2][0]) ?
+ mc_skin_color_look_up_alias (mc_skin, g_strstrip (g_strdup (values[2]))) :
+ (tmp != NULL) ? g_strdup (tmp->attrs) : NULL;
+
+ g_strfreev (values);
+
+ mc_skin_color->pair_index =
+ tty_try_alloc_color_pair2 (mc_skin_color->fgcolor, mc_skin_color->bgcolor,
+ mc_skin_color->attrs, FALSE);
+
+ return mc_skin_color;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_color_set_default_for_terminal (mc_skin_t * mc_skin)
+{
+ mc_skin_color_t *mc_skin_color;
+ mc_skin_color = g_try_new0 (mc_skin_color_t, 1);
+ if (mc_skin_color != NULL)
+ {
+ mc_skin_color->fgcolor = g_strdup ("default");
+ mc_skin_color->bgcolor = g_strdup ("default");
+ mc_skin_color->attrs = NULL;
+ mc_skin_color->pair_index =
+ tty_try_alloc_color_pair2 (mc_skin_color->fgcolor, mc_skin_color->bgcolor,
+ mc_skin_color->attrs, FALSE);
+ mc_skin_color_add_to_hash (mc_skin, "skin", "terminal_default_color", mc_skin_color);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_color_cache_init (void)
+{
+ DEFAULT_COLOR = mc_skin_color_get ("skin", "terminal_default_color");
+ NORMAL_COLOR = mc_skin_color_get ("core", "_default_");
+ MARKED_COLOR = mc_skin_color_get ("core", "marked");
+ SELECTED_COLOR = mc_skin_color_get ("core", "selected");
+ MARKED_SELECTED_COLOR = mc_skin_color_get ("core", "markselect");
+ DISABLED_COLOR = mc_skin_color_get ("core", "disabled");
+ REVERSE_COLOR = mc_skin_color_get ("core", "reverse");
+ HEADER_COLOR = mc_skin_color_get ("core", "header");
+ COMMAND_MARK_COLOR = mc_skin_color_get ("core", "commandlinemark");
+ SHADOW_COLOR = mc_skin_color_get ("core", "shadow");
+
+ COLOR_NORMAL = mc_skin_color_get ("dialog", "_default_");
+ COLOR_FOCUS = mc_skin_color_get ("dialog", "dfocus");
+ COLOR_HOT_NORMAL = mc_skin_color_get ("dialog", "dhotnormal");
+ COLOR_HOT_FOCUS = mc_skin_color_get ("dialog", "dhotfocus");
+ COLOR_TITLE = mc_skin_color_get ("dialog", "dtitle");
+
+ ERROR_COLOR = mc_skin_color_get ("error", "_default_");
+ ERROR_FOCUS = mc_skin_color_get ("error", "errdfocus");
+ ERROR_HOT_NORMAL = mc_skin_color_get ("error", "errdhotnormal");
+ ERROR_HOT_FOCUS = mc_skin_color_get ("error", "errdhotfocus");
+ ERROR_TITLE = mc_skin_color_get ("error", "errdtitle");
+
+ MENU_ENTRY_COLOR = mc_skin_color_get ("menu", "_default_");
+ MENU_SELECTED_COLOR = mc_skin_color_get ("menu", "menusel");
+ MENU_HOT_COLOR = mc_skin_color_get ("menu", "menuhot");
+ MENU_HOTSEL_COLOR = mc_skin_color_get ("menu", "menuhotsel");
+ MENU_INACTIVE_COLOR = mc_skin_color_get ("menu", "menuinactive");
+
+ PMENU_ENTRY_COLOR = mc_skin_color_get ("popupmenu", "_default_");
+ PMENU_SELECTED_COLOR = mc_skin_color_get ("popupmenu", "menusel");
+ PMENU_TITLE_COLOR = mc_skin_color_get ("popupmenu", "menutitle");
+
+ BUTTONBAR_HOTKEY_COLOR = mc_skin_color_get ("buttonbar", "hotkey");
+ BUTTONBAR_BUTTON_COLOR = mc_skin_color_get ("buttonbar", "button");
+
+ STATUSBAR_COLOR = mc_skin_color_get ("statusbar", "_default_");
+
+ GAUGE_COLOR = mc_skin_color_get ("core", "gauge");
+ INPUT_COLOR = mc_skin_color_get ("core", "input");
+ INPUT_HISTORY_COLOR = mc_skin_color_get ("core", "inputhistory");
+ COMMAND_HISTORY_COLOR = mc_skin_color_get ("core", "commandhistory");
+ INPUT_MARK_COLOR = mc_skin_color_get ("core", "inputmark");
+ INPUT_UNCHANGED_COLOR = mc_skin_color_get ("core", "inputunchanged");
+
+ HELP_NORMAL_COLOR = mc_skin_color_get ("help", "_default_");
+ HELP_ITALIC_COLOR = mc_skin_color_get ("help", "helpitalic");
+ HELP_BOLD_COLOR = mc_skin_color_get ("help", "helpbold");
+ HELP_LINK_COLOR = mc_skin_color_get ("help", "helplink");
+ HELP_SLINK_COLOR = mc_skin_color_get ("help", "helpslink");
+ HELP_TITLE_COLOR = mc_skin_color_get ("help", "helptitle");
+
+ VIEW_NORMAL_COLOR = mc_skin_color_get ("viewer", "_default_");
+ VIEW_BOLD_COLOR = mc_skin_color_get ("viewer", "viewbold");
+ VIEW_UNDERLINED_COLOR = mc_skin_color_get ("viewer", "viewunderline");
+ VIEW_SELECTED_COLOR = mc_skin_color_get ("viewer", "viewselected");
+
+ EDITOR_NORMAL_COLOR = mc_skin_color_get ("editor", "_default_");
+ EDITOR_BOLD_COLOR = mc_skin_color_get ("editor", "editbold");
+ EDITOR_MARKED_COLOR = mc_skin_color_get ("editor", "editmarked");
+ EDITOR_WHITESPACE_COLOR = mc_skin_color_get ("editor", "editwhitespace");
+ EDITOR_RIGHT_MARGIN_COLOR = mc_skin_color_get ("editor", "editrightmargin");
+ LINE_STATE_COLOR = mc_skin_color_get ("editor", "editlinestate");
+ EDITOR_BACKGROUND = mc_skin_color_get ("editor", "editbg");
+ EDITOR_FRAME = mc_skin_color_get ("editor", "editframe");
+ EDITOR_FRAME_ACTIVE = mc_skin_color_get ("editor", "editframeactive");
+ EDITOR_FRAME_DRAG = mc_skin_color_get ("editor", "editframedrag");
+
+ BOOK_MARK_COLOR = mc_skin_color_get ("editor", "bookmark");
+ BOOK_MARK_FOUND_COLOR = mc_skin_color_get ("editor", "bookmarkfound");
+
+ DFF_ADD_COLOR = mc_skin_color_get ("diffviewer", "added");
+ DFF_CHG_COLOR = mc_skin_color_get ("diffviewer", "changedline");
+ DFF_CHH_COLOR = mc_skin_color_get ("diffviewer", "changednew");
+ DFF_CHD_COLOR = mc_skin_color_get ("diffviewer", "changed");
+ DFF_DEL_COLOR = mc_skin_color_get ("diffviewer", "removed");
+ DFF_ERROR_COLOR = mc_skin_color_get ("diffviewer", "error");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_skin_color_check_inisection (const gchar * group)
+{
+ return !((strcasecmp ("skin", group) == 0) || (strcasecmp ("aliases", group) == 0)
+ || (strcasecmp ("lines", group) == 0) || (strncasecmp ("widget-", group, 7) == 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_color_check_bw_mode (mc_skin_t * mc_skin)
+{
+ gchar **groups, **orig_groups;
+
+ if (tty_use_colors () && !mc_global.tty.disable_colors)
+ return;
+
+ orig_groups = mc_config_get_groups (mc_skin->config, NULL);
+
+ for (groups = orig_groups; *groups != NULL; groups++)
+ if (mc_skin_color_check_inisection (*groups))
+ mc_config_del_group (mc_skin->config, *groups);
+
+ g_strfreev (orig_groups);
+
+ mc_skin_hardcoded_blackwhite_colors (mc_skin);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_skin_color_parse_ini_file (mc_skin_t * mc_skin)
+{
+ gboolean ret = FALSE;
+ gsize items_count;
+ gchar **groups, **orig_groups;
+ mc_skin_color_t *mc_skin_color;
+
+ mc_skin_color_check_bw_mode (mc_skin);
+
+ orig_groups = mc_config_get_groups (mc_skin->config, &items_count);
+ if (*orig_groups == NULL)
+ goto ret;
+
+ /* as first, need to set up default colors */
+ mc_skin_color_set_default_for_terminal (mc_skin);
+ mc_skin_color = mc_skin_color_get_from_ini_file (mc_skin, "core", "_default_");
+ if (mc_skin_color == NULL)
+ goto ret;
+
+ tty_color_set_defaults (mc_skin_color->fgcolor, mc_skin_color->bgcolor, mc_skin_color->attrs);
+ mc_skin_color_add_to_hash (mc_skin, "core", "_default_", mc_skin_color);
+
+ for (groups = orig_groups; *groups != NULL; groups++)
+ {
+ gchar **keys, **orig_keys;
+
+ if (!mc_skin_color_check_inisection (*groups))
+ continue;
+
+ orig_keys = mc_config_get_keys (mc_skin->config, *groups, NULL);
+
+ for (keys = orig_keys; *keys != NULL; keys++)
+ {
+ mc_skin_color = mc_skin_color_get_from_ini_file (mc_skin, *groups, *keys);
+ if (mc_skin_color != NULL)
+ mc_skin_color_add_to_hash (mc_skin, *groups, *keys, mc_skin_color);
+ }
+ g_strfreev (orig_keys);
+ }
+
+ mc_skin_color_cache_init ();
+
+ ret = TRUE;
+
+ ret:
+ g_strfreev (orig_groups);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_skin_color_get (const gchar * group, const gchar * name)
+{
+ mc_skin_color_t *mc_skin_color;
+
+ mc_skin_color = mc_skin_color_get_with_defaults (group, name);
+
+ return (mc_skin_color != NULL) ? mc_skin_color->pair_index : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/skin/common.c b/lib/skin/common.c
new file mode 100644
index 0000000..85790f2
--- /dev/null
+++ b/lib/skin/common.c
@@ -0,0 +1,209 @@
+/*
+ Skins engine.
+ Interface functions
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Egmont Koblinger <egmont@gmail.com>, 2010
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+
+#include "internal.h"
+#include "lib/util.h"
+
+#include "lib/tty/color.h" /* tty_use_256colors(); */
+
+/*** global variables ****************************************************************************/
+
+mc_skin_t mc_skin__default;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static gboolean mc_skin_is_init = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_hash_destroy_value (gpointer data)
+{
+ mc_skin_color_t *mc_skin_color = (mc_skin_color_t *) data;
+ g_free (mc_skin_color->fgcolor);
+ g_free (mc_skin_color->bgcolor);
+ g_free (mc_skin_color->attrs);
+ g_free (mc_skin_color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+mc_skin_get_default_name (void)
+{
+ char *tmp_str;
+
+ /* from command line */
+ if (mc_global.tty.skin != NULL)
+ return g_strdup (mc_global.tty.skin);
+
+ /* from envirovement variable */
+ tmp_str = getenv ("MC_SKIN");
+ if (tmp_str != NULL)
+ return g_strdup (tmp_str);
+
+ /* from config. Or 'default' if no present in config */
+ return mc_config_get_string (mc_global.main_config, CONFIG_APP_SECTION, "skin", "default");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_reinit (void)
+{
+ mc_skin_deinit ();
+ mc_skin__default.name = mc_skin_get_default_name ();
+ mc_skin__default.colors = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, mc_skin_hash_destroy_value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_try_to_load_default (void)
+{
+ mc_skin_reinit ();
+ g_free (mc_skin__default.name);
+ mc_skin__default.name = g_strdup ("default");
+ if (!mc_skin_ini_file_load (&mc_skin__default))
+ {
+ mc_skin_reinit ();
+ mc_skin_set_hardcoded_skin (&mc_skin__default);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_skin_init (const gchar * skin_override, GError ** mcerror)
+{
+ gboolean is_good_init = TRUE;
+ GError *error = NULL;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ mc_skin__default.have_256_colors = FALSE;
+ mc_skin__default.have_true_colors = FALSE;
+
+ mc_skin__default.name =
+ skin_override != NULL ? g_strdup (skin_override) : mc_skin_get_default_name ();
+
+ mc_skin__default.colors = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, mc_skin_hash_destroy_value);
+ if (!mc_skin_ini_file_load (&mc_skin__default))
+ {
+ mc_propagate_error (mcerror, 0,
+ _("Unable to load '%s' skin.\nDefault skin has been loaded"),
+ mc_skin__default.name);
+ mc_skin_try_to_load_default ();
+ is_good_init = FALSE;
+ }
+ mc_skin_colors_old_configure (&mc_skin__default);
+
+ if (!mc_skin_ini_file_parse (&mc_skin__default))
+ {
+ mc_propagate_error (mcerror, 0,
+ _("Unable to parse '%s' skin.\nDefault skin has been loaded"),
+ mc_skin__default.name);
+
+ mc_skin_try_to_load_default ();
+ mc_skin_colors_old_configure (&mc_skin__default);
+ (void) mc_skin_ini_file_parse (&mc_skin__default);
+ is_good_init = FALSE;
+ }
+ if (is_good_init && mc_skin__default.have_true_colors && !tty_use_truecolors (&error))
+ {
+ mc_propagate_error (mcerror, 0,
+ _
+ ("Unable to use '%s' skin with true colors support:\n%s\nDefault skin has been loaded"),
+ mc_skin__default.name, error->message);
+ g_error_free (error);
+ mc_skin_try_to_load_default ();
+ mc_skin_colors_old_configure (&mc_skin__default);
+ (void) mc_skin_ini_file_parse (&mc_skin__default);
+ is_good_init = FALSE;
+ }
+ if (is_good_init && mc_skin__default.have_256_colors && !tty_use_256colors (&error))
+ {
+ mc_propagate_error (mcerror, 0,
+ _
+ ("Unable to use '%s' skin with 256 colors support\non non-256 colors terminal.\nDefault skin has been loaded"),
+ mc_skin__default.name);
+ mc_skin_try_to_load_default ();
+ mc_skin_colors_old_configure (&mc_skin__default);
+ (void) mc_skin_ini_file_parse (&mc_skin__default);
+ is_good_init = FALSE;
+ }
+ mc_skin_is_init = TRUE;
+ return is_good_init;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_skin_deinit (void)
+{
+ tty_color_free_all_tmp ();
+ tty_color_free_all_non_tmp ();
+
+ MC_PTR_FREE (mc_skin__default.name);
+ g_hash_table_destroy (mc_skin__default.colors);
+ mc_skin__default.colors = NULL;
+
+ MC_PTR_FREE (mc_skin__default.description);
+
+ mc_config_deinit (mc_skin__default.config);
+ mc_skin__default.config = NULL;
+
+ mc_skin_is_init = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gchar *
+mc_skin_get (const gchar * group, const gchar * key, const gchar * default_value)
+{
+ if (mc_global.tty.ugly_line_drawing)
+ return g_strdup (default_value);
+
+ return mc_config_get_string (mc_skin__default.config, group, key, default_value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/skin/hc-skins.c b/lib/skin/hc-skins.c
new file mode 100644
index 0000000..95ac73d
--- /dev/null
+++ b/lib/skin/hc-skins.c
@@ -0,0 +1,146 @@
+/*
+ Skins engine.
+ Set of hardcoded skins
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2012
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define set_lines(x,y) mc_config_set_string(mc_skin->config, "Lines", x, y)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_skin_hardcoded_blackwhite_colors (mc_skin_t * mc_skin)
+{
+ mc_config_set_string (mc_skin->config, "core", "_default_", "default;default");
+ mc_config_set_string (mc_skin->config, "core", "selected", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "core", "marked", "A_BOLD");
+ mc_config_set_string (mc_skin->config, "core", "markselect", "A_BOLD_REVERSE");
+ mc_config_set_string (mc_skin->config, "core", "disabled", "default");
+ mc_config_set_string (mc_skin->config, "core", "reverse", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "dialog", "_default_", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "dialog", "dfocus", "A_BOLD");
+ mc_config_set_string (mc_skin->config, "dialog", "dhotnormal", "A_UNDERLINE");
+ mc_config_set_string (mc_skin->config, "dialog", "dhotfocus", "A_UNDERLINE");
+ mc_config_set_string (mc_skin->config, "error", "_default_", "A_BOLD");
+ mc_config_set_string (mc_skin->config, "menu", "_default_", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "menu", "menuhot", "A_BOLD");
+ mc_config_set_string (mc_skin->config, "menu", "menusel", "default");
+ mc_config_set_string (mc_skin->config, "menu", "menuhotsel", "A_UNDERLINE");
+ mc_config_set_string (mc_skin->config, "menu", "menuinactive", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "popupmenu", "_default_", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "popupmenu", "menusel", "default");
+ mc_config_set_string (mc_skin->config, "popupmenu", "menutitle", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "statusbar", "_default_", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "help", "_default_", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "help", "helpitalic", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "help", "helpbold", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "help", "helpslink", "A_BOLD");
+ mc_config_set_string (mc_skin->config, "viewer", "viewunderline", "A_UNDERLINE");
+ mc_config_set_string (mc_skin->config, "editor", "editbold", "A_BOLD");
+ mc_config_set_string (mc_skin->config, "editor", "editmarked", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "editor", "editframeactive", "A_BOLD");
+ mc_config_set_string (mc_skin->config, "editor", "editframedrag", "A_REVERSE");
+ mc_config_set_string (mc_skin->config, "buttonbar", "hotkey", "default");
+ mc_config_set_string (mc_skin->config, "buttonbar", "button", "A_REVERSE");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_skin_hardcoded_space_lines (mc_skin_t * mc_skin)
+{
+ /* single lines */
+ set_lines ("vert", " ");
+ set_lines ("horiz", " ");
+ set_lines ("lefttop", " ");
+ set_lines ("righttop", " ");
+ set_lines ("leftbottom", " ");
+ set_lines ("rightbottom", " ");
+ set_lines ("topmiddle", " ");
+ set_lines ("bottommiddle", " ");
+ set_lines ("leftmiddle", " ");
+ set_lines ("rightmiddle", " ");
+ set_lines ("cross", " ");
+
+ set_lines ("dvert", " ");
+ set_lines ("dhoriz", " ");
+ set_lines ("dlefttop", " ");
+ set_lines ("drighttop", " ");
+ set_lines ("dleftbottom", " ");
+ set_lines ("drightbottom", " ");
+ set_lines ("dtopmiddle", " ");
+ set_lines ("dbottommiddle", " ");
+ set_lines ("dleftmiddle", " ");
+ set_lines ("drightmiddle", " ");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_skin_hardcoded_ugly_lines (mc_skin_t * mc_skin)
+{
+ /* single lines */
+ set_lines ("vert", "|");
+ set_lines ("horiz", "-");
+ set_lines ("lefttop", "+");
+ set_lines ("righttop", "+");
+ set_lines ("leftbottom", "+");
+ set_lines ("rightbottom", "+");
+ set_lines ("topmiddle", "-");
+ set_lines ("bottommiddle", "-");
+ set_lines ("leftmiddle", "|");
+ set_lines ("rightmiddle", "|");
+ set_lines ("cross", "+");
+
+ /* double lines */
+ set_lines ("dvert", "|");
+ set_lines ("dhoriz", "-");
+ set_lines ("dlefttop", "+");
+ set_lines ("drighttop", "+");
+ set_lines ("dleftbottom", "+");
+ set_lines ("drightbottom", "+");
+ set_lines ("dtopmiddle", "-");
+ set_lines ("dbottommiddle", "-");
+ set_lines ("dleftmiddle", "|");
+ set_lines ("drightmiddle", "|");
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/skin/ini-file.c b/lib/skin/ini-file.c
new file mode 100644
index 0000000..6d92435
--- /dev/null
+++ b/lib/skin/ini-file.c
@@ -0,0 +1,210 @@
+/*
+ Skins engine.
+ Reading and parse ini-files
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include "lib/global.h" /* <glib.h> */
+
+#include "internal.h"
+#include "lib/fileloc.h"
+#include "lib/util.h" /* exist_file() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_skin_get_list_from_dir (const gchar * base_dir, GPtrArray * list)
+{
+ gchar *name;
+ GDir *dir;
+
+ name = g_build_filename (base_dir, MC_SKINS_DIR, (char *) NULL);
+ dir = g_dir_open (name, 0, NULL);
+ g_free (name);
+
+ if (dir != NULL)
+ {
+ const gchar *cname;
+
+ while ((cname = g_dir_read_name (dir)) != NULL)
+ {
+ gchar *sname;
+ size_t slen;
+ unsigned int i;
+
+ slen = strlen (cname);
+ sname = g_strndup (cname, slen);
+
+ if (slen > 4 && strcmp (sname + slen - 4, ".ini") == 0)
+ sname[slen - 4] = '\0';
+
+ for (i = 0; i < list->len; i++)
+ if (strcmp (sname, g_ptr_array_index (list, i)) == 0)
+ break;
+
+ if (i < list->len)
+ g_free (sname);
+ else
+ g_ptr_array_add (list, sname);
+ }
+
+ g_dir_close (dir);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+string_array_comparator (gconstpointer a, gconstpointer b)
+{
+ return strcmp (*(char *const *) a, *(char *const *) b);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_skin_ini_file_load_search_in_dir (mc_skin_t * mc_skin, const gchar * base_dir)
+{
+ char *file_name, *file_name2;
+
+ file_name = g_build_filename (base_dir, MC_SKINS_DIR, mc_skin->name, (char *) NULL);
+ if (exist_file (file_name))
+ {
+ mc_skin->config = mc_config_init (file_name, TRUE);
+ g_free (file_name);
+ return (mc_skin->config != NULL);
+ }
+ g_free (file_name);
+
+ file_name2 = g_strdup_printf ("%s.ini", mc_skin->name);
+ file_name = g_build_filename (base_dir, MC_SKINS_DIR, file_name2, (char *) NULL);
+ g_free (file_name2);
+
+ if (exist_file (file_name))
+ {
+ mc_skin->config = mc_config_init (file_name, TRUE);
+ g_free (file_name);
+ return (mc_skin->config != NULL);
+ }
+ g_free (file_name);
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+GPtrArray *
+mc_skin_list (void)
+{
+ GPtrArray *list;
+
+ list = g_ptr_array_new_with_free_func (g_free);
+ mc_skin_get_list_from_dir (mc_config_get_data_path (), list);
+ mc_skin_get_list_from_dir (mc_global.sysconfig_dir, list);
+ mc_skin_get_list_from_dir (mc_global.share_data_dir, list);
+ g_ptr_array_sort (list, (GCompareFunc) string_array_comparator);
+
+ return list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_skin_ini_file_load (mc_skin_t * mc_skin)
+{
+ char *file_name;
+
+ file_name = g_path_get_basename (mc_skin->name);
+ if (file_name == NULL)
+ return FALSE;
+
+ if (strcmp (file_name, mc_skin->name) != 0)
+ {
+ g_free (file_name);
+ if (!g_path_is_absolute (mc_skin->name))
+ return FALSE;
+ mc_skin->config = mc_config_init (mc_skin->name, TRUE);
+ return (mc_skin->config != NULL);
+ }
+ g_free (file_name);
+
+ /* ${XDG_DATA_HOME}/mc/skins/ */
+ if (mc_skin_ini_file_load_search_in_dir (mc_skin, mc_config_get_data_path ()))
+ return TRUE;
+
+ /* /etc/mc/skins/ */
+ if (mc_skin_ini_file_load_search_in_dir (mc_skin, mc_global.sysconfig_dir))
+ return TRUE;
+
+ /* /usr/share/mc/skins/ */
+ return mc_skin_ini_file_load_search_in_dir (mc_skin, mc_global.share_data_dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_skin_ini_file_parse (mc_skin_t * mc_skin)
+{
+ mc_skin->description =
+ mc_config_get_string (mc_skin->config, "skin", "description", "- no description -");
+ if (!mc_skin_color_parse_ini_file (mc_skin))
+ return FALSE;
+
+ mc_skin_lines_parse_ini_file (mc_skin);
+ mc_skin->have_256_colors = mc_config_get_bool (mc_skin->config, "skin", "256colors", FALSE);
+ mc_skin->have_true_colors = mc_config_get_bool (mc_skin->config, "skin", "truecolors", FALSE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_skin_set_hardcoded_skin (mc_skin_t * mc_skin)
+{
+ mc_skin->config = mc_config_init (NULL, TRUE);
+
+ mc_config_set_string (mc_skin->config, "skin", "description", "hardcoded skin");
+
+ mc_skin_hardcoded_ugly_lines (mc_skin);
+ mc_skin_hardcoded_blackwhite_colors (mc_skin);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/skin/internal.h b/lib/skin/internal.h
new file mode 100644
index 0000000..8b468db
--- /dev/null
+++ b/lib/skin/internal.h
@@ -0,0 +1,40 @@
+#ifndef MC__SKIN_INTERNAL_H
+#define MC__SKIN_INTERNAL_H
+
+#include "lib/global.h"
+#include "lib/skin.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_skin_color_struct
+{
+ gchar *fgcolor;
+ gchar *bgcolor;
+ gchar *attrs;
+ int pair_index;
+} mc_skin_color_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean mc_skin_ini_file_load (mc_skin_t * mc_skin);
+gboolean mc_skin_ini_file_parse (mc_skin_t * mc_skin);
+void mc_skin_set_hardcoded_skin (mc_skin_t * mc_skin);
+
+gboolean mc_skin_ini_file_parse_colors (mc_skin_t * mc_skin);
+gboolean mc_skin_color_parse_ini_file (mc_skin_t * mc_skin);
+
+void mc_skin_hardcoded_ugly_lines (mc_skin_t * mc_skin);
+void mc_skin_hardcoded_space_lines (mc_skin_t * mc_skin);
+void mc_skin_hardcoded_blackwhite_colors (mc_skin_t * mc_skin);
+
+void mc_skin_colors_old_configure (mc_skin_t * mc_skin);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__SKIN_INTERNAL_H */
diff --git a/lib/skin/lines.c b/lib/skin/lines.c
new file mode 100644
index 0000000..90cc028
--- /dev/null
+++ b/lib/skin/lines.c
@@ -0,0 +1,100 @@
+/*
+ Skins engine.
+ Work with line draving chars.
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+
+#include "internal.h"
+#include "lib/tty/tty.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mc_skin_lines_load_frm (mc_skin_t * mc_skin, const char *name)
+{
+ int ret;
+ char *frm_val;
+
+ frm_val = mc_config_get_string_raw (mc_skin->config, "Lines", name, " ");
+ ret = mc_tty_normalize_lines_char (frm_val);
+ g_free (frm_val);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_skin_lines_parse_ini_file (mc_skin_t * mc_skin)
+{
+ if (mc_global.tty.slow_terminal)
+ mc_skin_hardcoded_space_lines (mc_skin);
+ else if (mc_global.tty.ugly_line_drawing)
+ mc_skin_hardcoded_ugly_lines (mc_skin);
+ else
+ {
+ /* single lines */
+ mc_tty_frm[MC_TTY_FRM_VERT] = mc_skin_lines_load_frm (mc_skin, "vert");
+ mc_tty_frm[MC_TTY_FRM_HORIZ] = mc_skin_lines_load_frm (mc_skin, "horiz");
+ mc_tty_frm[MC_TTY_FRM_LEFTTOP] = mc_skin_lines_load_frm (mc_skin, "lefttop");
+ mc_tty_frm[MC_TTY_FRM_RIGHTTOP] = mc_skin_lines_load_frm (mc_skin, "righttop");
+ mc_tty_frm[MC_TTY_FRM_LEFTBOTTOM] = mc_skin_lines_load_frm (mc_skin, "leftbottom");
+ mc_tty_frm[MC_TTY_FRM_RIGHTBOTTOM] = mc_skin_lines_load_frm (mc_skin, "rightbottom");
+ mc_tty_frm[MC_TTY_FRM_TOPMIDDLE] = mc_skin_lines_load_frm (mc_skin, "topmiddle");
+ mc_tty_frm[MC_TTY_FRM_BOTTOMMIDDLE] = mc_skin_lines_load_frm (mc_skin, "bottommiddle");
+ mc_tty_frm[MC_TTY_FRM_LEFTMIDDLE] = mc_skin_lines_load_frm (mc_skin, "leftmiddle");
+ mc_tty_frm[MC_TTY_FRM_RIGHTMIDDLE] = mc_skin_lines_load_frm (mc_skin, "rightmiddle");
+ mc_tty_frm[MC_TTY_FRM_CROSS] = mc_skin_lines_load_frm (mc_skin, "cross");
+
+ /* double lines */
+ mc_tty_frm[MC_TTY_FRM_DVERT] = mc_skin_lines_load_frm (mc_skin, "dvert");
+ mc_tty_frm[MC_TTY_FRM_DHORIZ] = mc_skin_lines_load_frm (mc_skin, "dhoriz");
+ mc_tty_frm[MC_TTY_FRM_DLEFTTOP] = mc_skin_lines_load_frm (mc_skin, "dlefttop");
+ mc_tty_frm[MC_TTY_FRM_DRIGHTTOP] = mc_skin_lines_load_frm (mc_skin, "drighttop");
+ mc_tty_frm[MC_TTY_FRM_DLEFTBOTTOM] = mc_skin_lines_load_frm (mc_skin, "dleftbottom");
+ mc_tty_frm[MC_TTY_FRM_DRIGHTBOTTOM] = mc_skin_lines_load_frm (mc_skin, "drightbottom");
+ mc_tty_frm[MC_TTY_FRM_DTOPMIDDLE] = mc_skin_lines_load_frm (mc_skin, "dtopmiddle");
+ mc_tty_frm[MC_TTY_FRM_DBOTTOMMIDDLE] = mc_skin_lines_load_frm (mc_skin, "dbottommiddle");
+ mc_tty_frm[MC_TTY_FRM_DLEFTMIDDLE] = mc_skin_lines_load_frm (mc_skin, "dleftmiddle");
+ mc_tty_frm[MC_TTY_FRM_DRIGHTMIDDLE] = mc_skin_lines_load_frm (mc_skin, "drightmiddle");
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/stat-size.h b/lib/stat-size.h
new file mode 100644
index 0000000..cc8ec12
--- /dev/null
+++ b/lib/stat-size.h
@@ -0,0 +1,99 @@
+/* macros useful in interpreting size-related values in struct stat.
+ Copyright (C) 1989, 1991-2016 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 <http://www.gnu.org/licenses/>. */
+
+/* *INDENT-OFF* */
+/*
+ Macros defined by this file (s is an rvalue of type struct stat):
+
+ DEV_BSIZE: The device blocksize. But use ST_NBLOCKSIZE instead.
+ ST_BLKSIZE(s): Preferred (in the sense of best performance) I/O blocksize
+ for the file, in bytes.
+ ST_NBLOCKS(s): Number of blocks in the file, including indirect blocks.
+ ST_NBLOCKSIZE: Size of blocks used when calculating ST_NBLOCKS.
+ */
+/* *INDENT-ON* */
+
+#ifndef STAT_SIZE_H
+#define STAT_SIZE_H
+
+/* sys/param.h may define DEV_BSIZE */
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+
+/* Get or fake the disk device blocksize.
+ Usually defined by sys/param.h (if at all). */
+#if !defined DEV_BSIZE && defined BSIZE
+#define DEV_BSIZE BSIZE
+#endif
+#if !defined DEV_BSIZE && defined BBSIZE /* SGI sys/param.h */
+#define DEV_BSIZE BBSIZE
+#endif
+#ifndef DEV_BSIZE
+#define DEV_BSIZE 4096
+#endif
+
+
+
+/* Extract or fake data from a 'struct stat'.
+ ST_BLKSIZE: Preferred I/O blocksize for the file, in bytes.
+ ST_NBLOCKS: Number of blocks in the file, including indirect blocks.
+ ST_NBLOCKSIZE: Size of blocks used when calculating ST_NBLOCKS. */
+#ifndef HAVE_STRUCT_STAT_ST_BLOCKS
+#define ST_BLKSIZE(statbuf) DEV_BSIZE
+ /* coreutils' fileblocks.c also uses BSIZE. */
+#if defined _POSIX_SOURCE || !defined BSIZE
+#define ST_NBLOCKS(statbuf) \
+ ((statbuf).st_size / ST_NBLOCKSIZE + ((statbuf).st_size % ST_NBLOCKSIZE != 0))
+#else
+ /* This definition calls st_blocks, which is in the fileblocks module. */
+#define ST_NBLOCKS(statbuf) \
+ (S_ISREG ((statbuf).st_mode) || S_ISDIR ((statbuf).st_mode) ? \
+ st_blocks ((statbuf).st_size) : 0)
+#endif
+#else
+/* When running 'rsh hpux11-system cat any-file', cat would
+ determine that the output stream had an st_blksize of 2147421096.
+ Conversely st_blksize can be 2 GiB (or maybe even larger) with XFS
+ on 64-bit hosts. Somewhat arbitrarily, limit the "optimal" block
+ size to SIZE_MAX / 8 + 1. (Dividing SIZE_MAX by only 4 wouldn't
+ suffice, since "cat" sometimes multiplies the result by 4.) If
+ anyone knows of a system for which this limit is too small, please
+ report it as a bug in this code. */
+#define ST_BLKSIZE(statbuf) ((0 < (statbuf).st_blksize \
+ && (size_t) ((statbuf).st_blksize) <= ((size_t)-1) / 8 + 1) \
+ ? (size_t) ((statbuf).st_blksize) : DEV_BSIZE)
+#if defined hpux || defined __hpux__ || defined __hpux
+ /* HP-UX counts st_blocks in 1024-byte units.
+ This loses when mixing HP-UX and BSD file systems with NFS. */
+#define ST_NBLOCKSIZE 1024
+#endif
+#endif
+
+#ifndef ST_NBLOCKS
+#define ST_NBLOCKS(statbuf) ((statbuf).st_blocks)
+#endif
+
+#ifndef ST_NBLOCKSIZE
+#ifdef S_BLKSIZE
+#define ST_NBLOCKSIZE S_BLKSIZE
+#else
+#define ST_NBLOCKSIZE 512
+#endif
+#endif
+
+#endif /* STAT_SIZE_H */
diff --git a/lib/strescape.h b/lib/strescape.h
new file mode 100644
index 0000000..a24f5d6
--- /dev/null
+++ b/lib/strescape.h
@@ -0,0 +1,33 @@
+#ifndef MC__STRUTILS_ESCAPE_H
+#define MC__STRUTILS_ESCAPE_H
+
+#include <config.h>
+
+#include "lib/global.h" /* <glib.h> */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+char *strutils_escape (const char *src, gsize src_len, const char *escaped_chars,
+ gboolean escape_non_printable);
+char *strutils_unescape (const char *src, gsize src_len, const char *unescaped_chars,
+ gboolean unescape_non_printable);
+char *strutils_shell_unescape (const char *text);
+char *strutils_shell_escape (const char *text);
+
+char *strutils_glob_escape (const char *text);
+char *strutils_glob_unescape (const char *text);
+
+char *strutils_regex_escape (const char *text);
+char *strutils_regex_unescape (const char *text);
+
+gboolean strutils_is_char_escaped (const char *start, const char *current);
+
+#endif /* MC__STRUTILS_ESCAPE_H */
diff --git a/lib/strutil.h b/lib/strutil.h
new file mode 100644
index 0000000..e11dfda
--- /dev/null
+++ b/lib/strutil.h
@@ -0,0 +1,661 @@
+#ifndef MC_STRUTIL_H
+#define MC_STRUTIL_H
+
+#include "lib/global.h" /* include glib.h */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <string.h>
+
+/* Header file for strutil.c, strutilascii.c, strutil8bit.c, strutilutf8.c.
+ * There are two sort of functions:
+ * 1. functions for working with growing strings and conversion strings between
+ * different encodings.
+ * (implemented directly in strutil.c)
+ * 2. functions, that hide differences between encodings derived from ASCII.
+ * (implemented separately in strutilascii.c, strutil8bit.c, strutilutf8.c)
+ * documentation is made for UTF-8 version of functions.
+ */
+
+/* invalid strings
+ * function, that works with invalid strings are marked with "I"
+ * in documentation
+ * invalid bytes of string are handled as one byte characters with width 1, they
+ * are displayed as questionmarks, I-maked comparing functions try to keep
+ * the original value of these bytes.
+ */
+
+/* combining characters
+ * displaynig: all handled as zero with characters, expect combing character
+ * at the begin of string, this character has with one (space add before),
+ * so str_term_width is not good for computing width of singles characters
+ * (never return zero, expect empty string)
+ * for compatibility are strings composed before displaynig
+ * comparing: comparing decompose all string before comparing, n-compare
+ * functions do not work as is usual, because same strings do not have to be
+ * same length in UTF-8. So they return 0 if one string is prefix of the other
+ * one.
+ * str_prefix is used to determine, how many characters from one string are
+ * prefix in second string. However, str_prefix return number of characters in
+ * decompose form. (used in do_search (screen.c))
+ */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define IS_FIT(x) ((x) & 0x0010)
+#define MAKE_FIT(x) ((x) | 0x0010)
+#define HIDE_FIT(x) ((x) & 0x000f)
+
+#define INVALID_CONV ((GIConv) (-1))
+
+/*** enums ***************************************************************************************/
+
+/* results of conversion function
+ */
+typedef enum
+{
+ /* success means, that conversion has been finished successfully
+ */
+ ESTR_SUCCESS = 0,
+ /* problem means, that not every characters was successfully converted (They are
+ * replaced with questionmark). So is impossible convert string back.
+ */
+ ESTR_PROBLEM = 1,
+ /* failure means, that conversion is not possible (example: wrong encoding
+ * of input string)
+ */
+ ESTR_FAILURE = 2
+} estr_t;
+
+/* alignment strings on terminal
+ */
+typedef enum
+{
+ J_LEFT = 0x01,
+ J_RIGHT = 0x02,
+ J_CENTER = 0x03,
+ /* if there is enough space for string on terminal,
+ * string is centered otherwise is aligned to left */
+ J_CENTER_LEFT = 0x04,
+ /* fit alignment, if string is to long, is truncated with '~' */
+ J_LEFT_FIT = 0x11,
+ J_RIGHT_FIT = 0x12,
+ J_CENTER_FIT = 0x13,
+ J_CENTER_LEFT_FIT = 0x14
+} align_crt_t;
+
+/* string-to-integer parsing results
+ */
+typedef enum
+{
+ LONGINT_OK = 0,
+
+ /* These two values can be ORed together, to indicate that both errors occurred. */
+ LONGINT_OVERFLOW = 1,
+ LONGINT_INVALID_SUFFIX_CHAR = 2,
+
+ LONGINT_INVALID_SUFFIX_CHAR_WITH_OVERFLOW = (LONGINT_INVALID_SUFFIX_CHAR | LONGINT_OVERFLOW),
+ LONGINT_INVALID = 4
+} strtol_error_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* all functions in str_class must be defined for every encoding */
+struct str_class
+{
+ /* *INDENT-OFF* */
+ gchar *(*conv_gerror_message) (GError * error, const char *def_msg);
+ /*I*/ estr_t (*vfs_convert_to) (GIConv coder, const char *string, int size, GString * buffer);
+ /*I*/ void (*insert_replace_char) (GString * buffer);
+ gboolean (*is_valid_string) (const char *text);
+ /*I*/ int (*is_valid_char) (const char *ch, size_t size);
+ /*I*/ void (*cnext_char) (const char **text);
+ void (*cprev_char) (const char **text);
+ void (*cnext_char_safe) (const char **text);
+ /*I*/ void (*cprev_char_safe) (const char **text);
+ /*I*/ int (*cnext_noncomb_char) (const char **text);
+ /*I*/ int (*cprev_noncomb_char) (const char **text, const char *begin);
+ /*I*/ gboolean (*char_isspace) (const char *ch);
+ /*I*/ gboolean (*char_ispunct) (const char *ch);
+ /*I*/ gboolean (*char_isalnum) (const char *ch);
+ /*I*/ gboolean (*char_isdigit) (const char *ch);
+ /*I*/ gboolean (*char_isprint) (const char *ch);
+ /*I*/ gboolean (*char_iscombiningmark) (const char *ch);
+ /*I*/ int (*length) (const char *text);
+ /*I*/ int (*length2) (const char *text, int size);
+ /*I*/ int (*length_noncomb) (const char *text);
+ /*I*/ gboolean (*char_toupper) (const char *ch, char **out, size_t * remain);
+ gboolean (*char_tolower) (const char *ch, char **out, size_t * remain);
+ void (*fix_string) (char *text);
+ /*I*/ const char *(*term_form) (const char *text);
+ /*I*/ const char *(*fit_to_term) (const char *text, int width, align_crt_t just_mode);
+ /*I*/ const char *(*term_trim) (const char *text, int width);
+ /*I*/ const char *(*term_substring) (const char *text, int start, int width);
+ /*I*/ int (*term_width1) (const char *text);
+ /*I*/ int (*term_width2) (const char *text, size_t length);
+ /*I*/ int (*term_char_width) (const char *length);
+ /*I*/ const char *(*trunc) (const char *length, int width);
+ /*I*/ int (*offset_to_pos) (const char *text, size_t length);
+ /*I*/ int (*column_to_pos) (const char *text, size_t pos);
+ /*I*/ char *(*create_search_needle) (const char *needle, gboolean case_sen);
+ void (*release_search_needle) (char *needle, gboolean case_sen);
+ const char *(*search_first) (const char *text, const char *needle, gboolean case_sen);
+ const char *(*search_last) (const char *text, const char *needle, gboolean case_sen);
+ int (*compare) (const char *t1, const char *t2);
+ /*I*/ int (*ncompare) (const char *t1, const char *t2);
+ /*I*/ int (*casecmp) (const char *t1, const char *t2);
+ /*I*/ int (*ncasecmp) (const char *t1, const char *t2);
+ /*I*/ int (*prefix) (const char *text, const char *prefix);
+ /*I*/ int (*caseprefix) (const char *text, const char *prefix);
+ /*I*/ char *(*create_key) (const char *text, gboolean case_sen);
+ /*I*/ char *(*create_key_for_filename) (const char *text, gboolean case_sen);
+ /*I*/ int (*key_collate) (const char *t1, const char *t2, gboolean case_sen);
+ /*I*/ void (*release_key) (char *key, gboolean case_sen);
+ /* *INDENT-ON* */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/* standard converters */
+extern GIConv str_cnv_to_term;
+extern GIConv str_cnv_from_term;
+/* from terminal encoding to terminal encoding */
+extern GIConv str_cnv_not_convert;
+
+/*** declarations of public functions ************************************************************/
+
+struct str_class str_utf8_init (void);
+struct str_class str_8bit_init (void);
+struct str_class str_ascii_init (void);
+
+/* create converter from "from_enc" to terminal encoding
+ * if "from_enc" is not supported return INVALID_CONV
+ */
+GIConv str_crt_conv_from (const char *from_enc);
+
+/* create converter from terminal encoding to "to_enc"
+ * if "to_enc" is not supported return INVALID_CONV
+ */
+GIConv str_crt_conv_to (const char *to_enc);
+
+/* close converter, do not close str_cnv_to_term, str_cnv_from_term,
+ * str_cnv_not_convert
+ */
+void str_close_conv (GIConv conv);
+
+/* return on of not used buffers (.used == 0) or create new
+ * returned buffer has set .used to 1
+ */
+
+/* convert string using coder, result of conversion is appended at end of buffer
+ * return ESTR_SUCCESS if there was no problem.
+ * otherwise return ESTR_PROBLEM or ESTR_FAILURE
+ */
+estr_t str_convert (GIConv coder, const char *string, GString * buffer);
+estr_t str_nconvert (GIConv coder, const char *string, int size, GString * buffer);
+
+/* convert GError message (which in UTF-8) to terminal charset
+ * def_char is used if result of error->str conversion if ESTR_FAILURE
+ * return new allocated null-terminated string, which is need to be freed
+ * I
+ */
+gchar *str_conv_gerror_message (GError * error, const char *def_msg);
+
+/* return only ESTR_SUCCESS or ESTR_FAILURE, because vfs must be able to convert
+ * result to original string. (so no replace with questionmark)
+ * if coder is str_cnv_from_term or str_cnv_not_convert, string is only copied,
+ * so is possible to show file, that is not valid in terminal encoding
+ */
+estr_t str_vfs_convert_from (GIConv coder, const char *string, GString * buffer);
+
+/* if coder is str_cnv_to_term or str_cnv_not_convert, string is only copied,
+ * does replace with questionmark
+ * I
+ */
+estr_t str_vfs_convert_to (GIConv coder, const char *string, int size, GString * buffer);
+
+/* printf function for str_buffer, append result of printf at the end of buffer
+ */
+/* *INDENT-OFF* */
+void str_printf (GString * buffer, const char *format, ...) G_GNUC_PRINTF (2, 3);
+/* *INDENT-ON* */
+
+/* add standard replacement character in terminal encoding
+ */
+void str_insert_replace_char (GString * buffer);
+
+/* init strings and set terminal encoding,
+ * if is termenc NULL, detect terminal encoding
+ * create all str_cnv_* and set functions for terminal encoding
+ */
+void str_init_strings (const char *termenc);
+
+/* free all str_buffer and all str_cnv_*
+ */
+void str_uninit_strings (void);
+
+/* try convert characters in ch to output using conv
+ * ch_size is size of ch, can by (size_t)(-1) (-1 only for ASCII
+ * compatible encoding, for other must be set)
+ * return ESTR_SUCCESS if conversion was successfully,
+ * ESTR_PROBLEM if ch contains only part of characters,
+ * ESTR_FAILURE if conversion is not possible
+ */
+estr_t str_translate_char (GIConv conv, const char *ch, size_t ch_size,
+ char *output, size_t out_size);
+
+/* test, if text is valid in terminal encoding
+ * I
+ */
+gboolean str_is_valid_string (const char *text);
+
+/* test, if first char of ch is valid
+ * size, how many bytes characters occupied, could be (size_t)(-1)
+ * return 1 if it is valid, -1 if it is invalid or -2 if it is only part of
+ * multibyte character
+ * I
+ */
+int str_is_valid_char (const char *ch, size_t size);
+
+/* return next characters after text, do not call on the end of string
+ */
+char *str_get_next_char (char *text);
+const char *str_cget_next_char (const char *text);
+
+/* return previous characters before text, do not call on the start of strings
+ */
+char *str_get_prev_char (char *text);
+const char *str_cget_prev_char (const char *text);
+
+/* set text to next characters, do not call on the end of string
+ */
+void str_next_char (char **text);
+void str_cnext_char (const char **text);
+
+/* set text to previous characters, do not call on the start of strings
+ */
+void str_prev_char (char **text);
+void str_cprev_char (const char **text);
+
+/* return next characters after text, do not call on the end of string
+ * works with invalid string
+ * I
+ */
+char *str_get_next_char_safe (char *text);
+const char *str_cget_next_char_safe (const char *text);
+
+/* return previous characters before text, do not call on the start of strings
+ * works with invalid string
+ * I
+ */
+char *str_get_prev_char_safe (char *text);
+const char *str_cget_prev_char_safe (const char *text);
+
+/* set text to next characters, do not call on the end of string
+ * works with invalid string
+ * I
+ */
+void str_next_char_safe (char **text);
+void str_cnext_char_safe (const char **text);
+
+/* set text to previous characters, do not call on the start of strings
+ * works with invalid string
+ * I
+ */
+void str_prev_char_safe (char **text);
+void str_cprev_char_safe (const char **text);
+
+/* set text to next noncombining characters, check the end of text
+ * return how many characters was skipped
+ * works with invalid string
+ * I
+ */
+int str_next_noncomb_char (char **text);
+int str_cnext_noncomb_char (const char **text);
+
+/* set text to previous noncombining characters, search stop at begin
+ * return how many characters was skipped
+ * works with invalid string
+ * I
+ */
+int str_prev_noncomb_char (char **text, const char *begin);
+int str_cprev_noncomb_char (const char **text, const char *begin);
+
+/* if first characters in ch is space, tabulator or new lines
+ * I
+ */
+gboolean str_isspace (const char *ch);
+
+/* if first characters in ch is punctuation or symbol
+ * I
+ */
+gboolean str_ispunct (const char *ch);
+
+/* if first characters in ch is alphanum
+ * I
+ */
+gboolean str_isalnum (const char *ch);
+
+/* if first characters in ch is digit
+ * I
+ */
+gboolean str_isdigit (const char *ch);
+
+/* if first characters in ch is printable
+ * I
+ */
+gboolean str_isprint (const char *ch);
+
+/* if first characters in ch is a combining mark (only in utf-8)
+ * combining makrs are assumed to be zero width
+ * I
+ */
+gboolean str_iscombiningmark (const char *ch);
+
+/* write lower from of first characters in ch into out
+ * decrase remain by size of returned characters
+ * if out is not big enough, do nothing
+ */
+gboolean str_toupper (const char *ch, char **out, size_t * remain);
+
+/* write upper from of first characters in ch into out
+ * decrase remain by size of returned characters
+ * if out is not big enough, do nothing
+ */
+gboolean str_tolower (const char *ch, char **out, size_t * remain);
+
+/* return length of text in characters
+ * I
+ */
+int str_length (const char *text);
+
+/* return length of text in characters, limit to size
+ * I
+ */
+int str_length2 (const char *text, int size);
+
+/* return length of one char
+ * I
+ */
+int str_length_char (const char *text);
+
+/* return length of text in characters, count only noncombining characters
+ * I
+ */
+int str_length_noncomb (const char *text);
+
+/* replace all invalid characters in text with questionmark
+ * after return, text is valid string in terminal encoding
+ * I
+ */
+void str_fix_string (char *text);
+
+/* replace all invalid characters in text with questionmark
+ * replace all unprintable characters with '.'
+ * return static allocated string, "text" is not changed
+ * returned string do not need to be freed
+ * I
+ */
+const char *str_term_form (const char *text);
+
+/* like str_term_form, but text can be alignment to width
+ * alignment is specified in just_mode (J_LEFT, J_LEFT_FIT, ...)
+ * result is completed with spaces to width
+ * I
+ */
+const char *str_fit_to_term (const char *text, int width, align_crt_t just_mode);
+
+/* like str_term_form, but when text is wider than width, three dots are
+ * inserted at begin and result is completed with suffix of text
+ * no additional spaces are inserted
+ * I
+ */
+const char *str_term_trim (const char *text, int width);
+
+
+/* like str_term_form, but return only specified substring
+ * start - column (position) on terminal, where substring begin
+ * result is completed with spaces to width
+ * I
+ */
+const char *str_term_substring (const char *text, int start, int width);
+
+/* return width, that will be text occupied on terminal
+ * I
+ */
+int str_term_width1 (const char *text);
+
+/* return width, that will be text occupied on terminal
+ * text is limited by length in characters
+ * I
+ */
+int str_term_width2 (const char *text, size_t length);
+
+/* return width, that will be character occupied on terminal
+ * combining characters are always zero width
+ * I
+ */
+int str_term_char_width (const char *text);
+
+/* convert position in characters to position in bytes
+ * I
+ */
+int str_offset_to_pos (const char *text, size_t length);
+
+/* convert position on terminal to position in characters
+ * I
+ */
+int str_column_to_pos (const char *text, size_t pos);
+
+/* like str_fit_to_term width just_mode = J_LEFT_FIT,
+ * but do not insert additional spaces
+ * I
+ */
+const char *str_trunc (const char *text, int width);
+
+/* create needle, that will be searched in str_search_fist/last,
+ * so needle can be reused
+ * in UTF-8 return normalized form of needle
+ */
+char *str_create_search_needle (const char *needle, gboolean case_sen);
+
+/* free needle returned by str_create_search_needle
+ */
+void str_release_search_needle (char *needle, gboolean case_sen);
+
+/* search for first occurrence of search in text
+ */
+const char *str_search_first (const char *text, const char *needle, gboolean case_sen);
+
+/* search for last occurrence of search in text
+ */
+const char *str_search_last (const char *text, const char *needle, gboolean case_sen);
+
+/* case sensitive compare two strings
+ * I
+ */
+int str_compare (const char *t1, const char *t2);
+
+/* case sensitive compare two strings
+ * if one string is prefix of the other string, return 0
+ * I
+ */
+int str_ncompare (const char *t1, const char *t2);
+
+/* case insensitive compare two strings
+ * I
+ */
+int str_casecmp (const char *t1, const char *t2);
+
+/* case insensitive compare two strings
+ * if one string is prefix of the other string, return 0
+ * I
+ */
+int str_ncasecmp (const char *t1, const char *t2);
+
+/* return, how many bytes are are same from start in text and prefix
+ * both strings are decomposed before comparing and return value is counted
+ * in decomposed form, too. calling with prefix, prefix, you get size in bytes
+ * of prefix in decomposed form,
+ * I
+ */
+int str_prefix (const char *text, const char *prefix);
+
+/* case insensitive version of str_prefix
+ * I
+ */
+int str_caseprefix (const char *text, const char *prefix);
+
+/* create a key that is used by str_key_collate
+ * I
+ */
+char *str_create_key (const char *text, gboolean case_sen);
+
+/* create a key that is used by str_key_collate
+ * should aware dot '.' in text
+ * I
+ */
+char *str_create_key_for_filename (const char *text, gboolean case_sen);
+
+/* compare two string using LC_COLLATE, if is possible
+ * if case_sen is set, comparing is case sensitive,
+ * case_sen must be same for str_create_key, str_key_collate and str_release_key
+ * I
+ */
+int str_key_collate (const char *t1, const char *t2, gboolean case_sen);
+
+/* release_key created by str_create_key, only right way to release key
+ * I
+ */
+void str_release_key (char *key, gboolean case_sen);
+
+/* return TRUE if codeset_name is utf8 or utf-8
+ * I
+ */
+gboolean str_isutf8 (const char *codeset_name);
+
+const char *str_detect_termencoding (void);
+
+int str_verscmp (const char *s1, const char *s2);
+
+/* Compare version strings:
+
+ Compare strings a and b as file names containing version numbers, and return an integer
+ that is negative, zero, or positive depending on whether a compares less than, equal to,
+ or greater than b.
+
+ Use the following version sort algorithm:
+
+ 1. Compare the strings' maximal-length non-digit prefixes lexically.
+ If there is a difference return that difference.
+ Otherwise discard the prefixes and continue with the next step.
+
+ 2. Compare the strings' maximal-length digit prefixes, using numeric comparison
+ of the numbers represented by each prefix. (Treat an empty prefix as zero; this can
+ happen only at string end.)
+ If there is a difference, return that difference.
+ Otherwise discard the prefixes and continue with the next step.
+
+ 3. If both strings are empty, return 0. Otherwise continue with step 1.
+
+ In version sort, lexical comparison is left to right, byte by byte, using the byte's numeric
+ value (0-255), except that:
+
+ 1. ASCII letters sort before other bytes.
+ 2. A tilde sorts before anything, even an empty string.
+
+ In addition to the version sort rules, the following strings have special priority and sort
+ before all other strings (listed in order):
+
+ 1. The empty string.
+ 2. ".".
+ 3. "..".
+ 4. Strings starting with "." sort before other strings.
+
+ Before comparing two strings where both begin with non-".", or where both begin with "."
+ but neither is "." or "..", suffixes matching the C-locale extended regular expression
+ (\.[A-Za-z~][A-Za-z0-9~]*)*$ are removed and the strings compared without them, using version sort
+ without special priority; if they do not compare equal, this comparison result is used and
+ the suffixes are effectively ignored. Otherwise, the entire strings are compared using version sort.
+ When removing a suffix from a nonempty string, remove the maximal-length suffix such that
+ the remaining string is nonempty.
+ */
+int filevercmp (const char *a, const char *b);
+
+/* Like filevercmp, except compare the byte arrays a (of length alen) and b (of length blen)
+ so that a and b can contain '\0', which sorts just before '\1'. But if alen is -1 treat
+ a as a string terminated by '\0', and similarly for blen.
+ */
+int filenvercmp (char const *a, ssize_t alen, char const *b, ssize_t blen);
+
+
+/* return how many lines and columns will text occupy on terminal
+ */
+void str_msg_term_size (const char *text, int *lines, int *columns);
+
+/**
+ * skip first needle's in haystack
+ *
+ * @param haystack pointer to string
+ * @param needle pointer to string
+ * @param skip_count skip first bytes
+ *
+ * @return pointer to skip_count+1 needle (or NULL if not found).
+ */
+
+char *strrstr_skip_count (const char *haystack, const char *needle, size_t skip_count);
+
+char *str_replace_all (const char *haystack, const char *needle, const char *replacement);
+
+strtol_error_t xstrtoumax (const char *s, char **ptr, int base, uintmax_t * val,
+ const char *valid_suffixes);
+uintmax_t parse_integer (const char *str, gboolean * invalid);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+str_replace (char *s, char from, char to)
+{
+ for (; *s != '\0'; s++)
+ {
+ if (*s == from)
+ *s = to;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * strcpy is unsafe on overlapping memory areas, so define memmove-alike
+ * string function.
+ * Have sense only when:
+ * * dest <= src
+ * AND
+ * * dest and str are pointers to one object (as Roland Illig pointed).
+ *
+ * We can't use str*cpy funs here:
+ * http://kerneltrap.org/mailarchive/openbsd-misc/2008/5/27/1951294
+ *
+ * @param dest pointer to string
+ * @param src pointer to string
+ *
+ * @return newly allocated string
+ *
+ */
+
+static inline char *
+str_move (char *dest, const char *src)
+{
+ size_t n;
+
+ g_assert (dest <= src);
+
+ n = strlen (src) + 1; /* + '\0' */
+
+ return (char *) memmove (dest, src, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC_STRUTIL_H */
diff --git a/lib/strutil/Makefile.am b/lib/strutil/Makefile.am
new file mode 100644
index 0000000..5936a36
--- /dev/null
+++ b/lib/strutil/Makefile.am
@@ -0,0 +1,14 @@
+noinst_LTLIBRARIES = libmcstrutil.la
+
+libmcstrutil_la_SOURCES = \
+ filevercmp.c \
+ replace.c \
+ strescape.c \
+ strutil8bit.c \
+ strutilascii.c \
+ strutil.c \
+ strutilutf8.c \
+ strverscmp.c \
+ xstrtol.c
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
diff --git a/lib/strutil/Makefile.in b/lib/strutil/Makefile.in
new file mode 100644
index 0000000..fe19e5c
--- /dev/null
+++ b/lib/strutil/Makefile.in
@@ -0,0 +1,773 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lib/strutil
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcstrutil_la_LIBADD =
+am_libmcstrutil_la_OBJECTS = filevercmp.lo replace.lo strescape.lo \
+ strutil8bit.lo strutilascii.lo strutil.lo strutilutf8.lo \
+ strverscmp.lo xstrtol.lo
+libmcstrutil_la_OBJECTS = $(am_libmcstrutil_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/filevercmp.Plo \
+ ./$(DEPDIR)/replace.Plo ./$(DEPDIR)/strescape.Plo \
+ ./$(DEPDIR)/strutil.Plo ./$(DEPDIR)/strutil8bit.Plo \
+ ./$(DEPDIR)/strutilascii.Plo ./$(DEPDIR)/strutilutf8.Plo \
+ ./$(DEPDIR)/strverscmp.Plo ./$(DEPDIR)/xstrtol.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcstrutil_la_SOURCES)
+DIST_SOURCES = $(libmcstrutil_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcstrutil.la
+libmcstrutil_la_SOURCES = \
+ filevercmp.c \
+ replace.c \
+ strescape.c \
+ strutil8bit.c \
+ strutilascii.c \
+ strutil.c \
+ strutilutf8.c \
+ strverscmp.c \
+ xstrtol.c
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/strutil/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/strutil/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcstrutil.la: $(libmcstrutil_la_OBJECTS) $(libmcstrutil_la_DEPENDENCIES) $(EXTRA_libmcstrutil_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcstrutil_la_OBJECTS) $(libmcstrutil_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filevercmp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replace.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strescape.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strutil.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strutil8bit.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strutilascii.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strutilutf8.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strverscmp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xstrtol.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/filevercmp.Plo
+ -rm -f ./$(DEPDIR)/replace.Plo
+ -rm -f ./$(DEPDIR)/strescape.Plo
+ -rm -f ./$(DEPDIR)/strutil.Plo
+ -rm -f ./$(DEPDIR)/strutil8bit.Plo
+ -rm -f ./$(DEPDIR)/strutilascii.Plo
+ -rm -f ./$(DEPDIR)/strutilutf8.Plo
+ -rm -f ./$(DEPDIR)/strverscmp.Plo
+ -rm -f ./$(DEPDIR)/xstrtol.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/filevercmp.Plo
+ -rm -f ./$(DEPDIR)/replace.Plo
+ -rm -f ./$(DEPDIR)/strescape.Plo
+ -rm -f ./$(DEPDIR)/strutil.Plo
+ -rm -f ./$(DEPDIR)/strutil8bit.Plo
+ -rm -f ./$(DEPDIR)/strutilascii.Plo
+ -rm -f ./$(DEPDIR)/strutilutf8.Plo
+ -rm -f ./$(DEPDIR)/strverscmp.Plo
+ -rm -f ./$(DEPDIR)/xstrtol.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/strutil/filevercmp.c b/lib/strutil/filevercmp.c
new file mode 100644
index 0000000..c0a28dd
--- /dev/null
+++ b/lib/strutil/filevercmp.c
@@ -0,0 +1,267 @@
+/*
+ Copyright (C) 1995 Ian Jackson <iwj10@cus.cam.ac.uk>
+ Copyright (C) 2001 Anthony Towns <aj@azure.humbug.org.au>
+ Copyright (C) 2008-2022 Free Software Foundation, Inc.
+
+ This file is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This file 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <limits.h>
+
+#include "lib/strutil.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return the length of a prefix of @s that corresponds to the suffix defined by this extended
+ * regular expression in the C locale: (\.[A-Za-z~][A-Za-z0-9~]*)*$
+ *
+ * Use the longest suffix matching this regular expression, except do not use all of s as a suffix
+ * if s is nonempty.
+ *
+ * If *len is -1, s is a string; set *lem to s's length.
+ * Otherwise, *len should be nonnegative, s is a char array, and *len does not change.
+ */
+static ssize_t
+file_prefixlen (const char *s, ssize_t * len)
+{
+ size_t n = (size_t) (*len); /* SIZE_MAX if N == -1 */
+ size_t i = 0;
+ size_t prefixlen = 0;
+
+ while (TRUE)
+ {
+ gboolean done;
+
+ if (*len < 0)
+ done = s[i] == '\0';
+ else
+ done = i == n;
+
+ if (done)
+ {
+ *len = (ssize_t) i;
+ return (ssize_t) prefixlen;
+ }
+
+ i++;
+ prefixlen = i;
+
+ while (i + 1 < n && s[i] == '.' && (g_ascii_isalpha (s[i + 1]) || s[i + 1] == '~'))
+ for (i += 2; i < n && (g_ascii_isalnum (s[i]) || s[i] == '~'); i++)
+ ;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return a version sort comparison value for @s's byte at position @pos.
+ *
+ * @param s a string
+ * @param pos a position in @s
+ * @param len a length of @s. If @pos == @len, sort before all non-'~' bytes.
+ */
+
+static int
+order (const char *s, size_t pos, size_t len)
+{
+ unsigned char c;
+
+ if (pos == len)
+ return (-1);
+
+ c = s[pos];
+
+ if (g_ascii_isdigit (c))
+ return 0;
+ if (g_ascii_isalpha (c))
+ return c;
+ if (c == '~')
+ return (-2);
+
+ g_assert (UCHAR_MAX <= (INT_MAX - 1 - 2) / 2);
+
+ return (int) c + UCHAR_MAX + 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Slightly modified verrevcmp function from dpkg
+ *
+ * This implements the algorithm for comparison of version strings
+ * specified by Debian and now widely adopted. The detailed
+ * specification can be found in the Debian Policy Manual in the
+ * section on the 'Version' control field. This version of the code
+ * implements that from s5.6.12 of Debian Policy v3.8.0.1
+ * https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
+ *
+ * @param s1 first char array to compare
+ * @param s1_len length of @s1
+ * @param s2 second char array to compare
+ * @param s2_len length of @s2
+ *
+ * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2.
+ */
+static int
+verrevcmp (const char *s1, ssize_t s1_len, const char *s2, ssize_t s2_len)
+{
+ ssize_t s1_pos = 0;
+ ssize_t s2_pos = 0;
+
+ while (s1_pos < s1_len || s2_pos < s2_len)
+ {
+ int first_diff = 0;
+
+ while ((s1_pos < s1_len && !g_ascii_isdigit (s1[s1_pos]))
+ || (s2_pos < s2_len && !g_ascii_isdigit (s2[s2_pos])))
+ {
+ int s1_c, s2_c;
+
+ s1_c = order (s1, s1_pos, s1_len);
+ s2_c = order (s2, s2_pos, s2_len);
+
+ if (s1_c != s2_c)
+ return (s1_c - s2_c);
+
+ s1_pos++;
+ s2_pos++;
+ }
+
+ while (s1_pos < s1_len && s1[s1_pos] == '0')
+ s1_pos++;
+ while (s2_pos < s2_len && s2[s2_pos] == '0')
+ s2_pos++;
+
+ while (s1_pos < s1_len && s2_pos < s2_len
+ && g_ascii_isdigit (s1[s1_pos]) && g_ascii_isdigit (s2[s2_pos]))
+ {
+ if (first_diff == 0)
+ first_diff = s1[s1_pos] - s2[s2_pos];
+
+ s1_pos++;
+ s2_pos++;
+ }
+
+ if (s1_pos < s1_len && g_ascii_isdigit (s1[s1_pos]))
+ return 1;
+ if (s2_pos < s2_len && g_ascii_isdigit (s2[s2_pos]))
+ return (-1);
+ if (first_diff != 0)
+ return first_diff;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Compare version strings.
+ *
+ * @param s1 first string to compare
+ * @param s2 second string to compare
+ *
+ * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2.
+ */
+int
+filevercmp (const char *s1, const char *s2)
+{
+ return filenvercmp (s1, -1, s2, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Compare version strings.
+ *
+ * @param a first string to compare
+ * @param alen length of @a or (-1)
+ * @param b second string to compare
+ * @param blen length of @b or (-1)
+ *
+ * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2.
+ */
+int
+filenvercmp (const char *a, ssize_t alen, const char *b, ssize_t blen)
+{
+ gboolean aempty, bempty;
+ ssize_t aprefixlen, bprefixlen;
+ gboolean one_pass_only;
+ int result;
+
+ /* Special case for empty versions. */
+ aempty = alen < 0 ? a[0] == '\0' : alen == 0;
+ bempty = blen < 0 ? b[0] == '\0' : blen == 0;
+
+ if (aempty)
+ return (bempty ? 0 : -1);
+ if (bempty)
+ return 1;
+
+ /* Special cases for leading ".": "." sorts first, then "..", then other names with leading ".",
+ then other names. */
+ if (a[0] == '.')
+ {
+ gboolean adot, bdot;
+ gboolean adotdot, bdotdot;
+
+ if (b[0] != '.')
+ return (-1);
+
+ adot = alen < 0 ? a[1] == '\0' : alen == 1;
+ bdot = blen < 0 ? b[1] == '\0' : blen == 1;
+
+ if (adot)
+ return (bdot ? 0 : -1);
+ if (bdot)
+ return 1;
+
+ adotdot = a[1] == '.' && (alen < 0 ? a[2] == '\0' : alen == 2);
+ bdotdot = b[1] == '.' && (blen < 0 ? b[2] == '\0' : blen == 2);
+ if (adotdot)
+ return (bdotdot ? 0 : -1);
+ if (bdotdot)
+ return 1;
+ }
+ else if (b[0] == '.')
+ return 1;
+
+ /* Cut file suffixes. */
+ aprefixlen = file_prefixlen (a, &alen);
+ bprefixlen = file_prefixlen (b, &blen);
+
+ /* If both suffixes are empty, a second pass would return the same thing. */
+ one_pass_only = aprefixlen == alen && bprefixlen == blen;
+
+ result = verrevcmp (a, aprefixlen, b, bprefixlen);
+
+ /* Return the initial result if nonzero, or if no second pass is needed.
+ Otherwise, restore the suffixes and try again. */
+ return (result != 0 || one_pass_only ? result : verrevcmp (a, alen, b, blen));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/replace.c b/lib/strutil/replace.c
new file mode 100644
index 0000000..99356ee
--- /dev/null
+++ b/lib/strutil/replace.c
@@ -0,0 +1,104 @@
+/*
+ Functions for replacing substrings in strings.
+
+ Copyright (C) 2013-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013;
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Replace all substrings 'needle' in string 'haystack' by 'replacement'.
+ * If the 'needle' in the 'haystack' is escaped by backslash,
+ * then this occurrence isn't be replaced.
+ *
+ * @param haystack string contains substrings for replacement. Cannot be NULL.
+ * @param needle string for search. Cannot be NULL.
+ * @param replacement string for replace. Cannot be NULL.
+ * @return newly allocated string with replaced substrings or NULL if @haystack is empty.
+ */
+
+char *
+str_replace_all (const char *haystack, const char *needle, const char *replacement)
+{
+ size_t needle_len, replacement_len;
+ GString *return_str = NULL;
+ char *needle_in_str;
+
+ needle_len = strlen (needle);
+ replacement_len = strlen (replacement);
+
+ while ((needle_in_str = strstr (haystack, needle)) != NULL)
+ {
+ if (return_str == NULL)
+ return_str = g_string_sized_new (32);
+
+ if (strutils_is_char_escaped (haystack, needle_in_str))
+ {
+ char *backslash = needle_in_str - 1;
+
+ if (haystack != backslash)
+ g_string_append_len (return_str, haystack, backslash - haystack);
+ g_string_append_len (return_str, needle_in_str, needle_in_str - backslash);
+ haystack = needle_in_str + 1;
+ }
+ else
+ {
+ if (needle_in_str != haystack)
+ g_string_append_len (return_str, haystack, needle_in_str - haystack);
+ g_string_append_len (return_str, replacement, replacement_len);
+ haystack = needle_in_str + needle_len;
+ }
+ }
+
+ if (*haystack != '\0')
+ {
+ if (return_str == NULL)
+ return strdup (haystack);
+
+ g_string_append (return_str, haystack);
+ }
+
+ return (return_str != NULL ? g_string_free (return_str, FALSE) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/strescape.c b/lib/strutil/strescape.c
new file mode 100644
index 0000000..a605ad8
--- /dev/null
+++ b/lib/strutil/strescape.c
@@ -0,0 +1,266 @@
+/*
+ Functions for escaping and unescaping strings
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009;
+ Patrick Winnertz <winnie@debian.org>, 2009
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/strescape.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char ESCAPE_SHELL_CHARS[] = " !#$%()&{}[]`?|<>;*\\\"'";
+static const char ESCAPE_REGEX_CHARS[] = "^!#$%()&{}[]`?|<>;*+.\\";
+static const char ESCAPE_GLOB_CHARS[] = "$*\\?";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+strutils_escape (const char *src, gsize src_len, const char *escaped_chars,
+ gboolean escape_non_printable)
+{
+ GString *ret;
+ gsize curr_index;
+ /* do NOT break allocation semantics */
+ if (src == NULL)
+ return NULL;
+
+ if (*src == '\0')
+ return strdup ("");
+
+ ret = g_string_new ("");
+
+ if (src_len == (gsize) (-1))
+ src_len = strlen (src);
+
+ for (curr_index = 0; curr_index < src_len; curr_index++)
+ {
+ if (escape_non_printable)
+ {
+ switch (src[curr_index])
+ {
+ case '\n':
+ g_string_append (ret, "\\n");
+ continue;
+ case '\t':
+ g_string_append (ret, "\\t");
+ continue;
+ case '\b':
+ g_string_append (ret, "\\b");
+ continue;
+ case '\0':
+ g_string_append (ret, "\\0");
+ continue;
+ default:
+ break;
+ }
+ }
+
+ if (strchr (escaped_chars, (int) src[curr_index]))
+ g_string_append_c (ret, '\\');
+
+ g_string_append_c (ret, src[curr_index]);
+ }
+ return g_string_free (ret, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+strutils_unescape (const char *src, gsize src_len, const char *unescaped_chars,
+ gboolean unescape_non_printable)
+{
+ GString *ret;
+ gsize curr_index;
+
+ if (src == NULL)
+ return NULL;
+
+ if (*src == '\0')
+ return strdup ("");
+
+ ret = g_string_sized_new (16);
+
+ if (src_len == (gsize) (-1))
+ src_len = strlen (src);
+ src_len--;
+
+ for (curr_index = 0; curr_index < src_len; curr_index++)
+ {
+ if (src[curr_index] != '\\')
+ {
+ g_string_append_c (ret, src[curr_index]);
+ continue;
+ }
+
+ curr_index++;
+
+ if (unescaped_chars == ESCAPE_SHELL_CHARS && src[curr_index] == '$')
+ {
+ /* special case: \$ is used to disallow variable substitution */
+ g_string_append_c (ret, '\\');
+ }
+ else
+ {
+ if (unescape_non_printable)
+ {
+ switch (src[curr_index])
+ {
+ case 'n':
+ g_string_append_c (ret, '\n');
+ continue;
+ case 't':
+ g_string_append_c (ret, '\t');
+ continue;
+ case 'b':
+ g_string_append_c (ret, '\b');
+ continue;
+ case '0':
+ g_string_append_c (ret, '\0');
+ continue;
+ default:
+ break;
+ }
+ }
+
+ if (strchr (unescaped_chars, (int) src[curr_index]) == NULL)
+ g_string_append_c (ret, '\\');
+ }
+
+ g_string_append_c (ret, src[curr_index]);
+ }
+ g_string_append_c (ret, src[curr_index]);
+
+ return g_string_free (ret, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * To be compatible with the general posix command lines we have to escape
+ * strings for the command line
+ *
+ * @param src string for escaping
+ *
+ * @return escaped string (which needs to be freed later) or NULL when NULL string is passed.
+ */
+
+char *
+strutils_shell_escape (const char *src)
+{
+ return strutils_escape (src, -1, ESCAPE_SHELL_CHARS, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+strutils_glob_escape (const char *src)
+{
+ return strutils_escape (src, -1, ESCAPE_GLOB_CHARS, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+strutils_regex_escape (const char *src)
+{
+ return strutils_escape (src, -1, ESCAPE_REGEX_CHARS, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Unescape paths or other strings for e.g the internal cd
+ * shell-unescape within a given buffer (writing to it!)
+ *
+ * @param text string for unescaping
+ *
+ * @return unescaped string (which needs to be freed)
+ */
+
+char *
+strutils_shell_unescape (const char *text)
+{
+ return strutils_unescape (text, -1, ESCAPE_SHELL_CHARS, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+strutils_glob_unescape (const char *text)
+{
+ return strutils_unescape (text, -1, ESCAPE_GLOB_CHARS, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+char *
+strutils_regex_unescape (const char *text)
+{
+ return strutils_unescape (text, -1, ESCAPE_REGEX_CHARS, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Check if char in pointer contain escape'd chars
+ *
+ * @param start string for checking
+ * @param current pointer to checked character
+ *
+ * @return TRUE if string contain escaped chars otherwise return FALSE
+ */
+
+gboolean
+strutils_is_char_escaped (const char *start, const char *current)
+{
+ int num_esc = 0;
+
+ if (start == NULL || current == NULL || current <= start)
+ return FALSE;
+
+ current--;
+ while (current >= start && *current == '\\')
+ {
+ num_esc++;
+ current--;
+ }
+ return (gboolean) num_esc % 2;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/strutil.c b/lib/strutil/strutil.c
new file mode 100644
index 0000000..0c5f96a
--- /dev/null
+++ b/lib/strutil/strutil.c
@@ -0,0 +1,1026 @@
+/*
+ Common strings utilities
+
+ Copyright (C) 2007-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Rostislav Benes, 2007
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <langinfo.h>
+#include <string.h>
+#include <errno.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+#include "lib/strutil.h"
+
+/*** global variables ****************************************************************************/
+
+GIConv str_cnv_to_term;
+GIConv str_cnv_from_term;
+GIConv str_cnv_not_convert = INVALID_CONV;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* names, that are used for utf-8 */
+static const char *const str_utf8_encodings[] = {
+ "utf-8",
+ "utf8",
+ NULL
+};
+
+/* standard 8bit encodings, no wide or multibytes characters */
+static const char *const str_8bit_encodings[] = {
+ /* Solaris has different names of Windows 1251 encoding */
+#ifdef __sun
+ "ansi-1251",
+ "ansi1251",
+#else
+ "cp-1251",
+ "cp1251",
+#endif
+ "cp-1250",
+ "cp1250",
+ "cp-866",
+ "cp866",
+ "ibm-866",
+ "ibm866",
+ "cp-850",
+ "cp850",
+ "cp-852",
+ "cp852",
+ "iso-8859",
+ "iso8859",
+ "koi8",
+ NULL
+};
+
+/* terminal encoding */
+static char *codeset = NULL;
+static char *term_encoding = NULL;
+/* function for encoding specific operations */
+static struct str_class used_class;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* if enc is same encoding like on terminal */
+static int
+str_test_not_convert (const char *enc)
+{
+ return g_ascii_strcasecmp (enc, codeset) == 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static estr_t
+_str_convert (GIConv coder, const char *string, int size, GString * buffer)
+{
+ estr_t state = ESTR_SUCCESS;
+ gssize left;
+ gsize bytes_read = 0;
+ gsize bytes_written = 0;
+
+ errno = 0; /* FIXME: is it really needed? */
+
+ if (coder == INVALID_CONV)
+ return ESTR_FAILURE;
+
+ if (string == NULL || buffer == NULL)
+ return ESTR_FAILURE;
+
+ /*
+ if (! used_class.is_valid_string (string))
+ {
+ return ESTR_FAILURE;
+ }
+ */
+ if (size < 0)
+ size = strlen (string);
+ else
+ {
+ left = strlen (string);
+ if (left < size)
+ size = left;
+ }
+
+ left = size;
+ g_iconv (coder, NULL, NULL, NULL, NULL);
+
+ while (left != 0)
+ {
+ gchar *tmp_buff;
+ GError *mcerror = NULL;
+
+ tmp_buff = g_convert_with_iconv ((const gchar *) string,
+ left, coder, &bytes_read, &bytes_written, &mcerror);
+ if (mcerror != NULL)
+ {
+ int code = mcerror->code;
+
+ g_error_free (mcerror);
+ mcerror = NULL;
+
+ switch (code)
+ {
+ case G_CONVERT_ERROR_NO_CONVERSION:
+ /* Conversion between the requested character sets is not supported. */
+ g_free (tmp_buff);
+ tmp_buff = g_strnfill (strlen (string), '?');
+ g_string_append (buffer, tmp_buff);
+ g_free (tmp_buff);
+ return ESTR_FAILURE;
+
+ case G_CONVERT_ERROR_ILLEGAL_SEQUENCE:
+ /* Invalid byte sequence in conversion input. */
+ if ((tmp_buff == NULL) && (bytes_read != 0))
+ /* recode valid byte sequence */
+ tmp_buff = g_convert_with_iconv ((const gchar *) string,
+ bytes_read, coder, NULL, NULL, NULL);
+
+ if (tmp_buff != NULL)
+ {
+ g_string_append (buffer, tmp_buff);
+ g_free (tmp_buff);
+ }
+
+ if ((int) bytes_read >= left)
+ return ESTR_PROBLEM;
+
+ string += bytes_read + 1;
+ size -= (bytes_read + 1);
+ left -= (bytes_read + 1);
+ g_string_append_c (buffer, *(string - 1));
+ state = ESTR_PROBLEM;
+ break;
+
+ case G_CONVERT_ERROR_PARTIAL_INPUT:
+ /* Partial character sequence at end of input. */
+ g_string_append (buffer, tmp_buff);
+ g_free (tmp_buff);
+ if ((int) bytes_read < left)
+ {
+ left = left - bytes_read;
+ tmp_buff = g_strnfill (left, '?');
+ g_string_append (buffer, tmp_buff);
+ g_free (tmp_buff);
+ }
+ return ESTR_PROBLEM;
+
+ case G_CONVERT_ERROR_BAD_URI: /* Don't know how handle this error :( */
+ case G_CONVERT_ERROR_NOT_ABSOLUTE_PATH: /* Don't know how handle this error :( */
+ case G_CONVERT_ERROR_FAILED: /* Conversion failed for some reason. */
+ default:
+ g_free (tmp_buff);
+ return ESTR_FAILURE;
+ }
+ }
+ else if (tmp_buff == NULL)
+ {
+ g_string_append (buffer, string);
+ return ESTR_PROBLEM;
+ }
+ else if (*tmp_buff == '\0')
+ {
+ g_free (tmp_buff);
+ g_string_append (buffer, string);
+ return state;
+ }
+ else
+ {
+ g_string_append (buffer, tmp_buff);
+ g_free (tmp_buff);
+ string += bytes_read;
+ left -= bytes_read;
+ }
+ }
+
+ return state;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_test_encoding_class (const char *encoding, const char *const *table)
+{
+ int result = 0;
+
+ if (encoding != NULL)
+ {
+ int t;
+
+ for (t = 0; table[t] != NULL; t++)
+ if (g_ascii_strncasecmp (encoding, table[t], strlen (table[t])) == 0)
+ result++;
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_choose_str_functions (void)
+{
+ if (str_test_encoding_class (codeset, str_utf8_encodings))
+ used_class = str_utf8_init ();
+ else if (str_test_encoding_class (codeset, str_8bit_encodings))
+ used_class = str_8bit_init ();
+ else
+ used_class = str_ascii_init ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+GIConv
+str_crt_conv_to (const char *to_enc)
+{
+ return (!str_test_not_convert (to_enc)) ? g_iconv_open (to_enc, codeset) : str_cnv_not_convert;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GIConv
+str_crt_conv_from (const char *from_enc)
+{
+ return (!str_test_not_convert (from_enc))
+ ? g_iconv_open (codeset, from_enc) : str_cnv_not_convert;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_close_conv (GIConv conv)
+{
+ if (conv != str_cnv_not_convert)
+ g_iconv_close (conv);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+estr_t
+str_convert (GIConv coder, const char *string, GString * buffer)
+{
+ return _str_convert (coder, string, -1, buffer);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+estr_t
+str_nconvert (GIConv coder, const char *string, int size, GString * buffer)
+{
+ return _str_convert (coder, string, size, buffer);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gchar *
+str_conv_gerror_message (GError * mcerror, const char *def_msg)
+{
+ return used_class.conv_gerror_message (mcerror, def_msg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+estr_t
+str_vfs_convert_from (GIConv coder, const char *string, GString * buffer)
+{
+ estr_t result = ESTR_SUCCESS;
+
+ if (coder == str_cnv_not_convert)
+ g_string_append (buffer, string != NULL ? string : "");
+ else
+ result = _str_convert (coder, string, -1, buffer);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+estr_t
+str_vfs_convert_to (GIConv coder, const char *string, int size, GString * buffer)
+{
+ return used_class.vfs_convert_to (coder, string, size, buffer);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_printf (GString * buffer, const char *format, ...)
+{
+ va_list ap;
+ va_start (ap, format);
+
+ g_string_append_vprintf (buffer, format, ap);
+ va_end (ap);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_insert_replace_char (GString * buffer)
+{
+ used_class.insert_replace_char (buffer);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+estr_t
+str_translate_char (GIConv conv, const char *keys, size_t ch_size, char *output, size_t out_size)
+{
+ size_t left;
+ size_t cnv;
+
+ g_iconv (conv, NULL, NULL, NULL, NULL);
+
+ left = (ch_size == (size_t) (-1)) ? strlen (keys) : ch_size;
+
+ cnv = g_iconv (conv, (gchar **) & keys, &left, &output, &out_size);
+ if (cnv == (size_t) (-1))
+ return (errno == EINVAL) ? ESTR_PROBLEM : ESTR_FAILURE;
+
+ output[0] = '\0';
+ return ESTR_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_detect_termencoding (void)
+{
+ if (term_encoding == NULL)
+ {
+ /* On Linux, nl_langinfo (CODESET) returns upper case UTF-8 whether the LANG is set
+ to utf-8 or UTF-8.
+ On Mac OS X, it returns the same case as the LANG input.
+ So let transform result of nl_langinfo (CODESET) to upper case unconditionally. */
+ term_encoding = g_ascii_strup (nl_langinfo (CODESET), -1);
+ }
+
+ return term_encoding;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_isutf8 (const char *codeset_name)
+{
+ return (str_test_encoding_class (codeset_name, str_utf8_encodings) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_init_strings (const char *termenc)
+{
+ codeset = termenc != NULL ? g_ascii_strup (termenc, -1) : g_strdup (str_detect_termencoding ());
+
+ str_cnv_not_convert = g_iconv_open (codeset, codeset);
+ if (str_cnv_not_convert == INVALID_CONV)
+ {
+ if (termenc != NULL)
+ {
+ g_free (codeset);
+ codeset = g_strdup (str_detect_termencoding ());
+ str_cnv_not_convert = g_iconv_open (codeset, codeset);
+ }
+
+ if (str_cnv_not_convert == INVALID_CONV)
+ {
+ g_free (codeset);
+ codeset = g_strdup (DEFAULT_CHARSET);
+ str_cnv_not_convert = g_iconv_open (codeset, codeset);
+ }
+ }
+
+ str_cnv_to_term = str_cnv_not_convert;
+ str_cnv_from_term = str_cnv_not_convert;
+
+ str_choose_str_functions ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_uninit_strings (void)
+{
+ if (str_cnv_not_convert != INVALID_CONV)
+ g_iconv_close (str_cnv_not_convert);
+ /* NULL-ize pointers to avoid double free in unit tests */
+ MC_PTR_FREE (term_encoding);
+ MC_PTR_FREE (codeset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_term_form (const char *text)
+{
+ return used_class.term_form (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_fit_to_term (const char *text, int width, align_crt_t just_mode)
+{
+ return used_class.fit_to_term (text, width, just_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_term_trim (const char *text, int width)
+{
+ return used_class.term_trim (text, width);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_term_substring (const char *text, int start, int width)
+{
+ return used_class.term_substring (text, start, width);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+str_get_next_char (char *text)
+{
+
+ used_class.cnext_char ((const char **) &text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_cget_next_char (const char *text)
+{
+ used_class.cnext_char (&text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_next_char (char **text)
+{
+ used_class.cnext_char ((const char **) text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_cnext_char (const char **text)
+{
+ used_class.cnext_char (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+str_get_prev_char (char *text)
+{
+ used_class.cprev_char ((const char **) &text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_cget_prev_char (const char *text)
+{
+ used_class.cprev_char (&text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_prev_char (char **text)
+{
+ used_class.cprev_char ((const char **) text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_cprev_char (const char **text)
+{
+ used_class.cprev_char (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+str_get_next_char_safe (char *text)
+{
+ used_class.cnext_char_safe ((const char **) &text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_cget_next_char_safe (const char *text)
+{
+ used_class.cnext_char_safe (&text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_next_char_safe (char **text)
+{
+ used_class.cnext_char_safe ((const char **) text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_cnext_char_safe (const char **text)
+{
+ used_class.cnext_char_safe (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+str_get_prev_char_safe (char *text)
+{
+ used_class.cprev_char_safe ((const char **) &text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_cget_prev_char_safe (const char *text)
+{
+ used_class.cprev_char_safe (&text);
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_prev_char_safe (char **text)
+{
+ used_class.cprev_char_safe ((const char **) text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_cprev_char_safe (const char **text)
+{
+ used_class.cprev_char_safe (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_next_noncomb_char (char **text)
+{
+ return used_class.cnext_noncomb_char ((const char **) text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_cnext_noncomb_char (const char **text)
+{
+ return used_class.cnext_noncomb_char (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_prev_noncomb_char (char **text, const char *begin)
+{
+ return used_class.cprev_noncomb_char ((const char **) text, begin);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_cprev_noncomb_char (const char **text, const char *begin)
+{
+ return used_class.cprev_noncomb_char (text, begin);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_is_valid_char (const char *ch, size_t size)
+{
+ return used_class.is_valid_char (ch, size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_term_width1 (const char *text)
+{
+ return used_class.term_width1 (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_term_width2 (const char *text, size_t length)
+{
+ return used_class.term_width2 (text, length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_term_char_width (const char *text)
+{
+ return used_class.term_char_width (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_offset_to_pos (const char *text, size_t length)
+{
+ return used_class.offset_to_pos (text, length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_length (const char *text)
+{
+ return used_class.length (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_length_char (const char *text)
+{
+ return str_cget_next_char_safe (text) - text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_length2 (const char *text, int size)
+{
+ return used_class.length2 (text, size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_length_noncomb (const char *text)
+{
+ return used_class.length_noncomb (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_column_to_pos (const char *text, size_t pos)
+{
+ return used_class.column_to_pos (text, pos);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_isspace (const char *ch)
+{
+ return used_class.char_isspace (ch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_ispunct (const char *ch)
+{
+ return used_class.char_ispunct (ch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_isalnum (const char *ch)
+{
+ return used_class.char_isalnum (ch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_isdigit (const char *ch)
+{
+ return used_class.char_isdigit (ch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_toupper (const char *ch, char **out, size_t * remain)
+{
+ return used_class.char_toupper (ch, out, remain);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_tolower (const char *ch, char **out, size_t * remain)
+{
+ return used_class.char_tolower (ch, out, remain);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_isprint (const char *ch)
+{
+ return used_class.char_isprint (ch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_iscombiningmark (const char *ch)
+{
+ return used_class.char_iscombiningmark (ch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_trunc (const char *text, int width)
+{
+ return used_class.trunc (text, width);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+str_create_search_needle (const char *needle, gboolean case_sen)
+{
+ return used_class.create_search_needle (needle, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_release_search_needle (char *needle, gboolean case_sen)
+{
+ used_class.release_search_needle (needle, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_search_first (const char *text, const char *search, gboolean case_sen)
+{
+ return used_class.search_first (text, search, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+str_search_last (const char *text, const char *search, gboolean case_sen)
+{
+ return used_class.search_last (text, search, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+str_is_valid_string (const char *text)
+{
+ return used_class.is_valid_string (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_compare (const char *t1, const char *t2)
+{
+ return used_class.compare (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_ncompare (const char *t1, const char *t2)
+{
+ return used_class.ncompare (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_casecmp (const char *t1, const char *t2)
+{
+ return used_class.casecmp (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_ncasecmp (const char *t1, const char *t2)
+{
+ return used_class.ncasecmp (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_prefix (const char *text, const char *prefix)
+{
+ return used_class.prefix (text, prefix);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_caseprefix (const char *text, const char *prefix)
+{
+ return used_class.caseprefix (text, prefix);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_fix_string (char *text)
+{
+ used_class.fix_string (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+str_create_key (const char *text, gboolean case_sen)
+{
+ return used_class.create_key (text, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+str_create_key_for_filename (const char *text, gboolean case_sen)
+{
+ return used_class.create_key_for_filename (text, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+str_key_collate (const char *t1, const char *t2, gboolean case_sen)
+{
+ return used_class.key_collate (t1, t2, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_release_key (char *key, gboolean case_sen)
+{
+ used_class.release_key (key, case_sen);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+str_msg_term_size (const char *text, int *lines, int *columns)
+{
+ char *p, *tmp;
+ char *q;
+ char c = '\0';
+
+ *lines = 1;
+ *columns = 0;
+
+ tmp = g_strdup (text);
+ p = tmp;
+
+ while (TRUE)
+ {
+ int width;
+
+ q = strchr (p, '\n');
+ if (q != NULL)
+ {
+ c = q[0];
+ q[0] = '\0';
+ }
+
+ width = str_term_width1 (p);
+ if (width > *columns)
+ *columns = width;
+
+ if (q == NULL)
+ break;
+
+ q[0] = c;
+ p = q + 1;
+ (*lines)++;
+ }
+
+ g_free (tmp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+strrstr_skip_count (const char *haystack, const char *needle, size_t skip_count)
+{
+ char *semi;
+ ssize_t len;
+
+ len = strlen (haystack);
+
+ do
+ {
+ semi = g_strrstr_len (haystack, len, needle);
+ if (semi == NULL)
+ return NULL;
+ len = semi - haystack - 1;
+ }
+ while (skip_count-- != 0);
+
+ return semi;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Interpret string as a non-negative decimal integer, optionally multiplied by various values.
+ *
+ * @param str input value
+ * @param invalid set to TRUE if "str" does not represent a number in this format
+ *
+ * @return non-negative integer representation of "str", 0 in case of error.
+ */
+
+uintmax_t
+parse_integer (const char *str, gboolean * invalid)
+{
+ uintmax_t n;
+ char *suffix;
+ strtol_error_t e;
+
+ e = xstrtoumax (str, &suffix, 10, &n, "bcEGkKMPTwYZ0");
+ if (e == LONGINT_INVALID_SUFFIX_CHAR && *suffix == 'x')
+ {
+ uintmax_t multiplier;
+
+ multiplier = parse_integer (suffix + 1, invalid);
+ if (multiplier != 0 && n * multiplier / multiplier != n)
+ {
+ *invalid = TRUE;
+ return 0;
+ }
+
+ n *= multiplier;
+ }
+ else if (e != LONGINT_OK)
+ {
+ *invalid = TRUE;
+ n = 0;
+ }
+
+ return n;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/strutil8bit.c b/lib/strutil/strutil8bit.c
new file mode 100644
index 0000000..2002e5e
--- /dev/null
+++ b/lib/strutil/strutil8bit.c
@@ -0,0 +1,862 @@
+/*
+ 8bit strings utilities
+
+ Copyright (C) 2007-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Rostislav Benes, 2007
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+
+/* Functions for singlebyte encodings, all characters have width 1
+ * using standard system functions.
+ * There are only small differences between functions in strutil8bit.c
+ * and strutilascii.c.
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*
+ * Inlines to equalize 'char' signedness for single 'char' encodings.
+ * Instead of writing
+ * isspace ((unsigned char) c);
+ * you can write
+ * char_isspace (c);
+ */
+#define DECLARE_CTYPE_WRAPPER(func_name) \
+static inline int char_##func_name(char c) \
+{ \
+ return func_name((int)(unsigned char)c); \
+}
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char replch = '?';
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+DECLARE_CTYPE_WRAPPER (isalnum)
+DECLARE_CTYPE_WRAPPER (isdigit)
+DECLARE_CTYPE_WRAPPER (isprint)
+DECLARE_CTYPE_WRAPPER (ispunct)
+DECLARE_CTYPE_WRAPPER (isspace)
+DECLARE_CTYPE_WRAPPER (toupper)
+DECLARE_CTYPE_WRAPPER (tolower)
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_8bit_insert_replace_char (GString * buffer)
+{
+ g_string_append_c (buffer, replch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_is_valid_string (const char *text)
+{
+ (void) text;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_is_valid_char (const char *ch, size_t size)
+{
+ (void) ch;
+ (void) size;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_8bit_cnext_char (const char **text)
+{
+ (*text)++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_8bit_cprev_char (const char **text)
+{
+ (*text)--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_cnext_noncomb_char (const char **text)
+{
+ if (*text[0] == '\0')
+ return 0;
+
+ (*text)++;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_cprev_noncomb_char (const char **text, const char *begin)
+{
+ if ((*text) == begin)
+ return 0;
+
+ (*text)--;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_isspace (const char *text)
+{
+ return char_isspace (text[0]) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_ispunct (const char *text)
+{
+ return char_ispunct (text[0]) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_isalnum (const char *text)
+{
+ return char_isalnum (text[0]) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_isdigit (const char *text)
+{
+ return char_isdigit (text[0]) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_isprint (const char *text)
+{
+ return char_isprint (text[0]) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_iscombiningmark (const char *text)
+{
+ (void) text;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_toupper (const char *text, char **out, size_t * remain)
+{
+ if (*remain <= 1)
+ return FALSE;
+
+ (*out)[0] = char_toupper (text[0]);
+ (*out)++;
+ (*remain)--;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_8bit_tolower (const char *text, char **out, size_t * remain)
+{
+ if (*remain <= 1)
+ return FALSE;
+
+ (*out)[0] = char_tolower (text[0]);
+ (*out)++;
+ (*remain)--;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_length (const char *text)
+{
+ return strlen (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_length2 (const char *text, int size)
+{
+ return (size >= 0) ? MIN (strlen (text), (gsize) size) : strlen (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gchar *
+str_8bit_conv_gerror_message (GError * mcerror, const char *def_msg)
+{
+ GIConv conv;
+ gchar *ret;
+
+ /* glib messages are in UTF-8 charset */
+ conv = str_crt_conv_from ("UTF-8");
+
+ if (conv == INVALID_CONV)
+ ret = g_strdup (def_msg != NULL ? def_msg : "");
+ else
+ {
+ GString *buf;
+
+ buf = g_string_new ("");
+
+ if (str_convert (conv, mcerror->message, buf) != ESTR_FAILURE)
+ ret = g_string_free (buf, FALSE);
+ else
+ {
+ ret = g_strdup (def_msg != NULL ? def_msg : "");
+ g_string_free (buf, TRUE);
+ }
+
+ str_close_conv (conv);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static estr_t
+str_8bit_vfs_convert_to (GIConv coder, const char *string, int size, GString * buffer)
+{
+ estr_t result = ESTR_SUCCESS;
+
+ if (coder == str_cnv_not_convert)
+ g_string_append_len (buffer, string, size);
+ else
+ result = str_nconvert (coder, string, size, buffer);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_8bit_term_form (const char *text)
+{
+ static char result[BUF_MEDIUM];
+ char *actual;
+ size_t remain;
+ size_t length;
+ size_t pos = 0;
+
+ actual = result;
+ remain = sizeof (result);
+ length = strlen (text);
+
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_8bit_fit_to_term (const char *text, int width, align_crt_t just_mode)
+{
+ static char result[BUF_MEDIUM];
+ char *actual;
+ size_t remain;
+ int ident = 0;
+ size_t length;
+ size_t pos = 0;
+
+ length = strlen (text);
+ actual = result;
+ remain = sizeof (result);
+
+ if ((int) length <= width)
+ {
+ switch (HIDE_FIT (just_mode))
+ {
+ case J_CENTER_LEFT:
+ case J_CENTER:
+ ident = (width - length) / 2;
+ break;
+ case J_RIGHT:
+ ident = width - length;
+ break;
+ default:
+ break;
+ }
+
+ if ((int) remain <= ident)
+ goto finally;
+ memset (actual, ' ', ident);
+ actual += ident;
+ remain -= ident;
+
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+
+ if (width - length - ident > 0)
+ {
+ if (remain <= width - length - ident)
+ goto finally;
+ memset (actual, ' ', width - length - ident);
+ actual += width - length - ident;
+ }
+ }
+ else if (IS_FIT (just_mode))
+ {
+ for (; pos + 1 <= (gsize) width / 2 && remain > 1; actual++, pos++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+
+ if (remain <= 1)
+ goto finally;
+ actual[0] = '~';
+ actual++;
+ remain--;
+
+ pos += length - width + 1;
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+ }
+ else
+ {
+ switch (HIDE_FIT (just_mode))
+ {
+ case J_CENTER:
+ ident = (length - width) / 2;
+ break;
+ case J_RIGHT:
+ ident = length - width;
+ break;
+ default:
+ break;
+ }
+
+ pos += ident;
+ for (; pos < (gsize) (ident + width) && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+ }
+
+ finally:
+ if (actual >= result + sizeof (result))
+ actual = result + sizeof (result) - 1;
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_8bit_term_trim (const char *text, int width)
+{
+ static char result[BUF_MEDIUM];
+ size_t remain;
+ char *actual;
+ size_t length;
+
+ length = strlen (text);
+ actual = result;
+ remain = sizeof (result);
+
+ if (width > 0)
+ {
+ size_t pos;
+
+ if (width >= (int) length)
+ {
+ for (pos = 0; pos < length && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+ }
+ else if (width <= 3)
+ {
+ memset (actual, '.', width);
+ actual += width;
+ }
+ else
+ {
+ memset (actual, '.', 3);
+ actual += 3;
+ remain -= 3;
+
+ for (pos = length - width + 3; pos < length && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+ }
+ }
+
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_term_width2 (const char *text, size_t length)
+{
+ return (length != (size_t) (-1)) ? MIN (strlen (text), length) : strlen (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_term_width1 (const char *text)
+{
+ return str_8bit_term_width2 (text, (size_t) (-1));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_term_char_width (const char *text)
+{
+ (void) text;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_8bit_term_substring (const char *text, int start, int width)
+{
+ static char result[BUF_MEDIUM];
+ size_t remain;
+ char *actual;
+ size_t length;
+
+ actual = result;
+ remain = sizeof (result);
+ length = strlen (text);
+
+ if (start < (int) length)
+ {
+ size_t pos;
+
+ for (pos = start; pos < length && width > 0 && remain > 1;
+ pos++, width--, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+ }
+
+ for (; width > 0 && remain > 1; actual++, remain--, width--)
+ actual[0] = ' ';
+
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_8bit_trunc (const char *text, int width)
+{
+ static char result[MC_MAXPATHLEN];
+ int remain;
+ char *actual;
+ size_t pos = 0;
+ size_t length;
+
+ actual = result;
+ remain = sizeof (result);
+ length = strlen (text);
+
+ if ((int) length > width)
+ {
+ for (; pos + 1 <= (gsize) width / 2 && remain > 1; actual++, pos++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+
+ if (remain <= 1)
+ goto finally;
+ actual[0] = '~';
+ actual++;
+ remain--;
+
+ pos += length - width + 1;
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+ }
+ else
+ {
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ actual[0] = char_isprint (text[pos]) ? text[pos] : '.';
+ }
+
+ finally:
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_offset_to_pos (const char *text, size_t length)
+{
+ (void) text;
+ return (int) length;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_column_to_pos (const char *text, size_t pos)
+{
+ (void) text;
+ return (int) pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_8bit_create_search_needle (const char *needle, gboolean case_sen)
+{
+ (void) case_sen;
+ return (char *) needle;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_8bit_release_search_needle (char *needle, gboolean case_sen)
+{
+ (void) case_sen;
+ (void) needle;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_8bit_strdown (const char *str)
+{
+ char *rets, *p;
+
+ if (str == NULL)
+ return NULL;
+
+ rets = g_strdup (str);
+
+ for (p = rets; *p != '\0'; p++)
+ *p = char_tolower (*p);
+
+ return rets;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_8bit_search_first (const char *text, const char *search, gboolean case_sen)
+{
+ char *fold_text;
+ char *fold_search;
+ const char *match;
+
+ fold_text = case_sen ? (char *) text : str_8bit_strdown (text);
+ fold_search = case_sen ? (char *) search : str_8bit_strdown (search);
+
+ match = g_strstr_len (fold_text, -1, fold_search);
+ if (match != NULL)
+ {
+ size_t offset;
+
+ offset = match - fold_text;
+ match = text + offset;
+ }
+
+ if (!case_sen)
+ {
+ g_free (fold_text);
+ g_free (fold_search);
+ }
+
+ return match;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_8bit_search_last (const char *text, const char *search, gboolean case_sen)
+{
+ char *fold_text;
+ char *fold_search;
+ const char *match;
+
+ fold_text = case_sen ? (char *) text : str_8bit_strdown (text);
+ fold_search = case_sen ? (char *) search : str_8bit_strdown (search);
+
+ match = g_strrstr_len (fold_text, -1, fold_search);
+ if (match != NULL)
+ {
+ size_t offset;
+
+ offset = match - fold_text;
+ match = text + offset;
+ }
+
+ if (!case_sen)
+ {
+ g_free (fold_text);
+ g_free (fold_search);
+ }
+
+ return match;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_compare (const char *t1, const char *t2)
+{
+ return strcmp (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_ncompare (const char *t1, const char *t2)
+{
+ return strncmp (t1, t2, MIN (strlen (t1), strlen (t2)));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_casecmp (const char *s1, const char *s2)
+{
+ /* code from GLib */
+
+#ifdef HAVE_STRCASECMP
+ g_return_val_if_fail (s1 != NULL, 0);
+ g_return_val_if_fail (s2 != NULL, 0);
+
+ return strcasecmp (s1, s2);
+#else
+ gint c1, c2;
+
+ g_return_val_if_fail (s1 != NULL, 0);
+ g_return_val_if_fail (s2 != NULL, 0);
+
+ while (*s1 != '\0' && *s2 != '\0')
+ {
+ /* According to A. Cox, some platforms have islower's that
+ * don't work right on non-uppercase
+ */
+ c1 = isupper ((guchar) * s1) ? tolower ((guchar) * s1) : *s1;
+ c2 = isupper ((guchar) * s2) ? tolower ((guchar) * s2) : *s2;
+ if (c1 != c2)
+ return (c1 - c2);
+ s1++;
+ s2++;
+ }
+
+ return (((gint) (guchar) * s1) - ((gint) (guchar) * s2));
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_ncasecmp (const char *s1, const char *s2)
+{
+ size_t n;
+
+ g_return_val_if_fail (s1 != NULL, 0);
+ g_return_val_if_fail (s2 != NULL, 0);
+
+ n = MIN (strlen (s1), strlen (s2));
+
+ /* code from GLib */
+
+#ifdef HAVE_STRNCASECMP
+ return strncasecmp (s1, s2, n);
+#else
+ gint c1, c2;
+
+ while (n != 0 && *s1 != '\0' && *s2 != '\0')
+ {
+ n -= 1;
+ /* According to A. Cox, some platforms have islower's that
+ * don't work right on non-uppercase
+ */
+ c1 = isupper ((guchar) * s1) ? tolower ((guchar) * s1) : *s1;
+ c2 = isupper ((guchar) * s2) ? tolower ((guchar) * s2) : *s2;
+ if (c1 != c2)
+ return (c1 - c2);
+ s1++;
+ s2++;
+ }
+
+ if (n == 0)
+ return 0;
+
+ return (((gint) (guchar) * s1) - ((gint) (guchar) * s2));
+
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_prefix (const char *text, const char *prefix)
+{
+ int result;
+
+ for (result = 0; text[result] != '\0' && prefix[result] != '\0'
+ && text[result] == prefix[result]; result++);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_caseprefix (const char *text, const char *prefix)
+{
+ int result;
+
+ for (result = 0; text[result] != '\0' && prefix[result] != '\0'
+ && char_toupper (text[result]) == char_toupper (prefix[result]); result++);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_8bit_fix_string (char *text)
+{
+ (void) text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_8bit_create_key (const char *text, gboolean case_sen)
+{
+ return case_sen ? (char *) text : str_8bit_strdown (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_8bit_key_collate (const char *t1, const char *t2, gboolean case_sen)
+{
+ return case_sen ? strcmp (t1, t2) : strcoll (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_8bit_release_key (char *key, gboolean case_sen)
+{
+ if (!case_sen)
+ g_free (key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct str_class
+str_8bit_init (void)
+{
+ struct str_class result;
+
+ result.conv_gerror_message = str_8bit_conv_gerror_message;
+ result.vfs_convert_to = str_8bit_vfs_convert_to;
+ result.insert_replace_char = str_8bit_insert_replace_char;
+ result.is_valid_string = str_8bit_is_valid_string;
+ result.is_valid_char = str_8bit_is_valid_char;
+ result.cnext_char = str_8bit_cnext_char;
+ result.cprev_char = str_8bit_cprev_char;
+ result.cnext_char_safe = str_8bit_cnext_char;
+ result.cprev_char_safe = str_8bit_cprev_char;
+ result.cnext_noncomb_char = str_8bit_cnext_noncomb_char;
+ result.cprev_noncomb_char = str_8bit_cprev_noncomb_char;
+ result.char_isspace = str_8bit_isspace;
+ result.char_ispunct = str_8bit_ispunct;
+ result.char_isalnum = str_8bit_isalnum;
+ result.char_isdigit = str_8bit_isdigit;
+ result.char_isprint = str_8bit_isprint;
+ result.char_iscombiningmark = str_8bit_iscombiningmark;
+ result.char_toupper = str_8bit_toupper;
+ result.char_tolower = str_8bit_tolower;
+ result.length = str_8bit_length;
+ result.length2 = str_8bit_length2;
+ result.length_noncomb = str_8bit_length;
+ result.fix_string = str_8bit_fix_string;
+ result.term_form = str_8bit_term_form;
+ result.fit_to_term = str_8bit_fit_to_term;
+ result.term_trim = str_8bit_term_trim;
+ result.term_width2 = str_8bit_term_width2;
+ result.term_width1 = str_8bit_term_width1;
+ result.term_char_width = str_8bit_term_char_width;
+ result.term_substring = str_8bit_term_substring;
+ result.trunc = str_8bit_trunc;
+ result.offset_to_pos = str_8bit_offset_to_pos;
+ result.column_to_pos = str_8bit_column_to_pos;
+ result.create_search_needle = str_8bit_create_search_needle;
+ result.release_search_needle = str_8bit_release_search_needle;
+ result.search_first = str_8bit_search_first;
+ result.search_last = str_8bit_search_last;
+ result.compare = str_8bit_compare;
+ result.ncompare = str_8bit_ncompare;
+ result.casecmp = str_8bit_casecmp;
+ result.ncasecmp = str_8bit_ncasecmp;
+ result.prefix = str_8bit_prefix;
+ result.caseprefix = str_8bit_caseprefix;
+ result.create_key = str_8bit_create_key;
+ result.create_key_for_filename = str_8bit_create_key;
+ result.key_collate = str_8bit_key_collate;
+ result.release_key = str_8bit_release_key;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/strutilascii.c b/lib/strutil/strutilascii.c
new file mode 100644
index 0000000..421bfdb
--- /dev/null
+++ b/lib/strutil/strutilascii.c
@@ -0,0 +1,785 @@
+/*
+ ASCII strings utilities
+
+ Copyright (C) 2007-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Rostislav Benes, 2007
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+
+/* using g_ascii function from glib
+ * on terminal are showed only ascii characters (lower than 0x80)
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char replch = '?';
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_ascii_insert_replace_char (GString * buffer)
+{
+ g_string_append_c (buffer, replch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_is_valid_string (const char *text)
+{
+ (void) text;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_is_valid_char (const char *ch, size_t size)
+{
+ (void) ch;
+ (void) size;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_ascii_cnext_char (const char **text)
+{
+ (*text)++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_ascii_cprev_char (const char **text)
+{
+ (*text)--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_cnext_noncomb_char (const char **text)
+{
+ if (*text[0] == '\0')
+ return 0;
+
+ (*text)++;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_cprev_noncomb_char (const char **text, const char *begin)
+{
+ if ((*text) == begin)
+ return 0;
+
+ (*text)--;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_isspace (const char *text)
+{
+ return g_ascii_isspace ((gchar) text[0]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_ispunct (const char *text)
+{
+ return g_ascii_ispunct ((gchar) text[0]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_isalnum (const char *text)
+{
+ return g_ascii_isalnum ((gchar) text[0]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_isdigit (const char *text)
+{
+ return g_ascii_isdigit ((gchar) text[0]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_isprint (const char *text)
+{
+ return g_ascii_isprint ((gchar) text[0]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_iscombiningmark (const char *text)
+{
+ (void) text;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_toupper (const char *text, char **out, size_t * remain)
+{
+ if (*remain <= 1)
+ return FALSE;
+
+ (*out)[0] = (char) g_ascii_toupper ((gchar) text[0]);
+ (*out)++;
+ (*remain)--;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_ascii_tolower (const char *text, char **out, size_t * remain)
+{
+ if (*remain <= 1)
+ return FALSE;
+
+ (*out)[0] = (char) g_ascii_tolower ((gchar) text[0]);
+ (*out)++;
+ (*remain)--;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_length (const char *text)
+{
+ return strlen (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_length2 (const char *text, int size)
+{
+ return (size >= 0) ? MIN (strlen (text), (gsize) size) : strlen (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gchar *
+str_ascii_conv_gerror_message (GError * mcerror, const char *def_msg)
+{
+ /* the same as str_utf8_conv_gerror_message() */
+ if (mcerror != NULL)
+ return g_strdup (mcerror->message);
+
+ return g_strdup (def_msg != NULL ? def_msg : "");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static estr_t
+str_ascii_vfs_convert_to (GIConv coder, const char *string, int size, GString * buffer)
+{
+ (void) coder;
+ g_string_append_len (buffer, string, size);
+ return ESTR_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_ascii_term_form (const char *text)
+{
+ static char result[BUF_MEDIUM];
+ char *actual;
+ size_t remain;
+ size_t length;
+ size_t pos = 0;
+
+ actual = result;
+ remain = sizeof (result);
+ length = strlen (text);
+
+ /* go throw all characters and check, if they are ascii and printable */
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_ascii_fit_to_term (const char *text, int width, align_crt_t just_mode)
+{
+ static char result[BUF_MEDIUM];
+ char *actual;
+ size_t remain;
+ int ident = 0;
+ size_t length;
+ size_t pos = 0;
+
+ length = strlen (text);
+ actual = result;
+ remain = sizeof (result);
+
+ if ((int) length <= width)
+ {
+ switch (HIDE_FIT (just_mode))
+ {
+ case J_CENTER_LEFT:
+ case J_CENTER:
+ ident = (width - length) / 2;
+ break;
+ case J_RIGHT:
+ ident = width - length;
+ break;
+ default:
+ break;
+ }
+
+ /* add space before text */
+ if ((int) remain <= ident)
+ goto finally;
+ memset (actual, ' ', ident);
+ actual += ident;
+ remain -= ident;
+
+ /* copy all characters */
+ for (; pos < (gsize) length && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+
+ /* add space after text */
+ if (width - length - ident > 0)
+ {
+ if (remain <= width - length - ident)
+ goto finally;
+ memset (actual, ' ', width - length - ident);
+ actual += width - length - ident;
+ }
+ }
+ else if (IS_FIT (just_mode))
+ {
+ /* copy prefix of text, that is not wider than width / 2 */
+ for (; pos + 1 <= (gsize) width / 2 && remain > 1; actual++, pos++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+
+ if (remain <= 1)
+ goto finally;
+ actual[0] = '~';
+ actual++;
+ remain--;
+
+ pos += length - width + 1;
+
+ /* copy suffix of text */
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+ }
+ else
+ {
+ switch (HIDE_FIT (just_mode))
+ {
+ case J_CENTER:
+ ident = (length - width) / 2;
+ break;
+ case J_RIGHT:
+ ident = length - width;
+ break;
+ default:
+ break;
+ }
+
+ /* copy substring text, substring start from ident and take width
+ * characters from text */
+ pos += ident;
+ for (; pos < (gsize) (ident + width) && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+
+ }
+
+ finally:
+ if (actual >= result + sizeof (result))
+ actual = result + sizeof (result) - 1;
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_ascii_term_trim (const char *text, int width)
+{
+ static char result[BUF_MEDIUM];
+ size_t remain;
+ char *actual;
+ size_t length;
+
+ length = strlen (text);
+ actual = result;
+ remain = sizeof (result);
+
+ if (width > 0)
+ {
+ size_t pos;
+
+ if (width >= (int) length)
+ {
+ /* copy all characters */
+ for (pos = 0; pos < length && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+ }
+ else if (width <= 3)
+ {
+ memset (actual, '.', width);
+ actual += width;
+ }
+ else
+ {
+ memset (actual, '.', 3);
+ actual += 3;
+ remain -= 3;
+
+ /* copy suffix of text */
+ for (pos = length - width + 3; pos < length && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+ }
+ }
+
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_term_width2 (const char *text, size_t length)
+{
+ return (length != (size_t) (-1)) ? MIN (strlen (text), length) : strlen (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_term_width1 (const char *text)
+{
+ return str_ascii_term_width2 (text, (size_t) (-1));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_term_char_width (const char *text)
+{
+ (void) text;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_ascii_term_substring (const char *text, int start, int width)
+{
+ static char result[BUF_MEDIUM];
+ size_t remain;
+ char *actual;
+ size_t length;
+
+ actual = result;
+ remain = sizeof (result);
+ length = strlen (text);
+
+ if (start < (int) length)
+ {
+ size_t pos;
+
+ /* copy at most width characters from text from start */
+ for (pos = start; pos < length && width > 0 && remain > 1;
+ pos++, width--, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+ }
+
+ /* if text is shorter then width, add space to the end */
+ for (; width > 0 && remain > 1; actual++, remain--, width--)
+ actual[0] = ' ';
+
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_ascii_trunc (const char *text, int width)
+{
+ static char result[MC_MAXPATHLEN];
+ int remain;
+ char *actual;
+ size_t pos = 0;
+ size_t length;
+
+ actual = result;
+ remain = sizeof (result);
+ length = strlen (text);
+
+ if ((int) length > width)
+ {
+ /* copy prefix of text */
+ for (; pos + 1 <= (gsize) width / 2 && remain > 1; actual++, pos++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+
+ if (remain <= 1)
+ goto finally;
+ actual[0] = '~';
+ actual++;
+ remain--;
+
+ pos += length - width + 1;
+
+ /* copy suffix of text */
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+ }
+ else
+ {
+ /* copy all characters */
+ for (; pos < length && remain > 1; pos++, actual++, remain--)
+ {
+ actual[0] = isascii ((unsigned char) text[pos]) ? text[pos] : '?';
+ actual[0] = g_ascii_isprint ((gchar) actual[0]) ? actual[0] : '.';
+ }
+ }
+
+ finally:
+ actual[0] = '\0';
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_offset_to_pos (const char *text, size_t length)
+{
+ (void) text;
+ return (int) length;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_column_to_pos (const char *text, size_t pos)
+{
+ (void) text;
+ return (int) pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_ascii_create_search_needle (const char *needle, gboolean case_sen)
+{
+ (void) case_sen;
+ return (char *) needle;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_ascii_release_search_needle (char *needle, gboolean case_sen)
+{
+ (void) case_sen;
+ (void) needle;
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_ascii_search_first (const char *text, const char *search, gboolean case_sen)
+{
+ char *fold_text;
+ char *fold_search;
+ const char *match;
+
+ fold_text = case_sen ? (char *) text : g_ascii_strdown (text, -1);
+ fold_search = case_sen ? (char *) search : g_ascii_strdown (search, -1);
+
+ match = g_strstr_len (fold_text, -1, fold_search);
+ if (match != NULL)
+ {
+ size_t offset;
+
+ offset = match - fold_text;
+ match = text + offset;
+ }
+
+ if (!case_sen)
+ {
+ g_free (fold_text);
+ g_free (fold_search);
+ }
+
+ return match;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_ascii_search_last (const char *text, const char *search, gboolean case_sen)
+{
+ char *fold_text;
+ char *fold_search;
+ const char *match;
+
+ fold_text = case_sen ? (char *) text : g_ascii_strdown (text, -1);
+ fold_search = case_sen ? (char *) search : g_ascii_strdown (search, -1);
+
+ match = g_strrstr_len (fold_text, -1, fold_search);
+ if (match != NULL)
+ {
+ size_t offset;
+
+ offset = match - fold_text;
+ match = text + offset;
+ }
+
+ if (!case_sen)
+ {
+ g_free (fold_text);
+ g_free (fold_search);
+ }
+
+ return match;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_compare (const char *t1, const char *t2)
+{
+ return strcmp (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_ncompare (const char *t1, const char *t2)
+{
+ return strncmp (t1, t2, MIN (strlen (t1), strlen (t2)));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_casecmp (const char *t1, const char *t2)
+{
+ return g_ascii_strcasecmp (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_ncasecmp (const char *t1, const char *t2)
+{
+ return g_ascii_strncasecmp (t1, t2, MIN (strlen (t1), strlen (t2)));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_ascii_fix_string (char *text)
+{
+ for (; text[0] != '\0'; text++)
+ text[0] = ((unsigned char) text[0] < 128) ? text[0] : '?';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_ascii_create_key (const char *text, gboolean case_sen)
+{
+ (void) case_sen;
+ return (char *) text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_key_collate (const char *t1, const char *t2, gboolean case_sen)
+{
+ return case_sen ? strcmp (t1, t2) : g_ascii_strcasecmp (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_ascii_release_key (char *key, gboolean case_sen)
+{
+ (void) key;
+ (void) case_sen;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_prefix (const char *text, const char *prefix)
+{
+ int result;
+
+ for (result = 0; text[result] != '\0' && prefix[result] != '\0'
+ && text[result] == prefix[result]; result++);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_ascii_caseprefix (const char *text, const char *prefix)
+{
+ int result;
+
+ for (result = 0; text[result] != '\0' && prefix[result] != '\0'
+ && g_ascii_toupper (text[result]) == g_ascii_toupper (prefix[result]); result++);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct str_class
+str_ascii_init (void)
+{
+ struct str_class result;
+
+ result.conv_gerror_message = str_ascii_conv_gerror_message;
+ result.vfs_convert_to = str_ascii_vfs_convert_to;
+ result.insert_replace_char = str_ascii_insert_replace_char;
+ result.is_valid_string = str_ascii_is_valid_string;
+ result.is_valid_char = str_ascii_is_valid_char;
+ result.cnext_char = str_ascii_cnext_char;
+ result.cprev_char = str_ascii_cprev_char;
+ result.cnext_char_safe = str_ascii_cnext_char;
+ result.cprev_char_safe = str_ascii_cprev_char;
+ result.cnext_noncomb_char = str_ascii_cnext_noncomb_char;
+ result.cprev_noncomb_char = str_ascii_cprev_noncomb_char;
+ result.char_isspace = str_ascii_isspace;
+ result.char_ispunct = str_ascii_ispunct;
+ result.char_isalnum = str_ascii_isalnum;
+ result.char_isdigit = str_ascii_isdigit;
+ result.char_isprint = str_ascii_isprint;
+ result.char_iscombiningmark = str_ascii_iscombiningmark;
+ result.char_toupper = str_ascii_toupper;
+ result.char_tolower = str_ascii_tolower;
+ result.length = str_ascii_length;
+ result.length2 = str_ascii_length2;
+ result.length_noncomb = str_ascii_length;
+ result.fix_string = str_ascii_fix_string;
+ result.term_form = str_ascii_term_form;
+ result.fit_to_term = str_ascii_fit_to_term;
+ result.term_trim = str_ascii_term_trim;
+ result.term_width2 = str_ascii_term_width2;
+ result.term_width1 = str_ascii_term_width1;
+ result.term_char_width = str_ascii_term_char_width;
+ result.term_substring = str_ascii_term_substring;
+ result.trunc = str_ascii_trunc;
+ result.offset_to_pos = str_ascii_offset_to_pos;
+ result.column_to_pos = str_ascii_column_to_pos;
+ result.create_search_needle = str_ascii_create_search_needle;
+ result.release_search_needle = str_ascii_release_search_needle;
+ result.search_first = str_ascii_search_first;
+ result.search_last = str_ascii_search_last;
+ result.compare = str_ascii_compare;
+ result.ncompare = str_ascii_ncompare;
+ result.casecmp = str_ascii_casecmp;
+ result.ncasecmp = str_ascii_ncasecmp;
+ result.prefix = str_ascii_prefix;
+ result.caseprefix = str_ascii_caseprefix;
+ result.create_key = str_ascii_create_key;
+ result.create_key_for_filename = str_ascii_create_key;
+ result.key_collate = str_ascii_key_collate;
+ result.release_key = str_ascii_release_key;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/strutilutf8.c b/lib/strutil/strutilutf8.c
new file mode 100644
index 0000000..e143abe
--- /dev/null
+++ b/lib/strutil/strutilutf8.c
@@ -0,0 +1,1521 @@
+/*
+ UTF-8 strings utilities
+
+ Copyright (C) 2007-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Rostislav Benes, 2007
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <langinfo.h>
+#include <limits.h> /* MB_LEN_MAX */
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+
+/* using function for utf-8 from glib */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+struct utf8_tool
+{
+ char *actual;
+ size_t remain;
+ const char *checked;
+ int ident;
+ gboolean compose;
+};
+
+struct term_form
+{
+ char text[BUF_MEDIUM * MB_LEN_MAX];
+ size_t width;
+ gboolean compose;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char replch[] = "\xEF\xBF\xBD";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_unichar_iscombiningmark (gunichar uni)
+{
+ GUnicodeType type;
+
+ type = g_unichar_type (uni);
+ return (type == G_UNICODE_SPACING_MARK)
+ || (type == G_UNICODE_ENCLOSING_MARK) || (type == G_UNICODE_NON_SPACING_MARK);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_insert_replace_char (GString * buffer)
+{
+ g_string_append (buffer, replch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_is_valid_string (const char *text)
+{
+ return g_utf8_validate (text, -1, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_is_valid_char (const char *ch, size_t size)
+{
+ switch (g_utf8_get_char_validated (ch, size))
+ {
+ case (gunichar) (-2):
+ return (-2);
+ case (gunichar) (-1):
+ return (-1);
+ default:
+ return 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_cnext_char (const char **text)
+{
+ (*text) = g_utf8_next_char (*text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_cprev_char (const char **text)
+{
+ (*text) = g_utf8_prev_char (*text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_cnext_char_safe (const char **text)
+{
+ if (str_utf8_is_valid_char (*text, -1) == 1)
+ (*text) = g_utf8_next_char (*text);
+ else
+ (*text)++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_cprev_char_safe (const char **text)
+{
+ const char *result, *t;
+
+ result = g_utf8_prev_char (*text);
+ t = result;
+ str_utf8_cnext_char_safe (&t);
+ if (t == *text)
+ (*text) = result;
+ else
+ (*text)--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_fix_string (char *text)
+{
+ while (text[0] != '\0')
+ {
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ if ((uni != (gunichar) (-1)) && (uni != (gunichar) (-2)))
+ text = g_utf8_next_char (text);
+ else
+ {
+ text[0] = '?';
+ text++;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_isspace (const char *text)
+{
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ return g_unichar_isspace (uni);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_ispunct (const char *text)
+{
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ return g_unichar_ispunct (uni);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_isalnum (const char *text)
+{
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ return g_unichar_isalnum (uni);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_isdigit (const char *text)
+{
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ return g_unichar_isdigit (uni);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_isprint (const char *ch)
+{
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (ch, -1);
+ return g_unichar_isprint (uni);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_iscombiningmark (const char *ch)
+{
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (ch, -1);
+ return str_unichar_iscombiningmark (uni);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_cnext_noncomb_char (const char **text)
+{
+ int count = 0;
+
+ while ((*text)[0] != '\0')
+ {
+ str_utf8_cnext_char_safe (text);
+ count++;
+ if (!str_utf8_iscombiningmark (*text))
+ break;
+ }
+
+ return count;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_cprev_noncomb_char (const char **text, const char *begin)
+{
+ int count = 0;
+
+ while ((*text) != begin)
+ {
+ str_utf8_cprev_char_safe (text);
+ count++;
+ if (!str_utf8_iscombiningmark (*text))
+ break;
+ }
+
+ return count;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_toupper (const char *text, char **out, size_t * remain)
+{
+ gunichar uni;
+ size_t left;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ if (uni == (gunichar) (-1) || uni == (gunichar) (-2))
+ return FALSE;
+
+ uni = g_unichar_toupper (uni);
+ left = g_unichar_to_utf8 (uni, NULL);
+ if (left >= *remain)
+ return FALSE;
+
+ left = g_unichar_to_utf8 (uni, *out);
+ (*out) += left;
+ (*remain) -= left;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+str_utf8_tolower (const char *text, char **out, size_t * remain)
+{
+ gunichar uni;
+ size_t left;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ if (uni == (gunichar) (-1) || uni == (gunichar) (-2))
+ return FALSE;
+
+ uni = g_unichar_tolower (uni);
+ left = g_unichar_to_utf8 (uni, NULL);
+ if (left >= *remain)
+ return FALSE;
+
+ left = g_unichar_to_utf8 (uni, *out);
+ (*out) += left;
+ (*remain) -= left;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_length (const char *text)
+{
+ int result = 0;
+ const char *start;
+ const char *end;
+
+ start = text;
+ while (!g_utf8_validate (start, -1, &end) && start[0] != '\0')
+ {
+ if (start != end)
+ result += g_utf8_strlen (start, end - start);
+
+ result++;
+ start = end + 1;
+ }
+
+ if (start == text)
+ result = g_utf8_strlen (text, -1);
+ else if (start[0] != '\0' && start != end)
+ result += g_utf8_strlen (start, end - start);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_length2 (const char *text, int size)
+{
+ int result = 0;
+ const char *start;
+ const char *end;
+
+ start = text;
+ while (!g_utf8_validate (start, -1, &end) && start[0] != '\0' && size > 0)
+ {
+ if (start != end)
+ {
+ result += g_utf8_strlen (start, MIN (end - start, size));
+ size -= end - start;
+ }
+ result += (size > 0);
+ size--;
+ start = end + 1;
+ }
+
+ if (start == text)
+ result = g_utf8_strlen (text, size);
+ else if (start[0] != '\0' && start != end && size > 0)
+ result += g_utf8_strlen (start, MIN (end - start, size));
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_length_noncomb (const char *text)
+{
+ int result = 0;
+ const char *t = text;
+
+ while (t[0] != '\0')
+ {
+ str_utf8_cnext_noncomb_char (&t);
+ result++;
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static void
+str_utf8_questmark_sustb (char **string, size_t * left, GString * buffer)
+{
+ char *next;
+
+ next = g_utf8_next_char (*string);
+ (*left) -= next - (*string);
+ (*string) = next;
+ g_string_append_c (buffer, '?');
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gchar *
+str_utf8_conv_gerror_message (GError * mcerror, const char *def_msg)
+{
+ if (mcerror != NULL)
+ return g_strdup (mcerror->message);
+
+ return g_strdup (def_msg != NULL ? def_msg : "");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static estr_t
+str_utf8_vfs_convert_to (GIConv coder, const char *string, int size, GString * buffer)
+{
+ estr_t result = ESTR_SUCCESS;
+
+ if (coder == str_cnv_not_convert)
+ g_string_append_len (buffer, string, size);
+ else
+ result = str_nconvert (coder, string, size, buffer);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* utility function, that makes string valid in utf8 and all characters printable
+ * return width of string too */
+
+static const struct term_form *
+str_utf8_make_make_term_form (const char *text, size_t length)
+{
+ static struct term_form result;
+ gunichar uni;
+ size_t left;
+ char *actual;
+
+ result.text[0] = '\0';
+ result.width = 0;
+ result.compose = FALSE;
+ actual = result.text;
+
+ /* check if text start with combining character,
+ * add space at begin in this case */
+ if (length != 0 && text[0] != '\0')
+ {
+ uni = g_utf8_get_char_validated (text, -1);
+ if ((uni != (gunichar) (-1)) && (uni != (gunichar) (-2))
+ && str_unichar_iscombiningmark (uni))
+ {
+ actual[0] = ' ';
+ actual++;
+ result.width++;
+ result.compose = TRUE;
+ }
+ }
+
+ while (length != 0 && text[0] != '\0')
+ {
+ uni = g_utf8_get_char_validated (text, -1);
+ if ((uni != (gunichar) (-1)) && (uni != (gunichar) (-2)))
+ {
+ if (g_unichar_isprint (uni))
+ {
+ left = g_unichar_to_utf8 (uni, actual);
+ actual += left;
+ if (str_unichar_iscombiningmark (uni))
+ result.compose = TRUE;
+ else
+ {
+ result.width++;
+ if (g_unichar_iswide (uni))
+ result.width++;
+ }
+ }
+ else
+ {
+ actual[0] = '.';
+ actual++;
+ result.width++;
+ }
+ text = g_utf8_next_char (text);
+ }
+ else
+ {
+ text++;
+ /*actual[0] = '?'; */
+ memcpy (actual, replch, strlen (replch));
+ actual += strlen (replch);
+ result.width++;
+ }
+
+ if (length != (size_t) (-1))
+ length--;
+ }
+ actual[0] = '\0';
+
+ return &result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_utf8_term_form (const char *text)
+{
+ static char result[BUF_MEDIUM * MB_LEN_MAX];
+ const struct term_form *pre_form;
+
+ pre_form = str_utf8_make_make_term_form (text, (size_t) (-1));
+ if (pre_form->compose)
+ {
+ char *composed;
+
+ composed = g_utf8_normalize (pre_form->text, -1, G_NORMALIZE_DEFAULT_COMPOSE);
+ g_strlcpy (result, composed, sizeof (result));
+ g_free (composed);
+ }
+ else
+ g_strlcpy (result, pre_form->text, sizeof (result));
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* utility function, that copies all characters from checked to actual */
+
+static gboolean
+utf8_tool_copy_chars_to_end (struct utf8_tool *tool)
+{
+ tool->compose = FALSE;
+
+ while (tool->checked[0] != '\0')
+ {
+ gunichar uni;
+ size_t left;
+
+ uni = g_utf8_get_char (tool->checked);
+ tool->compose = tool->compose || str_unichar_iscombiningmark (uni);
+ left = g_unichar_to_utf8 (uni, NULL);
+ if (tool->remain <= left)
+ return FALSE;
+ left = g_unichar_to_utf8 (uni, tool->actual);
+ tool->actual += left;
+ tool->remain -= left;
+ tool->checked = g_utf8_next_char (tool->checked);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* utility function, that copies characters from checked to actual until ident is
+ * smaller than to_ident */
+
+static gboolean
+utf8_tool_copy_chars_to (struct utf8_tool *tool, int to_ident)
+{
+ tool->compose = FALSE;
+
+ while (tool->checked[0] != '\0')
+ {
+ gunichar uni;
+ size_t left;
+ int w = 0;
+
+ uni = g_utf8_get_char (tool->checked);
+ if (str_unichar_iscombiningmark (uni))
+ tool->compose = TRUE;
+ else
+ {
+ w = 1;
+ if (g_unichar_iswide (uni))
+ w++;
+ if (tool->ident + w > to_ident)
+ return TRUE;
+ }
+
+ left = g_unichar_to_utf8 (uni, NULL);
+ if (tool->remain <= left)
+ return FALSE;
+ left = g_unichar_to_utf8 (uni, tool->actual);
+ tool->actual += left;
+ tool->remain -= left;
+ tool->checked = g_utf8_next_char (tool->checked);
+ tool->ident += w;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* utility function, adds count spaces to actual */
+
+static int
+utf8_tool_insert_space (struct utf8_tool *tool, int count)
+{
+ if (count <= 0)
+ return 1;
+ if (tool->remain <= (gsize) count)
+ return 0;
+
+ memset (tool->actual, ' ', count);
+ tool->actual += count;
+ tool->remain -= count;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* utility function, adds one characters to actual */
+
+static int
+utf8_tool_insert_char (struct utf8_tool *tool, char ch)
+{
+ if (tool->remain <= 1)
+ return 0;
+
+ tool->actual[0] = ch;
+ tool->actual++;
+ tool->remain--;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* utility function, thah skips characters from checked until ident is greater or
+ * equal to to_ident */
+
+static gboolean
+utf8_tool_skip_chars_to (struct utf8_tool *tool, int to_ident)
+{
+ gunichar uni;
+
+ while (to_ident > tool->ident && tool->checked[0] != '\0')
+ {
+ uni = g_utf8_get_char (tool->checked);
+ if (!str_unichar_iscombiningmark (uni))
+ {
+ tool->ident++;
+ if (g_unichar_iswide (uni))
+ tool->ident++;
+ }
+ tool->checked = g_utf8_next_char (tool->checked);
+ }
+
+ uni = g_utf8_get_char (tool->checked);
+ while (str_unichar_iscombiningmark (uni))
+ {
+ tool->checked = g_utf8_next_char (tool->checked);
+ uni = g_utf8_get_char (tool->checked);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+utf8_tool_compose (char *buffer, size_t size)
+{
+ char *composed;
+
+ composed = g_utf8_normalize (buffer, -1, G_NORMALIZE_DEFAULT_COMPOSE);
+ g_strlcpy (buffer, composed, size);
+ g_free (composed);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_utf8_fit_to_term (const char *text, int width, align_crt_t just_mode)
+{
+ static char result[BUF_MEDIUM * MB_LEN_MAX];
+ const struct term_form *pre_form;
+ struct utf8_tool tool;
+
+ pre_form = str_utf8_make_make_term_form (text, (size_t) (-1));
+ tool.checked = pre_form->text;
+ tool.actual = result;
+ tool.remain = sizeof (result);
+ tool.compose = FALSE;
+
+ if (pre_form->width <= (gsize) width)
+ {
+ switch (HIDE_FIT (just_mode))
+ {
+ case J_CENTER_LEFT:
+ case J_CENTER:
+ tool.ident = (width - pre_form->width) / 2;
+ break;
+ case J_RIGHT:
+ tool.ident = width - pre_form->width;
+ break;
+ default:
+ tool.ident = 0;
+ break;
+ }
+
+ utf8_tool_insert_space (&tool, tool.ident);
+ utf8_tool_copy_chars_to_end (&tool);
+ utf8_tool_insert_space (&tool, width - pre_form->width - tool.ident);
+ }
+ else if (IS_FIT (just_mode))
+ {
+ tool.ident = 0;
+ utf8_tool_copy_chars_to (&tool, width / 2);
+ utf8_tool_insert_char (&tool, '~');
+
+ tool.ident = 0;
+ utf8_tool_skip_chars_to (&tool, pre_form->width - width + 1);
+ utf8_tool_copy_chars_to_end (&tool);
+ utf8_tool_insert_space (&tool, width - (pre_form->width - tool.ident + 1));
+ }
+ else
+ {
+ switch (HIDE_FIT (just_mode))
+ {
+ case J_CENTER:
+ tool.ident = (width - pre_form->width) / 2;
+ break;
+ case J_RIGHT:
+ tool.ident = width - pre_form->width;
+ break;
+ default:
+ tool.ident = 0;
+ break;
+ }
+
+ utf8_tool_skip_chars_to (&tool, 0);
+ utf8_tool_insert_space (&tool, tool.ident);
+ utf8_tool_copy_chars_to (&tool, width);
+ utf8_tool_insert_space (&tool, width - tool.ident);
+ }
+
+ tool.actual[0] = '\0';
+ if (tool.compose)
+ utf8_tool_compose (result, sizeof (result));
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_utf8_term_trim (const char *text, int width)
+{
+ static char result[BUF_MEDIUM * MB_LEN_MAX];
+ const struct term_form *pre_form;
+ struct utf8_tool tool;
+
+ if (width < 1)
+ {
+ result[0] = '\0';
+ return result;
+ }
+
+ pre_form = str_utf8_make_make_term_form (text, (size_t) (-1));
+
+ tool.checked = pre_form->text;
+ tool.actual = result;
+ tool.remain = sizeof (result);
+ tool.compose = FALSE;
+
+ if ((gsize) width >= pre_form->width)
+ utf8_tool_copy_chars_to_end (&tool);
+ else if (width <= 3)
+ {
+ memset (tool.actual, '.', width);
+ tool.actual += width;
+ tool.remain -= width;
+ }
+ else
+ {
+ memset (tool.actual, '.', 3);
+ tool.actual += 3;
+ tool.remain -= 3;
+
+ tool.ident = 0;
+ utf8_tool_skip_chars_to (&tool, pre_form->width - width + 3);
+ utf8_tool_copy_chars_to_end (&tool);
+ }
+
+ tool.actual[0] = '\0';
+ if (tool.compose)
+ utf8_tool_compose (result, sizeof (result));
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_term_width2 (const char *text, size_t length)
+{
+ const struct term_form *result;
+
+ result = str_utf8_make_make_term_form (text, length);
+ return result->width;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_term_width1 (const char *text)
+{
+ return str_utf8_term_width2 (text, (size_t) (-1));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_term_char_width (const char *text)
+{
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (text, -1);
+ return (str_unichar_iscombiningmark (uni)) ? 0 : ((g_unichar_iswide (uni)) ? 2 : 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_utf8_term_substring (const char *text, int start, int width)
+{
+ static char result[BUF_MEDIUM * MB_LEN_MAX];
+ const struct term_form *pre_form;
+ struct utf8_tool tool;
+
+ pre_form = str_utf8_make_make_term_form (text, (size_t) (-1));
+
+ tool.checked = pre_form->text;
+ tool.actual = result;
+ tool.remain = sizeof (result);
+ tool.compose = FALSE;
+
+ tool.ident = -start;
+ utf8_tool_skip_chars_to (&tool, 0);
+ if (tool.ident < 0)
+ tool.ident = 0;
+ utf8_tool_insert_space (&tool, tool.ident);
+
+ utf8_tool_copy_chars_to (&tool, width);
+ utf8_tool_insert_space (&tool, width - tool.ident);
+
+ tool.actual[0] = '\0';
+ if (tool.compose)
+ utf8_tool_compose (result, sizeof (result));
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_utf8_trunc (const char *text, int width)
+{
+ static char result[MC_MAXPATHLEN * MB_LEN_MAX * 2];
+ const struct term_form *pre_form;
+ struct utf8_tool tool;
+
+ pre_form = str_utf8_make_make_term_form (text, (size_t) (-1));
+
+ tool.checked = pre_form->text;
+ tool.actual = result;
+ tool.remain = sizeof (result);
+ tool.compose = FALSE;
+
+ if (pre_form->width <= (gsize) width)
+ utf8_tool_copy_chars_to_end (&tool);
+ else
+ {
+ tool.ident = 0;
+ utf8_tool_copy_chars_to (&tool, width / 2);
+ utf8_tool_insert_char (&tool, '~');
+
+ tool.ident = 0;
+ utf8_tool_skip_chars_to (&tool, pre_form->width - width + 1);
+ utf8_tool_copy_chars_to_end (&tool);
+ }
+
+ tool.actual[0] = '\0';
+ if (tool.compose)
+ utf8_tool_compose (result, sizeof (result));
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_offset_to_pos (const char *text, size_t length)
+{
+ if (str_utf8_is_valid_string (text))
+ return g_utf8_offset_to_pointer (text, length) - text;
+ else
+ {
+ int result;
+ GString *buffer;
+
+ buffer = g_string_new (text);
+ str_utf8_fix_string (buffer->str);
+ result = g_utf8_offset_to_pointer (buffer->str, length) - buffer->str;
+ g_string_free (buffer, TRUE);
+ return result;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_column_to_pos (const char *text, size_t pos)
+{
+ int result = 0;
+ int width = 0;
+
+ while (text[0] != '\0')
+ {
+ gunichar uni;
+
+ uni = g_utf8_get_char_validated (text, MB_LEN_MAX);
+ if ((uni != (gunichar) (-1)) && (uni != (gunichar) (-2)))
+ {
+ if (g_unichar_isprint (uni))
+ {
+ if (!str_unichar_iscombiningmark (uni))
+ {
+ width++;
+ if (g_unichar_iswide (uni))
+ width++;
+ }
+ }
+ else
+ {
+ width++;
+ }
+ text = g_utf8_next_char (text);
+ }
+ else
+ {
+ text++;
+ width++;
+ }
+
+ if ((gsize) width > pos)
+ return result;
+
+ result++;
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_utf8_create_search_needle (const char *needle, gboolean case_sen)
+{
+ char *fold, *result;
+
+ if (needle == NULL)
+ return NULL;
+
+ if (case_sen)
+ return g_utf8_normalize (needle, -1, G_NORMALIZE_ALL);
+
+ fold = g_utf8_casefold (needle, -1);
+ result = g_utf8_normalize (fold, -1, G_NORMALIZE_ALL);
+ g_free (fold);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_release_search_needle (char *needle, gboolean case_sen)
+{
+ (void) case_sen;
+ g_free (needle);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_utf8_search_first (const char *text, const char *search, gboolean case_sen)
+{
+ char *fold_text;
+ char *deco_text;
+ const char *match;
+ const char *result = NULL;
+ const char *m;
+
+ fold_text = case_sen ? (char *) text : g_utf8_casefold (text, -1);
+ deco_text = g_utf8_normalize (fold_text, -1, G_NORMALIZE_ALL);
+
+ match = deco_text;
+ do
+ {
+ match = g_strstr_len (match, -1, search);
+ if (match != NULL)
+ {
+ if ((!str_utf8_iscombiningmark (match) || (match == deco_text)) &&
+ !str_utf8_iscombiningmark (match + strlen (search)))
+ {
+ result = text;
+ m = deco_text;
+ while (m < match)
+ {
+ str_utf8_cnext_noncomb_char (&m);
+ str_utf8_cnext_noncomb_char (&result);
+ }
+ }
+ else
+ str_utf8_cnext_char (&match);
+ }
+ }
+ while (match != NULL && result == NULL);
+
+ g_free (deco_text);
+ if (!case_sen)
+ g_free (fold_text);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+str_utf8_search_last (const char *text, const char *search, gboolean case_sen)
+{
+ char *fold_text;
+ char *deco_text;
+ char *match;
+ const char *result = NULL;
+ const char *m;
+
+ fold_text = case_sen ? (char *) text : g_utf8_casefold (text, -1);
+ deco_text = g_utf8_normalize (fold_text, -1, G_NORMALIZE_ALL);
+
+ do
+ {
+ match = g_strrstr_len (deco_text, -1, search);
+ if (match != NULL)
+ {
+ if ((!str_utf8_iscombiningmark (match) || (match == deco_text)) &&
+ !str_utf8_iscombiningmark (match + strlen (search)))
+ {
+ result = text;
+ m = deco_text;
+ while (m < match)
+ {
+ str_utf8_cnext_noncomb_char (&m);
+ str_utf8_cnext_noncomb_char (&result);
+ }
+ }
+ else
+ match[0] = '\0';
+ }
+ }
+ while (match != NULL && result == NULL);
+
+ g_free (deco_text);
+ if (!case_sen)
+ g_free (fold_text);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_utf8_normalize (const char *text)
+{
+ GString *fixed;
+ char *tmp;
+ char *result;
+ const char *start;
+ const char *end;
+
+ /* g_utf8_normalize() is a heavyweight function, that converts UTF-8 into UCS-4,
+ * does the normalization and then converts UCS-4 back into UTF-8.
+ * Since file names are composed of ASCII characters in most cases, we can speed up
+ * utf8 normalization by checking if the heavyweight Unicode normalization is actually
+ * needed. Normalization of ASCII string is no-op.
+ */
+
+ /* find out whether text is ASCII only */
+ for (end = text; *end != '\0'; end++)
+ if ((*end & 0x80) != 0)
+ {
+ /* found 2nd byte of utf8-encoded symbol */
+ break;
+ }
+
+ /* if text is ASCII-only, return copy, normalize otherwise */
+ if (*end == '\0')
+ return g_strndup (text, end - text);
+
+ fixed = g_string_sized_new (4);
+
+ start = text;
+ while (!g_utf8_validate (start, -1, &end) && start[0] != '\0')
+ {
+ if (start != end)
+ {
+ tmp = g_utf8_normalize (start, end - start, G_NORMALIZE_ALL);
+ g_string_append (fixed, tmp);
+ g_free (tmp);
+ }
+ g_string_append_c (fixed, end[0]);
+ start = end + 1;
+ }
+
+ if (start == text)
+ {
+ result = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
+ g_string_free (fixed, TRUE);
+ }
+ else
+ {
+ if (start[0] != '\0' && start != end)
+ {
+ tmp = g_utf8_normalize (start, end - start, G_NORMALIZE_ALL);
+ g_string_append (fixed, tmp);
+ g_free (tmp);
+ }
+ result = g_string_free (fixed, FALSE);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_utf8_casefold_normalize (const char *text)
+{
+ GString *fixed;
+ char *tmp, *fold;
+ char *result;
+ const char *start;
+ const char *end;
+
+ fixed = g_string_sized_new (4);
+
+ start = text;
+ while (!g_utf8_validate (start, -1, &end) && start[0] != '\0')
+ {
+ if (start != end)
+ {
+ fold = g_utf8_casefold (start, end - start);
+ tmp = g_utf8_normalize (fold, -1, G_NORMALIZE_ALL);
+ g_string_append (fixed, tmp);
+ g_free (tmp);
+ g_free (fold);
+ }
+ g_string_append_c (fixed, end[0]);
+ start = end + 1;
+ }
+
+ if (start == text)
+ {
+ fold = g_utf8_casefold (text, -1);
+ result = g_utf8_normalize (fold, -1, G_NORMALIZE_ALL);
+ g_free (fold);
+ g_string_free (fixed, TRUE);
+ }
+ else
+ {
+ if (start[0] != '\0' && start != end)
+ {
+ fold = g_utf8_casefold (start, end - start);
+ tmp = g_utf8_normalize (fold, -1, G_NORMALIZE_ALL);
+ g_string_append (fixed, tmp);
+ g_free (tmp);
+ g_free (fold);
+ }
+ result = g_string_free (fixed, FALSE);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_compare (const char *t1, const char *t2)
+{
+ char *n1, *n2;
+ int result;
+
+ n1 = str_utf8_normalize (t1);
+ n2 = str_utf8_normalize (t2);
+
+ result = strcmp (n1, n2);
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_ncompare (const char *t1, const char *t2)
+{
+ char *n1, *n2;
+ size_t l1, l2;
+ int result;
+
+ n1 = str_utf8_normalize (t1);
+ n2 = str_utf8_normalize (t2);
+
+ l1 = strlen (n1);
+ l2 = strlen (n2);
+ result = strncmp (n1, n2, MIN (l1, l2));
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_casecmp (const char *t1, const char *t2)
+{
+ char *n1, *n2;
+ int result;
+
+ n1 = str_utf8_casefold_normalize (t1);
+ n2 = str_utf8_casefold_normalize (t2);
+
+ result = strcmp (n1, n2);
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_ncasecmp (const char *t1, const char *t2)
+{
+ char *n1, *n2;
+ size_t l1, l2;
+ int result;
+
+ n1 = str_utf8_casefold_normalize (t1);
+ n2 = str_utf8_casefold_normalize (t2);
+
+ l1 = strlen (n1);
+ l2 = strlen (n2);
+ result = strncmp (n1, n2, MIN (l1, l2));
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_prefix (const char *text, const char *prefix)
+{
+ char *t, *p;
+ const char *nt, *np;
+ const char *nnt, *nnp;
+ int result;
+
+ t = str_utf8_normalize (text);
+ p = str_utf8_normalize (prefix);
+ nt = t;
+ np = p;
+ nnt = t;
+ nnp = p;
+
+ while (nt[0] != '\0' && np[0] != '\0')
+ {
+ str_utf8_cnext_char_safe (&nnt);
+ str_utf8_cnext_char_safe (&nnp);
+ if (nnt - nt != nnp - np)
+ break;
+ if (strncmp (nt, np, nnt - nt) != 0)
+ break;
+ nt = nnt;
+ np = nnp;
+ }
+
+ result = np - p;
+
+ g_free (t);
+ g_free (p);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_caseprefix (const char *text, const char *prefix)
+{
+ char *t, *p;
+ const char *nt, *np;
+ const char *nnt, *nnp;
+ int result;
+
+ t = str_utf8_casefold_normalize (text);
+ p = str_utf8_casefold_normalize (prefix);
+ nt = t;
+ np = p;
+ nnt = t;
+ nnp = p;
+
+ while (nt[0] != '\0' && np[0] != '\0')
+ {
+ str_utf8_cnext_char_safe (&nnt);
+ str_utf8_cnext_char_safe (&nnp);
+ if (nnt - nt != nnp - np)
+ break;
+ if (strncmp (nt, np, nnt - nt) != 0)
+ break;
+ nt = nnt;
+ np = nnp;
+ }
+
+ result = np - p;
+
+ g_free (t);
+ g_free (p);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_utf8_create_key_gen (const char *text, gboolean case_sen,
+ gchar * (*keygen) (const gchar * text, gssize size))
+{
+ char *result;
+
+ if (case_sen)
+ result = str_utf8_normalize (text);
+ else
+ {
+ gboolean dot;
+ GString *fixed;
+ const char *start, *end;
+ char *fold, *key;
+
+ dot = text[0] == '.';
+ fixed = g_string_sized_new (16);
+
+ if (!dot)
+ start = text;
+ else
+ {
+ start = text + 1;
+ g_string_append_c (fixed, '.');
+ }
+
+ while (!g_utf8_validate (start, -1, &end) && start[0] != '\0')
+ {
+ if (start != end)
+ {
+ fold = g_utf8_casefold (start, end - start);
+ key = keygen (fold, -1);
+ g_string_append (fixed, key);
+ g_free (key);
+ g_free (fold);
+ }
+ g_string_append_c (fixed, end[0]);
+ start = end + 1;
+ }
+
+ if (start == text)
+ {
+ fold = g_utf8_casefold (start, -1);
+ result = keygen (fold, -1);
+ g_free (fold);
+ g_string_free (fixed, TRUE);
+ }
+ else if (dot && (start == text + 1))
+ {
+ fold = g_utf8_casefold (start, -1);
+ key = keygen (fold, -1);
+ g_string_append (fixed, key);
+ g_free (key);
+ g_free (fold);
+ result = g_string_free (fixed, FALSE);
+ }
+ else
+ {
+ if (start[0] != '\0' && start != end)
+ {
+ fold = g_utf8_casefold (start, end - start);
+ key = keygen (fold, -1);
+ g_string_append (fixed, key);
+ g_free (key);
+ g_free (fold);
+ }
+ result = g_string_free (fixed, FALSE);
+ }
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+str_utf8_create_key (const char *text, gboolean case_sen)
+{
+ return str_utf8_create_key_gen (text, case_sen, g_utf8_collate_key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef MC__USE_STR_UTF8_CREATE_KEY_FOR_FILENAME
+static char *
+str_utf8_create_key_for_filename (const char *text, gboolean case_sen)
+{
+ return str_utf8_create_key_gen (text, case_sen, g_utf8_collate_key_for_filename);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+str_utf8_key_collate (const char *t1, const char *t2, gboolean case_sen)
+{
+ (void) case_sen;
+ return strcmp (t1, t2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+str_utf8_release_key (char *key, gboolean case_sen)
+{
+ (void) case_sen;
+ g_free (key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct str_class
+str_utf8_init (void)
+{
+ struct str_class result;
+
+ result.conv_gerror_message = str_utf8_conv_gerror_message;
+ result.vfs_convert_to = str_utf8_vfs_convert_to;
+ result.insert_replace_char = str_utf8_insert_replace_char;
+ result.is_valid_string = str_utf8_is_valid_string;
+ result.is_valid_char = str_utf8_is_valid_char;
+ result.cnext_char = str_utf8_cnext_char;
+ result.cprev_char = str_utf8_cprev_char;
+ result.cnext_char_safe = str_utf8_cnext_char_safe;
+ result.cprev_char_safe = str_utf8_cprev_char_safe;
+ result.cnext_noncomb_char = str_utf8_cnext_noncomb_char;
+ result.cprev_noncomb_char = str_utf8_cprev_noncomb_char;
+ result.char_isspace = str_utf8_isspace;
+ result.char_ispunct = str_utf8_ispunct;
+ result.char_isalnum = str_utf8_isalnum;
+ result.char_isdigit = str_utf8_isdigit;
+ result.char_isprint = str_utf8_isprint;
+ result.char_iscombiningmark = str_utf8_iscombiningmark;
+ result.char_toupper = str_utf8_toupper;
+ result.char_tolower = str_utf8_tolower;
+ result.length = str_utf8_length;
+ result.length2 = str_utf8_length2;
+ result.length_noncomb = str_utf8_length_noncomb;
+ result.fix_string = str_utf8_fix_string;
+ result.term_form = str_utf8_term_form;
+ result.fit_to_term = str_utf8_fit_to_term;
+ result.term_trim = str_utf8_term_trim;
+ result.term_width2 = str_utf8_term_width2;
+ result.term_width1 = str_utf8_term_width1;
+ result.term_char_width = str_utf8_term_char_width;
+ result.term_substring = str_utf8_term_substring;
+ result.trunc = str_utf8_trunc;
+ result.offset_to_pos = str_utf8_offset_to_pos;
+ result.column_to_pos = str_utf8_column_to_pos;
+ result.create_search_needle = str_utf8_create_search_needle;
+ result.release_search_needle = str_utf8_release_search_needle;
+ result.search_first = str_utf8_search_first;
+ result.search_last = str_utf8_search_last;
+ result.compare = str_utf8_compare;
+ result.ncompare = str_utf8_ncompare;
+ result.casecmp = str_utf8_casecmp;
+ result.ncasecmp = str_utf8_ncasecmp;
+ result.prefix = str_utf8_prefix;
+ result.caseprefix = str_utf8_caseprefix;
+ result.create_key = str_utf8_create_key;
+#ifdef MC__USE_STR_UTF8_CREATE_KEY_FOR_FILENAME
+ /* case insensitive sort files in "a1 a2 a10" order */
+ result.create_key_for_filename = str_utf8_create_key_for_filename;
+#else
+ /* case insensitive sort files in "a1 a10 a2" order */
+ result.create_key_for_filename = str_utf8_create_key;
+#endif
+ result.key_collate = str_utf8_key_collate;
+ result.release_key = str_utf8_release_key;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/strverscmp.c b/lib/strutil/strverscmp.c
new file mode 100644
index 0000000..7d720c5
--- /dev/null
+++ b/lib/strutil/strverscmp.c
@@ -0,0 +1,158 @@
+/*
+ Compare strings while treating digits characters numerically.
+
+ Copyright (C) 1997-2023
+ Free Software Foundation, Inc.
+
+ This file is part of the GNU C Library.
+ Contributed by Jean-François Bignolles <bignolle@ecoledoc.ibp.fr>, 1997.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#ifdef HAVE_STRVERSCMP
+#include <string.h>
+#endif /* HAVE_STRVERSCMP */
+
+#include "lib/strutil.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef HAVE_STRVERSCMP
+
+/* states: S_N: normal, S_I: comparing integral part, S_F: comparing
+ fractional parts, S_Z: idem but with leading Zeroes only */
+#define S_N 0x0
+#define S_I 0x3
+#define S_F 0x6
+#define S_Z 0x9
+
+/* result_type: CMP: return diff; LEN: compare using len_diff/diff */
+#define CMP 2
+#define LEN 3
+
+#endif /* HAVE_STRVERSCMP */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/* Compare S1 and S2 as strings holding indices/version numbers,
+ returning less than, equal to or greater than zero if S1 is less than,
+ equal to or greater than S2 (for more info, see the texinfo doc).
+ */
+int
+str_verscmp (const char *s1, const char *s2)
+{
+#ifdef HAVE_STRVERSCMP
+ return strverscmp (s1, s2);
+
+#else /* HAVE_STRVERSCMP */
+ const unsigned char *p1 = (const unsigned char *) s1;
+ const unsigned char *p2 = (const unsigned char *) s2;
+ unsigned char c1, c2;
+ int state;
+ int diff;
+
+ /* *INDENT-OFF* */
+ /* Symbol(s) 0 [1-9] others
+ Transition (10) 0 (01) d (00) x */
+ static const unsigned char next_state[] =
+ {
+ /* state x d 0 */
+ /* S_N */ S_N, S_I, S_Z,
+ /* S_I */ S_N, S_I, S_I,
+ /* S_F */ S_N, S_F, S_F,
+ /* S_Z */ S_N, S_F, S_Z
+ };
+
+ static const signed char result_type[] =
+ {
+ /* state x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 */
+
+ /* S_N */ CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP,
+ /* S_I */ CMP, -1, -1, +1, LEN, LEN, +1, LEN, LEN,
+ /* S_F */ CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
+ /* S_Z */ CMP, +1, +1, -1, CMP, CMP, -1, CMP, CMP
+ };
+ /* *INDENT-ON* */
+
+ if (p1 == p2)
+ return 0;
+
+ c1 = *p1++;
+ c2 = *p2++;
+ /* Hint: '0' is a digit too. */
+ state = S_N + ((c1 == '0') + (isdigit (c1) != 0));
+
+ while ((diff = c1 - c2) == 0)
+ {
+ if (c1 == '\0')
+ return diff;
+
+ state = next_state[state];
+ c1 = *p1++;
+ c2 = *p2++;
+ state += (c1 == '0') + (isdigit (c1) != 0);
+ }
+
+ state = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))];
+
+ switch (state)
+ {
+ case CMP:
+ return diff;
+
+ case LEN:
+ while (isdigit (*p1++))
+ if (!isdigit (*p2++))
+ return 1;
+
+ return isdigit (*p2) ? -1 : diff;
+
+ default:
+ return state;
+ }
+#endif /* HAVE_STRVERSCMP */
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/strutil/xstrtol.c b/lib/strutil/xstrtol.c
new file mode 100644
index 0000000..a0f93ce
--- /dev/null
+++ b/lib/strutil/xstrtol.c
@@ -0,0 +1,268 @@
+/* A more useful interface to strtol.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 <http://www.gnu.org/licenses/>. */
+
+/* Written by Jim Meyering. */
+
+#include <config.h>
+
+/* Some pre-ANSI implementations (e.g. SunOS 4)
+ need stderr defined if assertion checking is enabled. */
+#include <stdio.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/strutil.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static strtol_error_t
+bkm_scale (uintmax_t * x, int scale_factor)
+{
+ if (UINTMAX_MAX / scale_factor < *x)
+ {
+ *x = UINTMAX_MAX;
+ return LONGINT_OVERFLOW;
+ }
+
+ *x *= scale_factor;
+ return LONGINT_OK;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static strtol_error_t
+bkm_scale_by_power (uintmax_t * x, int base, int power)
+{
+ strtol_error_t err = LONGINT_OK;
+ while (power-- != 0)
+ err |= bkm_scale (x, base);
+ return err;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+strtol_error_t
+xstrtoumax (const char *s, char **ptr, int base, uintmax_t * val, const char *valid_suffixes)
+{
+ char *t_ptr;
+ char **p;
+ uintmax_t tmp;
+ strtol_error_t err = LONGINT_OK;
+
+ g_assert (0 <= base && base <= 36);
+
+ p = (ptr != NULL ? ptr : &t_ptr);
+
+ {
+ const char *q = s;
+ unsigned char ch = *q;
+
+ while (isspace (ch))
+ ch = *++q;
+
+ if (ch == '-')
+ return LONGINT_INVALID;
+ }
+
+ errno = 0;
+ tmp = strtol (s, p, base);
+
+ if (*p == s)
+ {
+ /* If there is no number but there is a valid suffix, assume the
+ number is 1. The string is invalid otherwise. */
+ if (valid_suffixes != NULL && **p != '\0' && strchr (valid_suffixes, **p) != NULL)
+ tmp = 1;
+ else
+ return LONGINT_INVALID;
+ }
+ else if (errno != 0)
+ {
+ if (errno != ERANGE)
+ return LONGINT_INVALID;
+ err = LONGINT_OVERFLOW;
+ }
+
+ /* Let valid_suffixes == NULL mean "allow any suffix". */
+ /* FIXME: update all callers except the ones that allow suffixes
+ after the number, changing last parameter NULL to "". */
+ if (valid_suffixes == NULL)
+ {
+ *val = tmp;
+ return err;
+ }
+
+ if (**p != '\0')
+ {
+ int suffixes = 1;
+ strtol_error_t overflow;
+
+ if (strchr (valid_suffixes, **p) == NULL)
+ {
+ *val = tmp;
+ return err | LONGINT_INVALID_SUFFIX_CHAR;
+ }
+
+ base = 1024;
+
+ switch (**p)
+ {
+ case 'E':
+ case 'G':
+ case 'g':
+ case 'k':
+ case 'K':
+ case 'M':
+ case 'm':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'T':
+ case 't':
+ case 'Y':
+ case 'Z':
+ if (strchr (valid_suffixes, '0') != NULL)
+ {
+ /* The "valid suffix" '0' is a special flag meaning that
+ an optional second suffix is allowed, which can change
+ the base. A suffix "B" (e.g. "100MB") stands for a power
+ of 1000, whereas a suffix "iB" (e.g. "100MiB") stands for
+ a power of 1024. If no suffix (e.g. "100M"), assume
+ power-of-1024. */
+
+ switch (p[0][1])
+ {
+ case 'i':
+ if (p[0][2] == 'B')
+ suffixes += 2;
+ break;
+
+ case 'B':
+ case 'D': /* 'D' is obsolescent */
+ base = 1000;
+ suffixes++;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (**p)
+ {
+ case 'b':
+ overflow = bkm_scale (&tmp, 512);
+ break;
+
+ case 'B':
+ /* This obsolescent first suffix is distinct from the 'B'
+ second suffix above. E.g., 'tar -L 1000B' means change
+ the tape after writing 1000 KiB of data. */
+ overflow = bkm_scale (&tmp, 1024);
+ break;
+
+ case 'c':
+ overflow = LONGINT_OK;
+ break;
+
+ case 'E': /* exa or exbi */
+ overflow = bkm_scale_by_power (&tmp, base, 6);
+ break;
+
+ case 'G': /* giga or gibi */
+ case 'g': /* 'g' is undocumented; for compatibility only */
+ overflow = bkm_scale_by_power (&tmp, base, 3);
+ break;
+
+ case 'k': /* kilo */
+ case 'K': /* kibi */
+ overflow = bkm_scale_by_power (&tmp, base, 1);
+ break;
+
+ case 'M': /* mega or mebi */
+ case 'm': /* 'm' is undocumented; for compatibility only */
+ overflow = bkm_scale_by_power (&tmp, base, 2);
+ break;
+
+ case 'P': /* peta or pebi */
+ overflow = bkm_scale_by_power (&tmp, base, 5);
+ break;
+
+ case 'Q': /* quetta or 2**100 */
+ overflow = bkm_scale_by_power (&tmp, base, 10);
+ break;
+
+ case 'R': /* ronna or 2**90 */
+ overflow = bkm_scale_by_power (&tmp, base, 9);
+ break;
+
+ case 'T': /* tera or tebi */
+ case 't': /* 't' is undocumented; for compatibility only */
+ overflow = bkm_scale_by_power (&tmp, base, 4);
+ break;
+
+ case 'w':
+ overflow = bkm_scale (&tmp, 2);
+ break;
+
+ case 'Y': /* yotta or 2**80 */
+ overflow = bkm_scale_by_power (&tmp, base, 8);
+ break;
+
+ case 'Z': /* zetta or 2**70 */
+ overflow = bkm_scale_by_power (&tmp, base, 7);
+ break;
+
+ default:
+ *val = tmp;
+ return err | LONGINT_INVALID_SUFFIX_CHAR;
+ }
+
+ err |= overflow;
+ *p += suffixes;
+ if (**p != '\0')
+ err |= LONGINT_INVALID_SUFFIX_CHAR;
+ }
+
+ *val = tmp;
+ return err;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/timefmt.c b/lib/timefmt.c
new file mode 100644
index 0000000..43bd429
--- /dev/null
+++ b/lib/timefmt.c
@@ -0,0 +1,156 @@
+/*
+ Time formatting functions
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996
+ Janne Kukonlehto, 1994, 1995, 1996
+ Dugan Porter, 1994, 1995, 1996
+ Jakub Jelinek, 1994, 1995, 1996
+ Mauricio Plaza, 1994, 1995, 1996
+
+ The file_date routine is mostly from GNU's fileutils package,
+ written by Richard Stallman and David MacKenzie.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: time formatting functions
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <limits.h> /* MB_LEN_MAX */
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+
+#include "lib/timefmt.h"
+
+/*** global variables ****************************************************************************/
+
+char *user_recent_timeformat = NULL; /* time format string for recent dates */
+char *user_old_timeformat = NULL; /* time format string for older dates */
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*
+ * Cache variable for the i18n_checktimelength function,
+ * initially set to a clearly invalid value to show that
+ * it hasn't been initialized yet.
+ */
+static size_t i18n_timelength_cache = MAX_I18NTIMELENGTH + 1;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check strftime() results. Some systems (i.e. Solaris) have different
+ * short-month and month name sizes for different locales
+ */
+size_t
+i18n_checktimelength (void)
+{
+ size_t length = 0;
+ time_t testtime;
+ struct tm *lt;
+
+ if (i18n_timelength_cache <= MAX_I18NTIMELENGTH)
+ return i18n_timelength_cache;
+
+ testtime = time (NULL);
+ lt = localtime (&testtime);
+
+ if (lt == NULL)
+ {
+ /* huh, localtime() doesn't seem to work ... falling back to "(invalid)" */
+ length = str_term_width1 (_(INVALID_TIME_TEXT));
+ }
+ else
+ {
+ char buf[MB_LEN_MAX * MAX_I18NTIMELENGTH + 1];
+ size_t tlen;
+
+ /* We are interested in the longest possible date */
+ lt->tm_sec = lt->tm_min = lt->tm_hour = lt->tm_mday = 10;
+
+ /* Loop through all months to find out the longest one */
+ for (lt->tm_mon = 0; lt->tm_mon < 12; lt->tm_mon++)
+ {
+ strftime (buf, sizeof (buf) - 1, user_recent_timeformat, lt);
+ tlen = (size_t) str_term_width1 (buf);
+ length = MAX (tlen, length);
+ strftime (buf, sizeof (buf) - 1, user_old_timeformat, lt);
+ tlen = (size_t) str_term_width1 (buf);
+ length = MAX (tlen, length);
+ }
+
+ tlen = (size_t) str_term_width1 (_(INVALID_TIME_TEXT));
+ length = MAX (tlen, length);
+ }
+
+ /* Don't handle big differences. Use standard value (email bug, please) */
+ if (length > MAX_I18NTIMELENGTH || length < MIN_I18NTIMELENGTH)
+ length = STD_I18NTIMELENGTH;
+
+ /* Save obtained value to the cache */
+ i18n_timelength_cache = length;
+
+ return i18n_timelength_cache;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+file_date (time_t when)
+{
+ static char timebuf[MB_LEN_MAX * MAX_I18NTIMELENGTH + 1];
+ time_t current_time = time (NULL);
+ const char *fmt;
+
+ if (current_time > when + 6L * 30L * 24L * 60L * 60L /* Old. */
+ || current_time < when - 60L * 60L) /* In the future. */
+ /* The file is fairly old or in the future.
+ POSIX says the cutoff is 6 months old;
+ approximate this by 6*30 days.
+ Allow a 1 hour slop factor for what is considered "the future",
+ to allow for NFS server/client clock disagreement.
+ Show the year instead of the time of day. */
+
+ fmt = user_old_timeformat;
+ else
+ fmt = user_recent_timeformat;
+
+ FMT_LOCALTIME (timebuf, sizeof (timebuf), fmt, when);
+
+ return timebuf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/timefmt.h b/lib/timefmt.h
new file mode 100644
index 0000000..7e3a1d7
--- /dev/null
+++ b/lib/timefmt.h
@@ -0,0 +1,59 @@
+
+/** \file timefmt.h
+ * \brief Header: time formatting functions
+ */
+
+#ifndef MC__UTIL_TIMEFMT_H
+#define MC__UTIL_TIMEFMT_H
+
+#include <sys/time.h>
+#include <sys/types.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define MAX_I18NTIMELENGTH 20
+#define MIN_I18NTIMELENGTH 10
+#define STD_I18NTIMELENGTH 12
+
+#define INVALID_TIME_TEXT "(invalid)"
+
+/* safe localtime formatting - strftime()-using version */
+#define FMT_LOCALTIME(buffer, bufsize, fmt, when) \
+ { \
+ struct tm *whentm; \
+ whentm = localtime(&when); \
+ if (whentm == NULL) \
+ { \
+ strncpy(buffer, INVALID_TIME_TEXT, bufsize); \
+ buffer[bufsize-1] = 0; \
+ } \
+ else \
+ { \
+ strftime(buffer, bufsize, fmt, whentm); \
+ } \
+ } \
+
+#define FMT_LOCALTIME_CURRENT(buffer, bufsize, fmt) \
+ { \
+ time_t __current_time; \
+ time(&__current_time); \
+ FMT_LOCALTIME(buffer,bufsize,fmt,__current_time); \
+ }
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern char *user_recent_timeformat; /* time format string for recent dates */
+extern char *user_old_timeformat; /* time format string for older dates */
+
+/*** declarations of public functions ************************************************************/
+
+size_t i18n_checktimelength (void);
+const char *file_date (time_t when);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__UTIL_TIMEFMT_H */
diff --git a/lib/tty/Makefile.am b/lib/tty/Makefile.am
new file mode 100644
index 0000000..d4260fe
--- /dev/null
+++ b/lib/tty/Makefile.am
@@ -0,0 +1,36 @@
+
+noinst_LTLIBRARIES = libmctty.la
+
+if USE_SCREEN_SLANG
+ TTY_SCREEN_SRC = \
+ color-slang.c color-slang.h \
+ tty-slang.c tty-slang.h
+else
+ TTY_SCREEN_SRC = \
+ color-ncurses.c \
+ tty-ncurses.c tty-ncurses.h
+endif
+
+TTY_SRC = \
+ color-internal.c color-internal.h \
+ color.c color.h \
+ key.c key.h keyxdef.c \
+ mouse.c mouse.h \
+ tty-internal.c tty-internal.h \
+ tty.c tty.h \
+ win.c win.h
+
+if HAVE_TEXTMODE_X11_SUPPORT
+TTY_SRC += x11conn.c x11conn.h
+endif
+
+libmctty_la_SOURCES = $(TTY_SRC) $(TTY_SCREEN_SRC)
+
+AM_CPPFLAGS = -I$(top_srcdir)
+
+if HAVE_GMODULE
+AM_CPPFLAGS += $(GMODULE_CFLAGS)
+else
+AM_CPPFLAGS += $(GLIB_CFLAGS)
+endif
+
diff --git a/lib/tty/Makefile.in b/lib/tty/Makefile.in
new file mode 100644
index 0000000..0aa0af8
--- /dev/null
+++ b/lib/tty/Makefile.in
@@ -0,0 +1,801 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_TEXTMODE_X11_SUPPORT_TRUE@am__append_1 = x11conn.c x11conn.h
+@HAVE_GMODULE_TRUE@am__append_2 = $(GMODULE_CFLAGS)
+@HAVE_GMODULE_FALSE@am__append_3 = $(GLIB_CFLAGS)
+subdir = lib/tty
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmctty_la_LIBADD =
+am__libmctty_la_SOURCES_DIST = color-internal.c color-internal.h \
+ color.c color.h key.c key.h keyxdef.c mouse.c mouse.h \
+ tty-internal.c tty-internal.h tty.c tty.h win.c win.h \
+ x11conn.c x11conn.h color-ncurses.c tty-ncurses.c \
+ tty-ncurses.h color-slang.c color-slang.h tty-slang.c \
+ tty-slang.h
+@HAVE_TEXTMODE_X11_SUPPORT_TRUE@am__objects_1 = x11conn.lo
+am__objects_2 = color-internal.lo color.lo key.lo keyxdef.lo mouse.lo \
+ tty-internal.lo tty.lo win.lo $(am__objects_1)
+@USE_SCREEN_SLANG_FALSE@am__objects_3 = color-ncurses.lo \
+@USE_SCREEN_SLANG_FALSE@ tty-ncurses.lo
+@USE_SCREEN_SLANG_TRUE@am__objects_3 = color-slang.lo tty-slang.lo
+am_libmctty_la_OBJECTS = $(am__objects_2) $(am__objects_3)
+libmctty_la_OBJECTS = $(am_libmctty_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/color-internal.Plo \
+ ./$(DEPDIR)/color-ncurses.Plo ./$(DEPDIR)/color-slang.Plo \
+ ./$(DEPDIR)/color.Plo ./$(DEPDIR)/key.Plo \
+ ./$(DEPDIR)/keyxdef.Plo ./$(DEPDIR)/mouse.Plo \
+ ./$(DEPDIR)/tty-internal.Plo ./$(DEPDIR)/tty-ncurses.Plo \
+ ./$(DEPDIR)/tty-slang.Plo ./$(DEPDIR)/tty.Plo \
+ ./$(DEPDIR)/win.Plo ./$(DEPDIR)/x11conn.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmctty_la_SOURCES)
+DIST_SOURCES = $(am__libmctty_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmctty.la
+@USE_SCREEN_SLANG_FALSE@TTY_SCREEN_SRC = \
+@USE_SCREEN_SLANG_FALSE@ color-ncurses.c \
+@USE_SCREEN_SLANG_FALSE@ tty-ncurses.c tty-ncurses.h
+
+@USE_SCREEN_SLANG_TRUE@TTY_SCREEN_SRC = \
+@USE_SCREEN_SLANG_TRUE@ color-slang.c color-slang.h \
+@USE_SCREEN_SLANG_TRUE@ tty-slang.c tty-slang.h
+
+TTY_SRC = color-internal.c color-internal.h color.c color.h key.c \
+ key.h keyxdef.c mouse.c mouse.h tty-internal.c tty-internal.h \
+ tty.c tty.h win.c win.h $(am__append_1)
+libmctty_la_SOURCES = $(TTY_SRC) $(TTY_SCREEN_SRC)
+AM_CPPFLAGS = -I$(top_srcdir) $(am__append_2) $(am__append_3)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/tty/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/tty/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmctty.la: $(libmctty_la_OBJECTS) $(libmctty_la_DEPENDENCIES) $(EXTRA_libmctty_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmctty_la_OBJECTS) $(libmctty_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color-internal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color-ncurses.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color-slang.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/key.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyxdef.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mouse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty-internal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty-ncurses.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty-slang.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/win.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/x11conn.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/color-internal.Plo
+ -rm -f ./$(DEPDIR)/color-ncurses.Plo
+ -rm -f ./$(DEPDIR)/color-slang.Plo
+ -rm -f ./$(DEPDIR)/color.Plo
+ -rm -f ./$(DEPDIR)/key.Plo
+ -rm -f ./$(DEPDIR)/keyxdef.Plo
+ -rm -f ./$(DEPDIR)/mouse.Plo
+ -rm -f ./$(DEPDIR)/tty-internal.Plo
+ -rm -f ./$(DEPDIR)/tty-ncurses.Plo
+ -rm -f ./$(DEPDIR)/tty-slang.Plo
+ -rm -f ./$(DEPDIR)/tty.Plo
+ -rm -f ./$(DEPDIR)/win.Plo
+ -rm -f ./$(DEPDIR)/x11conn.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/color-internal.Plo
+ -rm -f ./$(DEPDIR)/color-ncurses.Plo
+ -rm -f ./$(DEPDIR)/color-slang.Plo
+ -rm -f ./$(DEPDIR)/color.Plo
+ -rm -f ./$(DEPDIR)/key.Plo
+ -rm -f ./$(DEPDIR)/keyxdef.Plo
+ -rm -f ./$(DEPDIR)/mouse.Plo
+ -rm -f ./$(DEPDIR)/tty-internal.Plo
+ -rm -f ./$(DEPDIR)/tty-ncurses.Plo
+ -rm -f ./$(DEPDIR)/tty-slang.Plo
+ -rm -f ./$(DEPDIR)/tty.Plo
+ -rm -f ./$(DEPDIR)/win.Plo
+ -rm -f ./$(DEPDIR)/x11conn.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/tty/color-internal.c b/lib/tty/color-internal.c
new file mode 100644
index 0000000..8db2b6c
--- /dev/null
+++ b/lib/tty/color-internal.c
@@ -0,0 +1,244 @@
+/*
+ Internal stuff of color setup
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Slava Zanko <slavazanko@gmail.com>, 2009, 2013
+ Egmont Koblinger <egmont@gmail.com>, 2010
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file color-internal.c
+ * \brief Source: Internal stuff of color setup
+ */
+
+#include <config.h>
+
+#include <string.h> /* strcmp */
+
+#include "color.h" /* colors and attributes */
+#include "color-internal.h"
+
+/*** global variables ****************************************************************************/
+
+gboolean mc_tty_color_disable;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define COLOR_INTENSITY 8
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct mc_tty_color_table_struct
+{
+ const char *name;
+ int value;
+} mc_tty_color_table_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static mc_tty_color_table_t const color_table[] = {
+ {"black", COLOR_BLACK},
+ {"gray", COLOR_BLACK + COLOR_INTENSITY},
+ {"red", COLOR_RED},
+ {"brightred", COLOR_RED + COLOR_INTENSITY},
+ {"green", COLOR_GREEN},
+ {"brightgreen", COLOR_GREEN + COLOR_INTENSITY},
+ {"brown", COLOR_YELLOW},
+ {"yellow", COLOR_YELLOW + COLOR_INTENSITY},
+ {"blue", COLOR_BLUE},
+ {"brightblue", COLOR_BLUE + COLOR_INTENSITY},
+ {"magenta", COLOR_MAGENTA},
+ {"brightmagenta", COLOR_MAGENTA + COLOR_INTENSITY},
+ {"cyan", COLOR_CYAN},
+ {"brightcyan", COLOR_CYAN + COLOR_INTENSITY},
+ {"lightgray", COLOR_WHITE},
+ {"white", COLOR_WHITE + COLOR_INTENSITY},
+ {"default", -1}, /* default color of the terminal */
+ /* special colors */
+ {"A_REVERSE", SPEC_A_REVERSE},
+ {"A_BOLD", SPEC_A_BOLD},
+ {"A_BOLD_REVERSE", SPEC_A_BOLD_REVERSE},
+ {"A_UNDERLINE", SPEC_A_UNDERLINE},
+ /* End of list */
+ {NULL, 0}
+};
+
+static mc_tty_color_table_t const attributes_table[] = {
+ {"bold", A_BOLD},
+#ifdef A_ITALIC /* available since ncurses-5.9-20130831 / slang-pre2.3.0-107 */
+ {"italic", A_ITALIC},
+#endif /* A_ITALIC */
+ {"underline", A_UNDERLINE},
+ {"reverse", A_REVERSE},
+ {"blink", A_BLINK},
+ /* End of list */
+ {NULL, 0}
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+parse_hex_digit (char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ c |= 0x20;
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+parse_256_or_true_color_name (const char *color_name)
+{
+ int i;
+ char dummy;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (color_name, "color%d%c", &i, &dummy) == 1 && i >= 0 && i < 256)
+ {
+ return i;
+ }
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (color_name, "gray%d%c", &i, &dummy) == 1 && i >= 0 && i < 24)
+ {
+ return 232 + i;
+ }
+ if (strncmp (color_name, "rgb", 3) == 0 &&
+ color_name[3] >= '0' && color_name[3] < '6' &&
+ color_name[4] >= '0' && color_name[4] < '6' &&
+ color_name[5] >= '0' && color_name[5] < '6' && color_name[6] == '\0')
+ {
+ return 16 + 36 * (color_name[3] - '0') + 6 * (color_name[4] - '0') + (color_name[5] - '0');
+ }
+ if (color_name[0] == '#')
+ {
+ int len;
+
+ color_name++;
+ len = (int) strlen (color_name);
+ if (len == 3 || len == 6)
+ {
+ int h[6];
+
+ for (i = 0; i < len; i++)
+ {
+ h[i] = parse_hex_digit (color_name[i]);
+ if (h[i] == -1)
+ return -1;
+ }
+
+ if (i == 3)
+ i = (h[0] << 20) | (h[0] << 16) | (h[1] << 12) | (h[1] << 8) | (h[2] << 4) | h[2];
+ else
+ i = (h[0] << 20) | (h[1] << 16) | (h[2] << 12) | (h[3] << 8) | (h[4] << 4) | h[5];
+ return (1 << 24) | i;
+ }
+ }
+
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+tty_color_get_name_by_index (int idx)
+{
+ int i;
+
+ /* Find the real English name of the first 16 colors, */
+ /* as well as the A_* special values. */
+ for (i = 0; color_table[i].name != NULL; i++)
+ if (idx == color_table[i].value)
+ return color_table[i].name;
+
+ /* Create and return the strings in "colorNNN" or "#rrggbb" format. */
+ if ((idx >= 16 && idx < 256) || (idx & (1 << 24)) != 0)
+ {
+ char name[9];
+
+ if (idx < 256)
+ sprintf (name, "color%d", idx);
+ else
+ sprintf (name, "#%06X", (unsigned int) idx & 0xFFFFFF);
+ return g_intern_string (name);
+ }
+ return "default";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_color_get_index_by_name (const char *color_name)
+{
+ if (color_name != NULL)
+ {
+ size_t i;
+
+ for (i = 0; color_table[i].name != NULL; i++)
+ if (strcmp (color_name, color_table[i].name) == 0)
+ return color_table[i].value;
+ return parse_256_or_true_color_name (color_name);
+ }
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_attr_get_bits (const char *attrs)
+{
+ int attr_bits = 0;
+
+ if (attrs != NULL)
+ {
+ gchar **attr_list;
+ int i;
+
+ attr_list = g_strsplit (attrs, "+", -1);
+
+ for (i = 0; attr_list[i] != NULL; i++)
+ {
+ int j;
+
+ for (j = 0; attributes_table[j].name != NULL; j++)
+ {
+ if (strcmp (attr_list[i], attributes_table[j].name) == 0)
+ {
+ attr_bits |= attributes_table[j].value;
+ break;
+ }
+ }
+ }
+ g_strfreev (attr_list);
+ }
+ return attr_bits;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/color-internal.h b/lib/tty/color-internal.h
new file mode 100644
index 0000000..dc85225
--- /dev/null
+++ b/lib/tty/color-internal.h
@@ -0,0 +1,61 @@
+
+/** \file color-internal.h
+ * \brief Header: Internal stuff of color setup
+ */
+
+#ifndef MC__COLOR_INTERNAL_H
+#define MC__COLOR_INTERNAL_H
+
+#include <sys/types.h> /* size_t */
+
+#include "lib/global.h"
+
+#ifdef HAVE_SLANG
+#include "tty-slang.h"
+#else
+#include "tty-ncurses.h"
+#endif /* HAVE_SLANG */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/* *INDENT-OFF* */
+typedef enum {
+ SPEC_A_REVERSE = -100,
+ SPEC_A_BOLD = -101,
+ SPEC_A_BOLD_REVERSE = -102,
+ SPEC_A_UNDERLINE = -103
+} tty_special_color_t;
+/* *INDENT-ON* */
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct mc_color_pair_struct
+{
+ int ifg;
+ int ibg;
+ int attr;
+ size_t pair_index;
+ gboolean is_temp;
+} tty_color_pair_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern gboolean use_colors;
+extern gboolean mc_tty_color_disable;
+
+/*** declarations of public functions ************************************************************/
+
+const char *tty_color_get_name_by_index (int idx);
+int tty_color_get_index_by_name (const char *color_name);
+int tty_attr_get_bits (const char *attrs);
+
+void tty_color_init_lib (gboolean disable, gboolean force);
+void tty_color_deinit_lib (void);
+
+void tty_color_try_alloc_pair_lib (tty_color_pair_t * mc_color_pair);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__COLOR_INTERNAL_H */
diff --git a/lib/tty/color-ncurses.c b/lib/tty/color-ncurses.c
new file mode 100644
index 0000000..f01d697
--- /dev/null
+++ b/lib/tty/color-ncurses.c
@@ -0,0 +1,251 @@
+/*
+ Color setup for NCurses screen library
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Slava Zanko <slavazanko@gmail.com>, 2010
+ Egmont Koblinger <egmont@gmail.com>, 2010
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file color-ncurses.c
+ * \brief Source: NCUrses-specific color setup
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h> /* size_t */
+
+#include "lib/global.h"
+
+#include "tty-ncurses.h"
+#include "color.h" /* variables */
+#include "color-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GHashTable *mc_tty_color_color_pair_attrs = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mc_tty_color_attr_destroy_cb (gpointer data)
+{
+ g_free (data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_tty_color_save_attr (int color_pair, int color_attr)
+{
+ int *attr, *key;
+
+ attr = g_try_new0 (int, 1);
+ if (attr == NULL)
+ return;
+
+ key = g_try_new (int, 1);
+ if (key == NULL)
+ {
+ g_free (attr);
+ return;
+ }
+
+ *key = color_pair;
+ *attr = color_attr;
+
+ g_hash_table_replace (mc_tty_color_color_pair_attrs, (gpointer) key, (gpointer) attr);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+color_get_attr (int color_pair)
+{
+ int *fnd = NULL;
+
+ if (mc_tty_color_color_pair_attrs != NULL)
+ fnd = (int *) g_hash_table_lookup (mc_tty_color_color_pair_attrs, (gpointer) & color_pair);
+ return (fnd != NULL) ? *fnd : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_tty_color_pair_init_special (tty_color_pair_t * mc_color_pair,
+ int fg1, int bg1, int fg2, int bg2, int attr)
+{
+ if (has_colors () && !mc_tty_color_disable)
+ init_pair (mc_color_pair->pair_index, fg1, bg1);
+ else
+ init_pair (mc_color_pair->pair_index, fg2, bg2);
+ mc_tty_color_save_attr (mc_color_pair->pair_index, attr);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_init_lib (gboolean disable, gboolean force)
+{
+ (void) force;
+
+ if (has_colors () && !disable)
+ {
+ use_colors = TRUE;
+ start_color ();
+ use_default_colors ();
+ }
+
+ mc_tty_color_color_pair_attrs = g_hash_table_new_full
+ (g_int_hash, g_int_equal, mc_tty_color_attr_destroy_cb, mc_tty_color_attr_destroy_cb);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_deinit_lib (void)
+{
+ g_hash_table_destroy (mc_tty_color_color_pair_attrs);
+ mc_tty_color_color_pair_attrs = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_try_alloc_pair_lib (tty_color_pair_t * mc_color_pair)
+{
+ if (mc_color_pair->ifg <= (int) SPEC_A_REVERSE)
+ {
+ switch (mc_color_pair->ifg)
+ {
+ case SPEC_A_REVERSE:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ COLOR_BLACK, COLOR_WHITE,
+ COLOR_BLACK, COLOR_WHITE | A_BOLD, A_REVERSE);
+ break;
+ case SPEC_A_BOLD:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ COLOR_WHITE, COLOR_BLACK,
+ COLOR_WHITE, COLOR_BLACK, A_BOLD);
+ break;
+ case SPEC_A_BOLD_REVERSE:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ COLOR_WHITE, COLOR_WHITE,
+ COLOR_WHITE, COLOR_WHITE, A_BOLD | A_REVERSE);
+ break;
+ case SPEC_A_UNDERLINE:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ COLOR_WHITE, COLOR_BLACK,
+ COLOR_WHITE, COLOR_BLACK, A_UNDERLINE);
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ int ifg, ibg, attr;
+
+ ifg = mc_color_pair->ifg;
+ ibg = mc_color_pair->ibg;
+ attr = mc_color_pair->attr;
+
+ /* In legacy color mode, change bright colors into bold */
+ if (!tty_use_256colors (NULL) && !tty_use_truecolors (NULL))
+ {
+ if (ifg >= 8 && ifg < 16)
+ {
+ ifg &= 0x07;
+ attr |= A_BOLD;
+ }
+
+ if (ibg >= 8 && ibg < 16)
+ {
+ ibg &= 0x07;
+ /* attr | = A_BOLD | A_REVERSE ; */
+ }
+ }
+
+ init_pair (mc_color_pair->pair_index, ifg, ibg);
+ mc_tty_color_save_attr (mc_color_pair->pair_index, attr);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_setcolor (int color)
+{
+ attrset (COLOR_PAIR (color) | color_get_attr (color));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_lowlevel_setcolor (int color)
+{
+ tty_setcolor (color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_set_normal_attrs (void)
+{
+ standend ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tty_use_256colors (GError ** error)
+{
+ (void) error;
+
+ return (COLORS == 256);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tty_use_truecolors (GError ** error)
+{
+ /* Not yet supported in ncurses */
+ g_set_error (error, MC_ERROR, -1, _("True color not supported with ncurses."));
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/color-slang.c b/lib/tty/color-slang.c
new file mode 100644
index 0000000..5dd2663
--- /dev/null
+++ b/lib/tty/color-slang.c
@@ -0,0 +1,260 @@
+/*
+ Color setup for S_Lang screen library
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Egmont Koblinger <egmont@gmail.com>, 2010
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file color-slang.c
+ * \brief Source: S-Lang-specific color setup
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h> /* size_t */
+
+#include "lib/global.h"
+#include "lib/util.h" /* whitespace() */
+
+#include "tty-slang.h"
+#include "color.h" /* variables */
+#include "color-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+has_colors (gboolean disable, gboolean force)
+{
+ mc_tty_color_disable = disable;
+
+ if (force || (getenv ("COLORTERM") != NULL))
+ SLtt_Use_Ansi_Colors = 1;
+
+ if (!mc_tty_color_disable)
+ {
+ const char *terminal = getenv ("TERM");
+ const size_t len = strlen (terminal);
+ char *cts = mc_global.tty.color_terminal_string;
+
+ /* check mc_global.tty.color_terminal_string */
+ while (*cts != '\0')
+ {
+ char *s;
+ size_t i = 0;
+
+ while (whitespace (*cts))
+ cts++;
+ s = cts;
+
+ while (*cts != '\0' && *cts != ',')
+ {
+ cts++;
+ i++;
+ }
+
+ if ((i != 0) && (i == len) && (strncmp (s, terminal, i) == 0))
+ SLtt_Use_Ansi_Colors = 1;
+
+ if (*cts == ',')
+ cts++;
+ }
+ }
+ return SLtt_Use_Ansi_Colors;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_tty_color_pair_init_special (tty_color_pair_t * mc_color_pair,
+ const char *fg1, const char *bg1,
+ const char *fg2, const char *bg2, SLtt_Char_Type mask)
+{
+ if (SLtt_Use_Ansi_Colors != 0)
+ {
+ if (!mc_tty_color_disable)
+ {
+ SLtt_set_color (mc_color_pair->pair_index, (char *) "", (char *) fg1, (char *) bg1);
+ }
+ else
+ {
+ SLtt_set_color (mc_color_pair->pair_index, (char *) "", (char *) fg2, (char *) bg2);
+ }
+ }
+ else
+ {
+ SLtt_set_mono (mc_color_pair->pair_index, NULL, mask);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_init_lib (gboolean disable, gboolean force)
+{
+ /* FIXME: if S-Lang is used, has_colors() must be called regardless
+ of whether we are interested in its result */
+ if (has_colors (disable, force) && !disable)
+ {
+ use_colors = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_deinit_lib (void)
+{
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_try_alloc_pair_lib (tty_color_pair_t * mc_color_pair)
+{
+ if (mc_color_pair->ifg <= (int) SPEC_A_REVERSE)
+ {
+ switch (mc_color_pair->ifg)
+ {
+ case SPEC_A_REVERSE:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ "black", "white", "black", "lightgray", SLTT_REV_MASK);
+ break;
+ case SPEC_A_BOLD:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ "white", "black", "white", "black", SLTT_BOLD_MASK);
+ break;
+ case SPEC_A_BOLD_REVERSE:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ "white", "white",
+ "white", "white", SLTT_BOLD_MASK | SLTT_REV_MASK);
+ break;
+ case SPEC_A_UNDERLINE:
+ mc_tty_color_pair_init_special (mc_color_pair,
+ "white", "black", "white", "black", SLTT_ULINE_MASK);
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ const char *fg, *bg;
+
+ fg = tty_color_get_name_by_index (mc_color_pair->ifg);
+ bg = tty_color_get_name_by_index (mc_color_pair->ibg);
+ SLtt_set_color (mc_color_pair->pair_index, (char *) "", (char *) fg, (char *) bg);
+ SLtt_add_color_attribute (mc_color_pair->pair_index, mc_color_pair->attr);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_setcolor (int color)
+{
+ SLsmg_set_color (color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set colorpair by index, don't interpret S-Lang "emulated attributes"
+ */
+
+void
+tty_lowlevel_setcolor (int color)
+{
+ SLsmg_set_color (color & 0x7F);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_set_normal_attrs (void)
+{
+ SLsmg_normal_video ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tty_use_256colors (GError ** error)
+{
+ gboolean ret;
+
+ ret = (SLtt_Use_Ansi_Colors && SLtt_tgetnum ((char *) "Co") == 256);
+
+ if (!ret)
+ g_set_error (error, MC_ERROR, -1,
+ _("Your terminal doesn't even seem to support 256 colors."));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tty_use_truecolors (GError ** error)
+{
+ char *colorterm;
+
+ /* True color is supported since slang-2.3.1 on 64-bit machines,
+ and expected to be supported from slang-3 on 32-bit machines:
+ http://lists.jedsoft.org/lists/slang-users/2016/0000014.html.
+ Check for sizeof (long) being 8, exactly as slang does. */
+ if (SLang_Version < 20301 || (sizeof (long) != 8 && SLang_Version < 30000))
+ {
+ g_set_error (error, MC_ERROR, -1, _("True color not supported in this slang version."));
+ return FALSE;
+ }
+
+ /* Duplicate slang's check so that we can pop up an error message
+ rather than silently use wrong colors. */
+ colorterm = getenv ("COLORTERM");
+ if (colorterm == NULL
+ || (strcmp (colorterm, "truecolor") != 0 && strcmp (colorterm, "24bit") != 0))
+ {
+ g_set_error (error, MC_ERROR, -1,
+ _("Set COLORTERM=truecolor if your terminal really supports true colors."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/color-slang.h b/lib/tty/color-slang.h
new file mode 100644
index 0000000..a1a8d55
--- /dev/null
+++ b/lib/tty/color-slang.h
@@ -0,0 +1,56 @@
+
+/** \file color-slang.h
+ * \brief Header: S-Lang-specific color setup
+ */
+
+#ifndef MC__COLOR_SLANG_H
+#define MC__COLOR_SLANG_H
+
+#include "tty-slang.h" /* S-Lang headers */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* When using Slang with color, we have all the indexes free but
+ * those defined here (A_BOLD, A_ITALIC, A_UNDERLINE, A_REVERSE, A_BLINK)
+ */
+
+#ifndef A_BOLD
+#define A_BOLD SLTT_BOLD_MASK
+#endif /* A_BOLD */
+#ifdef SLTT_ITALIC_MASK /* available since slang-pre2.3.0-107 */
+#ifndef A_ITALIC
+#define A_ITALIC SLTT_ITALIC_MASK
+#endif /* A_ITALIC */
+#endif /* SLTT_ITALIC_MASK */
+#ifndef A_UNDERLINE
+#define A_UNDERLINE SLTT_ULINE_MASK
+#endif /* A_UNDERLINE */
+#ifndef A_REVERSE
+#define A_REVERSE SLTT_REV_MASK
+#endif /* A_REVERSE */
+#ifndef A_BLINK
+#define A_BLINK SLTT_BLINK_MASK
+#endif /* A_BLINK */
+
+/*** enums ***************************************************************************************/
+
+enum
+{
+ COLOR_BLACK = 0,
+ COLOR_RED,
+ COLOR_GREEN,
+ COLOR_YELLOW,
+ COLOR_BLUE,
+ COLOR_MAGENTA,
+ COLOR_CYAN,
+ COLOR_WHITE
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_COLOR_SLANG_H */
diff --git a/lib/tty/color.c b/lib/tty/color.c
new file mode 100644
index 0000000..c79e13a
--- /dev/null
+++ b/lib/tty/color.c
@@ -0,0 +1,244 @@
+/*
+ Color setup.
+ Interface functions.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Egmont Koblinger <egmont@gmail.com>, 2010
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file color.c
+ * \brief Source: color setup
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h> /* size_t */
+
+#include "lib/global.h"
+
+#include "tty.h"
+#include "color.h"
+
+#include "color-internal.h"
+
+/*** global variables ****************************************************************************/
+
+static char *tty_color_defaults__fg = NULL;
+static char *tty_color_defaults__bg = NULL;
+static char *tty_color_defaults__attrs = NULL;
+
+/* Set if we are actually using colors */
+gboolean use_colors = FALSE;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GHashTable *mc_tty_color__hashtable = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tty_color_free_condition_cb (gpointer key, gpointer value, gpointer user_data)
+{
+ tty_color_pair_t *mc_color_pair = (tty_color_pair_t *) value;
+ gboolean is_temp_color;
+
+ (void) key;
+
+ is_temp_color = user_data != NULL;
+ return (mc_color_pair->is_temp == is_temp_color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tty_color_free_all (gboolean is_temp_color)
+{
+ g_hash_table_foreach_remove (mc_tty_color__hashtable, tty_color_free_condition_cb,
+ is_temp_color ? GSIZE_TO_POINTER (1) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tty_color_get_next_cpn_cb (gpointer key, gpointer value, gpointer user_data)
+{
+ tty_color_pair_t *mc_color_pair = (tty_color_pair_t *) value;
+ size_t cp = GPOINTER_TO_SIZE (user_data);
+
+ (void) key;
+
+ return (cp == mc_color_pair->pair_index);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static size_t
+tty_color_get_next__color_pair_number (void)
+{
+ size_t cp_count, cp;
+
+ cp_count = g_hash_table_size (mc_tty_color__hashtable);
+ for (cp = 0; cp < cp_count; cp++)
+ if (g_hash_table_find (mc_tty_color__hashtable, tty_color_get_next_cpn_cb,
+ GSIZE_TO_POINTER (cp)) == NULL)
+ break;
+
+ return cp;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_init_colors (gboolean disable, gboolean force)
+{
+ tty_color_init_lib (disable, force);
+ mc_tty_color__hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_colors_done (void)
+{
+ tty_color_deinit_lib ();
+ g_free (tty_color_defaults__fg);
+ g_free (tty_color_defaults__bg);
+ g_free (tty_color_defaults__attrs);
+
+ g_hash_table_destroy (mc_tty_color__hashtable);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tty_use_colors (void)
+{
+ return use_colors;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_try_alloc_color_pair2 (const char *fg, const char *bg, const char *attrs,
+ gboolean is_temp_color)
+{
+ gchar *color_pair;
+ tty_color_pair_t *mc_color_pair;
+ int ifg, ibg, attr;
+
+ if (fg == NULL || strcmp (fg, "base") == 0)
+ fg = tty_color_defaults__fg;
+ if (bg == NULL || strcmp (bg, "base") == 0)
+ bg = tty_color_defaults__bg;
+ if (attrs == NULL || strcmp (attrs, "base") == 0)
+ attrs = tty_color_defaults__attrs;
+
+ ifg = tty_color_get_index_by_name (fg);
+ ibg = tty_color_get_index_by_name (bg);
+ attr = tty_attr_get_bits (attrs);
+
+ color_pair = g_strdup_printf ("%d.%d.%d", ifg, ibg, attr);
+ if (color_pair == NULL)
+ return 0;
+
+ mc_color_pair =
+ (tty_color_pair_t *) g_hash_table_lookup (mc_tty_color__hashtable, (gpointer) color_pair);
+
+ if (mc_color_pair != NULL)
+ {
+ g_free (color_pair);
+ return mc_color_pair->pair_index;
+ }
+
+ mc_color_pair = g_try_new0 (tty_color_pair_t, 1);
+ if (mc_color_pair == NULL)
+ {
+ g_free (color_pair);
+ return 0;
+ }
+
+ mc_color_pair->is_temp = is_temp_color;
+ mc_color_pair->ifg = ifg;
+ mc_color_pair->ibg = ibg;
+ mc_color_pair->attr = attr;
+ mc_color_pair->pair_index = tty_color_get_next__color_pair_number ();
+
+ tty_color_try_alloc_pair_lib (mc_color_pair);
+
+ g_hash_table_insert (mc_tty_color__hashtable, (gpointer) color_pair, (gpointer) mc_color_pair);
+
+ return mc_color_pair->pair_index;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_try_alloc_color_pair (const char *fg, const char *bg, const char *attrs)
+{
+ return tty_try_alloc_color_pair2 (fg, bg, attrs, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_free_all_tmp (void)
+{
+ tty_color_free_all (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_free_all_non_tmp (void)
+{
+ tty_color_free_all (FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_color_set_defaults (const char *fgcolor, const char *bgcolor, const char *attrs)
+{
+ g_free (tty_color_defaults__fg);
+ g_free (tty_color_defaults__bg);
+ g_free (tty_color_defaults__attrs);
+
+ tty_color_defaults__fg = g_strdup (fgcolor);
+ tty_color_defaults__bg = g_strdup (bgcolor);
+ tty_color_defaults__attrs = g_strdup (attrs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/color.h b/lib/tty/color.h
new file mode 100644
index 0000000..583cce3
--- /dev/null
+++ b/lib/tty/color.h
@@ -0,0 +1,54 @@
+/** \file color.h
+ * \brief Header: color setup
+ *
+ * PLEASE FORGOT ABOUT tty/color.h!
+ * Use skin engine for getting needed color pairs.
+ *
+ * edit/syntax.c may use this file directly, I'm agree. :)
+ *
+ */
+
+#ifndef MC__COLOR_H
+#define MC__COLOR_H
+
+#include "lib/global.h" /* glib.h */
+
+#ifdef HAVE_SLANG
+#include "color-slang.h"
+#else
+#include "tty-ncurses.h"
+#endif
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void tty_init_colors (gboolean disable, gboolean force);
+void tty_colors_done (void);
+
+gboolean tty_use_colors (void);
+int tty_try_alloc_color_pair (const char *fg, const char *bg, const char *attrs);
+int tty_try_alloc_color_pair2 (const char *fg, const char *bg, const char *attrs,
+ gboolean is_temp_color);
+
+void tty_color_free_all_tmp (void);
+void tty_color_free_all_non_tmp (void);
+
+void tty_setcolor (int color);
+void tty_lowlevel_setcolor (int color);
+void tty_set_normal_attrs (void);
+
+void tty_color_set_defaults (const char *fgcolor, const char *bgcolor, const char *attrs);
+
+extern gboolean tty_use_256colors (GError ** error);
+extern gboolean tty_use_truecolors (GError ** error);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__COLOR_H */
diff --git a/lib/tty/key.c b/lib/tty/key.c
new file mode 100644
index 0000000..5671666
--- /dev/null
+++ b/lib/tty/key.c
@@ -0,0 +1,2252 @@
+/*
+ Keyboard support routines.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Norbert Warmuth, 1997
+ Denys Vlasenko <vda.linux@googlemail.com>, 2013
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Egmont Koblinger <egmont@gmail.com>, 2013
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file key.c
+ * \brief Source: keyboard support routines
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include "lib/global.h"
+
+#include "lib/vfs/vfs.h"
+
+#include "tty.h"
+#include "tty-internal.h" /* mouse_enabled */
+#include "mouse.h"
+#include "key.h"
+
+#include "lib/widget.h" /* mc_refresh() */
+
+#ifdef HAVE_TEXTMODE_X11_SUPPORT
+#include "x11conn.h"
+#endif
+
+#ifdef __linux__
+#if defined(__GLIBC__) && (__GLIBC__ < 2)
+#include <linux/termios.h> /* TIOCLINUX */
+#else
+#include <termios.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#endif /* __linux__ */
+
+#ifdef __CYGWIN__
+#include <termios.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#endif /* __CYGWIN__ */
+
+#ifdef __QNXNTO__
+#include <dlfcn.h>
+#include <Ph.h>
+#include <sys/dcmd_chr.h>
+#endif /* __QNXNTO__ */
+
+/*** global variables ****************************************************************************/
+
+int mou_auto_repeat = 100; /* ms */
+int double_click_speed = 250; /* ms */
+gboolean old_esc_mode = TRUE;
+/* timeout for old_esc_mode in usec */
+int old_esc_mode_timeout = G_USEC_PER_SEC; /* us, settable via env */
+gboolean use_8th_bit_as_meta = FALSE;
+
+gboolean bracketed_pasting_in_progress = FALSE;
+
+/* This table is a mapping between names and the constants we use
+ * We use this to allow users to define alternate definitions for
+ * certain keys that may be missing from the terminal database
+ */
+const key_code_name_t key_name_conv_tab[] = {
+ {ESC_CHAR, "escape", N_("Escape"), "Esc"},
+ /* KEY_F(0) is not here, since we are mapping it to f10, so there is no reason
+ to define f0 as well. Also, it makes Learn keys a bunch of problems :( */
+ {KEY_F (1), "f1", N_("Function key 1"), "F1"},
+ {KEY_F (2), "f2", N_("Function key 2"), "F2"},
+ {KEY_F (3), "f3", N_("Function key 3"), "F3"},
+ {KEY_F (4), "f4", N_("Function key 4"), "F4"},
+ {KEY_F (5), "f5", N_("Function key 5"), "F5"},
+ {KEY_F (6), "f6", N_("Function key 6"), "F6"},
+ {KEY_F (7), "f7", N_("Function key 7"), "F7"},
+ {KEY_F (8), "f8", N_("Function key 8"), "F8"},
+ {KEY_F (9), "f9", N_("Function key 9"), "F9"},
+ {KEY_F (10), "f10", N_("Function key 10"), "F10"},
+ {KEY_F (11), "f11", N_("Function key 11"), "F11"},
+ {KEY_F (12), "f12", N_("Function key 12"), "F12"},
+ {KEY_F (13), "f13", N_("Function key 13"), "F13"},
+ {KEY_F (14), "f14", N_("Function key 14"), "F14"},
+ {KEY_F (15), "f15", N_("Function key 15"), "F15"},
+ {KEY_F (16), "f16", N_("Function key 16"), "F16"},
+ {KEY_F (17), "f17", N_("Function key 17"), "F17"},
+ {KEY_F (18), "f18", N_("Function key 18"), "F18"},
+ {KEY_F (19), "f19", N_("Function key 19"), "F19"},
+ {KEY_F (20), "f20", N_("Function key 20"), "F20"},
+ {ALT ('\t'), "complete", N_("Completion/M-tab"), "Meta-Tab"},
+ {KEY_BTAB, "backtab", N_("BackTab/S-tab"), "Shift-Tab"},
+ {KEY_BACKSPACE, "backspace", N_("Backspace"), "Backspace"},
+ {KEY_UP, "up", N_("Up arrow"), "Up"},
+ {KEY_DOWN, "down", N_("Down arrow"), "Down"},
+ {KEY_LEFT, "left", N_("Left arrow"), "Left"},
+ {KEY_RIGHT, "right", N_("Right arrow"), "Right"},
+ {KEY_IC, "insert", N_("Insert"), "Ins"},
+ {KEY_DC, "delete", N_("Delete"), "Del"},
+ {KEY_HOME, "home", N_("Home"), "Home"},
+ {KEY_END, "end", N_("End key"), "End"},
+ {KEY_PPAGE, "pgup", N_("Page Up"), "PgUp"},
+ {KEY_NPAGE, "pgdn", N_("Page Down"), "PgDn"},
+ {(int) '/', "kpslash", N_("/ on keypad"), "/"},
+ {KEY_KP_MULTIPLY, "kpasterisk", N_("* on keypad"), "*"},
+ {KEY_KP_SUBTRACT, "kpminus", N_("- on keypad"), "-"},
+ {KEY_KP_ADD, "kpplus", N_("+ on keypad"), "+"},
+
+ /* From here on, these won't be shown in Learn keys (no space) */
+ {KEY_LEFT, "kpleft", N_("Left arrow keypad"), "Left"},
+ {KEY_RIGHT, "kpright", N_("Right arrow keypad"), "Right"},
+ {KEY_UP, "kpup", N_("Up arrow keypad"), "Up"},
+ {KEY_DOWN, "kpdown", N_("Down arrow keypad"), "Down"},
+ {KEY_HOME, "kphome", N_("Home on keypad"), "Home"},
+ {KEY_END, "kpend", N_("End on keypad"), "End"},
+ {KEY_NPAGE, "kpnpage", N_("Page Down keypad"), "PgDn"},
+ {KEY_PPAGE, "kpppage", N_("Page Up keypad"), "PgUp"},
+ {KEY_IC, "kpinsert", N_("Insert on keypad"), "Ins"},
+ {KEY_DC, "kpdelete", N_("Delete on keypad"), "Del"},
+ {(int) '\n', "kpenter", N_("Enter on keypad"), "Enter"},
+ {KEY_F (21), "f21", N_("Function key 21"), "F21"},
+ {KEY_F (22), "f22", N_("Function key 22"), "F22"},
+ {KEY_F (23), "f23", N_("Function key 23"), "F23"},
+ {KEY_F (24), "f24", N_("Function key 24"), "F24"},
+ {KEY_A1, "a1", N_("A1 key"), "A1"},
+ {KEY_C1, "c1", N_("C1 key"), "C1"},
+
+ /* Alternative label */
+ {ESC_CHAR, "esc", N_("Escape"), "Esc"},
+ {KEY_BACKSPACE, "bs", N_("Backspace"), "Bakspace"},
+ {KEY_IC, "ins", N_("Insert"), "Ins"},
+ {KEY_DC, "del", N_("Delete"), "Del"},
+ {(int) '*', "asterisk", N_("Asterisk"), "*"},
+ {(int) '-', "minus", N_("Minus"), "-"},
+ {(int) '+', "plus", N_("Plus"), "+"},
+ {(int) '.', "dot", N_("Dot"), "."},
+ {(int) '<', "lt", N_("Less than"), "<"},
+ {(int) '>', "gt", N_("Great than"), ">"},
+ {(int) '=', "equal", N_("Equal"), "="},
+ {(int) ',', "comma", N_("Comma"), ","},
+ {(int) '\'', "apostrophe", N_("Apostrophe"), "\'"},
+ {(int) ':', "colon", N_("Colon"), ":"},
+ {(int) ';', "semicolon", N_("Semicolon"), ";"},
+ {(int) '!', "exclamation", N_("Exclamation mark"), "!"},
+ {(int) '?', "question", N_("Question mark"), "?"},
+ {(int) '&', "ampersand", N_("Ampersand"), "&"},
+ {(int) '$', "dollar", N_("Dollar sign"), "$"},
+ {(int) '"', "quota", N_("Quotation mark"), "\""},
+ {(int) '%', "percent", N_("Percent sign"), "%"},
+ {(int) '^', "caret", N_("Caret"), "^"},
+ {(int) '~', "tilda", N_("Tilda"), "~"},
+ {(int) '`', "prime", N_("Prime"), "`"},
+ {(int) '_', "underline", N_("Underline"), "_"},
+ {(int) '_', "understrike", N_("Understrike"), "_"},
+ {(int) '|', "pipe", N_("Pipe"), "|"},
+ {(int) '(', "lparenthesis", N_("Left parenthesis"), "("},
+ {(int) ')', "rparenthesis", N_("Right parenthesis"), ")"},
+ {(int) '[', "lbracket", N_("Left bracket"), "["},
+ {(int) ']', "rbracket", N_("Right bracket"), "]"},
+ {(int) '{', "lbrace", N_("Left brace"), "{"},
+ {(int) '}', "rbrace", N_("Right brace"), "}"},
+ {(int) '\n', "enter", N_("Enter"), "Enter"},
+ {(int) '\t', "tab", N_("Tab key"), "Tab"},
+ {(int) ' ', "space", N_("Space key"), "Space"},
+ {(int) '/', "slash", N_("Slash key"), "/"},
+ {(int) '\\', "backslash", N_("Backslash key"), "\\"},
+ {(int) '#', "number", N_("Number sign #"), "#"},
+ {(int) '#', "hash", N_("Number sign #"), "#"},
+ /* TRANSLATORS: Please translate as in "at sign" (@). */
+ {(int) '@', "at", N_("At sign"), "@"},
+
+ /* meta keys */
+ {KEY_M_CTRL, "control", N_("Ctrl"), "C"},
+ {KEY_M_CTRL, "ctrl", N_("Ctrl"), "C"},
+ {KEY_M_ALT, "meta", N_("Alt"), "M"},
+ {KEY_M_ALT, "alt", N_("Alt"), "M"},
+ {KEY_M_ALT, "ralt", N_("Alt"), "M"},
+ {KEY_M_SHIFT, "shift", N_("Shift"), "S"},
+
+ {0, NULL, NULL, NULL}
+};
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MC_USEC_PER_MSEC 1000
+
+/* The maximum sequence length (32 + null terminator) */
+#define SEQ_BUFFER_LEN 33
+
+/*** file scope type declarations ****************************************************************/
+
+/* Linux console keyboard modifiers */
+typedef enum
+{
+ SHIFT_PRESSED = (1 << 0),
+ ALTR_PRESSED = (1 << 1),
+ CONTROL_PRESSED = (1 << 2),
+ ALTL_PRESSED = (1 << 3)
+} mod_pressed_t;
+
+typedef struct key_def
+{
+ char ch; /* Holds the matching char code */
+ int code; /* The code returned, valid if child == NULL */
+ struct key_def *next;
+ struct key_def *child; /* sequence continuation */
+ int action; /* optional action to be done. Now used only
+ to mark that we are just after the first
+ Escape */
+} key_def;
+
+typedef struct
+{
+ int code;
+ const char *seq;
+ int action;
+} key_define_t;
+
+/* File descriptor monitoring add/remove routines */
+typedef struct
+{
+ int fd;
+ select_fn callback;
+ void *info;
+} select_t;
+
+typedef enum KeySortType
+{
+ KEY_NOSORT = 0,
+ KEY_SORTBYNAME,
+ KEY_SORTBYCODE
+} KeySortType;
+
+#ifdef __QNXNTO__
+typedef int (*ph_dv_f) (void *, void *);
+typedef int (*ph_ov_f) (void *);
+typedef int (*ph_pqc_f) (unsigned short, PhCursorInfo_t *);
+#endif
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static key_define_t mc_default_keys[] = {
+ {ESC_CHAR, ESC_STR, MCKEY_ESCAPE},
+ {ESC_CHAR, ESC_STR ESC_STR, MCKEY_NOACTION},
+ {MCKEY_BRACKETED_PASTING_START, ESC_STR "[200~", MCKEY_NOACTION},
+ {MCKEY_BRACKETED_PASTING_END, ESC_STR "[201~", MCKEY_NOACTION},
+ {0, NULL, MCKEY_NOACTION},
+};
+
+/* Broken terminfo and termcap databases on xterminals */
+static key_define_t xterm_key_defines[] = {
+ {KEY_F (1), ESC_STR "OP", MCKEY_NOACTION},
+ {KEY_F (2), ESC_STR "OQ", MCKEY_NOACTION},
+ {KEY_F (3), ESC_STR "OR", MCKEY_NOACTION},
+ {KEY_F (4), ESC_STR "OS", MCKEY_NOACTION},
+ {KEY_F (1), ESC_STR "[11~", MCKEY_NOACTION},
+ {KEY_F (2), ESC_STR "[12~", MCKEY_NOACTION},
+ {KEY_F (3), ESC_STR "[13~", MCKEY_NOACTION},
+ {KEY_F (4), ESC_STR "[14~", MCKEY_NOACTION},
+ {KEY_F (5), ESC_STR "[15~", MCKEY_NOACTION},
+ {KEY_F (6), ESC_STR "[17~", MCKEY_NOACTION},
+ {KEY_F (7), ESC_STR "[18~", MCKEY_NOACTION},
+ {KEY_F (8), ESC_STR "[19~", MCKEY_NOACTION},
+ {KEY_F (9), ESC_STR "[20~", MCKEY_NOACTION},
+ {KEY_F (10), ESC_STR "[21~", MCKEY_NOACTION},
+
+ /* old xterm Shift-arrows */
+ {KEY_M_SHIFT | KEY_UP, ESC_STR "O2A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_DOWN, ESC_STR "O2B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_RIGHT, ESC_STR "O2C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_LEFT, ESC_STR "O2D", MCKEY_NOACTION},
+
+ /* new xterm Shift-arrows */
+ {KEY_M_SHIFT | KEY_UP, ESC_STR "[1;2A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_DOWN, ESC_STR "[1;2B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_RIGHT, ESC_STR "[1;2C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_LEFT, ESC_STR "[1;2D", MCKEY_NOACTION},
+
+ /* more xterm keys with modifiers */
+ {KEY_M_CTRL | KEY_PPAGE, ESC_STR "[5;5~", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_NPAGE, ESC_STR "[6;5~", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_IC, ESC_STR "[2;5~", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_DC, ESC_STR "[3;5~", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_HOME, ESC_STR "[1;5H", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_END, ESC_STR "[1;5F", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_HOME, ESC_STR "[1;2H", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_END, ESC_STR "[1;2F", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_UP, ESC_STR "[1;5A", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_DOWN, ESC_STR "[1;5B", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_RIGHT, ESC_STR "[1;5C", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_LEFT, ESC_STR "[1;5D", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_IC, ESC_STR "[2;2~", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_DC, ESC_STR "[3;2~", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_UP, ESC_STR "[1;6A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_DOWN, ESC_STR "[1;6B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_RIGHT, ESC_STR "[1;6C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_LEFT, ESC_STR "[1;6D", MCKEY_NOACTION},
+ {KEY_M_SHIFT | '\t', ESC_STR "[Z", MCKEY_NOACTION},
+
+ /* putty */
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_UP, ESC_STR "[[1;6A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_DOWN, ESC_STR "[[1;6B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_RIGHT, ESC_STR "[[1;6C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_LEFT, ESC_STR "[[1;6D", MCKEY_NOACTION},
+
+ /* putty alt-arrow keys */
+ /* removed as source esc esc esc trouble */
+ /*
+ { KEY_M_ALT | KEY_UP, ESC_STR ESC_STR "OA", MCKEY_NOACTION },
+ { KEY_M_ALT | KEY_DOWN, ESC_STR ESC_STR "OB", MCKEY_NOACTION },
+ { KEY_M_ALT | KEY_RIGHT, ESC_STR ESC_STR "OC", MCKEY_NOACTION },
+ { KEY_M_ALT | KEY_LEFT, ESC_STR ESC_STR "OD", MCKEY_NOACTION },
+ { KEY_M_ALT | KEY_PPAGE, ESC_STR ESC_STR "[5~", MCKEY_NOACTION },
+ { KEY_M_ALT | KEY_NPAGE, ESC_STR ESC_STR "[6~", MCKEY_NOACTION },
+ { KEY_M_ALT | KEY_HOME, ESC_STR ESC_STR "[1~", MCKEY_NOACTION },
+ { KEY_M_ALT | KEY_END, ESC_STR ESC_STR "[4~", MCKEY_NOACTION },
+
+ { KEY_M_CTRL | KEY_M_ALT | KEY_UP, ESC_STR ESC_STR "[1;2A", MCKEY_NOACTION },
+ { KEY_M_CTRL | KEY_M_ALT | KEY_DOWN, ESC_STR ESC_STR "[1;2B", MCKEY_NOACTION },
+ { KEY_M_CTRL | KEY_M_ALT | KEY_RIGHT, ESC_STR ESC_STR "[1;2C", MCKEY_NOACTION },
+ { KEY_M_CTRL | KEY_M_ALT | KEY_LEFT, ESC_STR ESC_STR "[1;2D", MCKEY_NOACTION },
+
+ { KEY_M_CTRL | KEY_M_ALT | KEY_PPAGE, ESC_STR ESC_STR "[[5;5~", MCKEY_NOACTION },
+ { KEY_M_CTRL | KEY_M_ALT | KEY_NPAGE, ESC_STR ESC_STR "[[6;5~", MCKEY_NOACTION },
+ { KEY_M_CTRL | KEY_M_ALT | KEY_HOME, ESC_STR ESC_STR "[1;5H", MCKEY_NOACTION },
+ { KEY_M_CTRL | KEY_M_ALT | KEY_END, ESC_STR ESC_STR "[1;5F", MCKEY_NOACTION },
+ */
+ /* xterm alt-arrow keys */
+ {KEY_M_ALT | KEY_UP, ESC_STR "[1;3A", MCKEY_NOACTION},
+ {KEY_M_ALT | KEY_DOWN, ESC_STR "[1;3B", MCKEY_NOACTION},
+ {KEY_M_ALT | KEY_RIGHT, ESC_STR "[1;3C", MCKEY_NOACTION},
+ {KEY_M_ALT | KEY_LEFT, ESC_STR "[1;3D", MCKEY_NOACTION},
+ {KEY_M_ALT | KEY_PPAGE, ESC_STR "[5;3~", MCKEY_NOACTION},
+ {KEY_M_ALT | KEY_NPAGE, ESC_STR "[6;3~", MCKEY_NOACTION},
+ {KEY_M_ALT | KEY_HOME, ESC_STR "[1~", MCKEY_NOACTION},
+ {KEY_M_ALT | KEY_END, ESC_STR "[4~", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_UP, ESC_STR "[1;7A", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_DOWN, ESC_STR "[1;7B", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_RIGHT, ESC_STR "[1;7C", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_LEFT, ESC_STR "[1;7D", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_PPAGE, ESC_STR "[5;7~", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_NPAGE, ESC_STR "[6;7~", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_HOME, ESC_STR "OH", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_M_ALT | KEY_END, ESC_STR "OF", MCKEY_NOACTION},
+
+ {KEY_M_SHIFT | KEY_M_ALT | KEY_UP, ESC_STR "[1;4A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_ALT | KEY_DOWN, ESC_STR "[1;4B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_ALT | KEY_RIGHT, ESC_STR "[1;4C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_ALT | KEY_LEFT, ESC_STR "[1;4D", MCKEY_NOACTION},
+
+ /* rxvt keys with modifiers */
+ {KEY_M_SHIFT | KEY_UP, ESC_STR "[a", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_DOWN, ESC_STR "[b", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_RIGHT, ESC_STR "[c", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_LEFT, ESC_STR "[d", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_UP, ESC_STR "Oa", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_DOWN, ESC_STR "Ob", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_RIGHT, ESC_STR "Oc", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_LEFT, ESC_STR "Od", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_PPAGE, ESC_STR "[5^", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_NPAGE, ESC_STR "[6^", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_HOME, ESC_STR "[7^", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_END, ESC_STR "[8^", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_HOME, ESC_STR "[7$", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_END, ESC_STR "[8$", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_IC, ESC_STR "[2^", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_DC, ESC_STR "[3^", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_DC, ESC_STR "[3$", MCKEY_NOACTION},
+
+ /* konsole keys with modifiers */
+ {KEY_M_SHIFT | KEY_HOME, ESC_STR "O2H", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_END, ESC_STR "O2F", MCKEY_NOACTION},
+
+ /* gnome-terminal */
+ {KEY_M_SHIFT | KEY_UP, ESC_STR "[2A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_DOWN, ESC_STR "[2B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_RIGHT, ESC_STR "[2C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_LEFT, ESC_STR "[2D", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_UP, ESC_STR "[5A", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_DOWN, ESC_STR "[5B", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_RIGHT, ESC_STR "[5C", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_LEFT, ESC_STR "[5D", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_UP, ESC_STR "[6A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_DOWN, ESC_STR "[6B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_RIGHT, ESC_STR "[6C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_LEFT, ESC_STR "[6D", MCKEY_NOACTION},
+
+ /* gnome-terminal - application mode */
+ {KEY_M_CTRL | KEY_UP, ESC_STR "O5A", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_DOWN, ESC_STR "O5B", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_RIGHT, ESC_STR "O5C", MCKEY_NOACTION},
+ {KEY_M_CTRL | KEY_LEFT, ESC_STR "O5D", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_UP, ESC_STR "O6A", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_DOWN, ESC_STR "O6B", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_RIGHT, ESC_STR "O6C", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_M_CTRL | KEY_LEFT, ESC_STR "O6D", MCKEY_NOACTION},
+
+ /* iTerm */
+ {KEY_M_SHIFT | KEY_PPAGE, ESC_STR "[5;2~", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_NPAGE, ESC_STR "[6;2~", MCKEY_NOACTION},
+
+ /* putty */
+ {KEY_M_SHIFT | KEY_PPAGE, ESC_STR "[[5;53~", MCKEY_NOACTION},
+ {KEY_M_SHIFT | KEY_NPAGE, ESC_STR "[[6;53~", MCKEY_NOACTION},
+
+ /* keypad keys */
+ {KEY_IC, ESC_STR "Op", MCKEY_NOACTION},
+ {KEY_DC, ESC_STR "On", MCKEY_NOACTION},
+ {'/', ESC_STR "Oo", MCKEY_NOACTION},
+ {'\n', ESC_STR "OM", MCKEY_NOACTION},
+
+ {0, NULL, MCKEY_NOACTION},
+};
+
+/* qansi-m terminals have a much more key combinations,
+ which are undefined in termcap/terminfo */
+static key_define_t qansi_key_defines[] = {
+ /* qansi-m terminal */
+ {KEY_M_CTRL | KEY_NPAGE, ESC_STR "[u", MCKEY_NOACTION}, /* Ctrl-PgDown */
+ {KEY_M_CTRL | KEY_PPAGE, ESC_STR "[v", MCKEY_NOACTION}, /* Ctrl-PgUp */
+ {KEY_M_CTRL | KEY_HOME, ESC_STR "[h", MCKEY_NOACTION}, /* Ctrl-Home */
+ {KEY_M_CTRL | KEY_END, ESC_STR "[y", MCKEY_NOACTION}, /* Ctrl-End */
+ {KEY_M_CTRL | KEY_IC, ESC_STR "[`", MCKEY_NOACTION}, /* Ctrl-Insert */
+ {KEY_M_CTRL | KEY_DC, ESC_STR "[p", MCKEY_NOACTION}, /* Ctrl-Delete */
+ {KEY_M_CTRL | KEY_LEFT, ESC_STR "[d", MCKEY_NOACTION}, /* Ctrl-Left */
+ {KEY_M_CTRL | KEY_RIGHT, ESC_STR "[c", MCKEY_NOACTION}, /* Ctrl-Right */
+ {KEY_M_CTRL | KEY_DOWN, ESC_STR "[b", MCKEY_NOACTION}, /* Ctrl-Down */
+ {KEY_M_CTRL | KEY_UP, ESC_STR "[a", MCKEY_NOACTION}, /* Ctrl-Up */
+ {KEY_M_CTRL | KEY_KP_ADD, ESC_STR "[s", MCKEY_NOACTION}, /* Ctrl-Gr-Plus */
+ {KEY_M_CTRL | KEY_KP_SUBTRACT, ESC_STR "[t", MCKEY_NOACTION}, /* Ctrl-Gr-Minus */
+ {KEY_M_CTRL | '\t', ESC_STR "[z", MCKEY_NOACTION}, /* Ctrl-Tab */
+ {KEY_M_SHIFT | '\t', ESC_STR "[Z", MCKEY_NOACTION}, /* Shift-Tab */
+ {KEY_M_CTRL | KEY_F (1), ESC_STR "[1~", MCKEY_NOACTION}, /* Ctrl-F1 */
+ {KEY_M_CTRL | KEY_F (2), ESC_STR "[2~", MCKEY_NOACTION}, /* Ctrl-F2 */
+ {KEY_M_CTRL | KEY_F (3), ESC_STR "[3~", MCKEY_NOACTION}, /* Ctrl-F3 */
+ {KEY_M_CTRL | KEY_F (4), ESC_STR "[4~", MCKEY_NOACTION}, /* Ctrl-F4 */
+ {KEY_M_CTRL | KEY_F (5), ESC_STR "[5~", MCKEY_NOACTION}, /* Ctrl-F5 */
+ {KEY_M_CTRL | KEY_F (6), ESC_STR "[6~", MCKEY_NOACTION}, /* Ctrl-F6 */
+ {KEY_M_CTRL | KEY_F (7), ESC_STR "[7~", MCKEY_NOACTION}, /* Ctrl-F7 */
+ {KEY_M_CTRL | KEY_F (8), ESC_STR "[8~", MCKEY_NOACTION}, /* Ctrl-F8 */
+ {KEY_M_CTRL | KEY_F (9), ESC_STR "[9~", MCKEY_NOACTION}, /* Ctrl-F9 */
+ {KEY_M_CTRL | KEY_F (10), ESC_STR "[10~", MCKEY_NOACTION}, /* Ctrl-F10 */
+ {KEY_M_CTRL | KEY_F (11), ESC_STR "[11~", MCKEY_NOACTION}, /* Ctrl-F11 */
+ {KEY_M_CTRL | KEY_F (12), ESC_STR "[12~", MCKEY_NOACTION}, /* Ctrl-F12 */
+ {KEY_M_ALT | KEY_F (1), ESC_STR "[17~", MCKEY_NOACTION}, /* Alt-F1 */
+ {KEY_M_ALT | KEY_F (2), ESC_STR "[18~", MCKEY_NOACTION}, /* Alt-F2 */
+ {KEY_M_ALT | KEY_F (3), ESC_STR "[19~", MCKEY_NOACTION}, /* Alt-F3 */
+ {KEY_M_ALT | KEY_F (4), ESC_STR "[20~", MCKEY_NOACTION}, /* Alt-F4 */
+ {KEY_M_ALT | KEY_F (5), ESC_STR "[21~", MCKEY_NOACTION}, /* Alt-F5 */
+ {KEY_M_ALT | KEY_F (6), ESC_STR "[22~", MCKEY_NOACTION}, /* Alt-F6 */
+ {KEY_M_ALT | KEY_F (7), ESC_STR "[23~", MCKEY_NOACTION}, /* Alt-F7 */
+ {KEY_M_ALT | KEY_F (8), ESC_STR "[24~", MCKEY_NOACTION}, /* Alt-F8 */
+ {KEY_M_ALT | KEY_F (9), ESC_STR "[25~", MCKEY_NOACTION}, /* Alt-F9 */
+ {KEY_M_ALT | KEY_F (10), ESC_STR "[26~", MCKEY_NOACTION}, /* Alt-F10 */
+ {KEY_M_ALT | KEY_F (11), ESC_STR "[27~", MCKEY_NOACTION}, /* Alt-F11 */
+ {KEY_M_ALT | KEY_F (12), ESC_STR "[28~", MCKEY_NOACTION}, /* Alt-F12 */
+ {KEY_M_ALT | 'a', ESC_STR "Na", MCKEY_NOACTION}, /* Alt-a */
+ {KEY_M_ALT | 'b', ESC_STR "Nb", MCKEY_NOACTION}, /* Alt-b */
+ {KEY_M_ALT | 'c', ESC_STR "Nc", MCKEY_NOACTION}, /* Alt-c */
+ {KEY_M_ALT | 'd', ESC_STR "Nd", MCKEY_NOACTION}, /* Alt-d */
+ {KEY_M_ALT | 'e', ESC_STR "Ne", MCKEY_NOACTION}, /* Alt-e */
+ {KEY_M_ALT | 'f', ESC_STR "Nf", MCKEY_NOACTION}, /* Alt-f */
+ {KEY_M_ALT | 'g', ESC_STR "Ng", MCKEY_NOACTION}, /* Alt-g */
+ {KEY_M_ALT | 'h', ESC_STR "Nh", MCKEY_NOACTION}, /* Alt-h */
+ {KEY_M_ALT | 'i', ESC_STR "Ni", MCKEY_NOACTION}, /* Alt-i */
+ {KEY_M_ALT | 'j', ESC_STR "Nj", MCKEY_NOACTION}, /* Alt-j */
+ {KEY_M_ALT | 'k', ESC_STR "Nk", MCKEY_NOACTION}, /* Alt-k */
+ {KEY_M_ALT | 'l', ESC_STR "Nl", MCKEY_NOACTION}, /* Alt-l */
+ {KEY_M_ALT | 'm', ESC_STR "Nm", MCKEY_NOACTION}, /* Alt-m */
+ {KEY_M_ALT | 'n', ESC_STR "Nn", MCKEY_NOACTION}, /* Alt-n */
+ {KEY_M_ALT | 'o', ESC_STR "No", MCKEY_NOACTION}, /* Alt-o */
+ {KEY_M_ALT | 'p', ESC_STR "Np", MCKEY_NOACTION}, /* Alt-p */
+ {KEY_M_ALT | 'q', ESC_STR "Nq", MCKEY_NOACTION}, /* Alt-q */
+ {KEY_M_ALT | 'r', ESC_STR "Nr", MCKEY_NOACTION}, /* Alt-r */
+ {KEY_M_ALT | 's', ESC_STR "Ns", MCKEY_NOACTION}, /* Alt-s */
+ {KEY_M_ALT | 't', ESC_STR "Nt", MCKEY_NOACTION}, /* Alt-t */
+ {KEY_M_ALT | 'u', ESC_STR "Nu", MCKEY_NOACTION}, /* Alt-u */
+ {KEY_M_ALT | 'v', ESC_STR "Nv", MCKEY_NOACTION}, /* Alt-v */
+ {KEY_M_ALT | 'w', ESC_STR "Nw", MCKEY_NOACTION}, /* Alt-w */
+ {KEY_M_ALT | 'x', ESC_STR "Nx", MCKEY_NOACTION}, /* Alt-x */
+ {KEY_M_ALT | 'y', ESC_STR "Ny", MCKEY_NOACTION}, /* Alt-y */
+ {KEY_M_ALT | 'z', ESC_STR "Nz", MCKEY_NOACTION}, /* Alt-z */
+ {KEY_KP_SUBTRACT, ESC_STR "[S", MCKEY_NOACTION}, /* Gr-Minus */
+ {KEY_KP_ADD, ESC_STR "[T", MCKEY_NOACTION}, /* Gr-Plus */
+ {0, NULL, MCKEY_NOACTION},
+};
+
+/* This holds all the key definitions */
+static key_def *keys = NULL;
+
+static int input_fd;
+static int disabled_channels = 0; /* Disable channels checking */
+
+static GSList *select_list = NULL;
+
+static int seq_buffer[SEQ_BUFFER_LEN];
+static int *seq_append = NULL;
+
+static int *pending_keys = NULL;
+
+#ifdef __QNXNTO__
+ph_dv_f ph_attach;
+ph_ov_f ph_input_group;
+ph_pqc_f ph_query_cursor;
+#endif
+
+#ifdef HAVE_TEXTMODE_X11_SUPPORT
+static Display *x11_display;
+static Window x11_window;
+#endif /* HAVE_TEXTMODE_X11_SUPPORT */
+
+static KeySortType has_been_sorted = KEY_NOSORT;
+
+/* *INDENT-OFF* */
+static const size_t key_conv_tab_size = G_N_ELEMENTS (key_name_conv_tab) - 1;
+/* *INDENT-ON* */
+
+static const key_code_name_t *key_conv_tab_sorted[G_N_ELEMENTS (key_name_conv_tab) - 1];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+select_cmp_by_fd_set (gconstpointer a, gconstpointer b)
+{
+ const select_t *s = (const select_t *) a;
+ const fd_set *f = (const fd_set *) b;
+
+ return (FD_ISSET (s->fd, f) ? 0 : 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+select_cmp_by_fd (gconstpointer a, gconstpointer b)
+{
+ const select_t *s = (const select_t *) a;
+ const int fd = GPOINTER_TO_INT (b);
+
+ return (s->fd == fd ? 0 : 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+add_selects (fd_set * select_set)
+{
+ int top_fd = 0;
+
+ if (disabled_channels == 0)
+ {
+ GSList *s;
+
+ for (s = select_list; s != NULL; s = g_slist_next (s))
+ {
+ select_t *p = (select_t *) s->data;
+
+ FD_SET (p->fd, select_set);
+ if (p->fd > top_fd)
+ top_fd = p->fd;
+ }
+ }
+
+ return top_fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_selects (fd_set * select_set)
+{
+ while (disabled_channels == 0)
+ {
+ GSList *s;
+ select_t *p;
+
+ s = g_slist_find_custom (select_list, select_set, select_cmp_by_fd_set);
+ if (s == NULL)
+ break;
+
+ p = (select_t *) s->data;
+ FD_CLR (p->fd, select_set);
+ p->callback (p->fd, p->info);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* If set timeout is set, then we wait 0.1 seconds, else, we block */
+
+static void
+try_channels (gboolean set_timeout)
+{
+ struct timeval time_out;
+ static fd_set select_set;
+
+ while (TRUE)
+ {
+ struct timeval *timeptr = NULL;
+ int maxfdp, v;
+
+ FD_ZERO (&select_set);
+ FD_SET (input_fd, &select_set); /* Add stdin */
+ maxfdp = MAX (add_selects (&select_set), input_fd);
+
+ if (set_timeout)
+ {
+ time_out.tv_sec = 0;
+ time_out.tv_usec = 100 * MC_USEC_PER_MSEC;
+ timeptr = &time_out;
+ }
+
+ v = select (maxfdp + 1, &select_set, NULL, NULL, timeptr);
+ if (v > 0)
+ {
+ check_selects (&select_set);
+ if (FD_ISSET (input_fd, &select_set))
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static key_def *
+create_sequence (const char *seq, int code, int action)
+{
+ key_def *base, *p, *attach;
+
+ for (base = attach = NULL; *seq != '\0'; seq++)
+ {
+ p = g_new (key_def, 1);
+ if (base == NULL)
+ base = p;
+ if (attach != NULL)
+ attach->child = p;
+
+ p->ch = *seq;
+ p->code = code;
+ p->child = p->next = NULL;
+ if (seq[1] == '\0')
+ p->action = action;
+ else
+ p->action = MCKEY_NOACTION;
+ attach = p;
+ }
+ return base;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+define_sequences (const key_define_t * kd)
+{
+ int i;
+
+ for (i = 0; kd[i].code != 0; i++)
+ define_sequence (kd[i].code, kd[i].seq, kd[i].action);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_TEXTMODE_X11_SUPPORT
+static void
+init_key_x11 (void)
+{
+ if (getenv ("DISPLAY") != NULL && !mc_global.tty.disable_x11)
+ {
+ x11_display = mc_XOpenDisplay (0);
+
+ if (x11_display != NULL)
+ x11_window = DefaultRootWindow (x11_display);
+ }
+}
+#endif /* HAVE_TEXTMODE_X11_SUPPORT */
+
+/* --------------------------------------------------------------------------------------------- */
+/* Workaround for System V Curses vt100 bug */
+
+static int
+getch_with_delay (void)
+{
+ int c;
+
+ /* This routine could be used on systems without mouse support,
+ so we need to do the select check :-( */
+ while (TRUE)
+ {
+ if (pending_keys == NULL)
+ try_channels (FALSE);
+
+ /* Try to get a character */
+ c = get_key_code (0);
+ if (c != -1)
+ break;
+
+ /* Failed -> wait 0.1 secs and try again */
+ try_channels (TRUE);
+ }
+
+ /* Success -> return the character */
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+xmouse_get_event (Gpm_Event * ev, gboolean extended)
+{
+ static gint64 tv1 = 0; /* Force first click as single */
+ static int clicks = 0;
+ static int last_btn = 0;
+ int btn;
+
+ /* Decode Xterm mouse information to a GPM style event */
+
+ if (!extended)
+ {
+ /* Variable btn has following meaning: */
+ /* 0 = btn1 dn, 1 = btn2 dn, 2 = btn3 dn, 3 = btn up */
+ btn = tty_lowlevel_getch () - 32;
+ /* Coordinates are 33-based */
+ /* Transform them to 1-based */
+ ev->x = tty_lowlevel_getch () - 32;
+ ev->y = tty_lowlevel_getch () - 32;
+ }
+ else
+ {
+ /* SGR 1006 extension (e.g. "\e[<0;12;300M"):
+ - Numbers are encoded in decimal to make it ASCII-safe
+ and to overcome the limit of 223 columns/rows.
+ - Mouse release is encoded by trailing 'm' rather than 'M'
+ so that the released button can be reported.
+ - Numbers are no longer offset by 32. */
+ char c;
+
+ btn = ev->x = ev->y = 0;
+ ev->type = 0; /* In case we return on an invalid sequence */
+
+ while ((c = tty_lowlevel_getch ()) != ';')
+ {
+ if (c < '0' || c > '9')
+ return;
+ btn = 10 * btn + (c - '0');
+ }
+ while ((c = tty_lowlevel_getch ()) != ';')
+ {
+ if (c < '0' || c > '9')
+ return;
+ ev->x = 10 * ev->x + (c - '0');
+ }
+ while ((c = tty_lowlevel_getch ()) != 'M' && c != 'm')
+ {
+ if (c < '0' || c > '9')
+ return;
+ ev->y = 10 * ev->y + (c - '0');
+ }
+ /* Legacy mouse protocol doesn't tell which button was released,
+ conveniently all of mc's widgets are written not to rely on this
+ information. With the SGR extension the released button becomes
+ known, but for the sake of simplicity we just ignore it. */
+ if (c == 'm')
+ btn = 3;
+ }
+
+ /* There seems to be no way of knowing which button was released */
+ /* So we assume all the buttons were released */
+
+ if (btn == 3)
+ {
+ if (last_btn != 0)
+ {
+ if ((last_btn & (GPM_B_UP | GPM_B_DOWN)) != 0)
+ {
+ /* FIXME: DIRTY HACK */
+ /* don't generate GPM_UP after mouse wheel */
+ /* need for menu event handling */
+ ev->type = 0;
+ tv1 = 0;
+ }
+ else
+ {
+ ev->type = GPM_UP | (GPM_SINGLE << clicks);
+ tv1 = g_get_monotonic_time ();
+ }
+ ev->buttons = 0;
+ last_btn = 0;
+ clicks = 0;
+ }
+ else
+ {
+ /* Bogus event, maybe mouse wheel */
+ ev->type = 0;
+ }
+ }
+ else
+ {
+ gint64 tv2;
+
+ if (btn >= 32 && btn <= 34)
+ {
+ btn -= 32;
+ ev->type = GPM_DRAG;
+ }
+ else
+ ev->type = GPM_DOWN;
+
+ tv2 = g_get_monotonic_time ();
+ if (tv1 != 0 && tv2 - tv1 < (gint64) double_click_speed * MC_USEC_PER_MSEC)
+ {
+ clicks++;
+ clicks %= 3;
+ }
+ else
+ clicks = 0;
+
+ switch (btn)
+ {
+ case 0:
+ ev->buttons = GPM_B_LEFT;
+ break;
+ case 1:
+ ev->buttons = GPM_B_MIDDLE;
+ break;
+ case 2:
+ ev->buttons = GPM_B_RIGHT;
+ break;
+ case 64:
+ ev->buttons = GPM_B_UP;
+ clicks = 0;
+ break;
+ case 65:
+ ev->buttons = GPM_B_DOWN;
+ clicks = 0;
+ break;
+ default:
+ /* Nothing */
+ ev->type = 0;
+ ev->buttons = 0;
+ break;
+ }
+ last_btn = ev->buttons;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get modifier state (shift, alt, ctrl) for the last key pressed.
+ * We are assuming that the state didn't change since the key press.
+ * This is only correct if get_modifier() is called very fast after
+ * the input was received, so that the user didn't release the
+ * modifier keys yet.
+ */
+
+static int
+get_modifier (void)
+{
+ int result = 0;
+#ifdef __QNXNTO__
+ static int in_photon = 0;
+ static int ph_ig = 0;
+#endif /* __QNXNTO__ */
+
+#ifdef HAVE_TEXTMODE_X11_SUPPORT
+ if (x11_window != 0)
+ {
+ Window root, child;
+ int root_x, root_y;
+ int win_x, win_y;
+ unsigned int mask;
+
+ mc_XQueryPointer (x11_display, x11_window, &root, &child, &root_x,
+ &root_y, &win_x, &win_y, &mask);
+
+ if ((mask & ShiftMask) != 0)
+ result |= KEY_M_SHIFT;
+ if ((mask & ControlMask) != 0)
+ result |= KEY_M_CTRL;
+ return result;
+ }
+#endif /* HAVE_TEXTMODE_X11_SUPPORT */
+
+#ifdef __QNXNTO__
+ if (in_photon == 0)
+ {
+ /* First time here, let's load Photon library and attach to Photon */
+ in_photon = -1;
+
+ if (getenv ("PHOTON2_PATH") != NULL)
+ {
+ /* QNX 6.x has no support for RTLD_LAZY */
+ void *ph_handle;
+
+ ph_handle = dlopen ("/usr/lib/libph.so", RTLD_NOW);
+ if (ph_handle != NULL)
+ {
+ ph_attach = (ph_dv_f) dlsym (ph_handle, "PhAttach");
+ ph_input_group = (ph_ov_f) dlsym (ph_handle, "PhInputGroup");
+ ph_query_cursor = (ph_pqc_f) dlsym (ph_handle, "PhQueryCursor");
+ if ((ph_attach != NULL) && (ph_input_group != NULL) && (ph_query_cursor != NULL)
+ && (*ph_attach) (0, 0) != NULL)
+ {
+ /* Attached */
+ ph_ig = (*ph_input_group) (0);
+ in_photon = 1;
+ }
+ }
+ }
+ }
+ /* We do not have Photon running. Assume we are in text console or xterm */
+ if (in_photon == -1)
+ {
+ int mod_status;
+ int shift_ext_status;
+
+ if (devctl (fileno (stdin), DCMD_CHR_LINESTATUS, &mod_status, sizeof (mod_status), NULL) ==
+ -1)
+ return 0;
+
+ shift_ext_status = mod_status & 0xffffff00UL;
+ mod_status &= 0x7f;
+ if ((mod_status & _LINESTATUS_CON_ALT) != 0)
+ result |= KEY_M_ALT;
+ if ((mod_status & _LINESTATUS_CON_CTRL) != 0)
+ result |= KEY_M_CTRL;
+ if ((mod_status & _LINESTATUS_CON_SHIFT) != 0 || (shift_ext_status & 0x00000800UL) != 0)
+ result |= KEY_M_SHIFT;
+ }
+ else
+ {
+ PhCursorInfo_t cursor_info;
+
+ (*ph_query_cursor) (ph_ig, &cursor_info);
+ if ((cursor_info.key_mods & 0x04) != 0)
+ result |= KEY_M_ALT;
+ if ((cursor_info.key_mods & 0x02) != 0)
+ result |= KEY_M_CTRL;
+ if ((cursor_info.key_mods & 0x01) != 0)
+ result |= KEY_M_SHIFT;
+ }
+#endif /* __QNXNTO__ */
+
+#if defined __linux__ || (defined __CYGWIN__ && defined TIOCLINUX)
+ {
+ unsigned char modifiers = 6;
+
+ if (ioctl (0, TIOCLINUX, &modifiers) < 0)
+ return 0;
+
+ /* Translate Linux modifiers into mc modifiers */
+ if ((modifiers & SHIFT_PRESSED) != 0)
+ result |= KEY_M_SHIFT;
+ if ((modifiers & (ALTL_PRESSED | ALTR_PRESSED)) != 0)
+ result |= KEY_M_ALT;
+ if ((modifiers & CONTROL_PRESSED) != 0)
+ result |= KEY_M_CTRL;
+ }
+#endif /* !__linux__ */
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+push_char (int c)
+{
+ gboolean ret = FALSE;
+
+ if (seq_append == NULL)
+ seq_append = seq_buffer;
+
+ if (seq_append != &(seq_buffer[SEQ_BUFFER_LEN - 2]))
+ {
+ *(seq_append++) = c;
+ *seq_append = '\0';
+ ret = TRUE;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Apply corrections for the keycode generated in get_key_code() */
+
+static int
+correct_key_code (int code)
+{
+ unsigned int c = code & ~KEY_M_MASK; /* code without modifier */
+ unsigned int mod = code & KEY_M_MASK; /* modifier */
+#ifdef __QNXNTO__
+ unsigned int qmod; /* bunch of the QNX console
+ modifiers needs unchanged */
+#endif /* __QNXNTO__ */
+
+ /*
+ * Add key modifiers directly from X11 or OS.
+ * Ordinary characters only get modifiers from sequences.
+ */
+ if (c < 32 || c >= 256)
+ mod |= get_modifier ();
+
+ /* This is needed if the newline is reported as carriage return */
+ if (c == '\r')
+ c = '\n';
+
+ /* This is reported to be useful on AIX */
+ if (c == KEY_SCANCEL)
+ c = '\t';
+
+ /* Convert Back Tab to Shift+Tab */
+ if (c == KEY_BTAB)
+ {
+ c = '\t';
+ mod = KEY_M_SHIFT;
+ }
+
+ /* F0 is the same as F10 for out purposes */
+ if (c == KEY_F (0))
+ c = KEY_F (10);
+
+ /*
+ * We are not interested if Ctrl was pressed when entering control
+ * characters, so assume that it was. When checking for such keys,
+ * XCTRL macro should be used. In some cases, we are interested,
+ * e.g. to distinguish Ctrl-Enter from Enter.
+ */
+ if (c == '\b')
+ {
+ /* Special case for backspase ('\b' < 32) */
+ c = KEY_BACKSPACE;
+ mod &= ~KEY_M_CTRL;
+ }
+ else if (c < 32 && c != ESC_CHAR && c != '\t' && c != '\n')
+ mod |= KEY_M_CTRL;
+
+#ifdef __QNXNTO__
+ qmod = get_modifier ();
+
+ if (c == 127 && mod == 0)
+ {
+ /* Add Ctrl/Alt/Shift-BackSpace */
+ mod |= get_modifier ();
+ c = KEY_BACKSPACE;
+ }
+
+ if (c == '0' && mod == 0 && (qmod & KEY_M_SHIFT) == KEY_M_SHIFT)
+ {
+ /* Add Shift-Insert on key pad */
+ mod = KEY_M_SHIFT;
+ c = KEY_IC;
+ }
+
+ if (c == '.' && mod == 0 && (qmod & KEY_M_SHIFT) == KEY_M_SHIFT)
+ {
+ /* Add Shift-Del on key pad */
+ mod = KEY_M_SHIFT;
+ c = KEY_DC;
+ }
+#endif /* __QNXNTO__ */
+
+ /* Unrecognized 0177 is delete (preserve Ctrl) */
+ if (c == 0177)
+ c = KEY_BACKSPACE;
+
+#if 0
+ /* Unrecognized Ctrl-d is delete */
+ if (c == 'd' & 31)
+ {
+ c = KEY_DC;
+ mod &= ~KEY_M_CTRL;
+ }
+
+ /* Unrecognized Ctrl-h is backspace */
+ if (c == 'h' & 31)
+ {
+ c = KEY_BACKSPACE;
+ mod &= ~KEY_M_CTRL;
+ }
+#endif
+
+ /* Shift+BackSpace is backspace */
+ if (c == KEY_BACKSPACE && (mod & KEY_M_SHIFT) != 0)
+ mod &= ~KEY_M_SHIFT;
+
+ /* Convert Shift+Fn to F(n+10) */
+ if (c >= KEY_F (1) && c <= KEY_F (10) && (mod & KEY_M_SHIFT) != 0)
+ c += 10;
+
+ /* Remove Shift information from function keys */
+ if (c >= KEY_F (1) && c <= KEY_F (20))
+ mod &= ~KEY_M_SHIFT;
+
+ if (!mc_global.tty.alternate_plus_minus)
+ switch (c)
+ {
+ case KEY_KP_ADD:
+ c = '+';
+ break;
+ case KEY_KP_SUBTRACT:
+ c = '-';
+ break;
+ case KEY_KP_MULTIPLY:
+ c = '*';
+ break;
+ default:
+ break;
+ }
+
+ return (mod | c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+getch_with_timeout (unsigned int delay_us)
+{
+ fd_set Read_FD_Set;
+ int c;
+ struct timeval time_out;
+
+ time_out.tv_sec = delay_us / G_USEC_PER_SEC;
+ time_out.tv_usec = delay_us % G_USEC_PER_SEC;
+ tty_nodelay (TRUE);
+ FD_ZERO (&Read_FD_Set);
+ FD_SET (input_fd, &Read_FD_Set);
+ select (input_fd + 1, &Read_FD_Set, NULL, NULL, &time_out);
+ c = tty_lowlevel_getch ();
+ tty_nodelay (FALSE);
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+learn_store_key (char *buffer, char **p, int c)
+{
+ if (*p - buffer > 253)
+ return;
+
+ if (c == ESC_CHAR)
+ {
+ *(*p)++ = '\\';
+ *(*p)++ = 'e';
+ }
+ else if (c < ' ')
+ {
+ *(*p)++ = '^';
+ *(*p)++ = c + 'a' - 1;
+ }
+ else if (c == '^')
+ {
+ *(*p)++ = '^';
+ *(*p)++ = '^';
+ }
+ else
+ *(*p)++ = (char) c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+k_dispose (key_def * k)
+{
+ if (k != NULL)
+ {
+ k_dispose (k->child);
+ k_dispose (k->next);
+ g_free (k);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+key_code_comparator_by_name (const void *p1, const void *p2)
+{
+ const key_code_name_t *n1 = *(const key_code_name_t * const *) p1;
+ const key_code_name_t *n2 = *(const key_code_name_t * const *) p2;
+
+ return g_ascii_strcasecmp (n1->name, n2->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+key_code_comparator_by_code (const void *p1, const void *p2)
+{
+ const key_code_name_t *n1 = *(const key_code_name_t * const *) p1;
+ const key_code_name_t *n2 = *(const key_code_name_t * const *) p2;
+
+ return n1->code - n2->code;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+sort_key_conv_tab (enum KeySortType type_sort)
+{
+ if (has_been_sorted != type_sort)
+ {
+ size_t i;
+
+ for (i = 0; i < key_conv_tab_size; i++)
+ key_conv_tab_sorted[i] = &key_name_conv_tab[i];
+
+ if (type_sort == KEY_SORTBYNAME)
+ qsort (key_conv_tab_sorted, key_conv_tab_size, sizeof (key_conv_tab_sorted[0]),
+ &key_code_comparator_by_name);
+ else if (type_sort == KEY_SORTBYCODE)
+ qsort (key_conv_tab_sorted, key_conv_tab_size, sizeof (key_conv_tab_sorted[0]),
+ &key_code_comparator_by_code);
+
+ has_been_sorted = type_sort;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+lookup_keyname (const char *name, int *idx)
+{
+ if (name[0] != '\0')
+ {
+ const key_code_name_t key = { 0, name, NULL, NULL };
+ const key_code_name_t *keyp = &key;
+ const key_code_name_t **res;
+
+ if (name[1] == '\0')
+ {
+ *idx = -1;
+ return (int) name[0];
+ }
+
+ sort_key_conv_tab (KEY_SORTBYNAME);
+
+ res = bsearch (&keyp, key_conv_tab_sorted, key_conv_tab_size,
+ sizeof (key_conv_tab_sorted[0]), key_code_comparator_by_name);
+
+ if (res != NULL)
+ {
+ *idx = (int) (res - key_conv_tab_sorted);
+ return (*res)->code;
+ }
+ }
+
+ *idx = -1;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+lookup_keycode (const long code, int *idx)
+{
+ if (code != 0)
+ {
+ const key_code_name_t key = { code, NULL, NULL, NULL };
+ const key_code_name_t *keyp = &key;
+ const key_code_name_t **res;
+
+ sort_key_conv_tab (KEY_SORTBYCODE);
+
+ res = bsearch (&keyp, key_conv_tab_sorted, key_conv_tab_size,
+ sizeof (key_conv_tab_sorted[0]), key_code_comparator_by_code);
+
+ if (res != NULL)
+ {
+ *idx = (int) (res - key_conv_tab_sorted);
+ return TRUE;
+ }
+ }
+
+ *idx = -1;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/* This has to be called before init_slang or whatever routine
+ calls any define_sequence */
+
+void
+init_key (void)
+{
+ const char *term;
+
+ term = getenv ("TERM");
+
+ /* This has to be the first define_sequence */
+ /* So, we can assume that the first keys member has ESC */
+ define_sequences (mc_default_keys);
+
+ /* Terminfo on irix does not have some keys */
+ if (mc_global.tty.xterm_flag
+ || (term != NULL
+ && (strncmp (term, "iris-ansi", 9) == 0
+ || strncmp (term, "xterm", 5) == 0
+ || strncmp (term, "rxvt", 4) == 0 || strncmp (term, "screen", 6) == 0)))
+ define_sequences (xterm_key_defines);
+
+ /* load some additional keys (e.g. direct Alt-? support) */
+ load_xtra_key_defines ();
+
+#ifdef __QNX__
+ if ((term != NULL) && (strncmp (term, "qnx", 3) == 0))
+ {
+ /* Modify the default value of use_8th_bit_as_meta: we would
+ * like to provide a working mc for a newbie who knows nothing
+ * about [Options|Display bits|Full 8 bits input]...
+ *
+ * Don't use 'meta'-bit, when we are dealing with a
+ * 'qnx*'-type terminal: clear the default value!
+ * These terminal types use 0xFF as an escape character,
+ * so use_8th_bit_as_meta==1 must not be enabled!
+ *
+ * [mc-4.1.21+,slint.c/getch(): the DEC_8BIT_HACK stuff
+ * is not used now (doesn't even depend on use_8th_bit_as_meta
+ * as in mc-3.1.2)...GREAT!...no additional code is required!]
+ */
+ use_8th_bit_as_meta = FALSE;
+ }
+#endif /* __QNX__ */
+
+#ifdef HAVE_TEXTMODE_X11_SUPPORT
+ init_key_x11 ();
+#endif
+
+ /* Load the qansi-m key definitions
+ if we are running under the qansi-m terminal */
+ if (term != NULL && (strncmp (term, "qansi-m", 7) == 0))
+ define_sequences (qansi_key_defines);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This has to be called after SLang_init_tty/slint_init
+ */
+
+void
+init_key_input_fd (void)
+{
+#ifdef HAVE_SLANG
+ input_fd = SLang_TT_Read_FD;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+done_key (void)
+{
+ k_dispose (keys);
+ g_slist_free_full (select_list, g_free);
+
+#ifdef HAVE_TEXTMODE_X11_SUPPORT
+ if (x11_display)
+ mc_XCloseDisplay (x11_display);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+add_select_channel (int fd, select_fn callback, void *info)
+{
+ select_t *new;
+
+ new = g_new (select_t, 1);
+ new->fd = fd;
+ new->callback = callback;
+ new->info = info;
+
+ select_list = g_slist_prepend (select_list, new);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+delete_select_channel (int fd)
+{
+ GSList *p;
+
+ p = g_slist_find_custom (select_list, GINT_TO_POINTER (fd), select_cmp_by_fd);
+ if (p != NULL)
+ select_list = g_slist_delete_link (select_list, p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+channels_up (void)
+{
+ if (disabled_channels == 0)
+ fputs ("Error: channels_up called with disabled_channels = 0\n", stderr);
+ disabled_channels--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+channels_down (void)
+{
+ disabled_channels++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return the code associated with the symbolic name keyname
+ */
+
+long
+tty_keyname_to_keycode (const char *name, char **label)
+{
+ char **lc_keys, **p;
+ char *cname;
+ int k = -1;
+ int key = 0;
+ int lc_index = -1;
+
+ int use_meta = -1;
+ int use_ctrl = -1;
+ int use_shift = -1;
+
+ if (name == NULL)
+ return 0;
+
+ cname = g_strstrip (g_strdup (name));
+ lc_keys = g_strsplit_set (cname, "-+ ", -1);
+ g_free (cname);
+
+ for (p = lc_keys; p != NULL && *p != NULL; p++)
+ {
+ if ((*p)[0] != '\0')
+ {
+ int idx;
+
+ key = lookup_keyname (g_strstrip (*p), &idx);
+
+ if (key == KEY_M_ALT)
+ use_meta = idx;
+ else if (key == KEY_M_CTRL)
+ use_ctrl = idx;
+ else if (key == KEY_M_SHIFT)
+ use_shift = idx;
+ else
+ {
+ k = key;
+ lc_index = idx;
+ break;
+ }
+ }
+ }
+
+ g_strfreev (lc_keys);
+
+ /* output */
+ if (k <= 0)
+ return 0;
+
+ if (label != NULL)
+ {
+ GString *s;
+
+ s = g_string_new ("");
+
+ if (use_meta != -1)
+ {
+ g_string_append (s, key_conv_tab_sorted[use_meta]->shortcut);
+ g_string_append_c (s, '-');
+ }
+ if (use_ctrl != -1)
+ {
+ g_string_append (s, key_conv_tab_sorted[use_ctrl]->shortcut);
+ g_string_append_c (s, '-');
+ }
+ if (use_shift != -1)
+ {
+ if (k < 127)
+ g_string_append_c (s, (gchar) g_ascii_toupper ((gchar) k));
+ else
+ {
+ g_string_append (s, key_conv_tab_sorted[use_shift]->shortcut);
+ g_string_append_c (s, '-');
+ g_string_append (s, key_conv_tab_sorted[lc_index]->shortcut);
+ }
+ }
+ else if (k < 128)
+ {
+ if ((k >= 'A') || (lc_index < 0) || (key_conv_tab_sorted[lc_index]->shortcut == NULL))
+ g_string_append_c (s, (gchar) g_ascii_tolower ((gchar) k));
+ else
+ g_string_append (s, key_conv_tab_sorted[lc_index]->shortcut);
+ }
+ else if ((lc_index != -1) && (key_conv_tab_sorted[lc_index]->shortcut != NULL))
+ g_string_append (s, key_conv_tab_sorted[lc_index]->shortcut);
+ else
+ g_string_append_c (s, (gchar) g_ascii_tolower ((gchar) key));
+
+ *label = g_string_free (s, FALSE);
+ }
+
+ if (use_shift != -1)
+ {
+ if (k < 127 && k > 31)
+ k = g_ascii_toupper ((gchar) k);
+ else
+ k |= KEY_M_SHIFT;
+ }
+
+ if (use_ctrl != -1)
+ {
+ if (k < 256)
+ k = XCTRL (k);
+ else
+ k |= KEY_M_CTRL;
+ }
+
+ if (use_meta != -1)
+ k = ALT (k);
+
+ return (long) k;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+tty_keycode_to_keyname (const int keycode)
+{
+ /* code without modifier */
+ unsigned int k = keycode & ~KEY_M_MASK;
+ /* modifier */
+ unsigned int mod = keycode & KEY_M_MASK;
+
+ int key_idx = -1;
+
+ GString *s;
+ int idx;
+
+ s = g_string_sized_new (8);
+
+ if (lookup_keycode (k, &key_idx) || (k > 0 && k < 256))
+ {
+ if ((mod & KEY_M_ALT) != 0 && lookup_keycode (KEY_M_ALT, &idx))
+ {
+ g_string_append (s, key_conv_tab_sorted[idx]->name);
+ g_string_append_c (s, '-');
+ }
+
+ if ((mod & KEY_M_CTRL) != 0)
+ {
+ /* non printeble chars like a CTRL-[A..Z] */
+ if (k < 32)
+ k += 64;
+
+ if (lookup_keycode (KEY_M_CTRL, &idx))
+ {
+ g_string_append (s, key_conv_tab_sorted[idx]->name);
+ g_string_append_c (s, '-');
+ }
+ }
+
+ if ((mod & KEY_M_SHIFT) != 0)
+ {
+ if (lookup_keycode (KEY_M_ALT, &idx))
+ {
+ if (k < 127)
+ g_string_append_c (s, (gchar) g_ascii_toupper ((gchar) k));
+ else
+ {
+ g_string_append (s, key_conv_tab_sorted[idx]->name);
+ g_string_append_c (s, '-');
+ g_string_append (s, key_conv_tab_sorted[key_idx]->name);
+ }
+ }
+ }
+ else if (k < 128)
+ {
+ if ((k >= 'A') || (key_idx < 0) || (key_conv_tab_sorted[key_idx]->name == NULL))
+ g_string_append_c (s, (gchar) k);
+ else
+ g_string_append (s, key_conv_tab_sorted[key_idx]->name);
+ }
+ else if ((key_idx != -1) && (key_conv_tab_sorted[key_idx]->name != NULL))
+ g_string_append (s, key_conv_tab_sorted[key_idx]->name);
+ else
+ g_string_append_c (s, (gchar) keycode);
+ }
+
+ return g_string_free (s, s->len == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return TRUE on success, FALSE on error.
+ * An error happens if SEQ is a beginning of an existing longer sequence.
+ */
+
+gboolean
+define_sequence (int code, const char *seq, int action)
+{
+ key_def *base;
+
+ if (strlen (seq) > SEQ_BUFFER_LEN - 1)
+ return FALSE;
+
+ for (base = keys; (base != NULL) && (*seq != '\0');)
+ if (*seq == base->ch)
+ {
+ if (base->child == NULL)
+ {
+ if (*(seq + 1) != '\0')
+ base->child = create_sequence (seq + 1, code, action);
+ else
+ {
+ /* The sequence matches an existing one. */
+ base->code = code;
+ base->action = action;
+ }
+ return TRUE;
+ }
+
+ base = base->child;
+ seq++;
+ }
+ else
+ {
+ if (base->next != NULL)
+ base = base->next;
+ else
+ {
+ base->next = create_sequence (seq, code, action);
+ return TRUE;
+ }
+ }
+
+ if (*seq == '\0')
+ {
+ /* Attempt to redefine a sequence with a shorter sequence. */
+ return FALSE;
+ }
+
+ keys = create_sequence (seq, code, action);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if we are idle, i.e. there are no pending keyboard or mouse
+ * events. Return 1 is idle, 0 is there are pending events.
+ */
+gboolean
+is_idle (void)
+{
+ int nfd;
+ fd_set select_set;
+ struct timeval time_out;
+
+ FD_ZERO (&select_set);
+ FD_SET (input_fd, &select_set);
+ nfd = MAX (0, input_fd) + 1;
+ time_out.tv_sec = 0;
+ time_out.tv_usec = 0;
+#ifdef HAVE_LIBGPM
+ if (mouse_enabled && use_mouse_p == MOUSE_GPM)
+ {
+ if (gpm_fd >= 0)
+ {
+ FD_SET (gpm_fd, &select_set);
+ nfd = MAX (nfd, gpm_fd + 1);
+ }
+ else
+ {
+ if (mouse_fd >= 0) /* error indicative */
+ {
+ if (FD_ISSET (mouse_fd, &select_set))
+ FD_CLR (mouse_fd, &select_set);
+ mouse_fd = gpm_fd;
+ }
+ /* gpm_fd == -2 means under some X terminal */
+ if (gpm_fd == -1)
+ {
+ mouse_enabled = FALSE;
+ use_mouse_p = MOUSE_NONE;
+ }
+ }
+ }
+#endif
+ return (select (nfd, &select_set, 0, 0, &time_out) <= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+get_key_code (int no_delay)
+{
+ int c;
+ static key_def *this = NULL, *parent;
+ static gint64 esc_time = -1;
+ static int lastnodelay = -1;
+
+ if (no_delay != lastnodelay)
+ {
+ this = NULL;
+ lastnodelay = no_delay;
+ }
+
+ pend_send:
+ if (pending_keys != NULL)
+ {
+ gboolean bad_seq;
+
+ c = *pending_keys++;
+ while (c == ESC_CHAR)
+ c = ALT (*pending_keys++);
+
+ bad_seq = (*pending_keys != ESC_CHAR && *pending_keys != '\0');
+ if (*pending_keys == '\0' || bad_seq)
+ pending_keys = seq_append = NULL;
+
+ if (bad_seq)
+ {
+ /* This is an unknown ESC sequence.
+ * To prevent interpreting its tail as a random garbage,
+ * eat and discard all buffered and quickly following chars.
+ * Small, but non-zero timeout is needed to reconnect
+ * escape sequence split up by e.g. a serial line.
+ */
+ int paranoia = 20;
+
+ while (getch_with_timeout (old_esc_mode_timeout) >= 0 && --paranoia != 0)
+ ;
+ }
+ else
+ {
+ if (c > 127 && c < 256 && use_8th_bit_as_meta)
+ c = ALT (c & 0x7f);
+
+ goto done;
+ }
+ }
+
+ nodelay_try_again:
+ if (no_delay != 0)
+ tty_nodelay (TRUE);
+
+ c = tty_lowlevel_getch ();
+#if (defined(USE_NCURSES) || defined(USE_NCURSESW)) && defined(KEY_RESIZE)
+ if (c == KEY_RESIZE)
+ goto nodelay_try_again;
+#endif
+
+ if (no_delay != 0)
+ {
+ tty_nodelay (FALSE);
+ if (c == -1)
+ {
+ if (this == NULL || parent == NULL || parent->action != MCKEY_ESCAPE || !old_esc_mode ||
+ esc_time == -1 || g_get_monotonic_time () < esc_time + old_esc_mode_timeout)
+ return -1;
+
+ this = NULL;
+ pending_keys = seq_append = NULL;
+ return ESC_CHAR;
+ }
+ }
+ else if (c == -1)
+ {
+ /* Maybe we got an incomplete match.
+ This we do only in delay mode, since otherwise
+ tty_lowlevel_getch can return -1 at any time. */
+ if (seq_append == NULL)
+ {
+ this = NULL;
+ return -1;
+ }
+
+ pending_keys = seq_buffer;
+ goto pend_send;
+ }
+
+ /* Search the key on the root */
+ if (no_delay == 0 || this == NULL)
+ {
+ this = keys;
+ parent = NULL;
+
+ if (c > 127 && c < 256 && use_8th_bit_as_meta)
+ {
+ c &= 0x7f;
+
+ /* The first sequence defined starts with esc */
+ parent = keys;
+ this = keys->child;
+ }
+ }
+
+ while (this != NULL)
+ {
+ if (c == this->ch)
+ {
+ if (this->child == NULL)
+ {
+ /* We got a complete match, return and reset search */
+ pending_keys = seq_append = NULL;
+ c = this->code;
+ goto done;
+ }
+
+ /* No match yet, but it may be a prefix for a valid seq */
+ if (!push_char (c))
+ {
+ pending_keys = seq_buffer;
+ goto pend_send;
+ }
+
+ parent = this;
+ this = this->child;
+ if (parent->action == MCKEY_ESCAPE && old_esc_mode)
+ {
+ if (no_delay != 0)
+ {
+ esc_time = g_get_monotonic_time ();
+ goto nodelay_try_again;
+ }
+
+ esc_time = -1;
+ c = getch_with_timeout (old_esc_mode_timeout);
+ if (c != -1)
+ continue;
+
+ pending_keys = seq_append = NULL;
+ this = NULL;
+ return ESC_CHAR;
+ }
+
+ if (no_delay != 0)
+ goto nodelay_try_again;
+ c = tty_lowlevel_getch ();
+ continue;
+ }
+
+ /* c != this->ch. Try other keys with this prefix */
+ if (this->next != NULL)
+ {
+ this = this->next;
+ continue;
+ }
+
+ /* No match found. Is it one of our ESC <key> specials? */
+ if ((parent != NULL) && (parent->action == MCKEY_ESCAPE))
+ {
+ /* Convert escape-digits to F-keys */
+ if (g_ascii_isdigit (c))
+ c = KEY_F (c - '0');
+ else if (c == ' ')
+ c = ESC_CHAR;
+ else
+ c = ALT (c);
+
+ pending_keys = seq_append = NULL;
+ goto done;
+ }
+
+ /* Unknown sequence. Maybe a prefix of a longer one. Save it. */
+ push_char (c);
+ pending_keys = seq_buffer;
+ goto pend_send;
+ } /* while (this != NULL) */
+
+ done:
+ this = NULL;
+ return correct_key_code (c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Returns a character read from stdin with appropriate interpretation */
+/* Also takes care of generated mouse events */
+/* Returns EV_MOUSE if it is a mouse event */
+/* Returns EV_NONE if non-blocking or interrupt set and nothing was done */
+
+int
+tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block)
+{
+ int c;
+ int flag = 0; /* Return value from select */
+#ifdef HAVE_LIBGPM
+ static struct Gpm_Event ev; /* Mouse event */
+#endif
+ struct timeval time_out;
+ struct timeval *time_addr = NULL;
+ static int dirty = 3;
+
+ if ((dirty == 3) || is_idle ())
+ {
+ mc_refresh ();
+ dirty = 1;
+ }
+ else
+ dirty++;
+
+ vfs_timeout_handler ();
+
+ /* Ok, we use (event->x < 0) to signal that the event does not contain
+ a suitable position for the mouse, so we can't use show_mouse_pointer
+ on it.
+ */
+ if (event->x > 0)
+ {
+ show_mouse_pointer (event->x, event->y);
+ if (!redo_event)
+ event->x = -1;
+ }
+
+ /* Repeat if using mouse */
+ while (pending_keys == NULL)
+ {
+ int nfd;
+ fd_set select_set;
+
+ FD_ZERO (&select_set);
+ FD_SET (input_fd, &select_set);
+ nfd = MAX (add_selects (&select_set), MAX (0, input_fd)) + 1;
+
+#ifdef HAVE_LIBGPM
+ if (mouse_enabled && (use_mouse_p == MOUSE_GPM))
+ {
+ if (gpm_fd >= 0)
+ {
+ FD_SET (gpm_fd, &select_set);
+ nfd = MAX (nfd, gpm_fd + 1);
+ }
+ else
+ {
+ if (mouse_fd >= 0) /* error indicative */
+ {
+ if (FD_ISSET (mouse_fd, &select_set))
+ FD_CLR (mouse_fd, &select_set);
+ mouse_fd = gpm_fd;
+ }
+ /* gpm_fd == -2 means under some X terminal */
+ if (gpm_fd == -1)
+ {
+ mouse_enabled = FALSE;
+ use_mouse_p = MOUSE_NONE;
+ }
+ break;
+ }
+ }
+#endif
+
+ if (redo_event)
+ {
+ time_out.tv_usec = mou_auto_repeat * MC_USEC_PER_MSEC;
+ time_out.tv_sec = 0;
+
+ time_addr = &time_out;
+ }
+ else
+ {
+ int seconds;
+
+ seconds = vfs_timeouts ();
+ time_addr = NULL;
+
+ if (seconds != 0)
+ {
+ /* the timeout could be improved and actually be
+ * the number of seconds until the next vfs entry
+ * timeouts in the stamp list.
+ */
+
+ time_out.tv_sec = seconds;
+ time_out.tv_usec = 0;
+ time_addr = &time_out;
+ }
+ }
+
+ if (!block || tty_got_winch ())
+ {
+ time_addr = &time_out;
+ time_out.tv_sec = 0;
+ time_out.tv_usec = 0;
+ }
+
+ tty_enable_interrupt_key ();
+ flag = select (nfd, &select_set, NULL, NULL, time_addr);
+ tty_disable_interrupt_key ();
+
+ /* select timed out: it could be for any of the following reasons:
+ * redo_event -> it was because of the MOU_REPEAT handler
+ * !block -> we did not block in the select call
+ * else -> 10 second timeout to check the vfs status.
+ */
+ if (flag == 0)
+ {
+ if (redo_event)
+ return EV_MOUSE;
+ if (!block || tty_got_winch ())
+ return EV_NONE;
+ vfs_timeout_handler ();
+ }
+ if (flag == -1 && errno == EINTR)
+ return EV_NONE;
+
+ check_selects (&select_set);
+
+ if (FD_ISSET (input_fd, &select_set))
+ break;
+
+#ifdef HAVE_LIBGPM
+ if (mouse_enabled && use_mouse_p == MOUSE_GPM)
+ {
+ if (gpm_fd >= 0)
+ {
+ if (FD_ISSET (gpm_fd, &select_set))
+ {
+ int status;
+
+ status = Gpm_GetEvent (&ev);
+ if (status == 1) /* success */
+ {
+ Gpm_FitEvent (&ev);
+ *event = ev;
+ return EV_MOUSE;
+ }
+ if (status <= 0) /* connection closed; -1 == error */
+ {
+ if (mouse_fd >= 0 && FD_ISSET (mouse_fd, &select_set))
+ FD_CLR (mouse_fd, &select_set);
+
+ disable_mouse ();
+ return EV_NONE;
+ }
+ }
+ }
+ else
+ {
+ if (mouse_fd >= 0) /* error indicative */
+ {
+ if (FD_ISSET (mouse_fd, &select_set))
+ FD_CLR (mouse_fd, &select_set);
+ mouse_fd = gpm_fd;
+ }
+ /* gpm_fd == -2 means under some X terminal */
+ if (gpm_fd == -1)
+ {
+ mouse_enabled = FALSE;
+ use_mouse_p = MOUSE_NONE;
+ }
+ break;
+ }
+ }
+#endif /* !HAVE_LIBGPM */
+ }
+
+#ifndef HAVE_SLANG
+ flag = is_wintouched (stdscr);
+ untouchwin (stdscr);
+#endif /* !HAVE_SLANG */
+ c = block ? getch_with_delay () : get_key_code (1);
+
+#ifndef HAVE_SLANG
+ if (flag > 0)
+ tty_touch_screen ();
+#endif /* !HAVE_SLANG */
+
+ if (mouse_enabled && (c == MCKEY_MOUSE
+#ifdef KEY_MOUSE
+ || c == KEY_MOUSE
+#endif /* KEY_MOUSE */
+ || c == MCKEY_EXTENDED_MOUSE))
+ {
+ /* Mouse event. See tickets 2956 and 3954 for extended mode detection. */
+ gboolean extended = c == MCKEY_EXTENDED_MOUSE;
+
+#ifdef KEY_MOUSE
+ extended = extended || (c == KEY_MOUSE && xmouse_seq == NULL
+ && xmouse_extended_seq != NULL);
+#endif /* KEY_MOUSE */
+
+ xmouse_get_event (event, extended);
+ c = (event->type != 0) ? EV_MOUSE : EV_NONE;
+ }
+ else if (c == MCKEY_BRACKETED_PASTING_START)
+ {
+ bracketed_pasting_in_progress = TRUE;
+ c = EV_NONE;
+ }
+ else if (c == MCKEY_BRACKETED_PASTING_END)
+ {
+ bracketed_pasting_in_progress = FALSE;
+ c = EV_NONE;
+ }
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Returns a key press, mouse events are discarded */
+
+int
+tty_getch (void)
+{
+ Gpm_Event ev;
+ int key;
+
+ ev.x = -1;
+ while ((key = tty_get_event (&ev, FALSE, TRUE)) == EV_NONE)
+ ;
+ return key;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+learn_key (void)
+{
+ /* LEARN_TIMEOUT in ms */
+#define LEARN_TIMEOUT 200
+
+ fd_set Read_FD_Set;
+ gint64 end_time;
+ int c;
+ char buffer[256];
+ char *p = buffer;
+
+ tty_keypad (FALSE); /* disable interpreting keys by ncurses */
+ c = tty_lowlevel_getch ();
+ while (c == -1)
+ c = tty_lowlevel_getch (); /* Sanity check, should be unnecessary */
+ learn_store_key (buffer, &p, c);
+
+ end_time = g_get_monotonic_time () + LEARN_TIMEOUT * MC_USEC_PER_MSEC;
+
+ tty_nodelay (TRUE);
+ while (TRUE)
+ {
+ while ((c = tty_lowlevel_getch ()) == -1)
+ {
+ gint64 time_out;
+ struct timeval tv;
+
+ time_out = end_time - g_get_monotonic_time ();
+ if (time_out <= 0)
+ break;
+
+ tv.tv_sec = time_out / G_USEC_PER_SEC;
+ tv.tv_usec = time_out % G_USEC_PER_SEC;
+ FD_ZERO (&Read_FD_Set);
+ FD_SET (input_fd, &Read_FD_Set);
+ select (input_fd + 1, &Read_FD_Set, NULL, NULL, &tv);
+ }
+ if (c == -1)
+ break;
+ learn_store_key (buffer, &p, c);
+ }
+ tty_keypad (TRUE);
+ tty_nodelay (FALSE);
+ *p = '\0';
+ return (buffer[0] != '\0' ? g_strdup (buffer) : NULL);
+#undef LEARN_TIMEOUT
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* xterm and linux console only: set keypad to numeric or application
+ mode. Only in application keypad mode it's possible to distinguish
+ the '+' key and the '+' on the keypad ('*' and '-' ditto) */
+
+void
+numeric_keypad_mode (void)
+{
+ if (mc_global.tty.console_flag != '\0' || mc_global.tty.xterm_flag)
+ {
+ fputs (ESC_STR ">", stdout);
+ fflush (stdout);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+application_keypad_mode (void)
+{
+ if (mc_global.tty.console_flag != '\0' || mc_global.tty.xterm_flag)
+ {
+ fputs (ESC_STR "=", stdout);
+ fflush (stdout);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+enable_bracketed_paste (void)
+{
+ printf (ESC_STR "[?2004h");
+ fflush (stdout);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+disable_bracketed_paste (void)
+{
+ printf (ESC_STR "[?2004l");
+ fflush (stdout);
+ bracketed_pasting_in_progress = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/key.h b/lib/tty/key.h
new file mode 100644
index 0000000..6dd2cee
--- /dev/null
+++ b/lib/tty/key.h
@@ -0,0 +1,121 @@
+/** \file key.h
+ * \brief Header: keyboard support routines
+ */
+
+#ifndef MC__KEY_H
+#define MC__KEY_H
+
+#include "lib/global.h" /* <glib.h> */
+#include "tty.h" /* KEY_F macro */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* Possible return values from tty_get_event: */
+#define EV_MOUSE -2
+#define EV_NONE -1
+
+/*
+ * Internal representation of the key modifiers. It is used in the
+ * sequence tables and the keycodes in the mc sources.
+ */
+#define KEY_M_SHIFT 0x1000
+#define KEY_M_ALT 0x2000
+#define KEY_M_CTRL 0x4000
+#define KEY_M_MASK 0x7000
+
+#define XCTRL(x) (KEY_M_CTRL | ((x) & 0x1F))
+#define ALT(x) (KEY_M_ALT | (unsigned int)(x))
+
+/* To define sequences and return codes */
+#define MCKEY_NOACTION 0
+#define MCKEY_ESCAPE 1
+
+/* Return code for the mouse sequence */
+#define MCKEY_MOUSE -2
+
+/* Return code for the extended mouse sequence */
+#define MCKEY_EXTENDED_MOUSE -3
+
+/* Return code for brackets of bracketed paste mode */
+#define MCKEY_BRACKETED_PASTING_START -4
+#define MCKEY_BRACKETED_PASTING_END -5
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ int code;
+ const char *name;
+ const char *longname;
+ const char *shortcut;
+} key_code_name_t;
+
+struct Gpm_Event;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const key_code_name_t key_name_conv_tab[];
+
+extern int old_esc_mode_timeout;
+
+extern int double_click_speed;
+extern gboolean old_esc_mode;
+extern gboolean use_8th_bit_as_meta;
+extern int mou_auto_repeat;
+
+extern gboolean bracketed_pasting_in_progress;
+
+/*** declarations of public functions ************************************************************/
+
+gboolean define_sequence (int code, const char *seq, int action);
+
+void init_key (void);
+void init_key_input_fd (void);
+void done_key (void);
+
+long tty_keyname_to_keycode (const char *name, char **label);
+char *tty_keycode_to_keyname (const int keycode);
+/* mouse support */
+int tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block);
+gboolean is_idle (void);
+int tty_getch (void);
+
+/* While waiting for input, the program can select on more than one file */
+typedef int (*select_fn) (int fd, void *info);
+
+/* Channel manipulation */
+void add_select_channel (int fd, select_fn callback, void *info);
+void delete_select_channel (int fd);
+
+/* Activate/deactivate the channel checking */
+void channels_up (void);
+void channels_down (void);
+
+/* internally used in key.c, defined in keyxtra.c */
+void load_xtra_key_defines (void);
+
+/* Learn a single key */
+char *learn_key (void);
+
+/* Returns a key code (interpreted) */
+int get_key_code (int nodelay);
+
+/* Set keypad mode (xterm and linux console only) */
+void numeric_keypad_mode (void);
+void application_keypad_mode (void);
+
+/* Bracketed paste mode */
+void enable_bracketed_paste (void);
+void disable_bracketed_paste (void);
+
+/*** inline functions ****************************************************************************/
+
+static inline gboolean
+is_abort_char (int c)
+{
+ return ((c == (int) ESC_CHAR) || (c == (int) KEY_F (10)));
+}
+
+#endif /* MC_KEY_H */
diff --git a/lib/tty/keyxdef.c b/lib/tty/keyxdef.c
new file mode 100644
index 0000000..a496f67
--- /dev/null
+++ b/lib/tty/keyxdef.c
@@ -0,0 +1,455 @@
+/* {{{ Copyright */
+
+/*
+ Additional keyboard support routines.
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Gyorgy Tamasi, 1998
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* }}} */
+
+/** \file keyxdef.c
+ * \brief Source: additional keyboard support routines
+ *
+ * PURPOSE:
+ * We would like to support the direct ALT-?/META-? and some other 'extra'
+ * keyboard functionality provided by some terminals under some OSes (and
+ * not supported by the 'learn keys...' facility of 'mc'.
+ * (First target platform: QNX.)
+ *
+ * REMARK:
+ *
+ * Implementation strategy: we don't want to rely on a specific terminal
+ * information database management API (termcap,terminfo,SLang,...), so we
+ * try to define a superset of the possible key identifiers here.
+ *
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+
+#include "tty.h"
+#include "mouse.h" /* required before key.h */
+#include "key.h"
+
+#if defined (__QNX__) && !defined (__QNXNTO__)
+#define HAVE_QNX_KEYS
+#endif
+
+#ifdef HAVE_QNX_KEYS
+
+/* select implementation: use QNX/term interface */
+#define __USE_QNX_TI
+
+/* implementation specific _TE() definition */
+#ifdef __USE_QNX_TI
+
+/* include QNX/term.h (not NCURSES/term.h!) */
+#if __WATCOMC__ > 1000
+#include <sys/term.h>
+#else
+#include <term.h>
+#endif
+#include <stdlib.h> /* getenv() */
+
+/* fieldname -> index conversion */
+#define __QTISX(_qtisn) \
+ (((int)(&((struct _strs*)0)->_qtisn))/sizeof(charoffset))
+
+/* define the OS/implementation-specific __TK() format */
+#define __TK(_tis,_tcs,_tisx,_qtisn) __QTISX(_qtisn)
+
+#endif /* __USE_QNX_TI */
+
+#endif /* HAVE_QNX_KEYS */
+
+
+/* {{{ */
+
+/* general key definitions:
+ *
+ * format:
+ *
+ * terminfo name,
+ * termcap name,
+ * index in the terminfo string table (ncurses),
+ * field name in the QNX terminfo strings struct
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+
+#define Key_backspace __TK("kbs", "kb", 55, _ky_backspace )
+#define Key_catab __TK("ktbc", "ka", 56, _ky_catab )
+#define Key_clear __TK("kclr", "kC", 57, _ky_clear )
+#define Key_ctab __TK("kctab", "kt", 58, _ky_ctab )
+#define Key_dc __TK("kdch1", "kD", 59, _ky_dc )
+#define Key_dl __TK("kdl1", "kL", 60, _ky_dl )
+#define Key_down __TK("kcud1", "kd", 61, _ky_down )
+#define Key_eic __TK("krmir", "kM", 62, _ky_eic )
+#define Key_eol __TK("kel", "kE", 63, _ky_eol )
+#define Key_eos __TK("ked", "kS", 64, _ky_eos )
+#define Key_f0 __TK("kf0", "k0", 65, _ky_f0 )
+#define Key_f1 __TK("kf1", "k1", 66, _ky_f1 )
+#define Key_f10 __TK("kf10", "k;", 67, _ky_f10 )
+#define Key_f2 __TK("kf2", "k2", 68, _ky_f2 )
+#define Key_f3 __TK("kf3", "k3", 69, _ky_f3 )
+#define Key_f4 __TK("kf4", "k4", 70, _ky_f4 )
+#define Key_f5 __TK("kf5", "k5", 71, _ky_f5 )
+#define Key_f6 __TK("kf6", "k6", 72, _ky_f6 )
+#define Key_f7 __TK("kf7", "k7", 73, _ky_f7 )
+#define Key_f8 __TK("kf8", "k8", 74, _ky_f8 )
+#define Key_f9 __TK("kf9", "k9", 75, _ky_f9 )
+#define Key_home __TK("khome", "kh", 76, _ky_home )
+#define Key_ic __TK("kich1", "kI", 77, _ky_ic )
+#define Key_il __TK("kil1", "kA", 78, _ky_il )
+#define Key_left __TK("kcub1", "kl", 79, _ky_left )
+#define Key_ll __TK("kll", "kH", 80, _ky_ll )
+#define Key_npage __TK("knp", "kN", 81, _ky_npage )
+#define Key_ppage __TK("kpp", "kP", 82, _ky_ppage )
+#define Key_right __TK("kcuf1", "kr", 83, _ky_right )
+#define Key_sf __TK("kind", "kF", 84, _ky_sf )
+#define Key_sr __TK("kri", "kR", 85, _ky_sr )
+#define Key_stab __TK("khts", "kT", 86, _ky_stab )
+#define Key_up __TK("kcuu1", "ku", 87, _ky_up )
+#define Key_a1 __TK("ka1", "K1", 139, _ky_a1 )
+#define Key_a3 __TK("ka3", "K3", 140, _ky_a3 )
+#define Key_b2 __TK("kb2", "K2", 141, _ky_b2 )
+#define Key_c1 __TK("kc1", "K4", 142, _ky_c1 )
+#define Key_c3 __TK("kc3", "K5", 143, _ky_c3 )
+#define Key_btab __TK("kcbt", "kB", 148, _ky_btab )
+#define Key_beg __TK("kbeg", "@1", 158, _ky_beg )
+#define Key_cancel __TK("kcan", "@2", 159, _ky_cancel )
+#define Key_close __TK("kclo", "@3", 160, _ky_close )
+#define Key_command __TK("kcmd", "@4", 161, _ky_command )
+#define Key_copy __TK("kcpy", "@5", 162, _ky_copy )
+#define Key_create __TK("kcrt", "@6", 163, _ky_create )
+#define Key_end __TK("kend", "@7", 164, _ky_end )
+#define Key_enter __TK("kent", "@8", 165, _ky_enter )
+#define Key_exit __TK("kext", "@9", 166, _ky_exit )
+#define Key_find __TK("kfnd", "@0", 167, _ky_find )
+#define Key_help __TK("khlp", "%1", 168, _ky_help )
+#define Key_mark __TK("kmrk", "%2", 169, _ky_mark )
+#define Key_message __TK("kmsg", "%3", 170, _ky_message )
+#define Key_move __TK("kmov", "%4", 171, _ky_move )
+#define Key_next __TK("knxt", "%5", 172, _ky_next )
+#define Key_open __TK("kopn", "%6", 173, _ky_open )
+#define Key_options __TK("kopt", "%7", 174, _ky_options )
+#define Key_previous __TK("kprv", "%8", 175, _ky_previous )
+#define Key_print __TK("kprt", "%9", 176, _ky_print )
+#define Key_redo __TK("krdo", "%0", 177, _ky_redo )
+#define Key_reference __TK("kref", "&1", 178, _ky_reference )
+#define Key_refresh __TK("krfr", "&2", 179, _ky_refresh )
+#define Key_replace __TK("krpl", "&3", 180, _ky_replace )
+#define Key_restart __TK("krst", "&4", 181, _ky_restart )
+#define Key_resume __TK("kres", "&5", 182, _ky_resume )
+#define Key_save __TK("ksav", "&6", 183, _ky_save )
+#define Key_suspend __TK("kspd", "&7", 184, _ky_suspend )
+#define Key_undo __TK("kund", "&8", 185, _ky_undo )
+#define Key_sbeg __TK("kBEG", "&9", 186, _ky_sbeg )
+#define Key_scancel __TK("kCAN", "&0", 187, _ky_scancel )
+#define Key_scommand __TK("kCMD", "*1", 188, _ky_scommand )
+#define Key_scopy __TK("kCPY", "*2", 189, _ky_scopy )
+#define Key_screate __TK("kCRT", "*3", 190, _ky_screate )
+#define Key_sdc __TK("kDC", "*4", 191, _ky_sdc )
+#define Key_sdl __TK("kDL", "*5", 192, _ky_sdl )
+#define Key_select __TK("kslt", "*6", 193, _ky_select )
+#define Key_send __TK("kEND", "*7", 194, _ky_send )
+#define Key_seol __TK("kEOL", "*8", 195, _ky_seol )
+#define Key_sexit __TK("kEXT", "*9", 196, _ky_sexit )
+#define Key_sfind __TK("kFND", "*0", 197, _ky_sfind )
+#define Key_shelp __TK("kHLP", "#1", 198, _ky_shelp )
+#define Key_shome __TK("kHOM", "#2", 199, _ky_shome )
+#define Key_sic __TK("kIC", "#3", 200, _ky_sic )
+#define Key_sleft __TK("kLFT", "#4", 201, _ky_sleft )
+#define Key_smessage __TK("kMSG", "%a", 202, _ky_smessage )
+#define Key_smove __TK("kMOV", "%b", 203, _ky_smove )
+#define Key_snext __TK("kNXT", "%c", 204, _ky_snext )
+#define Key_soptions __TK("kOPT", "%d", 205, _ky_soptions )
+#define Key_sprevious __TK("kPRV", "%e", 206, _ky_sprevious )
+#define Key_sprint __TK("kPRT", "%f", 207, _ky_sprint )
+#define Key_sredo __TK("kRDO", "%g", 208, _ky_sredo )
+#define Key_sreplace __TK("kRPL", "%h", 209, _ky_sreplace )
+#define Key_sright __TK("kRIT", "%i", 210, _ky_sright )
+#define Key_srsume __TK("kRES", "%j", 211, _ky_srsume )
+#define Key_ssave __TK("kSAV", "!1", 212, _ky_ssave )
+#define Key_ssuspend __TK("kSPD", "!2", 213, _ky_ssuspend )
+#define Key_sundo __TK("kUND", "!3", 214, _ky_sundo )
+#define Key_f11 __TK("kf11", "F1", 216, _ky_f11 )
+#define Key_f12 __TK("kf12", "F2", 217, _ky_f12 )
+#define Key_f13 __TK("kf13", "F3", 218, _ky_f13 )
+#define Key_f14 __TK("kf14", "F4", 219, _ky_f14 )
+#define Key_f15 __TK("kf15", "F5", 220, _ky_f15 )
+#define Key_f16 __TK("kf16", "F6", 221, _ky_f16 )
+#define Key_f17 __TK("kf17", "F7", 222, _ky_f17 )
+#define Key_f18 __TK("kf18", "F8", 223, _ky_f18 )
+#define Key_f19 __TK("kf19", "F9", 224, _ky_f19 )
+#define Key_f20 __TK("kf20", "FA", 225, _ky_f20 )
+#define Key_f21 __TK("kf21", "FB", 226, _ky_f21 )
+#define Key_f22 __TK("kf22", "FC", 227, _ky_f22 )
+#define Key_f23 __TK("kf23", "FD", 228, _ky_f23 )
+#define Key_f24 __TK("kf24", "FE", 229, _ky_f24 )
+#define Key_f25 __TK("kf25", "FF", 230, _ky_f25 )
+#define Key_f26 __TK("kf26", "FG", 231, _ky_f26 )
+#define Key_f27 __TK("kf27", "FH", 232, _ky_f27 )
+#define Key_f28 __TK("kf28", "FI", 233, _ky_f28 )
+#define Key_f29 __TK("kf29", "FJ", 234, _ky_f29 )
+#define Key_f30 __TK("kf30", "FK", 235, _ky_f30 )
+#define Key_f31 __TK("kf31", "FL", 236, _ky_f31 )
+#define Key_f32 __TK("kf32", "FM", 237, _ky_f32 )
+#define Key_f33 __TK("kf33", "FN", 238, _ky_f33 )
+#define Key_f34 __TK("kf34", "FO", 239, _ky_f34 )
+#define Key_f35 __TK("kf35", "FP", 240, _ky_f35 )
+#define Key_f36 __TK("kf36", "FQ", 241, _ky_f36 )
+#define Key_f37 __TK("kf37", "FR", 242, _ky_f37 )
+#define Key_f38 __TK("kf38", "FS", 243, _ky_f38 )
+#define Key_f39 __TK("kf39", "FT", 244, _ky_f39 )
+#define Key_f40 __TK("kf40", "FU", 245, _ky_f40 )
+#define Key_f41 __TK("kf41", "FV", 246, _ky_f41 )
+#define Key_f42 __TK("kf42", "FW", 247, _ky_f42 )
+#define Key_f43 __TK("kf43", "FX", 248, _ky_f43 )
+#define Key_f44 __TK("kf44", "FY", 249, _ky_f44 )
+#define Key_f45 __TK("kf45", "FZ", 250, _ky_f45 )
+#define Key_f46 __TK("kf46", "Fa", 251, _ky_f46 )
+#define Key_f47 __TK("kf47", "Fb", 252, _ky_f47 )
+#define Key_f48 __TK("kf48", "Fc", 253, _ky_f48 )
+#define Key_f49 __TK("kf49", "Fd", 254, _ky_f49 )
+#define Key_f50 __TK("kf50", "Fe", 255, _ky_f50 )
+#define Key_f51 __TK("kf51", "Ff", 256, _ky_f51 )
+#define Key_f52 __TK("kf52", "Fg", 257, _ky_f52 )
+#define Key_f53 __TK("kf53", "Fh", 258, _ky_f53 )
+#define Key_f54 __TK("kf54", "Fi", 259, _ky_f54 )
+#define Key_f55 __TK("kf55", "Fj", 260, _ky_f55 )
+#define Key_f56 __TK("kf56", "Fk", 261, _ky_f56 )
+#define Key_f57 __TK("kf57", "Fl", 262, _ky_f57 )
+#define Key_f58 __TK("kf58", "Fm", 263, _ky_f58 )
+#define Key_f59 __TK("kf59", "Fn", 264, _ky_f59 )
+#define Key_f60 __TK("kf60", "Fo", 265, _ky_f60 )
+#define Key_f61 __TK("kf61", "Fp", 266, _ky_f61 )
+#define Key_f62 __TK("kf62", "Fq", 267, _ky_f62 )
+#define Key_f63 __TK("kf63", "Fr", 268, _ky_f63 )
+
+/* }}} */
+
+#ifdef HAVE_QNX_KEYS
+
+/* don't force pre-defining of base keys under QNX */
+#define FORCE_BASE_KEY_DEFS 0
+
+/* OS specific key aliases */
+#define Key_alt_a Key_clear
+#define Key_alt_b Key_stab
+#define Key_alt_c Key_close
+#define Key_alt_d Key_catab
+#define Key_alt_e Key_message
+#define Key_alt_f Key_find
+#define Key_alt_g Key_refresh
+#define Key_alt_h Key_help
+#define Key_alt_i Key_move
+#define Key_alt_j Key_restart
+#define Key_alt_k Key_options
+#define Key_alt_l Key_reference
+#define Key_alt_m Key_mark
+#define Key_alt_n Key_sbeg
+#define Key_alt_o Key_open
+#define Key_alt_p Key_resume
+#define Key_alt_q Key_save
+#define Key_alt_r Key_replace
+#define Key_alt_s Key_scopy
+#define Key_alt_t Key_screate
+#define Key_alt_u Key_undo
+#define Key_alt_v Key_sdl
+#define Key_alt_w Key_sexit
+#define Key_alt_x Key_sfind
+#define Key_alt_y Key_shelp
+#define Key_alt_z Key_soptions
+
+#define Key_ctl_enter Key_enter
+#define Key_ctl_tab Key_ctab
+
+#define Key_alt_tab Key_ctl_tab /* map ALT-TAB to CTRL-TAB */
+#define Key_alt_enter Key_ctl_enter /* map ALT-ENTER to CTRL-ENTER */
+
+#ifdef __USE_QNX_TI
+/* define current xtra_key_define_t (enable OS/implementation) */
+#define xtra_key_define_t qnx_key_define_t
+#endif /* __USE_QNX_TI */
+#endif /* HAVE_QNX_KEYS */
+
+
+#ifdef xtra_key_define_t
+#ifndef FORCE_BASE_KEY_DEFS
+#define FORCE_BASE_KEY_DEFS 0
+#endif
+#endif /* xtra_key_define_t */
+
+#ifdef HAVE_QNX_KEYS
+#ifdef __USE_QNX_TI
+#define __CT (__cur_term)
+#define __QTISOFFS(_qtisx) (((charoffset*)(&__CT->_strs))[_qtisx])
+#define __QTISSTR(_qtisx) (&__CT->_strtab[0]+__QTISOFFS(_qtisx))
+#endif /* __USE_QNX_TI */
+#endif /* HAVE_QNX_KEYS */
+
+/*** file scope type declarations ****************************************************************/
+
+#ifdef HAVE_QNX_KEYS
+#ifdef __USE_QNX_TI
+/* OS/implementation specific key-define struct */
+typedef const struct qnx_key_define_s
+{
+ int mc_code;
+ int str_idx;
+} qnx_key_define_t;
+#endif /* __USE_QNX_TI */
+#endif /* HAVE_QNX_KEYS */
+
+/*** file scope variables ************************************************************************/
+
+
+#ifdef xtra_key_define_t
+
+/* general key define table */
+xtra_key_define_t xtra_key_defines[] = {
+#if FORCE_BASE_KEY_DEFS
+ {KEY_BACKSPACE, Key_backspace},
+ {KEY_LEFT, Key_left},
+ {KEY_RIGHT, Key_right},
+ {KEY_UP, Key_up},
+ {KEY_DOWN, Key_down},
+ {KEY_NPAGE, Key_npage},
+ {KEY_PPAGE, Key_ppage},
+ {KEY_HOME, Key_home},
+ {KEY_END, Key_end},
+ {KEY_DC, Key_dc},
+ {KEY_IC, Key_ic},
+ {KEY_F (1), Key_f1},
+ {KEY_F (2), Key_f2},
+ {KEY_F (3), Key_f3},
+ {KEY_F (4), Key_f4},
+ {KEY_F (5), Key_f5},
+ {KEY_F (6), Key_f6},
+ {KEY_F (7), Key_f7},
+ {KEY_F (8), Key_f8},
+ {KEY_F (9), Key_f9},
+ {KEY_F (10), Key_f10},
+ {KEY_F (11), Key_f11},
+ {KEY_F (12), Key_f12},
+ {KEY_F (13), Key_f13},
+ {KEY_F (14), Key_f14},
+ {KEY_F (15), Key_f15},
+ {KEY_F (16), Key_f16},
+ {KEY_F (17), Key_f17},
+ {KEY_F (18), Key_f18},
+ {KEY_F (19), Key_f19},
+ {KEY_F (20), Key_f20},
+#endif
+ {ALT ('a'), Key_alt_a},
+ {ALT ('b'), Key_alt_b},
+ {ALT ('c'), Key_alt_c},
+ {ALT ('d'), Key_alt_d},
+ {ALT ('e'), Key_alt_e},
+ {ALT ('f'), Key_alt_f},
+ {ALT ('g'), Key_alt_g},
+ {ALT ('h'), Key_alt_h},
+ {ALT ('i'), Key_alt_i},
+ {ALT ('j'), Key_alt_j},
+ {ALT ('k'), Key_alt_k},
+ {ALT ('l'), Key_alt_l},
+ {ALT ('m'), Key_alt_m},
+ {ALT ('n'), Key_alt_n},
+ {ALT ('o'), Key_alt_o},
+ {ALT ('p'), Key_alt_p},
+ {ALT ('q'), Key_alt_q},
+ {ALT ('r'), Key_alt_r},
+ {ALT ('s'), Key_alt_s},
+ {ALT ('t'), Key_alt_t},
+ {ALT ('u'), Key_alt_u},
+ {ALT ('v'), Key_alt_v},
+ {ALT ('w'), Key_alt_w},
+ {ALT ('x'), Key_alt_x},
+ {ALT ('y'), Key_alt_y},
+ {ALT ('z'), Key_alt_z},
+
+ {ALT ('\n'), Key_alt_enter},
+ {ALT ('\t'), Key_alt_tab}
+};
+
+#endif /* xtra_key_define_t */
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_QNX_KEYS
+#ifdef __USE_QNX_TI
+void
+load_qnx_key_defines (void)
+{
+ static int _qnx_keys_defined = 0;
+
+ if (!_qnx_keys_defined)
+ {
+ int idx;
+ int term_setup_ok;
+
+ __setupterm (NULL, fileno (stdout), &term_setup_ok);
+ if (term_setup_ok != 1)
+ return;
+
+ for (idx = 0; idx < sizeof (xtra_key_defines) / sizeof (xtra_key_defines[0]); idx++)
+ {
+ int str_idx = xtra_key_defines[idx].str_idx;
+
+ if (__QTISOFFS (str_idx))
+ {
+ if (*__QTISSTR (str_idx))
+ {
+ define_sequence (xtra_key_defines[idx].mc_code,
+ __QTISSTR (str_idx), MCKEY_NOACTION);
+ }
+ }
+ }
+ _qnx_keys_defined = 1;
+ }
+}
+#endif /* __USE_QNX_TI */
+#endif /* HAVE_QNX_KEYS */
+
+/* --------------------------------------------------------------------------------------------- */
+/* called from key.c/init_key() */
+
+void
+load_xtra_key_defines (void)
+{
+#ifdef HAVE_QNX_KEYS
+ load_qnx_key_defines ();
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/mouse.c b/lib/tty/mouse.c
new file mode 100644
index 0000000..cf42287
--- /dev/null
+++ b/lib/tty/mouse.c
@@ -0,0 +1,216 @@
+/*
+ Mouse managing
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file mouse.c
+ * \brief Source: mouse managing
+ *
+ * Events received by clients of this library have their coordinates 0 based
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "tty.h"
+#include "tty-internal.h" /* mouse_enabled */
+#include "mouse.h"
+#include "key.h" /* define sequence */
+
+/*** global variables ****************************************************************************/
+
+Mouse_Type use_mouse_p = MOUSE_NONE;
+gboolean mouse_enabled = FALSE;
+int mouse_fd = -1; /* for when gpm_fd changes to < 0 and the old one must be cleared from select_set */
+const char *xmouse_seq;
+const char *xmouse_extended_seq;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+show_mouse_pointer (int x, int y)
+{
+#ifdef HAVE_LIBGPM
+ if (use_mouse_p == MOUSE_GPM)
+ Gpm_DrawPointer (x, y, gpm_consolefd);
+#else
+ (void) x;
+ (void) y;
+#endif /* HAVE_LIBGPM */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+init_mouse (void)
+{
+ switch (use_mouse_p)
+ {
+#ifdef HAVE_LIBGPM
+ case MOUSE_NONE:
+ use_mouse_p = MOUSE_GPM;
+ break;
+#endif /* HAVE_LIBGPM */
+
+ case MOUSE_XTERM_NORMAL_TRACKING:
+ case MOUSE_XTERM_BUTTON_EVENT_TRACKING:
+ if (xmouse_seq != NULL)
+ define_sequence (MCKEY_MOUSE, xmouse_seq, MCKEY_NOACTION);
+ if (xmouse_extended_seq != NULL)
+ define_sequence (MCKEY_EXTENDED_MOUSE, xmouse_extended_seq, MCKEY_NOACTION);
+ break;
+
+ default:
+ break;
+ }
+
+ enable_mouse ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+enable_mouse (void)
+{
+ if (mouse_enabled)
+ return;
+
+ switch (use_mouse_p)
+ {
+#ifdef HAVE_LIBGPM
+ case MOUSE_GPM:
+ {
+ Gpm_Connect conn;
+
+ conn.eventMask = ~GPM_MOVE;
+ conn.defaultMask = GPM_MOVE;
+ conn.minMod = 0;
+ conn.maxMod = 0;
+
+ mouse_fd = Gpm_Open (&conn, 0);
+ if (mouse_fd == -1)
+ {
+ use_mouse_p = MOUSE_NONE;
+ return;
+ }
+ mouse_enabled = TRUE;
+ }
+ break;
+#endif /* HAVE_LIBGPM */
+
+ case MOUSE_XTERM_NORMAL_TRACKING:
+ /* save old highlight mouse tracking */
+ printf (ESC_STR "[?1001s");
+
+ /* enable mouse tracking */
+ printf (ESC_STR "[?1000h");
+
+ /* enable SGR extended mouse reporting */
+ printf (ESC_STR "[?1006h");
+
+ fflush (stdout);
+ mouse_enabled = TRUE;
+ break;
+
+ case MOUSE_XTERM_BUTTON_EVENT_TRACKING:
+ /* save old highlight mouse tracking */
+ printf (ESC_STR "[?1001s");
+
+ /* enable mouse tracking */
+ printf (ESC_STR "[?1002h");
+
+ /* enable SGR extended mouse reporting */
+ printf (ESC_STR "[?1006h");
+
+ fflush (stdout);
+ mouse_enabled = TRUE;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+disable_mouse (void)
+{
+ if (!mouse_enabled)
+ return;
+
+ mouse_enabled = FALSE;
+
+ switch (use_mouse_p)
+ {
+#ifdef HAVE_LIBGPM
+ case MOUSE_GPM:
+ Gpm_Close ();
+ break;
+#endif
+ case MOUSE_XTERM_NORMAL_TRACKING:
+ /* disable SGR extended mouse reporting */
+ printf (ESC_STR "[?1006l");
+
+ /* disable mouse tracking */
+ printf (ESC_STR "[?1000l");
+
+ /* restore old highlight mouse tracking */
+ printf (ESC_STR "[?1001r");
+
+ fflush (stdout);
+ break;
+ case MOUSE_XTERM_BUTTON_EVENT_TRACKING:
+ /* disable SGR extended mouse reporting */
+ printf (ESC_STR "[?1006l");
+
+ /* disable mouse tracking */
+ printf (ESC_STR "[?1002l");
+
+ /* restore old highlight mouse tracking */
+ printf (ESC_STR "[?1001r");
+
+ fflush (stdout);
+ break;
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/mouse.h b/lib/tty/mouse.h
new file mode 100644
index 0000000..99d0a69
--- /dev/null
+++ b/lib/tty/mouse.h
@@ -0,0 +1,117 @@
+
+/** \file mouse.h
+ * \brief Header: mouse managing
+ *
+ * Events received by clients of this library have their coordinates 0 based
+ */
+
+#ifndef MC__MOUSE_H
+#define MC__MOUSE_H
+
+#ifdef HAVE_LIBGPM
+/* GPM mouse support include file */
+#include <gpm.h>
+#endif /* !HAVE_LIBGPM */
+
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#ifndef HAVE_LIBGPM
+/* Equivalent definitions for non-GPM mouse support */
+/* These lines are modified version from the lines appearing in the */
+/* gpm.h include file of the Linux General Purpose Mouse server */
+
+#define GPM_B_LEFT (1 << 2)
+#define GPM_B_MIDDLE (1 << 1)
+#define GPM_B_RIGHT (1 << 0)
+
+#define GPM_BARE_EVENTS(ev) ((ev)&0xF)
+#endif /* !HAVE_LIBGPM */
+
+/* Mouse wheel events */
+#ifndef GPM_B_DOWN
+#define GPM_B_DOWN (1 << 5)
+#endif
+
+#ifndef GPM_B_UP
+#define GPM_B_UP (1 << 4)
+#endif
+
+/*** enums ***************************************************************************************/
+
+#ifndef HAVE_LIBGPM
+/* Xterm mouse support supports only GPM_DOWN and GPM_UP */
+/* If you use others make sure your code also works without them */
+enum Gpm_Etype
+{
+ GPM_MOVE = 1,
+ GPM_DRAG = 2, /* exactly one in four is active at a time */
+ GPM_DOWN = 4,
+ GPM_UP = 8,
+
+
+ GPM_SINGLE = 16, /* at most one in three is set */
+ GPM_DOUBLE = 32,
+ GPM_TRIPLE = 64,
+
+ GPM_MFLAG = 128, /* motion during click? */
+ GPM_HARD = 256 /* if set in the defaultMask, force an already
+ used event to pass over to another handler */
+};
+#endif /* !HAVE_LIBGPM */
+
+/* Constants returned from the mouse callback */
+enum
+{
+ MOU_UNHANDLED = 0,
+ MOU_NORMAL,
+ MOU_REPEAT
+};
+
+/* Type of mouse support */
+typedef enum
+{
+ MOUSE_NONE, /* Not detected yet */
+ MOUSE_DISABLED, /* Explicitly disabled by -d */
+ MOUSE_GPM, /* Support using GPM on Linux */
+ MOUSE_XTERM, /* Support using xterm-style mouse reporting */
+ MOUSE_XTERM_NORMAL_TRACKING = MOUSE_XTERM,
+ MOUSE_XTERM_BUTTON_EVENT_TRACKING
+} Mouse_Type;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+#ifndef HAVE_LIBGPM
+typedef struct Gpm_Event
+{
+ int buttons, x, y;
+ enum Gpm_Etype type;
+} Gpm_Event;
+#endif /* !HAVE_LIBGPM */
+
+/*** global variables defined in .c file *********************************************************/
+
+/* Type of the currently used mouse */
+extern Mouse_Type use_mouse_p;
+
+/* To be used when gpm_fd were initially >= 0 */
+extern int mouse_fd;
+
+/* String indicating that a mouse event has occurred, usually "\E[M" */
+extern const char *xmouse_seq;
+
+/* String indicating that an SGR extended mouse event has occurred, namely "\E[<" */
+extern const char *xmouse_extended_seq;
+
+/*** declarations of public functions ************************************************************/
+
+/* General (i.e. both for xterm and gpm) mouse support definitions */
+
+void init_mouse (void);
+void enable_mouse (void);
+void disable_mouse (void);
+
+void show_mouse_pointer (int x, int y);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_MOUSE_H */
diff --git a/lib/tty/tty-internal.c b/lib/tty/tty-internal.c
new file mode 100644
index 0000000..c79301d
--- /dev/null
+++ b/lib/tty/tty-internal.c
@@ -0,0 +1,110 @@
+/*
+ Internal stuff of the terminal controlling library.
+
+ Copyright (C) 2019-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2019.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: internal stuff of the terminal controlling library.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "lib/global.h"
+
+#include <glib-unix.h>
+
+#include "tty-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/* pipe to handle SIGWINCH */
+int sigwinch_pipe[2];
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** global variables ****************************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_create_winch_pipe (void)
+{
+ GError *mcerror = NULL;
+
+ if (!g_unix_open_pipe (sigwinch_pipe, FD_CLOEXEC, &mcerror))
+ {
+ fprintf (stderr, _("\nCannot create pipe for SIGWINCH: %s (%d)\n"),
+ mcerror->message, mcerror->code);
+ g_error_free (mcerror);
+ exit (EXIT_FAILURE);
+ }
+
+ /* If we read from an empty pipe, then read(2) will block until data is available.
+ * If we write to a full pipe, then write(2) blocks until sufficient data has been read
+ * from the pipe to allow the write to complete..
+ * Therefore, use nonblocking I/O.
+ */
+ if (!g_unix_set_fd_nonblocking (sigwinch_pipe[0], TRUE, &mcerror))
+ {
+ fprintf (stderr, _("\nCannot configure write end of SIGWINCH pipe: %s (%d)\n"),
+ mcerror->message, mcerror->code);
+ g_error_free (mcerror);
+ tty_destroy_winch_pipe ();
+ exit (EXIT_FAILURE);
+ }
+
+ if (!g_unix_set_fd_nonblocking (sigwinch_pipe[1], TRUE, &mcerror))
+ {
+ fprintf (stderr, _("\nCannot configure read end of SIGWINCH pipe: %s (%d)\n"),
+ mcerror->message, mcerror->code);
+ g_error_free (mcerror);
+ tty_destroy_winch_pipe ();
+ exit (EXIT_FAILURE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_destroy_winch_pipe (void)
+{
+ (void) close (sigwinch_pipe[0]);
+ (void) close (sigwinch_pipe[1]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/tty-internal.h b/lib/tty/tty-internal.h
new file mode 100644
index 0000000..a2cdfa7
--- /dev/null
+++ b/lib/tty/tty-internal.h
@@ -0,0 +1,49 @@
+
+/** \file tty-internal.h
+ * \brief Header: internal stuff of the terminal controlling library
+ */
+
+#ifndef MC__TTY_INTERNAL_H
+#define MC__TTY_INTERNAL_H
+
+#include "lib/global.h" /* include <glib.h> */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* Taken from S-Lang's slutty.c */
+#ifdef _POSIX_VDISABLE
+#define NULL_VALUE _POSIX_VDISABLE
+#else
+#define NULL_VALUE 255
+#endif
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/* The mouse is currently: TRUE - enabled, FALSE - disabled */
+extern gboolean mouse_enabled;
+
+/* terminal ca capabilities */
+extern char *smcup;
+extern char *rmcup;
+
+/* pipe to handle SIGWINCH */
+extern int sigwinch_pipe[2];
+
+/*** declarations of public functions ************************************************************/
+
+void tty_create_winch_pipe (void);
+void tty_destroy_winch_pipe (void);
+
+char *mc_tty_normalize_from_utf8 (const char *str);
+void tty_init_xterm_support (gboolean is_xterm);
+int tty_lowlevel_getch (void);
+
+void tty_colorize_area (int y, int x, int rows, int cols, int color);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_TTY_INTERNAL_H */
diff --git a/lib/tty/tty-ncurses.c b/lib/tty/tty-ncurses.c
new file mode 100644
index 0000000..08f663d
--- /dev/null
+++ b/lib/tty/tty-ncurses.c
@@ -0,0 +1,772 @@
+/*
+ Interface to the terminal controlling library.
+ Ncurses wrapper.
+
+ Copyright (C) 2005-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009.
+ Ilia Maslakov <il.smind@gmail.com>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: NCurses-based tty layer of Midnight-commander
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <signal.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#include <termios.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h" /* str_term_form */
+
+#ifndef WANT_TERM_H
+#define WANT_TERM_H
+#endif
+
+#include "tty-internal.h" /* mc_tty_normalize_from_utf8() */
+#include "tty.h"
+#include "color.h" /* tty_setcolor */
+#include "color-internal.h"
+#include "key.h"
+#include "mouse.h"
+#include "win.h"
+
+/* include at last !!! */
+#ifdef WANT_TERM_H
+#ifdef HAVE_NCURSES_TERM_H
+#include <ncurses/term.h>
+#else
+#include <term.h>
+#endif /* HAVE_NCURSES_TERM_H */
+#endif /* WANT_TERM_H */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#if !defined(CTRL)
+#define CTRL(x) ((x) & 0x1f)
+#endif
+
+#define yx_in_screen(y, x) \
+ (y >= 0 && y < LINES && x >= 0 && x < COLS)
+
+/*** global variables ****************************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* ncurses supports cursor positions only within window */
+/* We use our own cursor coordinates to support partially visible widgets */
+static int mc_curs_row, mc_curs_col;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tty_setup_sigwinch (void (*handler) (int))
+{
+#if (NCURSES_VERSION_MAJOR >= 4) && defined (SIGWINCH)
+ struct sigaction act, oact;
+
+ memset (&act, 0, sizeof (act));
+ act.sa_handler = handler;
+ sigemptyset (&act.sa_mask);
+#ifdef SA_RESTART
+ act.sa_flags = SA_RESTART;
+#endif /* SA_RESTART */
+ sigaction (SIGWINCH, &act, &oact);
+#endif /* SIGWINCH */
+
+ tty_create_winch_pipe ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sigwinch_handler (int dummy)
+{
+ ssize_t n = 0;
+
+ (void) dummy;
+
+ n = write (sigwinch_pipe[1], "", 1);
+ (void) n;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get visible part of area.
+ *
+ * @returns TRUE if any part of area is in screen bounds, FALSE otherwise.
+ */
+static gboolean
+tty_clip (int *y, int *x, int *rows, int *cols)
+{
+ if (*y < 0)
+ {
+ *rows += *y;
+
+ if (*rows <= 0)
+ return FALSE;
+
+ *y = 0;
+ }
+
+ if (*x < 0)
+ {
+ *cols += *x;
+
+ if (*cols <= 0)
+ return FALSE;
+
+ *x = 0;
+ }
+
+ if (*y + *rows > LINES)
+ *rows = LINES - *y;
+
+ if (*rows <= 0)
+ return FALSE;
+
+ if (*x + *cols > COLS)
+ *cols = COLS - *x;
+
+ if (*cols <= 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_tty_normalize_lines_char (const char *ch)
+{
+ char *str2;
+ int res;
+
+ struct mc_tty_lines_struct
+ {
+ const char *line;
+ int line_code;
+ } const lines_codes[] = {
+ {"\342\224\230", ACS_LRCORNER}, /* ┌ */
+ {"\342\224\224", ACS_LLCORNER}, /* └ */
+ {"\342\224\220", ACS_URCORNER}, /* ┐ */
+ {"\342\224\214", ACS_ULCORNER}, /* ┘ */
+ {"\342\224\234", ACS_LTEE}, /* ├ */
+ {"\342\224\244", ACS_RTEE}, /* ┤ */
+ {"\342\224\254", ACS_TTEE}, /* ┬ */
+ {"\342\224\264", ACS_BTEE}, /* ┴ */
+ {"\342\224\200", ACS_HLINE}, /* ─ */
+ {"\342\224\202", ACS_VLINE}, /* │ */
+ {"\342\224\274", ACS_PLUS}, /* ┼ */
+
+ {"\342\225\235", ACS_LRCORNER | A_BOLD}, /* ╔ */
+ {"\342\225\232", ACS_LLCORNER | A_BOLD}, /* ╚ */
+ {"\342\225\227", ACS_URCORNER | A_BOLD}, /* ╗ */
+ {"\342\225\224", ACS_ULCORNER | A_BOLD}, /* ╝ */
+ {"\342\225\237", ACS_LTEE | A_BOLD}, /* ╟ */
+ {"\342\225\242", ACS_RTEE | A_BOLD}, /* ╢ */
+ {"\342\225\244", ACS_TTEE | A_BOLD}, /* ╤ */
+ {"\342\225\247", ACS_BTEE | A_BOLD}, /* ╧ */
+ {"\342\225\220", ACS_HLINE | A_BOLD}, /* ═ */
+ {"\342\225\221", ACS_VLINE | A_BOLD}, /* ║ */
+
+ {NULL, 0}
+ };
+
+ if (ch == NULL)
+ return (int) ' ';
+
+ for (res = 0; lines_codes[res].line; res++)
+ {
+ if (strcmp (ch, lines_codes[res].line) == 0)
+ return lines_codes[res].line_code;
+ }
+
+ str2 = mc_tty_normalize_from_utf8 (ch);
+ res = g_utf8_get_char_validated (str2, -1);
+
+ if (res < 0)
+ res = (unsigned char) str2[0];
+ g_free (str2);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_init (gboolean mouse_enable, gboolean is_xterm)
+{
+ struct termios mode;
+
+ initscr ();
+
+#ifdef HAVE_ESCDELAY
+ /*
+ * If ncurses exports the ESCDELAY variable, it should be set to
+ * a low value, or you'll experience a delay in processing escape
+ * sequences that are recognized by mc (e.g. Esc-Esc). On the other
+ * hand, making ESCDELAY too small can result in some sequences
+ * (e.g. cursor arrows) being reported as separate keys under heavy
+ * processor load, and this can be a problem if mc hasn't learned
+ * them in the "Learn Keys" dialog. The value is in milliseconds.
+ */
+ ESCDELAY = 200;
+#endif /* HAVE_ESCDELAY */
+
+ tcgetattr (STDIN_FILENO, &mode);
+ /* use Ctrl-g to generate SIGINT */
+ mode.c_cc[VINTR] = CTRL ('g'); /* ^g */
+ /* disable SIGQUIT to allow use Ctrl-\ key */
+ mode.c_cc[VQUIT] = NULL_VALUE;
+ tcsetattr (STDIN_FILENO, TCSANOW, &mode);
+
+ /* curses remembers the "in-program" modes after this call */
+ def_prog_mode ();
+
+ tty_start_interrupt_key ();
+
+ if (!mouse_enable)
+ use_mouse_p = MOUSE_DISABLED;
+ tty_init_xterm_support (is_xterm); /* do it before tty_enter_ca_mode() call */
+ tty_enter_ca_mode ();
+ tty_raw_mode ();
+ noecho ();
+ keypad (stdscr, TRUE);
+ nodelay (stdscr, FALSE);
+
+ tty_setup_sigwinch (sigwinch_handler);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_shutdown (void)
+{
+ tty_destroy_winch_pipe ();
+ tty_reset_shell_mode ();
+ tty_noraw_mode ();
+ tty_keypad (FALSE);
+ tty_reset_screen ();
+ tty_exit_ca_mode ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_enter_ca_mode (void)
+{
+ if (mc_global.tty.xterm_flag && smcup != NULL)
+ {
+ fprintf (stdout, /* ESC_STR ")0" */ ESC_STR "7" ESC_STR "[?47h");
+ fflush (stdout);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_exit_ca_mode (void)
+{
+ if (mc_global.tty.xterm_flag && rmcup != NULL)
+ {
+ fprintf (stdout, ESC_STR "[?47l" ESC_STR "8" ESC_STR "[m");
+ fflush (stdout);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_change_screen_size (void)
+{
+#if defined(TIOCGWINSZ) && NCURSES_VERSION_MAJOR >= 4
+ struct winsize winsz;
+
+ winsz.ws_col = winsz.ws_row = 0;
+
+#ifndef NCURSES_VERSION
+ tty_noraw_mode ();
+ tty_reset_screen ();
+#endif
+
+ /* Ioctl on the STDIN_FILENO */
+ ioctl (fileno (stdout), TIOCGWINSZ, &winsz);
+ if (winsz.ws_col != 0 && winsz.ws_row != 0)
+ {
+#if defined(NCURSES_VERSION) && defined(HAVE_RESIZETERM)
+ resizeterm (winsz.ws_row, winsz.ws_col);
+ clearok (stdscr, TRUE); /* sigwinch's should use a semaphore! */
+#else
+ COLS = winsz.ws_col;
+ LINES = winsz.ws_row;
+#endif
+ }
+#endif /* defined(TIOCGWINSZ) || NCURSES_VERSION_MAJOR >= 4 */
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ tty_resize (mc_global.tty.subshell_pty);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_reset_prog_mode (void)
+{
+ reset_prog_mode ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_reset_shell_mode (void)
+{
+ reset_shell_mode ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_raw_mode (void)
+{
+ raw (); /* FIXME: unneeded? */
+ cbreak ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_noraw_mode (void)
+{
+ nocbreak (); /* FIXME: unneeded? */
+ noraw ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_noecho (void)
+{
+ noecho ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_flush_input (void)
+{
+ return flushinp ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_keypad (gboolean set)
+{
+ keypad (stdscr, (bool) set);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_nodelay (gboolean set)
+{
+ nodelay (stdscr, (bool) set);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_baudrate (void)
+{
+ return baudrate ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_lowlevel_getch (void)
+{
+ return getch ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_reset_screen (void)
+{
+ return endwin ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_touch_screen (void)
+{
+ touchwin (stdscr);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_gotoyx (int y, int x)
+{
+ mc_curs_row = y;
+ mc_curs_col = x;
+
+ if (y < 0)
+ y = 0;
+ if (y >= LINES)
+ y = LINES - 1;
+
+ if (x < 0)
+ x = 0;
+ if (x >= COLS)
+ x = COLS - 1;
+
+ move (y, x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_getyx (int *py, int *px)
+{
+ *py = mc_curs_row;
+ *px = mc_curs_col;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_draw_hline (int y, int x, int ch, int len)
+{
+ int x1;
+
+ if (y < 0 || y >= LINES || x >= COLS)
+ return;
+
+ x1 = x;
+
+ if (x < 0)
+ {
+ len += x;
+ if (len <= 0)
+ return;
+ x = 0;
+ }
+
+ if ((chtype) ch == ACS_HLINE)
+ ch = mc_tty_frm[MC_TTY_FRM_HORIZ];
+
+ move (y, x);
+ hline (ch, len);
+ move (y, x1);
+
+ mc_curs_row = y;
+ mc_curs_col = x1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_draw_vline (int y, int x, int ch, int len)
+{
+ int y1;
+
+ if (x < 0 || x >= COLS || y >= LINES)
+ return;
+
+ y1 = y;
+
+ if (y < 0)
+ {
+ len += y;
+ if (len <= 0)
+ return;
+ y = 0;
+ }
+
+ if ((chtype) ch == ACS_VLINE)
+ ch = mc_tty_frm[MC_TTY_FRM_VERT];
+
+ move (y, x);
+ vline (ch, len);
+ move (y1, x);
+
+ mc_curs_row = y1;
+ mc_curs_col = x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_fill_region (int y, int x, int rows, int cols, unsigned char ch)
+{
+ int i;
+
+ if (!tty_clip (&y, &x, &rows, &cols))
+ return;
+
+ for (i = 0; i < rows; i++)
+ {
+ move (y + i, x);
+ hline (ch, cols);
+ }
+
+ move (y, x);
+
+ mc_curs_row = y;
+ mc_curs_col = x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_colorize_area (int y, int x, int rows, int cols, int color)
+{
+#ifdef ENABLE_SHADOWS
+ cchar_t *ctext;
+ wchar_t wch[10]; /* TODO not sure if the length is correct */
+ attr_t attrs;
+ short color_pair;
+
+ if (!use_colors || !tty_clip (&y, &x, &rows, &cols))
+ return;
+
+ tty_setcolor (color);
+ ctext = g_malloc (sizeof (cchar_t) * (cols + 1));
+
+ for (int row = 0; row < rows; row++)
+ {
+ mvin_wchnstr (y + row, x, ctext, cols);
+
+ for (int col = 0; col < cols; col++)
+ {
+ getcchar (&ctext[col], wch, &attrs, &color_pair, NULL);
+ setcchar (&ctext[col], wch, attrs, color, NULL);
+ }
+
+ mvadd_wchnstr (y + row, x, ctext, cols);
+ }
+
+ g_free (ctext);
+#else
+ (void) y;
+ (void) x;
+ (void) rows;
+ (void) cols;
+ (void) color;
+#endif /* ENABLE_SHADOWS */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_set_alt_charset (gboolean alt_charset)
+{
+ (void) alt_charset;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_display_8bit (gboolean what)
+{
+ meta (stdscr, (int) what);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_char (int c)
+{
+ if (yx_in_screen (mc_curs_row, mc_curs_col))
+ addch (c);
+ mc_curs_col++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_anychar (int c)
+{
+ if (mc_global.utf8_display || c > 255)
+ {
+ int res;
+ unsigned char str[UTF8_CHAR_LEN + 1];
+
+ res = g_unichar_to_utf8 (c, (char *) str);
+ if (res == 0)
+ {
+ if (yx_in_screen (mc_curs_row, mc_curs_col))
+ addch ('.');
+ mc_curs_col++;
+ }
+ else
+ {
+ const char *s;
+
+ str[res] = '\0';
+ s = str_term_form ((char *) str);
+
+ if (yx_in_screen (mc_curs_row, mc_curs_col))
+ addstr (s);
+
+ if (g_unichar_iswide (c))
+ mc_curs_col += 2;
+ else if (!g_unichar_iszerowidth (c))
+ mc_curs_col++;
+ }
+ }
+ else
+ {
+ if (yx_in_screen (mc_curs_row, mc_curs_col))
+ addch (c);
+ mc_curs_col++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_alt_char (int c, gboolean single)
+{
+ if (yx_in_screen (mc_curs_row, mc_curs_col))
+ {
+ if ((chtype) c == ACS_VLINE)
+ c = mc_tty_frm[single ? MC_TTY_FRM_VERT : MC_TTY_FRM_DVERT];
+ else if ((chtype) c == ACS_HLINE)
+ c = mc_tty_frm[single ? MC_TTY_FRM_HORIZ : MC_TTY_FRM_DHORIZ];
+ else if ((chtype) c == ACS_LTEE)
+ c = mc_tty_frm[single ? MC_TTY_FRM_LEFTMIDDLE : MC_TTY_FRM_DLEFTMIDDLE];
+ else if ((chtype) c == ACS_RTEE)
+ c = mc_tty_frm[single ? MC_TTY_FRM_RIGHTMIDDLE : MC_TTY_FRM_DRIGHTMIDDLE];
+ else if ((chtype) c == ACS_ULCORNER)
+ c = mc_tty_frm[single ? MC_TTY_FRM_LEFTTOP : MC_TTY_FRM_DLEFTTOP];
+ else if ((chtype) c == ACS_LLCORNER)
+ c = mc_tty_frm[single ? MC_TTY_FRM_LEFTBOTTOM : MC_TTY_FRM_DLEFTBOTTOM];
+ else if ((chtype) c == ACS_URCORNER)
+ c = mc_tty_frm[single ? MC_TTY_FRM_RIGHTTOP : MC_TTY_FRM_DRIGHTTOP];
+ else if ((chtype) c == ACS_LRCORNER)
+ c = mc_tty_frm[single ? MC_TTY_FRM_RIGHTBOTTOM : MC_TTY_FRM_DRIGHTBOTTOM];
+ else if ((chtype) c == ACS_PLUS)
+ c = mc_tty_frm[MC_TTY_FRM_CROSS];
+
+ addch (c);
+ }
+
+ mc_curs_col++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_string (const char *s)
+{
+ int len;
+ int start = 0;
+
+ s = str_term_form (s);
+ len = str_term_width1 (s);
+
+ /* line is upper or below the screen or entire line is before or after screen */
+ if (mc_curs_row < 0 || mc_curs_row >= LINES || mc_curs_col + len <= 0 || mc_curs_col >= COLS)
+ {
+ mc_curs_col += len;
+ return;
+ }
+
+ /* skip invisible left part */
+ if (mc_curs_col < 0)
+ {
+ start = -mc_curs_col;
+ len += mc_curs_col;
+ mc_curs_col = 0;
+ }
+
+ mc_curs_col += len;
+ if (mc_curs_col >= COLS)
+ len = COLS - (mc_curs_col - len);
+
+ addstr (str_term_substring (s, start, len));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_printf (const char *fmt, ...)
+{
+ va_list args;
+ char buf[BUF_1K]; /* FIXME: is it enough? */
+
+ va_start (args, fmt);
+ g_vsnprintf (buf, sizeof (buf), fmt, args);
+ va_end (args);
+ tty_print_string (buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+tty_tgetstr (const char *cap)
+{
+ char *unused = NULL;
+
+ return tgetstr ((NCURSES_CONST char *) cap, &unused);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_refresh (void)
+{
+ refresh ();
+ doupdate ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_beep (void)
+{
+ beep ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/tty-ncurses.h b/lib/tty/tty-ncurses.h
new file mode 100644
index 0000000..8feb17c
--- /dev/null
+++ b/lib/tty/tty-ncurses.h
@@ -0,0 +1,50 @@
+
+#ifndef MC__TTY_NCURSES_H
+#define MC__TTY_NCURSES_H
+
+/* for cchar_t, getcchar(), setcchar() */
+#ifndef _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_SOURCE_EXTENDED
+#endif
+
+#ifdef USE_NCURSES
+#ifdef HAVE_NCURSES_CURSES_H
+#include <ncurses/curses.h>
+#elif defined (HAVE_NCURSES_NCURSES_H)
+#include <ncurses/ncurses.h>
+#elif defined (HAVE_NCURSESW_CURSES_H)
+#include <ncursesw/curses.h>
+#elif defined (HAVE_NCURSES_HCURSES_H) || defined (HAVE_NCURSES_H)
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+#endif /* USE_NCURSES */
+
+#ifdef USE_NCURSESW
+#include <ncursesw/curses.h>
+#endif /* USE_NCURSESW */
+
+/* netbsd-libcurses doesn't define NCURSES_CONST */
+#ifndef NCURSES_CONST
+#define NCURSES_CONST const
+#endif
+
+/* do not draw shadows if NCurses is built with --disable-widec */
+#if defined(NCURSES_WIDECHAR) && NCURSES_WIDECHAR
+#define ENABLE_SHADOWS 1
+#endif
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_TTY_NCURSES_H */
diff --git a/lib/tty/tty-slang.c b/lib/tty/tty-slang.c
new file mode 100644
index 0000000..3aa74de
--- /dev/null
+++ b/lib/tty/tty-slang.c
@@ -0,0 +1,781 @@
+/*
+ Interface to the terminal controlling library.
+ Slang wrapper.
+
+ Copyright (C) 2005-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Egmont Koblinger <egmont@gmail.com>, 2010
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * \brief Source: S-Lang-based tty layer of Midnight Commander
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h> /* size_t */
+#include <unistd.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#include <termios.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h" /* str_term_form */
+#include "lib/util.h" /* is_printable() */
+
+#include "tty-internal.h" /* mc_tty_normalize_from_utf8() */
+#include "tty.h"
+#include "color.h"
+#include "color-slang.h"
+#include "color-internal.h"
+#include "mouse.h" /* Gpm_Event is required in key.h */
+#include "key.h" /* define_sequence */
+#include "win.h"
+
+
+/*** global variables ****************************************************************************/
+
+/* If true program softkeys (HP terminals only) on startup and after every
+ command ran in the subshell to the description found in the termcap/terminfo
+ database */
+int reset_hp_softkeys = 0;
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef SLTT_MAX_SCREEN_COLS
+#define SLTT_MAX_SCREEN_COLS 512
+#endif
+
+#ifndef SLTT_MAX_SCREEN_ROWS
+#define SLTT_MAX_SCREEN_ROWS 512
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Various saved termios settings that we control here */
+static struct termios boot_mode;
+static struct termios new_mode;
+
+/* Controls whether we should wait for input in tty_lowlevel_getch */
+static gboolean no_slang_delay;
+
+static gboolean slsmg_active = FALSE;
+
+/* This table describes which capabilities we want and which values we
+ * assign to them.
+ */
+static const struct
+{
+ int key_code;
+ const char *key_name;
+} key_table[] =
+{
+ /* *INDENT-OFF* */
+ { KEY_F (0), "k0" },
+ { KEY_F (1), "k1" },
+ { KEY_F (2), "k2" },
+ { KEY_F (3), "k3" },
+ { KEY_F (4), "k4" },
+ { KEY_F (5), "k5" },
+ { KEY_F (6), "k6" },
+ { KEY_F (7), "k7" },
+ { KEY_F (8), "k8" },
+ { KEY_F (9), "k9" },
+ { KEY_F (10), "k;" },
+ { KEY_F (11), "F1" },
+ { KEY_F (12), "F2" },
+ { KEY_F (13), "F3" },
+ { KEY_F (14), "F4" },
+ { KEY_F (15), "F5" },
+ { KEY_F (16), "F6" },
+ { KEY_F (17), "F7" },
+ { KEY_F (18), "F8" },
+ { KEY_F (19), "F9" },
+ { KEY_F (20), "FA" },
+ { KEY_IC, "kI" },
+ { KEY_NPAGE, "kN" },
+ { KEY_PPAGE, "kP" },
+ { KEY_LEFT, "kl" },
+ { KEY_RIGHT, "kr" },
+ { KEY_UP, "ku" },
+ { KEY_DOWN, "kd" },
+ { KEY_DC, "kD" },
+ { KEY_BACKSPACE, "kb" },
+ { KEY_HOME, "kh" },
+ { KEY_END, "@7" },
+ { 0, NULL }
+ /* *INDENT-ON* */
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tty_setup_sigwinch (void (*handler) (int))
+{
+ (void) SLsignal (SIGWINCH, handler);
+ tty_create_winch_pipe ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sigwinch_handler (int dummy)
+{
+ ssize_t n = 0;
+
+ (void) dummy;
+
+ n = write (sigwinch_pipe[1], "", 1);
+ (void) n;
+
+ (void) SLsignal (SIGWINCH, sigwinch_handler);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* HP Terminals have capabilities (pfkey, pfloc, pfx) to program function keys.
+ elm 2.4pl15 invoked with the -K option utilizes these softkeys and the
+ consequence is that function keys don't work in MC sometimes...
+ Unfortunately I don't now the one and only escape sequence to turn off.
+ softkeys (elm uses three different capabilities to turn on softkeys and two.
+ capabilities to turn them off)..
+ Among other things elm uses the pair we already use in slang_keypad. That's.
+ the reason why I call slang_reset_softkeys from slang_keypad. In lack of
+ something better the softkeys are programmed to their defaults from the
+ termcap/terminfo database.
+ The escape sequence to program the softkeys is taken from elm and it is.
+ hardcoded because neither slang nor ncurses 4.1 know how to 'printf' this.
+ sequence. -- Norbert
+ */
+
+static void
+slang_reset_softkeys (void)
+{
+ int key;
+ static const char display[] = " ";
+ char tmp[BUF_SMALL];
+
+ for (key = 1; key < 9; key++)
+ {
+ char *send;
+
+ g_snprintf (tmp, sizeof (tmp), "k%d", key);
+ send = SLtt_tgetstr (tmp);
+ if (send != NULL)
+ {
+ g_snprintf (tmp, sizeof (tmp), ESC_STR "&f%dk%dd%dL%s%s", key,
+ (int) (sizeof (display) - 1), (int) strlen (send), display, send);
+ SLtt_write_string (tmp);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_define_key (int code, const char *strcap)
+{
+ char *seq;
+
+ seq = SLtt_tgetstr ((SLFUTURE_CONST char *) strcap);
+ if (seq != NULL)
+ define_sequence (code, seq, MCKEY_NOACTION);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_terminfo_keys (void)
+{
+ int i;
+
+ for (i = 0; key_table[i].key_code; i++)
+ do_define_key (key_table[i].key_code, key_table[i].key_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_tty_normalize_lines_char (const char *str)
+{
+ char *str2;
+ int res;
+
+ struct mc_tty_lines_struct
+ {
+ const char *line;
+ int line_code;
+ } const lines_codes[] = {
+ {"\342\224\214", SLSMG_ULCORN_CHAR},
+ {"\342\224\220", SLSMG_URCORN_CHAR},
+ {"\342\224\224", SLSMG_LLCORN_CHAR},
+ {"\342\224\230", SLSMG_LRCORN_CHAR},
+ {"\342\224\234", SLSMG_LTEE_CHAR},
+ {"\342\224\244", SLSMG_RTEE_CHAR},
+ {"\342\224\254", SLSMG_UTEE_CHAR},
+ {"\342\224\264", SLSMG_DTEE_CHAR},
+ {"\342\224\200", SLSMG_HLINE_CHAR},
+ {"\342\224\202", SLSMG_VLINE_CHAR},
+ {"\342\224\274", SLSMG_PLUS_CHAR},
+
+ {NULL, 0}
+ };
+
+ if (!str)
+ return (int) ' ';
+
+ for (res = 0; lines_codes[res].line; res++)
+ {
+ if (strcmp (str, lines_codes[res].line) == 0)
+ return lines_codes[res].line_code;
+ }
+
+ str2 = mc_tty_normalize_from_utf8 (str);
+ res = g_utf8_get_char_validated (str2, -1);
+
+ if (res < 0)
+ res = (unsigned char) str2[0];
+ g_free (str2);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_init (gboolean mouse_enable, gboolean is_xterm)
+{
+ SLtt_Ignore_Beep = 1;
+
+ SLutf8_enable (-1); /* has to be called first before any of the other functions. */
+ SLtt_get_terminfo ();
+ /*
+ * If the terminal in not in terminfo but begins with a well-known
+ * string such as "linux" or "xterm" S-Lang will go on, but the
+ * terminal size and several other variables won't be initialized
+ * (as of S-Lang 1.4.4). Detect it and abort. Also detect extremely
+ * small screen dimensions.
+ */
+ if ((COLS < 10) || (LINES < 5)
+#if SLANG_VERSION < 20303
+ /* Beginning from pre2.3.3-8 (55f58798c267d76a1b93d0d916027b71a10ac1ee),
+ these limitations were eliminated. */
+ || (COLS > SLTT_MAX_SCREEN_COLS) || (LINES > SLTT_MAX_SCREEN_ROWS)
+#endif
+ )
+ {
+ fprintf (stderr,
+ _("Screen size %dx%d is not supported.\n"
+ "Check the TERM environment variable.\n"), COLS, LINES);
+ exit (EXIT_FAILURE);
+ }
+
+ tcgetattr (fileno (stdin), &boot_mode);
+ /* 255 = ignore abort char; XCTRL('g') for abort char = ^g */
+ SLang_init_tty (XCTRL ('g'), 1, 0);
+
+ if (mc_global.tty.ugly_line_drawing)
+ SLtt_Has_Alt_Charset = 0;
+
+ tcgetattr (SLang_TT_Read_FD, &new_mode);
+
+ tty_reset_prog_mode ();
+ load_terminfo_keys ();
+
+ SLtt_Blink_Mode = (tty_use_256colors (NULL) || tty_use_truecolors (NULL)) ? 1 : 0;
+
+ tty_start_interrupt_key ();
+
+ /* It's the small part from the previous init_key() */
+ init_key_input_fd ();
+
+ /* For 8-bit locales, NCurses handles 154 (0x9A) symbol properly, while S-Lang
+ * requires SLsmg_Display_Eight_Bit >= 154 (OR manual filtering if xterm display
+ * detected - but checking TERM would fail under screen, OR running xterm
+ * with allowC1Printable).
+ */
+ tty_display_8bit (FALSE);
+
+ SLsmg_init_smg ();
+ slsmg_active = TRUE;
+ if (!mouse_enable)
+ use_mouse_p = MOUSE_DISABLED;
+ tty_init_xterm_support (is_xterm); /* do it before tty_enter_ca_mode() call */
+ tty_enter_ca_mode ();
+ tty_keypad (TRUE);
+ tty_nodelay (FALSE);
+
+ tty_setup_sigwinch (sigwinch_handler);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_shutdown (void)
+{
+ char *op_cap;
+
+ tty_destroy_winch_pipe ();
+ tty_reset_shell_mode ();
+ tty_noraw_mode ();
+ tty_keypad (FALSE);
+ tty_reset_screen ();
+ tty_exit_ca_mode ();
+ SLang_reset_tty ();
+ slsmg_active = FALSE;
+
+ /* Load the op capability to reset the colors to those that were
+ * active when the program was started up
+ */
+ op_cap = SLtt_tgetstr ((SLFUTURE_CONST char *) "op");
+ if (op_cap != NULL)
+ {
+ fputs (op_cap, stdout);
+ fflush (stdout);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_enter_ca_mode (void)
+{
+ /* S-Lang handles alternate screen switching and cursor position saving */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_exit_ca_mode (void)
+{
+ /* S-Lang handles alternate screen switching and cursor position restoring */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_change_screen_size (void)
+{
+ SLtt_get_screen_size ();
+ if (slsmg_active)
+ SLsmg_reinit_smg ();
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ tty_resize (mc_global.tty.subshell_pty);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Done each time we come back from done mode */
+
+void
+tty_reset_prog_mode (void)
+{
+ tcsetattr (SLang_TT_Read_FD, TCSANOW, &new_mode);
+ SLsmg_init_smg ();
+ slsmg_active = TRUE;
+ SLsmg_touch_lines (0, LINES);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Called each time we want to shutdown slang screen manager */
+
+void
+tty_reset_shell_mode (void)
+{
+ tcsetattr (SLang_TT_Read_FD, TCSANOW, &boot_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_raw_mode (void)
+{
+ tcsetattr (SLang_TT_Read_FD, TCSANOW, &new_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_noraw_mode (void)
+{
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_noecho (void)
+{
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_flush_input (void)
+{
+ return 0; /* OK */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_keypad (gboolean set)
+{
+ char *keypad_string;
+
+ keypad_string = SLtt_tgetstr ((SLFUTURE_CONST char *) (set ? "ks" : "ke"));
+ if (keypad_string != NULL)
+ SLtt_write_string (keypad_string);
+ if (set && reset_hp_softkeys)
+ slang_reset_softkeys ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_nodelay (gboolean set)
+{
+ no_slang_delay = set;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_baudrate (void)
+{
+ return SLang_TT_Baud_Rate;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_lowlevel_getch (void)
+{
+ int c;
+
+ if (no_slang_delay && (SLang_input_pending (0) == 0))
+ return -1;
+
+ c = SLang_getkey ();
+ if (c == SLANG_GETKEY_ERROR)
+ {
+ fprintf (stderr,
+ "SLang_getkey returned SLANG_GETKEY_ERROR\n"
+ "Assuming EOF on stdin and exiting\n");
+ exit (EXIT_FAILURE);
+ }
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+tty_reset_screen (void)
+{
+ SLsmg_reset_smg ();
+ slsmg_active = FALSE;
+ return 0; /* OK */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_touch_screen (void)
+{
+ SLsmg_touch_lines (0, LINES);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_gotoyx (int y, int x)
+{
+ SLsmg_gotorc (y, x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_getyx (int *py, int *px)
+{
+ *py = SLsmg_get_row ();
+ *px = SLsmg_get_column ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_draw_hline (int y, int x, int ch, int len)
+{
+ int x1;
+
+ if (y < 0 || y >= LINES || x >= COLS)
+ return;
+
+ x1 = x;
+
+ if (x < 0)
+ {
+ len += x;
+ if (len <= 0)
+ return;
+ x = 0;
+ }
+
+ if (ch == ACS_HLINE)
+ ch = mc_tty_frm[MC_TTY_FRM_HORIZ];
+ if (ch == 0)
+ ch = ACS_HLINE;
+
+ SLsmg_gotorc (y, x);
+
+ if (ch == ACS_HLINE)
+ SLsmg_draw_hline (len);
+ else
+ while (len-- != 0)
+ tty_print_char (ch);
+
+ SLsmg_gotorc (y, x1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_draw_vline (int y, int x, int ch, int len)
+{
+ int y1;
+
+ if (x < 0 || x >= COLS || y >= LINES)
+ return;
+
+ y1 = y;
+
+ if (y < 0)
+ {
+ len += y;
+ if (len <= 0)
+ return;
+ y = 0;
+ }
+
+ if (ch == ACS_VLINE)
+ ch = mc_tty_frm[MC_TTY_FRM_VERT];
+ if (ch == 0)
+ ch = ACS_VLINE;
+
+ SLsmg_gotorc (y, x);
+
+ if (ch == ACS_VLINE)
+ SLsmg_draw_vline (len);
+ else
+ {
+ int pos = 0;
+
+ while (len-- != 0)
+ {
+ SLsmg_gotorc (y + pos, x);
+ tty_print_char (ch);
+ pos++;
+ }
+ }
+
+ SLsmg_gotorc (y1, x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_fill_region (int y, int x, int rows, int cols, unsigned char ch)
+{
+ SLsmg_fill_region (y, x, rows, cols, ch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_colorize_area (int y, int x, int rows, int cols, int color)
+{
+ if (use_colors)
+ SLsmg_set_color_in_region (color, y, x, rows, cols);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_set_alt_charset (gboolean alt_charset)
+{
+ SLsmg_set_char_set ((int) alt_charset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_display_8bit (gboolean what)
+{
+ SLsmg_Display_Eight_Bit = what ? 128 : 160;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_char (int c)
+{
+ SLsmg_write_char ((SLwchar_Type) ((unsigned int) c));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_alt_char (int c, gboolean single)
+{
+#define DRAW(x, y) (x == y) \
+ ? SLsmg_draw_object (SLsmg_get_row(), SLsmg_get_column(), x) \
+ : SLsmg_write_char ((unsigned int) y)
+ switch (c)
+ {
+ case ACS_VLINE:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_VERT : MC_TTY_FRM_DVERT]);
+ break;
+ case ACS_HLINE:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_HORIZ : MC_TTY_FRM_DHORIZ]);
+ break;
+ case ACS_LTEE:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_LEFTMIDDLE : MC_TTY_FRM_DLEFTMIDDLE]);
+ break;
+ case ACS_RTEE:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_RIGHTMIDDLE : MC_TTY_FRM_DRIGHTMIDDLE]);
+ break;
+ case ACS_TTEE:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_TOPMIDDLE : MC_TTY_FRM_DTOPMIDDLE]);
+ break;
+ case ACS_BTEE:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_BOTTOMMIDDLE : MC_TTY_FRM_DBOTTOMMIDDLE]);
+ break;
+ case ACS_ULCORNER:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_LEFTTOP : MC_TTY_FRM_DLEFTTOP]);
+ break;
+ case ACS_LLCORNER:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_LEFTBOTTOM : MC_TTY_FRM_DLEFTBOTTOM]);
+ break;
+ case ACS_URCORNER:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_RIGHTTOP : MC_TTY_FRM_DRIGHTTOP]);
+ break;
+ case ACS_LRCORNER:
+ DRAW (c, mc_tty_frm[single ? MC_TTY_FRM_RIGHTBOTTOM : MC_TTY_FRM_DRIGHTBOTTOM]);
+ break;
+ case ACS_PLUS:
+ DRAW (c, mc_tty_frm[MC_TTY_FRM_CROSS]);
+ break;
+ default:
+ SLsmg_write_char ((unsigned int) c);
+ }
+#undef DRAW
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_anychar (int c)
+{
+ if (c > 255)
+ {
+ char str[UTF8_CHAR_LEN + 1];
+ int res;
+
+ res = g_unichar_to_utf8 (c, str);
+ if (res == 0)
+ {
+ str[0] = '.';
+ str[1] = '\0';
+ }
+ else
+ {
+ str[res] = '\0';
+ }
+ SLsmg_write_string ((char *) str_term_form (str));
+ }
+ else
+ {
+ if (!is_printable (c))
+ c = '.';
+ SLsmg_write_char ((SLwchar_Type) ((unsigned int) c));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_string (const char *s)
+{
+ SLsmg_write_string ((char *) str_term_form (s));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_printf (const char *fmt, ...)
+{
+ va_list args;
+
+ va_start (args, fmt);
+ SLsmg_vprintf ((char *) fmt, args);
+ va_end (args);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+tty_tgetstr (const char *cap)
+{
+ return SLtt_tgetstr ((SLFUTURE_CONST char *) cap);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_refresh (void)
+{
+ SLsmg_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_beep (void)
+{
+ SLtt_beep ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/tty-slang.h b/lib/tty/tty-slang.h
new file mode 100644
index 0000000..eeaade3
--- /dev/null
+++ b/lib/tty/tty-slang.h
@@ -0,0 +1,48 @@
+
+#ifndef MC__TTY_SLANG_H
+#define MC__TTY_SLANG_H
+
+#include <slang.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define KEY_F(x) (1000 + x)
+
+#define ACS_VLINE SLSMG_VLINE_CHAR
+#define ACS_HLINE SLSMG_HLINE_CHAR
+#define ACS_LTEE SLSMG_LTEE_CHAR
+#define ACS_RTEE SLSMG_RTEE_CHAR
+#define ACS_TTEE SLSMG_UTEE_CHAR
+#define ACS_BTEE SLSMG_DTEE_CHAR
+#define ACS_ULCORNER SLSMG_ULCORN_CHAR
+#define ACS_LLCORNER SLSMG_LLCORN_CHAR
+#define ACS_URCORNER SLSMG_URCORN_CHAR
+#define ACS_LRCORNER SLSMG_LRCORN_CHAR
+#define ACS_PLUS SLSMG_PLUS_CHAR
+
+#define COLS SLtt_Screen_Cols
+#define LINES SLtt_Screen_Rows
+
+#define ENABLE_SHADOWS 1
+
+/*** enums ***************************************************************************************/
+
+enum
+{
+ KEY_BACKSPACE = 400,
+ KEY_END, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT,
+ KEY_HOME, KEY_A1, KEY_C1, KEY_NPAGE, KEY_PPAGE, KEY_IC,
+ KEY_ENTER, KEY_DC, KEY_SCANCEL, KEY_BTAB
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int reset_hp_softkeys;
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_TTY_SLANG_H */
diff --git a/lib/tty/tty.c b/lib/tty/tty.c
new file mode 100644
index 0000000..cae0a05
--- /dev/null
+++ b/lib/tty/tty.c
@@ -0,0 +1,416 @@
+/*
+ Interface to the terminal controlling library.
+
+ Copyright (C) 2005-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Roland Illig <roland.illig@gmx.de>, 2005.
+ Andrew Borodin <aborodin@vmail.ru>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file tty.c
+ * \brief Source: %interface to the terminal controlling library
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h> /* memset() */
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else
+#include <sys/time.h>
+#include <sys/types.h>
+#endif
+#include <unistd.h> /* exit() */
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+/* In some systems (like Solaris 11.4 SPARC), TIOCSWINSZ is defined in termios.h */
+#include <termios.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+
+#include "tty.h"
+#include "tty-internal.h"
+#include "color.h" /* tty_set_normal_attrs() */
+#include "mouse.h" /* use_mouse_p */
+#include "win.h"
+
+/*** global variables ****************************************************************************/
+
+int mc_tty_frm[MC_TTY_FRM_MAX];
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static SIG_ATOMIC_VOLATILE_T got_interrupt = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sigintr_handler (int signo)
+{
+ (void) &signo;
+ got_interrupt = 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Check terminal type. If $TERM is not set or value is empty, mc finishes with EXIT_FAILURE.
+ *
+ * @param force_xterm Set forced the XTerm type
+ *
+ * @return true if @param force_xterm is true or value of $TERM is one of following:
+ * term*
+ * konsole*
+ * rxvt*
+ * Eterm
+ * dtterm
+ * alacritty*
+ * foot*
+ * screen*
+ * tmux*
+ * contour*
+ */
+gboolean
+tty_check_term (gboolean force_xterm)
+{
+ const char *termvalue;
+
+ termvalue = getenv ("TERM");
+ if (termvalue == NULL || *termvalue == '\0')
+ {
+ fputs (_("The TERM environment variable is unset!\n"), stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ /* *INDENT-OFF* */
+ return force_xterm
+ || strncmp (termvalue, "xterm", 5) == 0
+ || strncmp (termvalue, "konsole", 7) == 0
+ || strncmp (termvalue, "rxvt", 4) == 0
+ || strcmp (termvalue, "Eterm") == 0
+ || strcmp (termvalue, "dtterm") == 0
+ || strncmp (termvalue, "alacritty", 9) == 0
+ || strncmp (termvalue, "foot", 4) == 0
+ || strncmp (termvalue, "screen", 6) == 0
+ || strncmp (termvalue, "tmux", 4) == 0
+ || strncmp (termvalue, "contour", 7) == 0;
+ /* *INDENT-ON* */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+extern void
+tty_start_interrupt_key (void)
+{
+ struct sigaction act;
+
+ memset (&act, 0, sizeof (act));
+ act.sa_handler = sigintr_handler;
+ sigemptyset (&act.sa_mask);
+#ifdef SA_RESTART
+ act.sa_flags = SA_RESTART;
+#endif /* SA_RESTART */
+ sigaction (SIGINT, &act, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+extern void
+tty_enable_interrupt_key (void)
+{
+ struct sigaction act;
+
+ memset (&act, 0, sizeof (act));
+ act.sa_handler = sigintr_handler;
+ sigemptyset (&act.sa_mask);
+ sigaction (SIGINT, &act, NULL);
+ got_interrupt = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+extern void
+tty_disable_interrupt_key (void)
+{
+ struct sigaction act;
+
+ memset (&act, 0, sizeof (act));
+ act.sa_handler = SIG_IGN;
+ sigemptyset (&act.sa_mask);
+ sigaction (SIGINT, &act, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+extern gboolean
+tty_got_interrupt (void)
+{
+ gboolean rv;
+
+ rv = (got_interrupt != 0);
+ got_interrupt = 0;
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tty_got_winch (void)
+{
+ fd_set fdset;
+ /* *INDENT-OFF* */
+ /* instant timeout */
+ struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 };
+ /* *INDENT-ON* */
+ int ok;
+
+ FD_ZERO (&fdset);
+ FD_SET (sigwinch_pipe[0], &fdset);
+
+ while ((ok = select (sigwinch_pipe[0] + 1, &fdset, NULL, NULL, &timeout)) < 0)
+ if (errno != EINTR)
+ {
+ perror (_("Cannot check SIGWINCH pipe"));
+ exit (EXIT_FAILURE);
+ }
+
+ return (ok != 0 && FD_ISSET (sigwinch_pipe[0], &fdset));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_flush_winch (void)
+{
+ ssize_t n;
+
+ /* merge all SIGWINCH events raised to this moment */
+ do
+ {
+ char x[16];
+
+ /* read multiple events at a time */
+ n = read (sigwinch_pipe[0], &x, sizeof (x));
+ }
+ while (n > 0 || (n == -1 && errno == EINTR));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_one_hline (gboolean single)
+{
+ tty_print_alt_char (ACS_HLINE, single);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_print_one_vline (gboolean single)
+{
+ tty_print_alt_char (ACS_VLINE, single);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_draw_box (int y, int x, int ys, int xs, gboolean single)
+{
+ int y2, x2;
+
+ if (ys <= 0 || xs <= 0)
+ return;
+
+ ys--;
+ xs--;
+
+ y2 = y + ys;
+ x2 = x + xs;
+
+ tty_draw_vline (y, x, mc_tty_frm[single ? MC_TTY_FRM_VERT : MC_TTY_FRM_DVERT], ys);
+ tty_draw_vline (y, x2, mc_tty_frm[single ? MC_TTY_FRM_VERT : MC_TTY_FRM_DVERT], ys);
+ tty_draw_hline (y, x, mc_tty_frm[single ? MC_TTY_FRM_HORIZ : MC_TTY_FRM_DHORIZ], xs);
+ tty_draw_hline (y2, x, mc_tty_frm[single ? MC_TTY_FRM_HORIZ : MC_TTY_FRM_DHORIZ], xs);
+ tty_gotoyx (y, x);
+ tty_print_alt_char (ACS_ULCORNER, single);
+ tty_gotoyx (y2, x);
+ tty_print_alt_char (ACS_LLCORNER, single);
+ tty_gotoyx (y, x2);
+ tty_print_alt_char (ACS_URCORNER, single);
+ tty_gotoyx (y2, x2);
+ tty_print_alt_char (ACS_LRCORNER, single);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_draw_box_shadow (int y, int x, int rows, int cols, int shadow_color)
+{
+ /* draw right shadow */
+ tty_colorize_area (y + 1, x + cols, rows - 1, 2, shadow_color);
+ /* draw bottom shadow */
+ tty_colorize_area (y + rows, x + 2, 1, cols, shadow_color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mc_tty_normalize_from_utf8 (const char *str)
+{
+ GIConv conv;
+ GString *buffer;
+ const char *_system_codepage = str_detect_termencoding ();
+
+ if (str_isutf8 (_system_codepage))
+ return g_strdup (str);
+
+ conv = g_iconv_open (_system_codepage, "UTF-8");
+ if (conv == INVALID_CONV)
+ return g_strdup (str);
+
+ buffer = g_string_new ("");
+
+ if (str_convert (conv, str, buffer) == ESTR_FAILURE)
+ {
+ g_string_free (buffer, TRUE);
+ str_close_conv (conv);
+ return g_strdup (str);
+ }
+ str_close_conv (conv);
+
+ return g_string_free (buffer, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Resize given terminal using TIOCSWINSZ, return ioctl() result */
+int
+tty_resize (int fd)
+{
+#if defined TIOCSWINSZ
+ struct winsize tty_size;
+
+ tty_size.ws_row = LINES;
+ tty_size.ws_col = COLS;
+ tty_size.ws_xpixel = tty_size.ws_ypixel = 0;
+
+ return ioctl (fd, TIOCSWINSZ, &tty_size);
+#else
+ return 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Clear screen */
+void
+tty_clear_screen (void)
+{
+ tty_set_normal_attrs ();
+ tty_fill_region (0, 0, LINES, COLS, ' ');
+ tty_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tty_init_xterm_support (gboolean is_xterm)
+{
+ const char *termvalue;
+
+ termvalue = getenv ("TERM");
+
+ /* Check mouse and ca capabilities */
+ /* terminfo/termcap structures have been already initialized,
+ in slang_init() or/and init_curses() */
+ /* Check terminfo at first, then check termcap */
+ xmouse_seq = tty_tgetstr ("kmous");
+ if (xmouse_seq == NULL)
+ xmouse_seq = tty_tgetstr ("Km");
+ smcup = tty_tgetstr ("smcup");
+ if (smcup == NULL)
+ smcup = tty_tgetstr ("ti");
+ rmcup = tty_tgetstr ("rmcup");
+ if (rmcup == NULL)
+ rmcup = tty_tgetstr ("te");
+
+ if (strcmp (termvalue, "cygwin") == 0)
+ {
+ is_xterm = TRUE;
+ use_mouse_p = MOUSE_DISABLED;
+ }
+
+ if (is_xterm)
+ {
+ /* Default to the standard xterm sequence */
+ if (xmouse_seq == NULL)
+ xmouse_seq = ESC_STR "[M";
+
+ /* Enable mouse unless explicitly disabled by --nomouse */
+ if (use_mouse_p != MOUSE_DISABLED)
+ {
+ if (mc_global.tty.old_mouse)
+ use_mouse_p = MOUSE_XTERM_NORMAL_TRACKING;
+ else
+ {
+ /* FIXME: this dirty hack to set supported type of tracking the mouse */
+ const char *color_term = getenv ("COLORTERM");
+ if (strncmp (termvalue, "rxvt", 4) == 0 ||
+ (color_term != NULL && strncmp (color_term, "rxvt", 4) == 0) ||
+ strcmp (termvalue, "Eterm") == 0)
+ use_mouse_p = MOUSE_XTERM_NORMAL_TRACKING;
+ else
+ use_mouse_p = MOUSE_XTERM_BUTTON_EVENT_TRACKING;
+ }
+ }
+ }
+
+ /* There's only one termcap entry "kmous", typically containing "\E[M" or "\E[<".
+ * We need the former in xmouse_seq, the latter in xmouse_extended_seq.
+ * See tickets 2956, 3954, and 4063 for details. */
+ if (xmouse_seq != NULL)
+ {
+ if (strcmp (xmouse_seq, ESC_STR "[<") == 0)
+ xmouse_seq = ESC_STR "[M";
+
+ xmouse_extended_seq = ESC_STR "[<";
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/tty.h b/lib/tty/tty.h
new file mode 100644
index 0000000..90cbbc6
--- /dev/null
+++ b/lib/tty/tty.h
@@ -0,0 +1,146 @@
+
+/** \file tty.h
+ * \brief Header: %interface to the terminal controlling library
+ *
+ * This file is the %interface to the terminal controlling library:
+ * slang or ncurses. It provides an additional layer of abstraction
+ * above the "real" libraries to keep the number of ifdefs in the other
+ * files small.
+ */
+
+#ifndef MC__TTY_H
+#define MC__TTY_H
+
+#include "lib/global.h" /* include <glib.h> */
+
+#ifdef HAVE_SLANG
+#include "tty-slang.h"
+#else
+#include "tty-ncurses.h"
+#endif
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define KEY_KP_ADD 4001
+#define KEY_KP_SUBTRACT 4002
+#define KEY_KP_MULTIPLY 4003
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ /* single lines */
+ MC_TTY_FRM_VERT,
+ MC_TTY_FRM_HORIZ,
+ MC_TTY_FRM_LEFTTOP,
+ MC_TTY_FRM_RIGHTTOP,
+ MC_TTY_FRM_LEFTBOTTOM,
+ MC_TTY_FRM_RIGHTBOTTOM,
+ MC_TTY_FRM_TOPMIDDLE,
+ MC_TTY_FRM_BOTTOMMIDDLE,
+ MC_TTY_FRM_LEFTMIDDLE,
+ MC_TTY_FRM_RIGHTMIDDLE,
+ MC_TTY_FRM_CROSS,
+
+ /* double lines */
+ MC_TTY_FRM_DVERT,
+ MC_TTY_FRM_DHORIZ,
+ MC_TTY_FRM_DLEFTTOP,
+ MC_TTY_FRM_DRIGHTTOP,
+ MC_TTY_FRM_DLEFTBOTTOM,
+ MC_TTY_FRM_DRIGHTBOTTOM,
+ MC_TTY_FRM_DTOPMIDDLE,
+ MC_TTY_FRM_DBOTTOMMIDDLE,
+ MC_TTY_FRM_DLEFTMIDDLE,
+ MC_TTY_FRM_DRIGHTMIDDLE,
+
+ MC_TTY_FRM_MAX
+} mc_tty_frm_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int mc_tty_frm[];
+
+extern char *tty_tgetstr (const char *name);
+
+/*** declarations of public functions ************************************************************/
+
+extern void tty_beep (void);
+
+/* {{{ Input }}} */
+
+extern gboolean tty_check_term (gboolean force_xterm);
+extern void tty_init (gboolean mouse_enable, gboolean is_xterm);
+extern void tty_shutdown (void);
+
+extern void tty_start_interrupt_key (void);
+extern void tty_enable_interrupt_key (void);
+extern void tty_disable_interrupt_key (void);
+extern gboolean tty_got_interrupt (void);
+
+extern gboolean tty_got_winch (void);
+extern void tty_flush_winch (void);
+
+extern void tty_reset_prog_mode (void);
+extern void tty_reset_shell_mode (void);
+
+extern void tty_raw_mode (void);
+extern void tty_noraw_mode (void);
+
+extern void tty_noecho (void);
+extern int tty_flush_input (void);
+
+extern void tty_keypad (gboolean set);
+extern void tty_nodelay (gboolean set);
+extern int tty_baudrate (void);
+
+/* {{{ Output }}} */
+
+/*
+ The output functions do not check themselves for screen overflows,
+ so make sure that you never write more than what fits on the screen.
+ While SLang provides such a feature, ncurses does not.
+ */
+
+extern int tty_reset_screen (void);
+extern void tty_touch_screen (void);
+
+extern void tty_gotoyx (int y, int x);
+extern void tty_getyx (int *py, int *px);
+
+extern void tty_set_alt_charset (gboolean alt_charset);
+
+extern void tty_display_8bit (gboolean what);
+extern void tty_print_char (int c);
+extern void tty_print_alt_char (int c, gboolean single);
+extern void tty_print_anychar (int c);
+extern void tty_print_string (const char *s);
+/* *INDENT-OFF* */
+extern void tty_printf (const char *s, ...) G_GNUC_PRINTF (1, 2);
+/* *INDENT-ON* */
+
+extern void tty_print_one_vline (gboolean single);
+extern void tty_print_one_hline (gboolean single);
+extern void tty_draw_hline (int y, int x, int ch, int len);
+extern void tty_draw_vline (int y, int x, int ch, int len);
+extern void tty_draw_box (int y, int x, int rows, int cols, gboolean single);
+extern void tty_draw_box_shadow (int y, int x, int rows, int cols, int shadow_color);
+extern void tty_fill_region (int y, int x, int rows, int cols, unsigned char ch);
+
+extern int tty_resize (int fd);
+extern void tty_refresh (void);
+extern void tty_change_screen_size (void);
+
+/* Clear screen */
+extern void tty_clear_screen (void);
+
+extern int mc_tty_normalize_lines_char (const char *str);
+
+extern void tty_enter_ca_mode (void);
+extern void tty_exit_ca_mode (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__TTY_H */
diff --git a/lib/tty/win.c b/lib/tty/win.c
new file mode 100644
index 0000000..45451a4
--- /dev/null
+++ b/lib/tty/win.c
@@ -0,0 +1,168 @@
+/*
+ Terminal management xterm and rxvt support
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2009.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file win.c
+ * \brief Source: Terminal management xterm and rxvt support
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/util.h" /* is_printable() */
+#include "tty-internal.h"
+#include "tty.h" /* tty_gotoyx, tty_print_char */
+#include "win.h"
+
+/*** global variables ****************************************************************************/
+
+char *smcup = NULL;
+char *rmcup = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static gboolean rxvt_extensions = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* my own weird protocol base 16 - paul */
+static int
+rxvt_getc (void)
+{
+ int r;
+ unsigned char c;
+
+ while (read (0, &c, 1) != 1);
+ if (c == '\n')
+ return -1;
+ r = (c - 'A') * 16;
+ while (read (0, &c, 1) != 1);
+ r += (c - 'A');
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+anything_ready (void)
+{
+ fd_set fds;
+ struct timeval tv;
+
+ FD_ZERO (&fds);
+ FD_SET (0, &fds);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ return select (1, &fds, 0, 0, &tv);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+show_rxvt_contents (int starty, unsigned char y1, unsigned char y2)
+{
+ unsigned char *k;
+ int bytes, i, j, cols = 0;
+
+ y1 += mc_global.keybar_visible != 0 ? 1 : 0; /* i don't know why we need this - paul */
+ y2 += mc_global.keybar_visible != 0 ? 1 : 0;
+ while (anything_ready ())
+ tty_lowlevel_getch ();
+
+ /* my own weird protocol base 26 - paul */
+ printf (ESC_STR "CL%c%c%c%c\n", (y1 / 26) + 'A', (y1 % 26) + 'A', (y2 / 26) + 'A',
+ (y2 % 26) + 'A');
+
+ bytes = (y2 - y1) * (COLS + 1) + 1; /* *should* be the number of bytes read */
+ j = 0;
+ k = g_malloc (bytes);
+ while (TRUE)
+ {
+ int c;
+
+ c = rxvt_getc ();
+ if (c < 0)
+ break;
+ if (j < bytes)
+ k[j++] = c;
+ for (cols = 1;; cols++)
+ {
+ c = rxvt_getc ();
+ if (c < 0)
+ break;
+ if (j < bytes)
+ k[j++] = c;
+ }
+ }
+ for (i = 0; i < j; i++)
+ {
+ if ((i % cols) == 0)
+ tty_gotoyx (starty + (i / cols), 0);
+ tty_print_char (is_printable (k[i]) ? k[i] : ' ');
+ }
+ g_free (k);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+look_for_rxvt_extensions (void)
+{
+ static gboolean been_called = FALSE;
+
+ if (!been_called)
+ {
+ const char *e = getenv ("RXVT_EXT");
+ rxvt_extensions = ((e != NULL) && (strcmp (e, "1.0") == 0));
+ been_called = TRUE;
+ }
+
+ if (rxvt_extensions)
+ mc_global.tty.console_flag = '\004';
+
+ return rxvt_extensions;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/win.h b/lib/tty/win.h
new file mode 100644
index 0000000..4c31607
--- /dev/null
+++ b/lib/tty/win.h
@@ -0,0 +1,24 @@
+/** \file win.h
+ * \brief Header: X terminal management: xterm and rxvt
+ */
+
+#ifndef MC__WIN_H
+#define MC__WIN_H
+
+#include "lib/global.h" /* <glib.h> */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void show_rxvt_contents (int starty, unsigned char y1, unsigned char y2);
+gboolean look_for_rxvt_extensions (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_WIN_H */
diff --git a/lib/tty/x11conn.c b/lib/tty/x11conn.c
new file mode 100644
index 0000000..20e201b
--- /dev/null
+++ b/lib/tty/x11conn.c
@@ -0,0 +1,266 @@
+/*
+ X11 support for the Midnight Commander.
+
+ Copyright (C) 2005-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Roland Illig <roland.illig@gmx.de>, 2005.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file x11conn.c
+ * \brief Source: X11 support
+ * \warning This code uses setjmp() and longjmp(). Before you modify _anything_ here,
+ * please read the relevant sections of the C standard.
+ */
+
+#include <config.h>
+
+#include <setjmp.h>
+#include <X11/Xlib.h>
+#ifdef HAVE_GMODULE
+#include <gmodule.h>
+#endif
+
+#include "lib/global.h"
+#include "x11conn.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef HAVE_GMODULE
+#define func_XOpenDisplay XOpenDisplay
+#define func_XCloseDisplay XCloseDisplay
+#define func_XSetErrorHandler XSetErrorHandler
+#define func_XSetIOErrorHandler XSetIOErrorHandler
+#define func_XQueryPointer XQueryPointer
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+typedef int (*mc_XErrorHandler_callback) (Display *, XErrorEvent *);
+typedef int (*mc_XIOErrorHandler_callback) (Display *);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef HAVE_GMODULE
+static Display *(*func_XOpenDisplay) (_Xconst char *);
+static int (*func_XCloseDisplay) (Display *);
+static mc_XErrorHandler_callback (*func_XSetErrorHandler) (mc_XErrorHandler_callback);
+static mc_XIOErrorHandler_callback (*func_XSetIOErrorHandler) (mc_XIOErrorHandler_callback);
+static Bool (*func_XQueryPointer) (Display *, Window, Window *, Window *,
+ int *, int *, int *, int *, unsigned int *);
+
+static GModule *x11_module;
+#endif
+
+static gboolean handlers_installed = FALSE;
+
+/* This flag is set as soon as an X11 error is reported. Usually that
+ * means that the DISPLAY is not available anymore. We do not try to
+ * reconnect, as that would violate the X11 protocol. */
+static gboolean lost_connection = FALSE;
+
+static jmp_buf x11_exception; /* FIXME: get a better name */
+static gboolean longjmp_allowed = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+x_io_error_handler (Display * dpy)
+{
+ (void) dpy;
+
+ lost_connection = TRUE;
+ if (longjmp_allowed)
+ {
+ longjmp_allowed = FALSE;
+ longjmp (x11_exception, 1);
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+x_error_handler (Display * dpy, XErrorEvent * ee)
+{
+ (void) ee;
+ (void) func_XCloseDisplay (dpy);
+ return x_io_error_handler (dpy);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+install_error_handlers (void)
+{
+ if (handlers_installed)
+ return;
+
+ (void) func_XSetErrorHandler (x_error_handler);
+ (void) func_XSetIOErrorHandler (x_io_error_handler);
+ handlers_installed = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+x11_available (void)
+{
+#ifdef HAVE_GMODULE
+ gchar *x11_module_fname;
+
+ if (lost_connection)
+ return FALSE;
+
+ if (x11_module != NULL)
+ return TRUE;
+
+ x11_module_fname = g_module_build_path (NULL, "X11");
+ x11_module = g_module_open (x11_module_fname, G_MODULE_BIND_LAZY);
+ if (x11_module == NULL)
+ x11_module = g_module_open ("libX11.so.6", G_MODULE_BIND_LAZY);
+
+ g_free (x11_module_fname);
+
+ if (x11_module == NULL)
+ return FALSE;
+
+ if (!g_module_symbol (x11_module, "XOpenDisplay", (void *) &func_XOpenDisplay))
+ goto cleanup;
+ if (!g_module_symbol (x11_module, "XCloseDisplay", (void *) &func_XCloseDisplay))
+ goto cleanup;
+ if (!g_module_symbol (x11_module, "XQueryPointer", (void *) &func_XQueryPointer))
+ goto cleanup;
+ if (!g_module_symbol (x11_module, "XSetErrorHandler", (void *) &func_XSetErrorHandler))
+ goto cleanup;
+ if (!g_module_symbol (x11_module, "XSetIOErrorHandler", (void *) &func_XSetIOErrorHandler))
+ goto cleanup;
+
+ install_error_handlers ();
+ return TRUE;
+
+ cleanup:
+ func_XOpenDisplay = 0;
+ func_XCloseDisplay = 0;
+ func_XQueryPointer = 0;
+ func_XSetErrorHandler = 0;
+ func_XSetIOErrorHandler = 0;
+ g_module_close (x11_module);
+ x11_module = NULL;
+ return FALSE;
+#else
+ install_error_handlers ();
+ return !(lost_connection);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+Display *
+mc_XOpenDisplay (const char *displayname)
+{
+ if (x11_available ())
+ {
+ if (setjmp (x11_exception) == 0)
+ {
+ Display *retval;
+
+ /* cppcheck-suppress redundantAssignment */
+ longjmp_allowed = TRUE;
+
+ retval = func_XOpenDisplay (displayname);
+
+ /* cppcheck-suppress redundantAssignment */
+ longjmp_allowed = FALSE;
+ return retval;
+ }
+ }
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_XCloseDisplay (Display * display)
+{
+ if (x11_available ())
+ {
+ if (setjmp (x11_exception) == 0)
+ {
+ int retval;
+
+ /* cppcheck-suppress redundantAssignment */
+ longjmp_allowed = TRUE;
+
+ retval = func_XCloseDisplay (display);
+
+ /* cppcheck-suppress redundantAssignment */
+ longjmp_allowed = FALSE;
+
+ return retval;
+ }
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+Bool
+mc_XQueryPointer (Display * display, Window win, Window * root_return,
+ Window * child_return, int *root_x_return, int *root_y_return,
+ int *win_x_return, int *win_y_return, unsigned int *mask_return)
+{
+ Bool retval;
+
+ if (x11_available ())
+ {
+ if (setjmp (x11_exception) == 0)
+ {
+ /* cppcheck-suppress redundantAssignment */
+ longjmp_allowed = TRUE;
+
+ retval = func_XQueryPointer (display, win, root_return,
+ child_return, root_x_return, root_y_return,
+ win_x_return, win_y_return, mask_return);
+
+ /* cppcheck-suppress redundantAssignment */
+ longjmp_allowed = FALSE;
+
+ return retval;
+ }
+ }
+ *root_return = None;
+ *child_return = None;
+ *root_x_return = 0;
+ *root_y_return = 0;
+ *win_x_return = 0;
+ *win_y_return = 0;
+ *mask_return = 0;
+ return False;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/tty/x11conn.h b/lib/tty/x11conn.h
new file mode 100644
index 0000000..fbfe15a
--- /dev/null
+++ b/lib/tty/x11conn.h
@@ -0,0 +1,40 @@
+/** \file x11conn.h
+ * \brief Header: X11 support
+ * \warning This code uses setjmp() and longjmp(). Before you modify _anything_ here,
+ * please read the relevant sections of the C standard.
+ */
+
+#ifndef MC__X11CONN_H
+#define MC__X11CONN_H
+
+/*
+ This module provides support for some X11 functions. The functions
+ are loaded dynamically if GModule is available, and statically if
+ not. X11 session handling is somewhat robust. If there is an X11
+ error or a connection error, all further traffic to the X server
+ will be suppressed, and the functions will return reasonable default
+ values.
+ */
+
+#include <X11/Xlib.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+extern Display *mc_XOpenDisplay (const char *displayname);
+extern int mc_XCloseDisplay (Display * display);
+
+extern Bool mc_XQueryPointer (Display * display, Window win, Window * root_return,
+ Window * child_return, int *root_x_return, int *root_y_return,
+ int *win_x_return, int *win_y_return, unsigned int *mask_return);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__X11CONN_H */
diff --git a/lib/unixcompat.h b/lib/unixcompat.h
new file mode 100644
index 0000000..c7ff12d
--- /dev/null
+++ b/lib/unixcompat.h
@@ -0,0 +1,63 @@
+/** \file unixcompat.h
+ * \brief Header: collects differences between the various Unix
+ *
+ * This header file collects differences between the various Unix
+ * variants that are supported by the Midnight Commander and provides
+ * replacement routines if they are not natively available.
+ * The major/minor macros are not specified in SUSv3, so we can only hope
+ * they are provided by the operating system or emulate it.
+ */
+
+#ifndef MC_UNIXCOMPAT_H
+#define MC_UNIXCOMPAT_H
+
+#include <sys/types.h> /* BSD */
+
+#ifdef MAJOR_IN_MKDEV
+#include <sys/mkdev.h>
+#elif defined MAJOR_IN_SYSMACROS
+#include <sys/sysmacros.h>
+#endif
+
+#include <unistd.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#ifndef major
+#warning major() is undefined. Device numbers will not be shown correctly.
+#define major(devnum) (((devnum) >> 8) & 0xff)
+#endif
+
+#ifndef minor
+#warning minor() is undefined. Device numbers will not be shown correctly.
+#define minor(devnum) (((devnum) & 0xff))
+#endif
+
+#ifndef makedev
+#warning makedev() is undefined. Device numbers will not be shown correctly.
+#define makedev(major,minor) ((((major) & 0xff) << 8) | ((minor) & 0xff))
+#endif
+
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/lib/util.c b/lib/util.c
new file mode 100644
index 0000000..744bd9e
--- /dev/null
+++ b/lib/util.c
@@ -0,0 +1,1538 @@
+/*
+ Various utilities
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996
+ Janne Kukonlehto, 1994, 1995, 1996
+ Dugan Porter, 1994, 1995, 1996
+ Jakub Jelinek, 1994, 1995, 1996
+ Mauricio Plaza, 1994, 1995, 1996
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file lib/util.c
+ * \brief Source: various utilities
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stddef.h> /* ptrdiff_t */
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/fileloc.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define ismode(n,m) ((n & m) == m)
+
+/* Number of attempts to create a temporary file */
+#ifndef TMP_MAX
+#define TMP_MAX 16384
+#endif /* !TMP_MAX */
+
+#define TMP_SUFFIX ".tmp"
+
+#define ASCII_A (0x40 + 1)
+#define ASCII_Z (0x40 + 26)
+#define ASCII_a (0x60 + 1)
+#define ASCII_z (0x60 + 26)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifndef HAVE_CHARSET
+static inline int
+is_7bit_printable (unsigned char c)
+{
+ return (c > 31 && c < 127);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+is_iso_printable (unsigned char c)
+{
+ return ((c > 31 && c < 127) || c >= 160);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+is_8bit_printable (unsigned char c)
+{
+ /* "Full 8 bits output" doesn't work on xterm */
+ if (mc_global.tty.xterm_flag)
+ return is_iso_printable (c);
+
+ return (c > 31 && c != 127 && c != 155);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+resolve_symlinks (const vfs_path_t * vpath)
+{
+ char *p, *p2;
+ char *buf, *buf2, *q, *r, c;
+ struct stat mybuf;
+
+ if (vpath->relative)
+ return NULL;
+
+ p = p2 = g_strdup (vfs_path_as_str (vpath));
+ r = buf = g_malloc (MC_MAXPATHLEN);
+ buf2 = g_malloc (MC_MAXPATHLEN);
+ *r++ = PATH_SEP;
+ *r = '\0';
+
+ do
+ {
+ q = strchr (p + 1, PATH_SEP);
+ if (q == NULL)
+ {
+ q = strchr (p + 1, '\0');
+ if (q == p + 1)
+ break;
+ }
+ c = *q;
+ *q = '\0';
+ if (mc_lstat (vpath, &mybuf) < 0)
+ {
+ MC_PTR_FREE (buf);
+ goto ret;
+ }
+ if (!S_ISLNK (mybuf.st_mode))
+ strcpy (r, p + 1);
+ else
+ {
+ int len;
+
+ len = mc_readlink (vpath, buf2, MC_MAXPATHLEN - 1);
+ if (len < 0)
+ {
+ MC_PTR_FREE (buf);
+ goto ret;
+ }
+ buf2[len] = '\0';
+ if (IS_PATH_SEP (*buf2))
+ strcpy (buf, buf2);
+ else
+ strcpy (r, buf2);
+ }
+ canonicalize_pathname (buf);
+ r = strchr (buf, '\0');
+ if (*r == '\0' || !IS_PATH_SEP (r[-1]))
+ /* FIXME: this condition is always true because r points to the EOL */
+ {
+ *r++ = PATH_SEP;
+ *r = '\0';
+ }
+ *q = c;
+ p = q;
+ }
+ while (c != '\0');
+
+ if (*buf == '\0')
+ strcpy (buf, PATH_SEP_STR);
+ else if (IS_PATH_SEP (r[-1]) && r != buf + 1)
+ r[-1] = '\0';
+
+ ret:
+ g_free (buf2);
+ g_free (p2);
+ return buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mc_util_write_backup_content (const char *from_file_name, const char *to_file_name)
+{
+ FILE *backup_fd;
+ char *contents;
+ gsize length;
+ gboolean ret1 = TRUE;
+
+ if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
+ return FALSE;
+
+ backup_fd = fopen (to_file_name, "w");
+ if (backup_fd == NULL)
+ {
+ g_free (contents);
+ return FALSE;
+ }
+
+ if (fwrite ((const void *) contents, 1, length, backup_fd) != length)
+ ret1 = FALSE;
+
+ {
+ int ret2;
+
+ /* cppcheck-suppress redundantAssignment */
+ ret2 = fflush (backup_fd);
+ /* cppcheck-suppress redundantAssignment */
+ ret2 = fclose (backup_fd);
+ (void) ret2;
+ }
+
+ g_free (contents);
+ return ret1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+is_printable (int c)
+{
+ c &= 0xff;
+
+#ifdef HAVE_CHARSET
+ /* "Display bits" is ignored, since the user controls the output
+ by setting the output codepage */
+ return is_8bit_printable (c);
+#else
+ if (!mc_global.eight_bit_clean)
+ return is_7bit_printable (c);
+
+ if (mc_global.full_eight_bits)
+ return is_8bit_printable (c);
+
+ return is_iso_printable (c);
+#endif /* !HAVE_CHARSET */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Quote the filename for the purpose of inserting it into the command
+ * line. If quote_percent is TRUE, replace "%" with "%%" - the percent is
+ * processed by the mc command line.
+ */
+char *
+name_quote (const char *s, gboolean quote_percent)
+{
+ GString *ret;
+
+ ret = g_string_sized_new (64);
+
+ if (*s == '-')
+ g_string_append (ret, "." PATH_SEP_STR);
+
+ for (; *s != '\0'; s++)
+ {
+ switch (*s)
+ {
+ case '%':
+ if (quote_percent)
+ g_string_append_c (ret, '%');
+ break;
+ case '\'':
+ case '\\':
+ case '\r':
+ case '\n':
+ case '\t':
+ case '"':
+ case ';':
+ case ' ':
+ case '?':
+ case '|':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case '<':
+ case '>':
+ case '`':
+ case '!':
+ case '$':
+ case '&':
+ case '*':
+ case '(':
+ case ')':
+ g_string_append_c (ret, '\\');
+ break;
+ case '~':
+ case '#':
+ if (ret->len == 0)
+ g_string_append_c (ret, '\\');
+ break;
+ default:
+ break;
+ }
+ g_string_append_c (ret, *s);
+ }
+
+ return g_string_free (ret, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+fake_name_quote (const char *s, gboolean quote_percent)
+{
+ (void) quote_percent;
+ return g_strdup (s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * path_trunc() is the same as str_trunc() but
+ * it deletes possible password from path for security
+ * reasons.
+ */
+
+const char *
+path_trunc (const char *path, size_t trunc_len)
+{
+ vfs_path_t *vpath;
+ char *secure_path;
+ const char *ret;
+
+ vpath = vfs_path_from_str (path);
+ secure_path = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
+ vfs_path_free (vpath, TRUE);
+
+ ret = str_trunc (secure_path, trunc_len);
+ g_free (secure_path);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+size_trunc (uintmax_t size, gboolean use_si)
+{
+ static char x[BUF_TINY];
+ uintmax_t divisor = 1;
+ const char *xtra = _("B");
+
+ if (size > 999999999UL)
+ {
+ divisor = use_si ? 1000 : 1024;
+ xtra = use_si ? _("kB") : _("KiB");
+
+ if (size / divisor > 999999999UL)
+ {
+ divisor = use_si ? (1000 * 1000) : (1024 * 1024);
+ xtra = use_si ? _("MB") : _("MiB");
+
+ if (size / divisor > 999999999UL)
+ {
+ divisor = use_si ? (1000 * 1000 * 1000) : (1024 * 1024 * 1024);
+ xtra = use_si ? _("GB") : _("GiB");
+ }
+ }
+ }
+ g_snprintf (x, sizeof (x), "%.0f %s", 1.0 * size / divisor, xtra);
+ return x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+size_trunc_sep (uintmax_t size, gboolean use_si)
+{
+ static char x[60];
+ int count;
+ const char *p, *y;
+ char *d;
+
+ p = y = size_trunc (size, use_si);
+ p += strlen (p) - 1;
+ d = x + sizeof (x) - 1;
+ *d-- = '\0';
+ /* @size format is "size unit", i.e. "[digits][space][letters]".
+ Copy all characters after digits. */
+ while (p >= y && !g_ascii_isdigit (*p))
+ *d-- = *p--;
+ for (count = 0; p >= y; count++)
+ {
+ if (count == 3)
+ {
+ *d-- = ',';
+ count = 0;
+ }
+ *d-- = *p--;
+ }
+ d++;
+ if (*d == ',')
+ d++;
+ return d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Print file SIZE to BUFFER, but don't exceed LEN characters,
+ * not including trailing 0. BUFFER should be at least LEN+1 long.
+ * This function is called for every file on panels, so avoid
+ * floating point by any means.
+ *
+ * Units: size units (filesystem sizes are 1K blocks)
+ * 0=bytes, 1=Kbytes, 2=Mbytes, etc.
+ */
+
+void
+size_trunc_len (char *buffer, unsigned int len, uintmax_t size, int units, gboolean use_si)
+{
+ /* Avoid taking power for every file. */
+ /* *INDENT-OFF* */
+ static const uintmax_t power10[] = {
+ /* we hope that size of uintmax_t is 4 bytes at least */
+ 1ULL,
+ 10ULL,
+ 100ULL,
+ 1000ULL,
+ 10000ULL,
+ 100000ULL,
+ 1000000ULL,
+ 10000000ULL,
+ 100000000ULL,
+ 1000000000ULL
+ /* maximum value of uintmax_t (in case of 4 bytes) is
+ 4294967295
+ */
+#if SIZEOF_UINTMAX_T == 8
+ ,
+ 10000000000ULL,
+ 100000000000ULL,
+ 1000000000000ULL,
+ 10000000000000ULL,
+ 100000000000000ULL,
+ 1000000000000000ULL,
+ 10000000000000000ULL,
+ 100000000000000000ULL,
+ 1000000000000000000ULL,
+ 10000000000000000000ULL
+ /* maximum value of uintmax_t (in case of 8 bytes) is
+ 18447644073710439615
+ */
+#endif
+ };
+ /* *INDENT-ON* */
+ static const char *const suffix[] =
+ { "", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q", NULL };
+ static const char *const suffix_lc[] =
+ { "", "k", "m", "g", "t", "p", "e", "z", "y", "r", "q", NULL };
+
+ const char *const *sfx = use_si ? suffix_lc : suffix;
+ int j = 0;
+
+ if (len == 0)
+ len = 9;
+#if SIZEOF_UINTMAX_T == 8
+ /* 20 decimal digits are required to represent 8 bytes */
+ else if (len > 19)
+ len = 19;
+#else
+ /* 10 decimal digits are required to represent 4 bytes */
+ else if (len > 9)
+ len = 9;
+#endif
+
+ /*
+ * recalculate from 1024 base to 1000 base if units>0
+ * We can't just multiply by 1024 - that might cause overflow
+ * if uintmax_t type is too small
+ */
+ if (use_si)
+ for (j = 0; j < units; j++)
+ {
+ uintmax_t size_remain;
+
+ size_remain = ((size % 125) * 1024) / 1000; /* size mod 125, recalculated */
+ size /= 125; /* 128/125 = 1024/1000 */
+ size *= 128; /* This will convert size from multiple of 1024 to multiple of 1000 */
+ size += size_remain; /* Re-add remainder lost by division/multiplication */
+ }
+
+ for (j = units; sfx[j] != NULL; j++)
+ {
+ if (size == 0)
+ {
+ if (j == units)
+ {
+ /* Empty files will print "0" even with minimal width. */
+ g_snprintf (buffer, len + 1, "%s", "0");
+ }
+ else
+ {
+ /* Use "~K" or just "K" if len is 1. Use "B" for bytes. */
+ g_snprintf (buffer, len + 1, (len > 1) ? "~%s" : "%s", (j > 1) ? sfx[j - 1] : "B");
+ }
+ break;
+ }
+
+ if (size < power10[len - (j > 0 ? 1 : 0)])
+ {
+ g_snprintf (buffer, len + 1, "%" PRIuMAX "%s", size, sfx[j]);
+ break;
+ }
+
+ /* Powers of 1000 or 1024, with rounding. */
+ if (use_si)
+ size = (size + 500) / 1000;
+ else
+ size = (size + 512) >> 10;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+string_perm (mode_t mode_bits)
+{
+ static char mode[11];
+
+ strcpy (mode, "----------");
+ if (S_ISDIR (mode_bits))
+ mode[0] = 'd';
+ if (S_ISCHR (mode_bits))
+ mode[0] = 'c';
+ if (S_ISBLK (mode_bits))
+ mode[0] = 'b';
+ if (S_ISLNK (mode_bits))
+ mode[0] = 'l';
+ if (S_ISFIFO (mode_bits))
+ mode[0] = 'p';
+ if (S_ISNAM (mode_bits))
+ mode[0] = 'n';
+ if (S_ISSOCK (mode_bits))
+ mode[0] = 's';
+ if (S_ISDOOR (mode_bits))
+ mode[0] = 'D';
+ if (ismode (mode_bits, S_IXOTH))
+ mode[9] = 'x';
+ if (ismode (mode_bits, S_IWOTH))
+ mode[8] = 'w';
+ if (ismode (mode_bits, S_IROTH))
+ mode[7] = 'r';
+ if (ismode (mode_bits, S_IXGRP))
+ mode[6] = 'x';
+ if (ismode (mode_bits, S_IWGRP))
+ mode[5] = 'w';
+ if (ismode (mode_bits, S_IRGRP))
+ mode[4] = 'r';
+ if (ismode (mode_bits, S_IXUSR))
+ mode[3] = 'x';
+ if (ismode (mode_bits, S_IWUSR))
+ mode[2] = 'w';
+ if (ismode (mode_bits, S_IRUSR))
+ mode[1] = 'r';
+#ifdef S_ISUID
+ if (ismode (mode_bits, S_ISUID))
+ mode[3] = (mode[3] == 'x') ? 's' : 'S';
+#endif /* S_ISUID */
+#ifdef S_ISGID
+ if (ismode (mode_bits, S_ISGID))
+ mode[6] = (mode[6] == 'x') ? 's' : 'S';
+#endif /* S_ISGID */
+#ifdef S_ISVTX
+ if (ismode (mode_bits, S_ISVTX))
+ mode[9] = (mode[9] == 'x') ? 't' : 'T';
+#endif /* S_ISVTX */
+ return mode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+extension (const char *filename)
+{
+ const char *d;
+
+ d = strrchr (filename, '.');
+
+ return d != NULL ? d + 1 : "";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+load_mc_home_file (const char *from, const char *filename, char **allocated_filename,
+ size_t * length)
+{
+ char *hintfile_base, *hintfile;
+ char *lang;
+ char *data;
+
+ hintfile_base = g_build_filename (from, filename, (char *) NULL);
+ lang = guess_message_value ();
+
+ hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
+ if (!g_file_get_contents (hintfile, &data, length, NULL))
+ {
+ /* Fall back to the two-letter language code */
+ if (lang[0] != '\0' && lang[1] != '\0')
+ lang[2] = '\0';
+ g_free (hintfile);
+ hintfile = g_strconcat (hintfile_base, ".", lang, (char *) NULL);
+ if (!g_file_get_contents (hintfile, &data, length, NULL))
+ {
+ g_free (hintfile);
+ hintfile = hintfile_base;
+ g_file_get_contents (hintfile_base, &data, length, NULL);
+ }
+ }
+
+ g_free (lang);
+
+ if (hintfile != hintfile_base)
+ g_free (hintfile_base);
+
+ if (allocated_filename != NULL)
+ *allocated_filename = hintfile;
+ else
+ g_free (hintfile);
+
+ return data;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+extract_line (const char *s, const char *top)
+{
+ static char tmp_line[BUF_MEDIUM];
+ char *t = tmp_line;
+
+ while (*s != '\0' && *s != '\n' && (size_t) (t - tmp_line) < sizeof (tmp_line) - 1 && s < top)
+ *t++ = *s++;
+ *t = '\0';
+ return tmp_line;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * The basename routine
+ */
+
+const char *
+x_basename (const char *s)
+{
+ const char *url_delim, *path_sep;
+
+ url_delim = g_strrstr (s, VFS_PATH_URL_DELIMITER);
+ path_sep = strrchr (s, PATH_SEP);
+
+ if (path_sep == NULL)
+ return s;
+
+ if (url_delim == NULL
+ || url_delim < path_sep - strlen (VFS_PATH_URL_DELIMITER)
+ || url_delim - s + strlen (VFS_PATH_URL_DELIMITER) < strlen (s))
+ {
+ /* avoid trailing PATH_SEP, if present */
+ if (!IS_PATH_SEP (s[strlen (s) - 1]))
+ return path_sep + 1;
+
+ while (--path_sep > s && !IS_PATH_SEP (*path_sep))
+ ;
+ return (path_sep != s) ? path_sep + 1 : s;
+ }
+
+ while (--url_delim > s && !IS_PATH_SEP (*url_delim))
+ ;
+ while (--url_delim > s && !IS_PATH_SEP (*url_delim))
+ ;
+
+ return url_delim == s ? s : url_delim + 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+unix_error_string (int error_num)
+{
+ static char buffer[BUF_LARGE];
+ gchar *strerror_currentlocale;
+
+ strerror_currentlocale = g_locale_from_utf8 (g_strerror (error_num), -1, NULL, NULL, NULL);
+ g_snprintf (buffer, sizeof (buffer), "%s (%d)", strerror_currentlocale, error_num);
+ g_free (strerror_currentlocale);
+
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+skip_separators (const char *s)
+{
+ const char *su = s;
+
+ for (; *su != '\0'; str_cnext_char (&su))
+ if (!whitespace (*su) && *su != ',')
+ break;
+
+ return su;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+skip_numbers (const char *s)
+{
+ const char *su = s;
+
+ for (; *su != '\0'; str_cnext_char (&su))
+ if (!str_isdigit (su))
+ break;
+
+ return su;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove all control sequences from the argument string. We define
+ * "control sequence", in a sort of pidgin BNF, as follows:
+ *
+ * control-seq = Esc non-'['
+ * | Esc '[' (0 or more digits or ';' or ':' or '?') (any other char)
+ *
+ * The 256-color and true-color escape sequences should allow either ';' or ':' inside as separator,
+ * actually, ':' is the more correct according to ECMA-48.
+ * Some terminal emulators (e.g. xterm, gnome-terminal) support this.
+ *
+ * Non-printable characters are also removed.
+ */
+
+char *
+strip_ctrl_codes (char *s)
+{
+ char *w; /* Current position where the stripped data is written */
+ char *r; /* Current position where the original data is read */
+
+ if (s == NULL)
+ return NULL;
+
+ for (w = s, r = s; *r != '\0';)
+ {
+ if (*r == ESC_CHAR)
+ {
+ /* Skip the control sequence's arguments */ ;
+ /* '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) */
+ if (*(++r) == '[' || *r == '(')
+ {
+ /* strchr() matches trailing binary 0 */
+ while (*(++r) != '\0' && strchr ("0123456789;:?", *r) != NULL)
+ ;
+ }
+ else if (*r == ']')
+ {
+ /*
+ * Skip xterm's OSC (Operating System Command)
+ * http://www.xfree86.org/current/ctlseqs.html
+ * OSC P s ; P t ST
+ * OSC P s ; P t BEL
+ */
+ char *new_r;
+
+ for (new_r = r; *new_r != '\0'; new_r++)
+ {
+ switch (*new_r)
+ {
+ /* BEL */
+ case '\a':
+ r = new_r;
+ goto osc_out;
+ case ESC_CHAR:
+ /* ST */
+ if (new_r[1] == '\\')
+ {
+ r = new_r + 1;
+ goto osc_out;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ osc_out:
+ ;
+ }
+
+ /*
+ * Now we are at the last character of the sequence.
+ * Skip it unless it's binary 0.
+ */
+ if (*r != '\0')
+ r++;
+ }
+ else
+ {
+ char *n;
+
+ n = str_get_next_char (r);
+ if (str_isprint (r))
+ {
+ memmove (w, r, n - r);
+ w += n - r;
+ }
+ r = n;
+ }
+ }
+
+ *w = '\0';
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+enum compression_type
+get_compression_type (int fd, const char *name)
+{
+ unsigned char magic[16];
+ size_t str_len;
+
+ /* Read the magic signature */
+ if (mc_read (fd, (char *) magic, 4) != 4)
+ return COMPRESSION_NONE;
+
+ /* GZIP_MAGIC and OLD_GZIP_MAGIC */
+ if (magic[0] == 0x1F && (magic[1] == 0x8B || magic[1] == 0x9E))
+ return COMPRESSION_GZIP;
+
+ /* PKZIP_MAGIC */
+ if (magic[0] == 'P' && magic[1] == 'K' && magic[2] == 0x03 && magic[3] == 0x04)
+ {
+ /* Read compression type */
+ mc_lseek (fd, 8, SEEK_SET);
+ if (mc_read (fd, (char *) magic, 2) != 2)
+ return COMPRESSION_NONE;
+
+ if ((magic[0] != 8 && magic[0] != 0) || magic[1] != 0)
+ return COMPRESSION_NONE;
+
+ return COMPRESSION_ZIP;
+ }
+
+ /* PACK_MAGIC and LZH_MAGIC and compress magic */
+ if (magic[0] == 0x1F && (magic[1] == 0x1E || magic[1] == 0xA0 || magic[1] == 0x9D))
+ /* Compatible with gzip */
+ return COMPRESSION_GZIP;
+
+ /* BZIP and BZIP2 files */
+ if ((magic[0] == 'B') && (magic[1] == 'Z') && (magic[3] >= '1') && (magic[3] <= '9'))
+ switch (magic[2])
+ {
+ case '0':
+ return COMPRESSION_BZIP;
+ case 'h':
+ return COMPRESSION_BZIP2;
+ default:
+ break;
+ }
+
+ /* LZ4 format - v1.5.0 - 0x184D2204 (little endian) */
+ if (magic[0] == 0x04 && magic[1] == 0x22 && magic[2] == 0x4d && magic[3] == 0x18)
+ return COMPRESSION_LZ4;
+
+ if (mc_read (fd, (char *) magic + 4, 2) != 2)
+ return COMPRESSION_NONE;
+
+ /* LZIP files */
+ if (magic[0] == 'L'
+ && magic[1] == 'Z'
+ && magic[2] == 'I' && magic[3] == 'P' && (magic[4] == 0x00 || magic[4] == 0x01))
+ return COMPRESSION_LZIP;
+
+ /* Support for LZMA (only utils format with magic in header).
+ * This is the default format of LZMA utils 4.32.1 and later. */
+ if (magic[0] == 0xFF
+ && magic[1] == 'L'
+ && magic[2] == 'Z' && magic[3] == 'M' && magic[4] == 'A' && magic[5] == 0x00)
+ return COMPRESSION_LZMA;
+
+ /* XZ compression magic */
+ if (magic[0] == 0xFD
+ && magic[1] == 0x37
+ && magic[2] == 0x7A && magic[3] == 0x58 && magic[4] == 0x5A && magic[5] == 0x00)
+ return COMPRESSION_XZ;
+
+ if (magic[0] == 0x28 && magic[1] == 0xB5 && magic[2] == 0x2F && magic[3] == 0xFD)
+ return COMPRESSION_ZSTD;
+
+ str_len = strlen (name);
+ /* HACK: we must believe to extension of LZMA file :) ... */
+ if ((str_len > 5 && strcmp (&name[str_len - 5], ".lzma") == 0) ||
+ (str_len > 4 && strcmp (&name[str_len - 4], ".tlz") == 0))
+ return COMPRESSION_LZMA;
+
+ return COMPRESSION_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+decompress_extension (int type)
+{
+ switch (type)
+ {
+ case COMPRESSION_ZIP:
+ return "/uz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_GZIP:
+ return "/ugz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_BZIP:
+ return "/ubz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_BZIP2:
+ return "/ubz2" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_LZIP:
+ return "/ulz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_LZ4:
+ return "/ulz4" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_LZMA:
+ return "/ulzma" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_XZ:
+ return "/uxz" VFS_PATH_URL_DELIMITER;
+ case COMPRESSION_ZSTD:
+ return "/uzst" VFS_PATH_URL_DELIMITER;
+ default:
+ break;
+ }
+ /* Should never reach this place */
+ fprintf (stderr, "Fatal: decompress_extension called with an unknown argument\n");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+wipe_password (char *passwd)
+{
+ if (passwd != NULL)
+ {
+ char *p;
+
+ for (p = passwd; *p != '\0'; p++)
+ *p = '\0';
+ g_free (passwd);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key
+ *
+ * @param p pointer to string
+ *
+ * @return newly allocated string
+ */
+
+char *
+convert_controls (const char *p)
+{
+ char *valcopy;
+ char *q;
+
+ valcopy = g_strdup (p);
+
+ /* Parse the escape special character */
+ for (q = valcopy; *p != '\0';)
+ switch (*p)
+ {
+ case '\\':
+ p++;
+
+ if (*p == 'e' || *p == 'E')
+ {
+ p++;
+ *q++ = ESC_CHAR;
+ }
+ break;
+
+ case '^':
+ p++;
+ if (*p == '^')
+ *q++ = *p++;
+ else
+ {
+ char c;
+
+ c = *p | 0x20;
+ if (c >= 'a' && c <= 'z')
+ {
+ *q++ = c - 'a' + 1;
+ p++;
+ }
+ else if (*p != '\0')
+ p++;
+ }
+ break;
+
+ default:
+ *q++ = *p++;
+ }
+
+ *q = '\0';
+ return valcopy;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Finds out a relative path from first to second, i.e. goes as many ..
+ * as needed up in first and then goes down using second
+ */
+
+char *
+diff_two_paths (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int j, prevlen = -1, currlen;
+ char *my_first = NULL, *my_second = NULL;
+ char *buf = NULL;
+
+ my_first = resolve_symlinks (vpath1);
+ if (my_first == NULL)
+ goto ret;
+
+ my_second = resolve_symlinks (vpath2);
+ if (my_second == NULL)
+ goto ret;
+
+ for (j = 0; j < 2; j++)
+ {
+ char *p, *q;
+ int i;
+
+ p = my_first;
+ q = my_second;
+
+ while (TRUE)
+ {
+ char *r, *s;
+ ptrdiff_t len;
+
+ r = strchr (p, PATH_SEP);
+ if (r == NULL)
+ break;
+ s = strchr (q, PATH_SEP);
+ if (s == NULL)
+ break;
+
+ len = r - p;
+ if (len != (s - q) || strncmp (p, q, (size_t) len) != 0)
+ break;
+
+ p = r + 1;
+ q = s + 1;
+ }
+ p--;
+ for (i = 0; (p = strchr (p + 1, PATH_SEP)) != NULL; i++)
+ ;
+ currlen = (i + 1) * 3 + strlen (q) + 1;
+ if (j != 0)
+ {
+ if (currlen < prevlen)
+ g_free (buf);
+ else
+ goto ret;
+ }
+ p = buf = g_malloc (currlen);
+ prevlen = currlen;
+ for (; i >= 0; i--, p += 3)
+ strcpy (p, "../");
+ strcpy (p, q);
+ }
+
+ ret:
+ g_free (my_first);
+ g_free (my_second);
+ return buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Append text to GList, remove all entries with the same text
+ */
+
+GList *
+list_append_unique (GList * list, char *text)
+{
+ GList *lc_link;
+
+ /*
+ * Go to the last position and traverse the list backwards
+ * starting from the second last entry to make sure that we
+ * are not removing the current link.
+ */
+ list = g_list_append (list, text);
+ list = g_list_last (list);
+ lc_link = g_list_previous (list);
+
+ while (lc_link != NULL)
+ {
+ GList *newlink;
+
+ newlink = g_list_previous (lc_link);
+ if (strcmp ((char *) lc_link->data, text) == 0)
+ {
+ GList *tmp;
+
+ g_free (lc_link->data);
+ tmp = g_list_remove_link (list, lc_link);
+ (void) tmp;
+ g_list_free_1 (lc_link);
+ }
+ lc_link = newlink;
+ }
+
+ return list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read and restore position for the given filename.
+ * If there is no stored data, return line 1 and col 0.
+ */
+
+void
+load_file_position (const vfs_path_t * filename_vpath, long *line, long *column, off_t * offset,
+ GArray ** bookmarks)
+{
+ char *fn;
+ FILE *f;
+ char buf[MC_MAXPATHLEN + 100];
+ const size_t len = vfs_path_len (filename_vpath);
+
+ /* defaults */
+ *line = 1;
+ *column = 0;
+ *offset = 0;
+
+ /* open file with positions */
+ fn = mc_config_get_full_path (MC_FILEPOS_FILE);
+ f = fopen (fn, "r");
+ g_free (fn);
+ if (f == NULL)
+ return;
+
+ /* prepare array for serialized bookmarks */
+ if (bookmarks != NULL)
+ *bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t), MAX_SAVED_BOOKMARKS);
+
+ while (fgets (buf, sizeof (buf), f) != NULL)
+ {
+ const char *p;
+ gchar **pos_tokens;
+
+ /* check if the filename matches the beginning of string */
+ if (strncmp (buf, vfs_path_as_str (filename_vpath), len) != 0)
+ continue;
+
+ /* followed by single space */
+ if (buf[len] != ' ')
+ continue;
+
+ /* and string without spaces */
+ p = &buf[len + 1];
+ if (strchr (p, ' ') != NULL)
+ continue;
+
+ pos_tokens = g_strsplit (p, ";", 3 + MAX_SAVED_BOOKMARKS);
+ if (pos_tokens[0] == NULL)
+ {
+ *line = 1;
+ *column = 0;
+ *offset = 0;
+ }
+ else
+ {
+ *line = strtol (pos_tokens[0], NULL, 10);
+ if (pos_tokens[1] == NULL)
+ {
+ *column = 0;
+ *offset = 0;
+ }
+ else
+ {
+ *column = strtol (pos_tokens[1], NULL, 10);
+ if (pos_tokens[2] == NULL)
+ *offset = 0;
+ else if (bookmarks != NULL)
+ {
+ size_t i;
+
+ *offset = (off_t) g_ascii_strtoll (pos_tokens[2], NULL, 10);
+
+ for (i = 0; i < MAX_SAVED_BOOKMARKS && pos_tokens[3 + i] != NULL; i++)
+ {
+ size_t val;
+
+ val = strtoul (pos_tokens[3 + i], NULL, 10);
+ g_array_append_val (*bookmarks, val);
+ }
+ }
+ }
+ }
+
+ g_strfreev (pos_tokens);
+ }
+
+ fclose (f);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Save position for the given file
+ */
+
+void
+save_file_position (const vfs_path_t * filename_vpath, long line, long column, off_t offset,
+ GArray * bookmarks)
+{
+ static size_t filepos_max_saved_entries = 0;
+ char *fn, *tmp_fn;
+ FILE *f, *tmp_f;
+ char buf[MC_MAXPATHLEN + 100];
+ size_t i;
+ const size_t len = vfs_path_len (filename_vpath);
+ gboolean src_error = FALSE;
+
+ if (filepos_max_saved_entries == 0)
+ filepos_max_saved_entries = mc_config_get_int (mc_global.main_config, CONFIG_APP_SECTION,
+ "filepos_max_saved_entries", 1024);
+
+ fn = mc_config_get_full_path (MC_FILEPOS_FILE);
+ if (fn == NULL)
+ goto early_error;
+
+ mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
+
+ /* open file */
+ f = fopen (fn, "w");
+ if (f == NULL)
+ goto open_target_error;
+
+ tmp_fn = g_strdup_printf ("%s" TMP_SUFFIX, fn);
+ tmp_f = fopen (tmp_fn, "r");
+ if (tmp_f == NULL)
+ {
+ src_error = TRUE;
+ goto open_source_error;
+ }
+
+ /* put the new record */
+ if (line != 1 || column != 0 || bookmarks != NULL)
+ {
+ if (fprintf
+ (f, "%s %ld;%ld;%" PRIuMAX, vfs_path_as_str (filename_vpath), line, column,
+ (uintmax_t) offset) < 0)
+ goto write_position_error;
+ if (bookmarks != NULL)
+ for (i = 0; i < bookmarks->len && i < MAX_SAVED_BOOKMARKS; i++)
+ if (fprintf (f, ";%zu", g_array_index (bookmarks, size_t, i)) < 0)
+ goto write_position_error;
+
+ if (fprintf (f, "\n") < 0)
+ goto write_position_error;
+ }
+
+ i = 1;
+ while (fgets (buf, sizeof (buf), tmp_f) != NULL)
+ {
+ if (buf[len] == ' ' && strncmp (buf, vfs_path_as_str (filename_vpath), len) == 0
+ && strchr (&buf[len + 1], ' ') == NULL)
+ continue;
+
+ fprintf (f, "%s", buf);
+ if (++i > filepos_max_saved_entries)
+ break;
+ }
+
+ write_position_error:
+ fclose (tmp_f);
+ open_source_error:
+ g_free (tmp_fn);
+ fclose (f);
+ if (src_error)
+ mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
+ else
+ mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
+ open_target_error:
+ g_free (fn);
+ early_error:
+ if (bookmarks != NULL)
+ g_array_free (bookmarks, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+extern int
+ascii_alpha_to_cntrl (int ch)
+{
+ if ((ch >= ASCII_A && ch <= ASCII_Z) || (ch >= ASCII_a && ch <= ASCII_z))
+ ch &= 0x1f;
+
+ return ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+Q_ (const char *s)
+{
+ const char *result, *sep;
+
+ result = _(s);
+ sep = strchr (result, '|');
+
+ return sep != NULL ? sep + 1 : result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix)
+{
+ struct stat stat_buf;
+ char *backup_path;
+ gboolean ret;
+
+ if (!exist_file (file_name))
+ return FALSE;
+
+ backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
+ if (backup_path == NULL)
+ return FALSE;
+
+ ret = mc_util_write_backup_content (file_name, backup_path);
+ if (ret)
+ {
+ /* Backup file will have same ownership with main file. */
+ if (stat (file_name, &stat_buf) == 0)
+ chmod (backup_path, stat_buf.st_mode);
+ else
+ chmod (backup_path, S_IRUSR | S_IWUSR);
+ }
+
+ g_free (backup_path);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix)
+{
+ gboolean ret;
+ char *backup_path;
+
+ backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
+ if (backup_path == NULL)
+ return FALSE;
+
+ ret = mc_util_write_backup_content (backup_path, file_name);
+ g_free (backup_path);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix)
+{
+ char *backup_path;
+
+ backup_path = g_strdup_printf ("%s%s", file_name, backup_suffix);
+ if (backup_path == NULL)
+ return FALSE;
+
+ if (exist_file (backup_path))
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (backup_path);
+ mc_unlink (vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+
+ g_free (backup_path);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * partly taken from dcigettext.c, returns "" for default locale
+ * value should be freed by calling function g_free()
+ */
+
+char *
+guess_message_value (void)
+{
+ static const char *const var[] = {
+ /* Setting of LC_ALL overwrites all other. */
+ /* Do not use LANGUAGE for check user locale and drowing hints */
+ "LC_ALL",
+ /* Next comes the name of the desired category. */
+ "LC_MESSAGES",
+ /* Last possibility is the LANG environment variable. */
+ "LANG",
+ /* NULL exit loops */
+ NULL
+ };
+
+ size_t i;
+ const char *locale = NULL;
+
+ for (i = 0; var[i] != NULL; i++)
+ {
+ locale = getenv (var[i]);
+ if (locale != NULL && locale[0] != '\0')
+ break;
+ }
+
+ if (locale == NULL)
+ locale = "";
+
+ return g_strdup (locale);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * The "profile root" is the tree under which all of MC's user data &
+ * settings are stored.
+ *
+ * It defaults to the user's home dir. The user may override this default
+ * with the environment variable $MC_PROFILE_ROOT.
+ */
+const char *
+mc_get_profile_root (void)
+{
+ static const char *profile_root = NULL;
+
+ if (profile_root == NULL)
+ {
+ profile_root = g_getenv ("MC_PROFILE_ROOT");
+ if (profile_root == NULL || *profile_root == '\0')
+ profile_root = mc_config_get_home_dir ();
+ }
+
+ return profile_root;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Propagate error in simple way.
+ *
+ * @param dest error return location
+ * @param code error code
+ * @param format printf()-style format for error message
+ * @param ... parameters for message format
+ */
+
+void
+mc_propagate_error (GError ** dest, int code, const char *format, ...)
+{
+ if (dest != NULL && *dest == NULL)
+ {
+ GError *tmp_error;
+ va_list args;
+
+ va_start (args, format);
+ tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
+ va_end (args);
+
+ g_propagate_error (dest, tmp_error);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Replace existing error in simple way.
+ *
+ * @param dest error return location
+ * @param code error code
+ * @param format printf()-style format for error message
+ * @param ... parameters for message format
+ */
+
+void
+mc_replace_error (GError ** dest, int code, const char *format, ...)
+{
+ if (dest != NULL)
+ {
+ GError *tmp_error;
+ va_list args;
+
+ va_start (args, format);
+ tmp_error = g_error_new_valist (MC_ERROR, code, format, args);
+ va_end (args);
+
+ g_error_free (*dest);
+ *dest = NULL;
+ g_propagate_error (dest, tmp_error);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Returns if the given duration has elapsed since the given timestamp,
+ * and if it has then updates the timestamp.
+ *
+ * @param timestamp the last timestamp in microseconds, updated if the given time elapsed
+ * @param delay amount of time in microseconds
+
+ * @return TRUE if clock skew detected, FALSE otherwise
+ */
+gboolean
+mc_time_elapsed (gint64 * timestamp, gint64 delay)
+{
+ gint64 now;
+
+ now = g_get_monotonic_time ();
+
+ if (now >= *timestamp && now < *timestamp + delay)
+ return FALSE;
+
+ *timestamp = now;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/util.h b/lib/util.h
new file mode 100644
index 0000000..ec8b25e
--- /dev/null
+++ b/lib/util.h
@@ -0,0 +1,297 @@
+/** \file lib/util.h
+ * \brief Header: various utilities
+ */
+
+#ifndef MC_UTIL_H
+#define MC_UTIL_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <inttypes.h> /* uintmax_t */
+#include <unistd.h>
+
+#include "lib/global.h" /* include <glib.h> */
+
+#include "lib/vfs/vfs.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#ifndef MAXSYMLINKS
+#define MAXSYMLINKS 32
+#endif
+
+#define MAX_SAVED_BOOKMARKS 10
+
+#define MC_PTR_FREE(ptr) do { g_free (ptr); (ptr) = NULL; } while (0)
+
+#define mc_return_if_error(mcerror) do { if (mcerror != NULL && *mcerror != NULL) return; } while (0)
+#define mc_return_val_if_error(mcerror, mcvalue) do { if (mcerror != NULL && *mcerror != NULL) return mcvalue; } while (0)
+
+#define whitespace(c) ((c) == ' ' || (c) == '\t')
+#define whiteness(c) (whitespace (c) || (c) == '\n')
+
+#define MC_PIPE_BUFSIZE BUF_8K
+#define MC_PIPE_STREAM_EOF 0
+#define MC_PIPE_STREAM_UNREAD -1
+#define MC_PIPE_ERROR_CREATE_PIPE -2
+#define MC_PIPE_ERROR_PARSE_COMMAND -3
+#define MC_PIPE_ERROR_CREATE_PIPE_STREAM -4
+#define MC_PIPE_ERROR_READ -5
+
+/* gnulib efa15594e17fc20827dba66414fb391e99905394
+
+ *_GL_CMP (n1, n2) performs a three-valued comparison on n1 vs. n2.
+ * It returns
+ * 1 if n1 > n2
+ * 0 if n1 == n2
+ * -1 if n1 < n2
+ * The native code (n1 > n2 ? 1 : n1 < n2 ? -1 : 0) produces a conditional
+ * jump with nearly all GCC versions up to GCC 10.
+ * This variant (n1 < n2 ? -1 : n1 > n2) produces a conditional with many
+ * GCC versions up to GCC 9.
+ * The better code (n1 > n2) - (n1 < n2) from Hacker's Delight para 2-9
+ * avoids conditional jumps in all GCC versions >= 3.4.
+ */
+#define _GL_CMP(n1, n2) (((n1) > (n2)) - ((n1) < (n2)))
+
+/* Difference or zero */
+#define DOZ(a, b) ((a) > (b) ? (a) - (b) : 0)
+
+/*** enums ***************************************************************************************/
+
+/* Pathname canonicalization */
+/* *INDENT-OFF* */
+typedef enum
+{
+ CANON_PATH_NOCHANGE = 0,
+ CANON_PATH_JOINSLASHES = 1L << 0, /**< Multiple '/'s are collapsed to a single '/' */
+ CANON_PATH_REMSLASHDOTS = 1L << 1, /**< Leading './'s, '/'s and trailing '/.'s are removed */
+ CANON_PATH_REMDOUBLEDOTS = 1L << 3, /**< Non-leading '../'s and trailing '..'s are handled by removing
+ portions of the path */
+ CANON_PATH_GUARDUNC = 1L << 4, /**< Detect and preserve UNC paths: //server/... */
+ CANON_PATH_ALL = CANON_PATH_JOINSLASHES | CANON_PATH_REMSLASHDOTS
+ | CANON_PATH_REMDOUBLEDOTS | CANON_PATH_GUARDUNC /**< All flags */
+} canon_path_flags_t;
+/* *INDENT-ON* */
+
+enum compression_type
+{
+ COMPRESSION_NONE,
+ COMPRESSION_ZIP,
+ COMPRESSION_GZIP,
+ COMPRESSION_BZIP,
+ COMPRESSION_BZIP2,
+ COMPRESSION_LZIP,
+ COMPRESSION_LZ4,
+ COMPRESSION_LZMA,
+ COMPRESSION_XZ,
+ COMPRESSION_ZSTD,
+};
+
+/* stdout or stderr stream of child process */
+typedef struct
+{
+ /* file descriptor */
+ int fd;
+ /* data read from fd */
+ char buf[MC_PIPE_BUFSIZE];
+ /* current position in @buf (used by mc_pstream_get_string()) */
+ size_t pos;
+ /* positive: length of data in buf;
+ * MC_PIPE_STREAM_EOF: EOF of fd;
+ * MC_PIPE_STREAM_UNREAD: there was not read from fd;
+ * MC_PIPE_ERROR_READ: reading error from fd.
+ */
+ ssize_t len;
+ /* whether buf is null-terminated or not */
+ gboolean null_term;
+ /* error code in case of len == MC_PIPE_ERROR_READ */
+ int error;
+} mc_pipe_stream_t;
+
+/* Pipe descriptor for child process */
+typedef struct
+{
+ /* PID of child process */
+ GPid child_pid;
+ /* stdout of child process */
+ mc_pipe_stream_t out;
+ /* stderr of child process */
+ mc_pipe_stream_t err;
+} mc_pipe_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern struct sigaction startup_handler;
+
+/*** declarations of public functions ************************************************************/
+
+int is_printable (int c);
+
+/* Quote the filename for the purpose of inserting it into the command
+ * line. If quote_percent is 1, replace "%" with "%%" - the percent is
+ * processed by the mc command line. */
+char *name_quote (const char *c, gboolean quote_percent);
+
+/* returns a duplicate of c. */
+char *fake_name_quote (const char *c, gboolean quote_percent);
+
+/* path_trunc() is the same as str_trunc() but
+ * it deletes possible password from path for security
+ * reasons. */
+const char *path_trunc (const char *path, size_t trunc_len);
+
+/* return a static string representing size, appending "K" or "M" for
+ * big sizes.
+ * NOTE: uses the same static buffer as size_trunc_sep. */
+const char *size_trunc (uintmax_t size, gboolean use_si);
+
+/* return a static string representing size, appending "K" or "M" for
+ * big sizes. Separates every three digits by ",".
+ * NOTE: uses the same static buffer as size_trunc. */
+const char *size_trunc_sep (uintmax_t size, gboolean use_si);
+
+/* Print file SIZE to BUFFER, but don't exceed LEN characters,
+ * not including trailing 0. BUFFER should be at least LEN+1 long.
+ *
+ * Units: size units (0=bytes, 1=Kbytes, 2=Mbytes, etc.) */
+void size_trunc_len (char *buffer, unsigned int len, uintmax_t size, int units, gboolean use_si);
+const char *string_perm (mode_t mode_bits);
+
+const char *extension (const char *);
+const char *unix_error_string (int error_num);
+const char *skip_separators (const char *s);
+const char *skip_numbers (const char *s);
+char *strip_ctrl_codes (char *s);
+
+/* Replaces "\\E" and "\\e" with "\033". Replaces "^" + [a-z] with
+ * ((char) 1 + (c - 'a')). The same goes for "^" + [A-Z].
+ * Returns a newly allocated string. */
+char *convert_controls (const char *s);
+
+/* overwrites passwd with '\0's and frees it. */
+void wipe_password (char *passwd);
+
+char *diff_two_paths (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+
+/* Returns the basename of fname. The result is a pointer into fname. */
+const char *x_basename (const char *fname);
+
+char *load_mc_home_file (const char *from, const char *filename, char **allocated_filename,
+ size_t * length);
+
+/* uid/gid managing */
+void init_groups (void);
+void destroy_groups (void);
+int get_user_permissions (struct stat *buf);
+
+void init_uid_gid_cache (void);
+const char *get_group (gid_t gid);
+const char *get_owner (uid_t uid);
+
+/* Returns a copy of *s until a \n is found and is below top */
+const char *extract_line (const char *s, const char *top);
+
+/* Process spawning */
+int my_system (int flags, const char *shell, const char *command);
+int my_systeml (int flags, const char *shell, ...);
+int my_systemv (const char *command, char *const argv[]);
+int my_systemv_flags (int flags, const char *command, char *const argv[]);
+
+mc_pipe_t *mc_popen (const char *command, gboolean read_out, gboolean read_err, GError ** error);
+void mc_pread (mc_pipe_t * p, GError ** error);
+void mc_pclose (mc_pipe_t * p, GError ** error);
+
+GString *mc_pstream_get_string (mc_pipe_stream_t * ps);
+
+void my_exit (int status);
+void save_stop_handler (void);
+
+/* Tilde expansion */
+char *tilde_expand (const char *directory);
+
+void canonicalize_pathname_custom (char *path, canon_path_flags_t flags);
+
+char *mc_realpath (const char *path, char *resolved_path);
+
+/* Looks for "magic" bytes at the start of the VFS file to guess the
+ * compression type. Side effect: modifies the file position. */
+enum compression_type get_compression_type (int fd, const char *name);
+const char *decompress_extension (int type);
+
+GList *list_append_unique (GList * list, char *text);
+
+/* Position saving and restoring */
+/* Load position for the given filename */
+void load_file_position (const vfs_path_t * filename_vpath, long *line, long *column,
+ off_t * offset, GArray ** bookmarks);
+/* Save position for the given filename */
+void save_file_position (const vfs_path_t * filename_vpath, long line, long column, off_t offset,
+ GArray * bookmarks);
+
+
+/* if ch is in [A-Za-z], returns the corresponding control character,
+ * else returns the argument. */
+extern int ascii_alpha_to_cntrl (int ch);
+
+#undef Q_
+const char *Q_ (const char *s);
+
+gboolean mc_util_make_backup_if_possible (const char *file_name, const char *backup_suffix);
+gboolean mc_util_restore_from_backup_if_possible (const char *file_name, const char *backup_suffix);
+gboolean mc_util_unlink_backup_if_possible (const char *file_name, const char *backup_suffix);
+
+char *guess_message_value (void);
+
+char *mc_build_filename (const char *first_element, ...);
+char *mc_build_filenamev (const char *first_element, va_list args);
+
+const char *mc_get_profile_root (void);
+
+/* *INDENT-OFF* */
+void mc_propagate_error (GError ** dest, int code, const char *format, ...) G_GNUC_PRINTF (3, 4);
+void mc_replace_error (GError ** dest, int code, const char *format, ...) G_GNUC_PRINTF (3, 4);
+/* *INDENT-ON* */
+
+gboolean mc_time_elapsed (gint64 * timestamp, gint64 delay);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+exist_file (const char *name)
+{
+ return (access (name, R_OK) == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+is_exe (mode_t mode)
+{
+ return ((mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Canonicalize path with CANON_PATH_ALL.
+ *
+ * @param path path to file
+ * @param flags canonicalization flags
+ *
+ * All modifications of @path are made in place.
+ * Well formed UNC paths are modified only in the local part.
+ */
+
+static inline void
+canonicalize_pathname (char *path)
+{
+ canonicalize_pathname_custom (path, CANON_PATH_ALL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC_UTIL_H */
diff --git a/lib/utilunix.c b/lib/utilunix.c
new file mode 100644
index 0000000..42e0f6d
--- /dev/null
+++ b/lib/utilunix.c
@@ -0,0 +1,1293 @@
+/*
+ Various utilities - Unix variants
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996
+ Janne Kukonlehto, 1994, 1995, 1996
+ Dugan Porter, 1994, 1995, 1996
+ Jakub Jelinek, 1994, 1995, 1996
+ Mauricio Plaza, 1994, 1995, 1996
+ Andrew Borodin <aborodin@vmail.ru> 2010-2022
+
+ The mc_realpath routine is mostly from uClibc package, written
+ by Rick Sladkey <jrs@world.std.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file utilunix.c
+ * \brief Source: various utilities - Unix variant
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/wait.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "lib/global.h"
+
+#include "lib/unixcompat.h"
+#include "lib/vfs/vfs.h" /* VFS_ENCODING_PREFIX */
+#include "lib/strutil.h" /* str_move() */
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+#include "lib/vfs/xdirentry.h"
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "utilunix.h"
+
+/*** global variables ****************************************************************************/
+
+struct sigaction startup_handler;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define UID_CACHE_SIZE 200
+#define GID_CACHE_SIZE 30
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ int index;
+ char *string;
+} int_cache;
+
+typedef enum
+{
+ FORK_ERROR = -1,
+ FORK_CHILD,
+ FORK_PARENT,
+} my_fork_state_t;
+
+typedef struct
+{
+ struct sigaction intr;
+ struct sigaction quit;
+ struct sigaction stop;
+} my_system_sigactions_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static int_cache uid_cache[UID_CACHE_SIZE];
+static int_cache gid_cache[GID_CACHE_SIZE];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+i_cache_match (int id, int_cache * cache, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ if (cache[i].index == id)
+ return cache[i].string;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+i_cache_add (int id, int_cache * cache, int size, char *text, int *last)
+{
+ g_free (cache[*last].string);
+ cache[*last].string = g_strdup (text);
+ cache[*last].index = id;
+ *last = ((*last) + 1) % size;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static my_fork_state_t
+my_fork (void)
+{
+ pid_t pid;
+
+ pid = fork ();
+
+ if (pid < 0)
+ {
+ fprintf (stderr, "\n\nfork () = -1\n");
+ return FORK_ERROR;
+ }
+
+ if (pid == 0)
+ return FORK_CHILD;
+
+ while (TRUE)
+ {
+ int status = 0;
+
+ if (waitpid (pid, &status, 0) > 0)
+ return WEXITSTATUS (status) == 0 ? FORK_PARENT : FORK_ERROR;
+
+ if (errno != EINTR)
+ return FORK_ERROR;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+my_system__save_sigaction_handlers (my_system_sigactions_t * sigactions)
+{
+ struct sigaction ignore;
+
+ memset (&ignore, 0, sizeof (ignore));
+ ignore.sa_handler = SIG_IGN;
+ sigemptyset (&ignore.sa_mask);
+
+ sigaction (SIGINT, &ignore, &sigactions->intr);
+ sigaction (SIGQUIT, &ignore, &sigactions->quit);
+
+ /* Restore the original SIGTSTP handler, we don't want ncurses' */
+ /* handler messing the screen after the SIGCONT */
+ sigaction (SIGTSTP, &startup_handler, &sigactions->stop);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+my_system__restore_sigaction_handlers (my_system_sigactions_t * sigactions)
+{
+ sigaction (SIGINT, &sigactions->intr, NULL);
+ sigaction (SIGQUIT, &sigactions->quit, NULL);
+ sigaction (SIGTSTP, &sigactions->stop, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GPtrArray *
+my_system_make_arg_array (int flags, const char *shell, char **execute_name)
+{
+ GPtrArray *args_array;
+
+ args_array = g_ptr_array_new ();
+
+ if ((flags & EXECUTE_AS_SHELL) != 0)
+ {
+ g_ptr_array_add (args_array, (gpointer) shell);
+ g_ptr_array_add (args_array, (gpointer) "-c");
+ *execute_name = g_strdup (shell);
+ }
+ else
+ {
+ char *shell_token;
+
+ shell_token = shell != NULL ? strchr (shell, ' ') : NULL;
+ if (shell_token == NULL)
+ *execute_name = g_strdup (shell);
+ else
+ *execute_name = g_strndup (shell, (gsize) (shell_token - shell));
+
+ g_ptr_array_add (args_array, (gpointer) shell);
+ }
+ return args_array;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_pread_stream (mc_pipe_stream_t * ps, const fd_set * fds)
+{
+ size_t buf_len;
+ ssize_t read_len;
+
+ if (!FD_ISSET (ps->fd, fds))
+ {
+ ps->len = MC_PIPE_STREAM_UNREAD;
+ return;
+ }
+
+ buf_len = (size_t) ps->len;
+
+ if (buf_len >= MC_PIPE_BUFSIZE)
+ buf_len = ps->null_term ? MC_PIPE_BUFSIZE - 1 : MC_PIPE_BUFSIZE;
+
+ do
+ {
+ read_len = read (ps->fd, ps->buf, buf_len);
+ }
+ while (read_len < 0 && errno == EINTR);
+
+ if (read_len < 0)
+ {
+ /* reading error */
+ ps->len = MC_PIPE_ERROR_READ;
+ ps->error = errno;
+ }
+ else if (read_len == 0)
+ /* EOF */
+ ps->len = MC_PIPE_STREAM_EOF;
+ else
+ {
+ /* success */
+ ps->len = read_len;
+
+ if (ps->null_term)
+ ps->buf[(size_t) ps->len] = '\0';
+ }
+
+ ps->pos = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+get_owner (uid_t uid)
+{
+ struct passwd *pwd;
+ char *name;
+ static uid_t uid_last;
+
+ name = i_cache_match ((int) uid, uid_cache, UID_CACHE_SIZE);
+ if (name != NULL)
+ return name;
+
+ pwd = getpwuid (uid);
+ if (pwd != NULL)
+ {
+ i_cache_add ((int) uid, uid_cache, UID_CACHE_SIZE, pwd->pw_name, (int *) &uid_last);
+ return pwd->pw_name;
+ }
+ else
+ {
+ static char ibuf[10];
+
+ g_snprintf (ibuf, sizeof (ibuf), "%d", (int) uid);
+ return ibuf;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+get_group (gid_t gid)
+{
+ struct group *grp;
+ char *name;
+ static gid_t gid_last;
+
+ name = i_cache_match ((int) gid, gid_cache, GID_CACHE_SIZE);
+ if (name != NULL)
+ return name;
+
+ grp = getgrgid (gid);
+ if (grp != NULL)
+ {
+ i_cache_add ((int) gid, gid_cache, GID_CACHE_SIZE, grp->gr_name, (int *) &gid_last);
+ return grp->gr_name;
+ }
+ else
+ {
+ static char gbuf[10];
+
+ g_snprintf (gbuf, sizeof (gbuf), "%d", (int) gid);
+ return gbuf;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Since ncurses uses a handler that automatically refreshes the */
+/* screen after a SIGCONT, and we don't want this behavior when */
+/* spawning a child, we save the original handler here */
+
+void
+save_stop_handler (void)
+{
+ sigaction (SIGTSTP, NULL, &startup_handler);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Wrapper for _exit() system call.
+ * The _exit() function has gcc's attribute 'noreturn', and this is reason why we can't
+ * mock the call.
+ *
+ * @param status exit code
+ */
+
+void
+/* __attribute__ ((noreturn)) */
+my_exit (int status)
+{
+ _exit (status);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Call external programs.
+ *
+ * @parameter flags addition conditions for running external programs.
+ * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
+ * Shell (or command) will be found in paths described in PATH variable
+ * (if shell parameter doesn't begin from path delimiter)
+ * @parameter command Command for shell (or first parameter for command, if flags contain EXECUTE_AS_SHELL)
+ * @return 0 if successful, -1 otherwise
+ */
+
+int
+my_system (int flags, const char *shell, const char *command)
+{
+ return my_systeml (flags, shell, command, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Call external programs with various parameters number.
+ *
+ * @parameter flags addition conditions for running external programs.
+ * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
+ * Shell (or command) will be found in paths described in PATH variable
+ * (if shell parameter doesn't begin from path delimiter)
+ * @parameter ... Command for shell with addition parameters for shell
+ * (or parameters for command, if flags contain EXECUTE_AS_SHELL).
+ * Should be NULL terminated.
+ * @return 0 if successful, -1 otherwise
+ */
+
+int
+my_systeml (int flags, const char *shell, ...)
+{
+ GPtrArray *args_array;
+ int status = 0;
+ va_list vargs;
+ char *one_arg;
+
+ args_array = g_ptr_array_new ();
+
+ va_start (vargs, shell);
+ while ((one_arg = va_arg (vargs, char *)) != NULL)
+ g_ptr_array_add (args_array, one_arg);
+ va_end (vargs);
+
+ g_ptr_array_add (args_array, NULL);
+ status = my_systemv_flags (flags, shell, (char *const *) args_array->pdata);
+
+ g_ptr_array_free (args_array, TRUE);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Call external programs with array of strings as parameters.
+ *
+ * @parameter command command to run. Command will be found in paths described in PATH variable
+ * (if command parameter doesn't begin from path delimiter)
+ * @parameter argv Array of strings (NULL-terminated) with parameters for command
+ * @return 0 if successful, -1 otherwise
+ */
+
+int
+my_systemv (const char *command, char *const argv[])
+{
+ my_fork_state_t fork_state;
+ int status = 0;
+ my_system_sigactions_t sigactions;
+
+ my_system__save_sigaction_handlers (&sigactions);
+
+ fork_state = my_fork ();
+ switch (fork_state)
+ {
+ case FORK_ERROR:
+ status = -1;
+ break;
+ case FORK_CHILD:
+ {
+ signal (SIGINT, SIG_DFL);
+ signal (SIGQUIT, SIG_DFL);
+ signal (SIGTSTP, SIG_DFL);
+ signal (SIGCHLD, SIG_DFL);
+
+ execvp (command, argv);
+ my_exit (127); /* Exec error */
+ }
+ MC_FALLTHROUGH;
+ /* no break here, or unreachable-code warning by no returning my_exit() */
+ default:
+ status = 0;
+ break;
+ }
+ my_system__restore_sigaction_handlers (&sigactions);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Call external programs with flags and with array of strings as parameters.
+ *
+ * @parameter flags addition conditions for running external programs.
+ * @parameter command shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
+ * Shell (or command) will be found in paths described in PATH variable
+ * (if shell parameter doesn't begin from path delimiter)
+ * @parameter argv Array of strings (NULL-terminated) with parameters for command
+ * @return 0 if successful, -1 otherwise
+ */
+
+int
+my_systemv_flags (int flags, const char *command, char *const argv[])
+{
+ char *execute_name = NULL;
+ GPtrArray *args_array;
+ int status = 0;
+
+ args_array = my_system_make_arg_array (flags, command, &execute_name);
+
+ for (; argv != NULL && *argv != NULL; argv++)
+ g_ptr_array_add (args_array, *argv);
+
+ g_ptr_array_add (args_array, NULL);
+ status = my_systemv (execute_name, (char *const *) args_array->pdata);
+
+ g_free (execute_name);
+ g_ptr_array_free (args_array, TRUE);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create pipe and run child process.
+ *
+ * @parameter command command line of child process
+ * @parameter read_out do or don't read the stdout of child process
+ * @parameter read_err do or don't read the stderr of child process
+ * @parameter error contains pointer to object to handle error code and message
+ *
+ * @return newly created object of mc_pipe_t class in success, NULL otherwise
+ */
+
+mc_pipe_t *
+mc_popen (const char *command, gboolean read_out, gboolean read_err, GError ** error)
+{
+ mc_pipe_t *p;
+ const char *const argv[] = { "/bin/sh", "sh", "-c", command, NULL };
+
+ p = g_try_new (mc_pipe_t, 1);
+ if (p == NULL)
+ {
+ mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE, "%s",
+ _("Cannot create pipe descriptor"));
+ goto ret_err;
+ }
+
+ p->out.fd = -1;
+ p->err.fd = -1;
+
+ if (!g_spawn_async_with_pipes
+ (NULL, (gchar **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_FILE_AND_ARGV_ZERO, NULL,
+ NULL, &p->child_pid, NULL, read_out ? &p->out.fd : NULL, read_err ? &p->err.fd : NULL,
+ error))
+ {
+ mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE_STREAM, "%s",
+ _("Cannot create pipe streams"));
+ goto ret_err;
+ }
+
+ p->out.buf[0] = '\0';
+ p->out.len = MC_PIPE_BUFSIZE;
+ p->out.null_term = FALSE;
+
+ p->err.buf[0] = '\0';
+ p->err.len = MC_PIPE_BUFSIZE;
+ p->err.null_term = FALSE;
+
+ return p;
+
+ ret_err:
+ g_free (p);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read stdout and stderr of pipe asynchronously.
+ *
+ * @parameter p pipe descriptor
+ *
+ * The lengths of read data contain in p->out.len and p->err.len.
+ *
+ * Before read, p->xxx.len is an input. It defines the number of data to read.
+ * Should not be greater than MC_PIPE_BUFSIZE.
+ *
+ * After read, p->xxx.len is an output and contains the following:
+ * p->xxx.len > 0: an actual length of read data stored in p->xxx.buf;
+ * p->xxx.len == MC_PIPE_STREAM_EOF: EOF of stream p->xxx;
+ * p->xxx.len == MC_PIPE_STREAM_UNREAD: stream p->xxx was not read;
+ * p->xxx.len == MC_PIPE_ERROR_READ: reading error, and p->xxx.errno is set appropriately.
+ *
+ * @parameter error contains pointer to object to handle error code and message
+ */
+
+void
+mc_pread (mc_pipe_t * p, GError ** error)
+{
+ gboolean read_out, read_err;
+ fd_set fds;
+ int maxfd = 0;
+ int res;
+
+ if (error != NULL)
+ *error = NULL;
+
+ read_out = p->out.fd >= 0;
+ read_err = p->err.fd >= 0;
+
+ if (!read_out && !read_err)
+ {
+ p->out.len = MC_PIPE_STREAM_UNREAD;
+ p->err.len = MC_PIPE_STREAM_UNREAD;
+ return;
+ }
+
+ FD_ZERO (&fds);
+ if (read_out)
+ {
+ FD_SET (p->out.fd, &fds);
+ maxfd = p->out.fd;
+ }
+
+ if (read_err)
+ {
+ FD_SET (p->err.fd, &fds);
+ maxfd = MAX (maxfd, p->err.fd);
+ }
+
+ /* no timeout */
+ res = select (maxfd + 1, &fds, NULL, NULL, NULL);
+ if (res < 0 && errno != EINTR)
+ {
+ mc_propagate_error (error, MC_PIPE_ERROR_READ,
+ _
+ ("Unexpected error in select() reading data from a child process:\n%s"),
+ unix_error_string (errno));
+ return;
+ }
+
+ if (read_out)
+ mc_pread_stream (&p->out, &fds);
+ else
+ p->out.len = MC_PIPE_STREAM_UNREAD;
+
+ if (read_err)
+ mc_pread_stream (&p->err, &fds);
+ else
+ p->err.len = MC_PIPE_STREAM_UNREAD;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Reads a line from @stream. Reading stops after an EOL or a newline. If a newline is read,
+ * it is appended to the line.
+ *
+ * @stream mc_pipe_stream_t object
+ *
+ * @return newly created GString or NULL in case of EOL;
+ */
+
+GString *
+mc_pstream_get_string (mc_pipe_stream_t * ps)
+{
+ char *s;
+ size_t size, i;
+ gboolean escape = FALSE;
+
+ g_return_val_if_fail (ps != NULL, NULL);
+
+ if (ps->len < 0)
+ return NULL;
+
+ size = ps->len - ps->pos;
+
+ if (size == 0)
+ return NULL;
+
+ s = ps->buf + ps->pos;
+
+ if (s[0] == '\0')
+ return NULL;
+
+ /* find '\0' or unescaped '\n' */
+ for (i = 0; i < size && !(s[i] == '\0' || (s[i] == '\n' && !escape)); i++)
+ escape = s[i] == '\\' ? !escape : FALSE;
+
+ if (i != size && s[i] == '\n')
+ i++;
+
+ ps->pos += i;
+
+ return g_string_new_len (s, i);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close pipe and destroy pipe descriptor.
+ *
+ * @parameter p pipe descriptor
+ * @parameter error contains pointer to object to handle error code and message
+ */
+
+void
+mc_pclose (mc_pipe_t * p, GError ** error)
+{
+ int res;
+
+ if (p == NULL)
+ {
+ mc_replace_error (error, MC_PIPE_ERROR_READ, "%s",
+ _("Cannot close pipe descriptor (p == NULL)"));
+ return;
+ }
+
+ if (p->out.fd >= 0)
+ res = close (p->out.fd);
+ if (p->err.fd >= 0)
+ res = close (p->err.fd);
+
+ do
+ {
+ int status;
+
+ res = waitpid (p->child_pid, &status, 0);
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ mc_replace_error (error, MC_PIPE_ERROR_READ, _("Unexpected error in waitpid():\n%s"),
+ unix_error_string (errno));
+
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Perform tilde expansion if possible.
+ *
+ * @param directory pointer to the path
+ *
+ * @return newly allocated string, even if it's unchanged.
+ */
+
+char *
+tilde_expand (const char *directory)
+{
+ struct passwd *passwd;
+ const char *p, *q;
+
+ if (*directory != '~')
+ return g_strdup (directory);
+
+ p = directory + 1;
+
+ /* d = "~" or d = "~/" */
+ if (*p == '\0' || IS_PATH_SEP (*p))
+ {
+ passwd = getpwuid (geteuid ());
+ q = IS_PATH_SEP (*p) ? p + 1 : "";
+ }
+ else
+ {
+ q = strchr (p, PATH_SEP);
+ if (q == NULL)
+ passwd = getpwnam (p);
+ else
+ {
+ char *name;
+
+ name = g_strndup (p, q - p);
+ passwd = getpwnam (name);
+ q++;
+ g_free (name);
+ }
+ }
+
+ /* If we can't figure the user name, leave tilde unexpanded */
+ if (passwd == NULL)
+ return g_strdup (directory);
+
+ return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Canonicalize path.
+ *
+ * @param path path to file
+ * @param flags canonicalization flags
+ *
+ * All modifications of @path are made in place.
+ * Well formed UNC paths are modified only in the local part.
+ */
+
+void
+canonicalize_pathname_custom (char *path, canon_path_flags_t flags)
+{
+ char *p, *s;
+ char *lpath = path; /* path without leading UNC part */
+ const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
+
+ /* Detect and preserve UNC paths: //server/... */
+ if ((flags & CANON_PATH_GUARDUNC) != 0 && IS_PATH_SEP (path[0]) && IS_PATH_SEP (path[1]))
+ {
+ for (p = path + 2; p[0] != '\0' && !IS_PATH_SEP (p[0]); p++)
+ ;
+ if (IS_PATH_SEP (p[0]) && p > path + 2)
+ lpath = p;
+ }
+
+ if (lpath[0] == '\0' || lpath[1] == '\0')
+ return;
+
+ if ((flags & CANON_PATH_JOINSLASHES) != 0)
+ {
+ /* Collapse multiple slashes */
+ for (p = lpath; *p != '\0'; p++)
+ if (IS_PATH_SEP (p[0]) && IS_PATH_SEP (p[1]) && (p == lpath || *(p - 1) != ':'))
+ {
+ s = p + 1;
+ while (IS_PATH_SEP (*(++s)))
+ ;
+ str_move (p + 1, s);
+ }
+
+ /* Collapse "/./" -> "/" */
+ for (p = lpath; *p != '\0';)
+ if (IS_PATH_SEP (p[0]) && p[1] == '.' && IS_PATH_SEP (p[2]))
+ str_move (p, p + 2);
+ else
+ p++;
+ }
+
+ if ((flags & CANON_PATH_REMSLASHDOTS) != 0)
+ {
+ size_t len;
+
+ /* Remove trailing slashes */
+ for (p = lpath + strlen (lpath) - 1; p > lpath && IS_PATH_SEP (*p); p--)
+ {
+ if (p >= lpath + url_delim_len - 1
+ && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
+ break;
+ *p = '\0';
+ }
+
+ /* Remove leading "./" */
+ if (lpath[0] == '.' && IS_PATH_SEP (lpath[1]))
+ {
+ if (lpath[2] == '\0')
+ {
+ lpath[1] = '\0';
+ return;
+ }
+
+ str_move (lpath, lpath + 2);
+ }
+
+ /* Remove trailing "/" or "/." */
+ len = strlen (lpath);
+ if (len < 2)
+ return;
+
+ if (IS_PATH_SEP (lpath[len - 1])
+ && (len < url_delim_len
+ || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
+ url_delim_len) != 0))
+ lpath[len - 1] = '\0';
+ else if (lpath[len - 1] == '.' && IS_PATH_SEP (lpath[len - 2]))
+ {
+ if (len == 2)
+ {
+ lpath[1] = '\0';
+ return;
+ }
+
+ lpath[len - 2] = '\0';
+ }
+ }
+
+ /* Collapse "/.." with the previous part of path */
+ if ((flags & CANON_PATH_REMDOUBLEDOTS) != 0)
+ {
+#ifdef HAVE_CHARSET
+ const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
+#endif /* HAVE_CHARSET */
+
+ for (p = lpath; p[0] != '\0' && p[1] != '\0' && p[2] != '\0';)
+ {
+ if (!IS_PATH_SEP (p[0]) || p[1] != '.' || p[2] != '.'
+ || (!IS_PATH_SEP (p[3]) && p[3] != '\0'))
+ {
+ p++;
+ continue;
+ }
+
+ /* search for the previous token */
+ s = p - 1;
+ if (s >= lpath + url_delim_len - 2
+ && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
+ {
+ s -= (url_delim_len - 2);
+ while (s >= lpath && !IS_PATH_SEP (*s--))
+ ;
+ }
+
+ while (s >= lpath)
+ {
+ if (s - url_delim_len > lpath
+ && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
+ {
+ char *vfs_prefix = s - url_delim_len;
+ vfs_class *vclass;
+
+ while (vfs_prefix > lpath && !IS_PATH_SEP (*--vfs_prefix))
+ ;
+ if (IS_PATH_SEP (*vfs_prefix))
+ vfs_prefix++;
+ *(s - url_delim_len) = '\0';
+
+ vclass = vfs_prefix_to_class (vfs_prefix);
+ *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
+
+ if (vclass != NULL && (vclass->flags & VFSF_REMOTE) != 0)
+ {
+ s = vfs_prefix;
+ continue;
+ }
+ }
+
+ if (IS_PATH_SEP (*s))
+ break;
+
+ s--;
+ }
+
+ s++;
+
+ /* If the previous token is "..", we cannot collapse it */
+ if (s[0] == '.' && s[1] == '.' && s + 2 == p)
+ {
+ p += 3;
+ continue;
+ }
+
+ if (p[3] != '\0')
+ {
+ if (s == lpath && IS_PATH_SEP (*s))
+ {
+ /* "/../foo" -> "/foo" */
+ str_move (s + 1, p + 4);
+ }
+ else
+ {
+ /* "token/../foo" -> "foo" */
+#ifdef HAVE_CHARSET
+ if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
+ && (is_supported_encoding (s + enc_prefix_len)))
+ /* special case: remove encoding */
+ str_move (s, p + 1);
+ else
+#endif /* HAVE_CHARSET */
+ str_move (s, p + 4);
+ }
+
+ p = s > lpath ? s - 1 : s;
+ continue;
+ }
+
+ /* trailing ".." */
+ if (s == lpath)
+ {
+ /* "token/.." -> "." */
+ if (!IS_PATH_SEP (lpath[0]))
+ lpath[0] = '.';
+ lpath[1] = '\0';
+ }
+ else
+ {
+ /* "foo/token/.." -> "foo" */
+ if (s == lpath + 1)
+ s[0] = '\0';
+#ifdef HAVE_CHARSET
+ else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
+ && (is_supported_encoding (s + enc_prefix_len)))
+ {
+ /* special case: remove encoding */
+ s[0] = '.';
+ s[1] = '.';
+ s[2] = '\0';
+
+ /* search for the previous token */
+ /* IS_PATH_SEP (s[-1]) */
+ for (p = s - 1; p >= lpath && !IS_PATH_SEP (*p); p--)
+ ;
+
+ if (p >= lpath)
+ continue;
+ }
+#endif /* HAVE_CHARSET */
+ else
+ {
+ if (s >= lpath + url_delim_len
+ && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
+ *s = '\0';
+ else
+ s[-1] = '\0';
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mc_realpath (const char *path, char *resolved_path)
+{
+#ifdef HAVE_CHARSET
+ const char *p = path;
+ gboolean absolute_path = FALSE;
+
+ if (IS_PATH_SEP (*p))
+ {
+ absolute_path = TRUE;
+ p++;
+ }
+
+ /* ignore encoding: skip "#enc:" */
+ if (g_str_has_prefix (p, VFS_ENCODING_PREFIX))
+ {
+ p += strlen (VFS_ENCODING_PREFIX);
+ p = strchr (p, PATH_SEP);
+ if (p != NULL)
+ {
+ if (!absolute_path && p[1] != '\0')
+ p++;
+
+ path = p;
+ }
+ }
+#endif /* HAVE_CHARSET */
+
+#ifdef HAVE_REALPATH
+ return realpath (path, resolved_path);
+#else
+ {
+ char copy_path[PATH_MAX];
+ char got_path[PATH_MAX];
+ char *new_path = got_path;
+ char *max_path;
+#ifdef S_IFLNK
+ char link_path[PATH_MAX];
+ int readlinks = 0;
+ int n;
+#endif /* S_IFLNK */
+
+ /* Make a copy of the source path since we may need to modify it. */
+ if (strlen (path) >= PATH_MAX - 2)
+ {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ strcpy (copy_path, path);
+ path = copy_path;
+ max_path = copy_path + PATH_MAX - 2;
+ /* If it's a relative pathname use getwd for starters. */
+ if (!IS_PATH_SEP (*path))
+ {
+ new_path = g_get_current_dir ();
+ if (new_path == NULL)
+ strcpy (got_path, "");
+ else
+ {
+ g_snprintf (got_path, sizeof (got_path), "%s", new_path);
+ g_free (new_path);
+ new_path = got_path;
+ }
+
+ new_path += strlen (got_path);
+ if (!IS_PATH_SEP (new_path[-1]))
+ *new_path++ = PATH_SEP;
+ }
+ else
+ {
+ *new_path++ = PATH_SEP;
+ path++;
+ }
+ /* Expand each slash-separated pathname component. */
+ while (*path != '\0')
+ {
+ /* Ignore stray "/". */
+ if (IS_PATH_SEP (*path))
+ {
+ path++;
+ continue;
+ }
+ if (*path == '.')
+ {
+ /* Ignore ".". */
+ if (path[1] == '\0' || IS_PATH_SEP (path[1]))
+ {
+ path++;
+ continue;
+ }
+ if (path[1] == '.')
+ {
+ if (path[2] == '\0' || IS_PATH_SEP (path[2]))
+ {
+ path += 2;
+ /* Ignore ".." at root. */
+ if (new_path == got_path + 1)
+ continue;
+ /* Handle ".." by backing up. */
+ while (!IS_PATH_SEP ((--new_path)[-1]))
+ ;
+ continue;
+ }
+ }
+ }
+ /* Safely copy the next pathname component. */
+ while (*path != '\0' && !IS_PATH_SEP (*path))
+ {
+ if (path > max_path)
+ {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+ *new_path++ = *path++;
+ }
+#ifdef S_IFLNK
+ /* Protect against infinite loops. */
+ if (readlinks++ > MAXSYMLINKS)
+ {
+ errno = ELOOP;
+ return NULL;
+ }
+ /* See if latest pathname component is a symlink. */
+ *new_path = '\0';
+ n = readlink (got_path, link_path, PATH_MAX - 1);
+ if (n < 0)
+ {
+ /* EINVAL means the file exists but isn't a symlink. */
+ if (errno != EINVAL)
+ {
+ /* Make sure it's null terminated. */
+ *new_path = '\0';
+ strcpy (resolved_path, got_path);
+ return NULL;
+ }
+ }
+ else
+ {
+ /* Note: readlink doesn't add the null byte. */
+ link_path[n] = '\0';
+ if (IS_PATH_SEP (*link_path))
+ /* Start over for an absolute symlink. */
+ new_path = got_path;
+ else
+ /* Otherwise back up over this component. */
+ while (!IS_PATH_SEP (*(--new_path)))
+ ;
+ /* Safe sex check. */
+ if (strlen (path) + n >= PATH_MAX - 2)
+ {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+ /* Insert symlink contents into path. */
+ strcat (link_path, path);
+ strcpy (copy_path, link_path);
+ path = copy_path;
+ }
+#endif /* S_IFLNK */
+ *new_path++ = PATH_SEP;
+ }
+ /* Delete trailing slash but don't whomp a lone slash. */
+ if (new_path != got_path + 1 && IS_PATH_SEP (new_path[-1]))
+ new_path--;
+ /* Make sure it's null terminated. */
+ *new_path = '\0';
+ strcpy (resolved_path, got_path);
+ return resolved_path;
+ }
+#endif /* HAVE_REALPATH */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return the index of the permissions triplet
+ *
+ */
+
+int
+get_user_permissions (struct stat *st)
+{
+ static gboolean initialized = FALSE;
+ static gid_t *groups;
+ static int ngroups;
+ static uid_t uid;
+ int i;
+
+ if (!initialized)
+ {
+ uid = geteuid ();
+
+ ngroups = getgroups (0, NULL);
+ if (ngroups == -1)
+ ngroups = 0; /* ignore errors */
+
+ /* allocate space for one element in addition to what
+ * will be filled by getgroups(). */
+ groups = g_new (gid_t, ngroups + 1);
+
+ if (ngroups != 0)
+ {
+ ngroups = getgroups (ngroups, groups);
+ if (ngroups == -1)
+ ngroups = 0; /* ignore errors */
+ }
+
+ /* getgroups() may or may not return the effective group ID,
+ * so we always include it at the end of the list. */
+ groups[ngroups++] = getegid ();
+
+ initialized = TRUE;
+ }
+
+ if (st->st_uid == uid || uid == 0)
+ return 0;
+
+ for (i = 0; i < ngroups; i++)
+ if (st->st_gid == groups[i])
+ return 1;
+
+ return 2;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Build filename from arguments.
+ * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
+ */
+
+char *
+mc_build_filenamev (const char *first_element, va_list args)
+{
+ gboolean absolute;
+ const char *element = first_element;
+ GString *path;
+ char *ret;
+
+ if (element == NULL)
+ return NULL;
+
+ path = g_string_new ("");
+
+ absolute = IS_PATH_SEP (*first_element);
+
+ do
+ {
+ if (*element == '\0')
+ element = va_arg (args, char *);
+ else
+ {
+ char *tmp_element;
+ size_t len;
+ const char *start;
+
+ tmp_element = g_strdup (element);
+
+ element = va_arg (args, char *);
+
+ canonicalize_pathname (tmp_element);
+ len = strlen (tmp_element);
+ start = IS_PATH_SEP (tmp_element[0]) ? tmp_element + 1 : tmp_element;
+
+ g_string_append (path, start);
+ if (!IS_PATH_SEP (tmp_element[len - 1]) && element != NULL)
+ g_string_append_c (path, PATH_SEP);
+
+ g_free (tmp_element);
+ }
+ }
+ while (element != NULL);
+
+ if (absolute)
+ g_string_prepend_c (path, PATH_SEP);
+
+ ret = g_string_free (path, FALSE);
+ canonicalize_pathname (ret);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Build filename from arguments.
+ * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
+ */
+
+char *
+mc_build_filename (const char *first_element, ...)
+{
+ va_list args;
+ char *ret;
+
+ if (first_element == NULL)
+ return NULL;
+
+ va_start (args, first_element);
+ ret = mc_build_filenamev (first_element, args);
+ va_end (args);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/utilunix.h b/lib/utilunix.h
new file mode 100644
index 0000000..922d265
--- /dev/null
+++ b/lib/utilunix.h
@@ -0,0 +1,25 @@
+/** \file execute.h
+ * \brief Header: execution routines
+ */
+
+#ifndef MC__UTILUNIX_H
+#define MC__UTILUNIX_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* flags for shell_execute */
+#define EXECUTE_INTERNAL (1 << 0)
+#define EXECUTE_AS_SHELL (1 << 2)
+#define EXECUTE_HIDE (1 << 3)
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__UTILUNIX_H */
diff --git a/lib/vfs/HACKING b/lib/vfs/HACKING
new file mode 100644
index 0000000..c02e23d
--- /dev/null
+++ b/lib/vfs/HACKING
@@ -0,0 +1,104 @@
+Intended audience
+=================
+
+This document is intended for everybody who wants to understand VFS
+code. Knowledge of programming is a must.
+
+
+Preface
+=======
+
+While VFS should be considered an excellent idea, which came ahead of
+its time, the implementation used in GNU Midnight Commander is now
+showing its age.
+
+The VFS code was left us without any decent documentation. Most
+functions don't have comments explaining what they do. Most comments
+describe quirks and implementation details, rather than the intended
+functionality of the code. This document is an attempt to reconstruct
+understanding of the VFS code and help its future developers.
+
+Being the part of GNU Midnight Commander most exposed to potential
+security threats, the VFS code needs to be kept is a good shape.
+Understanding the code is the key to making and keeping it secure.
+
+
+Basics of code organization
+===========================
+
+VFS code it to a certain extent object oriented. The code dealing with
+a certain type of data (e.g. tar archives) can be thought
+of as a class in the terms of object oriented programming. They may
+reuse some code from their parent classes. For instance, tar and cpio
+archives have a common parent class direntry, which contains some common
+code for archives.
+
+Individual archives or connections can be considered as instances of
+those classes. They provide POSIX like interface to their structure,
+but don't expose that structure directly to the common VFS layer.
+
+Each VFS object has a directory tree associated with it. The tree
+consists of entries for files and directories. In some VFS classes, the
+entries have names and a are associated with nameless inodes, which
+contain information such as size, timestamps and other data normally
+contained in POSIX "struct stat".
+
+File vfs.c serves as a multiplexor. It exports functions similar to
+POSIX but with "mc_" prepended to them. For example, mc_open() will act
+like open(), but will treat VFS names in a special way.
+
+Common utility functions not intended to be used outside the VFS code
+should go to utilvfs.c and possibly to other files. Presently, there is
+a lot of such code in vfs.c.
+
+
+Hierarchy of classes
+====================
+
+vfs ---- direntry ---- cpio } archives
+ | | ---- tar }
+ | |
+ | | ---- fish } remote systems
+ | | ---- ftpfs }
+ |
+ |---- extfs ---- extfs archives
+ |---- localfs ---- sfs ---- sfs archives
+ |---- undelfs
+
+
+Properties of classes
+=====================
+
+ read only inode->entry local cache full tree
+ mapping loaded
+
+cpio yes* yes* no yes
+tar yes* yes* no yes
+fish no yes yes no
+ftpfs no yes yes no
+extfs no no yes yes
+localfs no no N/A N/A
+sfs no yes yes N/A
+undelfs no yes no yes
+
+
+"*" means that this property should change during further development.
+Mapping from inode to entry prevents implementing hard links. It is
+permissible for directories, which cannot be hardlinked. Not loading
+the full tree speeds up access to large archives and conserves memory.
+
+
+Stamping
+========
+
+Stamping is the VFS equivalent of garbage collection. It's purpose is
+to destroy unreferenced VFS objects, in other words close archives or
+connections once they are unused for some time. There is a tree of
+items representing VFS objects. The common layer doesn't know the
+structure of the pointers, but it knows the class that should handle the
+pointer. Every item has a timestamp. Once the timestamp becomes too
+old, the object is freed.
+
+There are ways to keep objects alive if they are used. Also, objects
+can have parent objects, which are freed together with there original
+object if they are otherwise unreferenced.
diff --git a/lib/vfs/Makefile.am b/lib/vfs/Makefile.am
new file mode 100644
index 0000000..87a51c6
--- /dev/null
+++ b/lib/vfs/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libmcvfs.la
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+libmcvfs_la_SOURCES = \
+ direntry.c \
+ gc.c gc.h \
+ interface.c \
+ parse_ls_vga.c \
+ path.c path.h \
+ vfs.c vfs.h \
+ utilvfs.c utilvfs.h \
+ xdirentry.h
+
+if ENABLE_VFS_NET
+libmcvfs_la_SOURCES += netutil.c netutil.h
+endif
+
+EXTRA_DIST = HACKING README
diff --git a/lib/vfs/Makefile.in b/lib/vfs/Makefile.in
new file mode 100644
index 0000000..bf588f9
--- /dev/null
+++ b/lib/vfs/Makefile.in
@@ -0,0 +1,767 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@ENABLE_VFS_NET_TRUE@am__append_1 = netutil.c netutil.h
+subdir = lib/vfs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcvfs_la_LIBADD =
+am__libmcvfs_la_SOURCES_DIST = direntry.c gc.c gc.h interface.c \
+ parse_ls_vga.c path.c path.h vfs.c vfs.h utilvfs.c utilvfs.h \
+ xdirentry.h netutil.c netutil.h
+@ENABLE_VFS_NET_TRUE@am__objects_1 = netutil.lo
+am_libmcvfs_la_OBJECTS = direntry.lo gc.lo interface.lo \
+ parse_ls_vga.lo path.lo vfs.lo utilvfs.lo $(am__objects_1)
+libmcvfs_la_OBJECTS = $(am_libmcvfs_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/direntry.Plo ./$(DEPDIR)/gc.Plo \
+ ./$(DEPDIR)/interface.Plo ./$(DEPDIR)/netutil.Plo \
+ ./$(DEPDIR)/parse_ls_vga.Plo ./$(DEPDIR)/path.Plo \
+ ./$(DEPDIR)/utilvfs.Plo ./$(DEPDIR)/vfs.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcvfs_la_SOURCES)
+DIST_SOURCES = $(am__libmcvfs_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp \
+ README
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcvfs.la
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+libmcvfs_la_SOURCES = direntry.c gc.c gc.h interface.c parse_ls_vga.c \
+ path.c path.h vfs.c vfs.h utilvfs.c utilvfs.h xdirentry.h \
+ $(am__append_1)
+EXTRA_DIST = HACKING README
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/vfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/vfs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcvfs.la: $(libmcvfs_la_OBJECTS) $(libmcvfs_la_DEPENDENCIES) $(EXTRA_libmcvfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcvfs_la_OBJECTS) $(libmcvfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/direntry.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/interface.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netutil.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse_ls_vga.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/path.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utilvfs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vfs.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/direntry.Plo
+ -rm -f ./$(DEPDIR)/gc.Plo
+ -rm -f ./$(DEPDIR)/interface.Plo
+ -rm -f ./$(DEPDIR)/netutil.Plo
+ -rm -f ./$(DEPDIR)/parse_ls_vga.Plo
+ -rm -f ./$(DEPDIR)/path.Plo
+ -rm -f ./$(DEPDIR)/utilvfs.Plo
+ -rm -f ./$(DEPDIR)/vfs.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/direntry.Plo
+ -rm -f ./$(DEPDIR)/gc.Plo
+ -rm -f ./$(DEPDIR)/interface.Plo
+ -rm -f ./$(DEPDIR)/netutil.Plo
+ -rm -f ./$(DEPDIR)/parse_ls_vga.Plo
+ -rm -f ./$(DEPDIR)/path.Plo
+ -rm -f ./$(DEPDIR)/utilvfs.Plo
+ -rm -f ./$(DEPDIR)/vfs.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/vfs/README b/lib/vfs/README
new file mode 100644
index 0000000..14d4397
--- /dev/null
+++ b/lib/vfs/README
@@ -0,0 +1,70 @@
+NOTE: Although vfs has been meant to be implemented as a separate
+entity redistributable under the LGPL in its current implementation it
+uses GPLed code from src/. So there are two possibilities if you want
+to use vfs:
+
+1. Distribute your copy of vfs under the GPL. Then you can freely
+include the GPLed functions from the rest of the mc source code.
+
+2. Distribute your copy of vfs under the LGPL. Then you cannot include
+the functions outside the vfs subdirectory. You must then either
+rewrite them or work around them in other ways.
+
+========================================================================
+
+Hi!
+
+I'm midnight commander's vfs layer. Before you start hacking me,
+please read this file. I'm integral part of midnight commander, but I
+try to go out and live my life myself as a shared library, too. That
+means that I should try to use as little functions from midnight as
+possible (so I'm tiny, nice and people like me), that I should not
+pollute name space by unnecessary symbols (so I do not crash fellow
+programs) and that I should have a clean interface between myself and
+midnight.
+
+Because I'm rather close to midnight, try to:
+
+* Keep the indentation as the rest of the code. Following could help
+you with your friend emacs:
+
+(defun mc-c-mode ()
+ "C mode with adjusted defaults for use with the Midnight commander."
+ (interactive)
+ (c-mode)
+ (c-set-style "K&R")
+ (setq c-indent-level 4
+ c-continued-statement-offset 4
+ c-brace-offset 0
+ c-argdecl-indent 4
+ c-label-offset -4
+ c-brace-imaginary-offset 0
+ c-continued-brace-offset 0
+ c-tab-always-indent nil
+ c-basic-offset 4
+ tab-width 8
+ comment-column 60))
+
+(setq auto-mode-alist (cons '(".*/mc/.*\\.[ch]$" . mc-c-mode)
+ auto-mode-alist))
+
+And because I'm trying to live life on my own as libvfs.so, try to:
+
+* Make sure all exported symbols are defined in vfs.h and begin with
+'vfs_'.
+
+* Do not make any references from midnight into modules like tar. It
+would probably pollute name space and midnight would depend on concrete
+configuration of libvfs. mc_setctl() and mc_ctl() are your
+friends. (And mine too :-).
+
+ Pavel Machek
+ pavel@ucw.cz
+
+PS: If you'd like to use my features in whole operating system, you
+might want to link me to rpc.nfsd. On
+http://atrey.karlin.mff.cuni.cz/~pavel/podfuk/podfuk.html you'll find
+how to do it.
+
+PPS: I have a friend, shared library called avfs, which is LD_PRELOAD
+capable. You can reach her at http://www.inf.bme.hu/~mszeredi/avfs.
diff --git a/lib/vfs/direntry.c b/lib/vfs/direntry.c
new file mode 100644
index 0000000..32b8594
--- /dev/null
+++ b/lib/vfs/direntry.c
@@ -0,0 +1,1740 @@
+/*
+ Directory cache support
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Pavel Machek <pavel@ucw.cz>, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2010-2013
+ Andrew Borodin <aborodin@vmail.ru> 2010-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ \warning Paths here do _not_ begin with '/', so root directory of
+ archive/site is simply "".
+ */
+
+/** \file
+ * \brief Source: directory cache support
+ *
+ * So that you do not have copy of this in each and every filesystem.
+ *
+ * Very loosely based on tar.c from midnight and archives.[ch] from
+ * avfs by Miklos Szeredi (mszeredi@inf.bme.hu)
+ *
+ * Unfortunately, I was unable to keep all filesystems
+ * uniform. tar-like filesystems use tree structure where each
+ * directory has pointers to its subdirectories. We can do this
+ * because we have full information about our archive.
+ *
+ * At ftp-like filesystems, situation is a little bit different. When
+ * you cd /usr/src/linux/drivers/char, you do _not_ want /usr,
+ * /usr/src, /usr/src/linux and /usr/src/linux/drivers to be
+ * listed. That means that we do not have complete information, and if
+ * /usr is symlink to /4, we will not know. Also we have to time out
+ * entries and things would get messy with tree-like approach. So we
+ * do different trick: root directory is completely special and
+ * completely fake, it contains entries such as 'usr', 'usr/src', ...,
+ * and we'll try to use custom find_entry function.
+ *
+ * \author Pavel Machek <pavel@ucw.cz>
+ * \date 1998
+ *
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+#include <stdarg.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* enable/disable interrupt key */
+#include "lib/util.h" /* canonicalize_pathname_custom() */
+#if 0
+#include "lib/widget.h" /* message() */
+#endif
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "xdirentry.h"
+#include "gc.h" /* vfs_rmstamp */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define CALL(x) \
+ if (VFS_SUBCLASS (me)->x != NULL) \
+ VFS_SUBCLASS (me)->x
+
+/*** file scope type declarations ****************************************************************/
+
+struct dirhandle
+{
+ GList *cur;
+ struct vfs_s_inode *dir;
+};
+
+/*** file scope variables ************************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* We were asked to create entries automagically */
+
+static struct vfs_s_entry *
+vfs_s_automake (struct vfs_class *me, struct vfs_s_inode *dir, char *path, int flags)
+{
+ struct vfs_s_entry *res;
+ char *sep;
+
+ sep = strchr (path, PATH_SEP);
+ if (sep != NULL)
+ *sep = '\0';
+
+ res = vfs_s_generate_entry (me, path, dir, (flags & FL_MKDIR) != 0 ? (0777 | S_IFDIR) : 0777);
+ vfs_s_insert_entry (me, dir, res);
+
+ if (sep != NULL)
+ *sep = PATH_SEP;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* If the entry is a symlink, find the entry for its target */
+
+static struct vfs_s_entry *
+vfs_s_resolve_symlink (struct vfs_class *me, struct vfs_s_entry *entry, int follow)
+{
+ char *linkname;
+ char *fullname = NULL;
+ struct vfs_s_entry *target;
+
+ if (follow == LINK_NO_FOLLOW)
+ return entry;
+ if (follow == 0)
+ ERRNOR (ELOOP, NULL);
+ if (entry == NULL)
+ ERRNOR (ENOENT, NULL);
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ return entry;
+
+ linkname = entry->ino->linkname;
+ if (linkname == NULL)
+ ERRNOR (EFAULT, NULL);
+
+ /* make full path from relative */
+ if (!IS_PATH_SEP (*linkname))
+ {
+ char *fullpath;
+
+ fullpath = vfs_s_fullpath (me, entry->dir);
+ if (fullpath != NULL)
+ {
+ fullname = g_strconcat (fullpath, PATH_SEP_STR, linkname, (char *) NULL);
+ linkname = fullname;
+ g_free (fullpath);
+ }
+ }
+
+ target =
+ VFS_SUBCLASS (me)->find_entry (me, entry->dir->super->root, linkname, follow - 1, FL_NONE);
+ g_free (fullname);
+ return target;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Follow > 0: follow links, serves as loop protect,
+ * == -1: do not follow links
+ */
+
+static struct vfs_s_entry *
+vfs_s_find_entry_tree (struct vfs_class *me, struct vfs_s_inode *root,
+ const char *a_path, int follow, int flags)
+{
+ size_t pseg;
+ struct vfs_s_entry *ent = NULL;
+ char *const pathref = g_strdup (a_path);
+ char *path = pathref;
+
+ /* canonicalize as well, but don't remove '../' from path */
+ canonicalize_pathname_custom (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
+
+ while (root != NULL)
+ {
+ GList *iter;
+
+ while (IS_PATH_SEP (*path)) /* Strip leading '/' */
+ path++;
+
+ if (path[0] == '\0')
+ {
+ g_free (pathref);
+ return ent;
+ }
+
+ for (pseg = 0; path[pseg] != '\0' && !IS_PATH_SEP (path[pseg]); pseg++)
+ ;
+
+ for (iter = g_queue_peek_head_link (root->subdir); iter != NULL; iter = g_list_next (iter))
+ {
+ ent = VFS_ENTRY (iter->data);
+ if (strlen (ent->name) == pseg && strncmp (ent->name, path, pseg) == 0)
+ /* FOUND! */
+ break;
+ }
+
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+
+ if (ent == NULL && (flags & (FL_MKFILE | FL_MKDIR)) != 0)
+ ent = vfs_s_automake (me, root, path, flags);
+ if (ent == NULL)
+ {
+ me->verrno = ENOENT;
+ goto cleanup;
+ }
+
+ path += pseg;
+ /* here we must follow leading directories always;
+ only the actual file is optional */
+ ent = vfs_s_resolve_symlink (me, ent,
+ strchr (path, PATH_SEP) != NULL ? LINK_FOLLOW : follow);
+ if (ent == NULL)
+ goto cleanup;
+ root = ent->ino;
+ }
+ cleanup:
+ g_free (pathref);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+vfs_s_find_entry_linear (struct vfs_class *me, struct vfs_s_inode *root,
+ const char *a_path, int follow, int flags)
+{
+ struct vfs_s_entry *ent = NULL;
+ char *const path = g_strdup (a_path);
+ GList *iter;
+
+ if (root->super->root != root)
+ vfs_die ("We have to use _real_ root. Always. Sorry.");
+
+ /* canonicalize as well, but don't remove '../' from path */
+ canonicalize_pathname_custom (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
+
+ if ((flags & FL_DIR) == 0)
+ {
+ char *dirname, *name;
+ struct vfs_s_inode *ino;
+
+ dirname = g_path_get_dirname (path);
+ name = g_path_get_basename (path);
+ ino = vfs_s_find_inode (me, root->super, dirname, follow, flags | FL_DIR);
+ ent = vfs_s_find_entry_tree (me, ino, name, follow, flags);
+ g_free (dirname);
+ g_free (name);
+ g_free (path);
+ return ent;
+ }
+
+ iter = g_queue_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+
+ if (ent != NULL && !VFS_SUBCLASS (me)->dir_uptodate (me, ent->ino))
+ {
+#if 1
+ vfs_print_message (_("Directory cache expired for %s"), path);
+#endif
+ vfs_s_free_entry (me, ent);
+ ent = NULL;
+ }
+
+ if (ent == NULL)
+ {
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_new_inode (me, root->super, vfs_s_default_stat (me, S_IFDIR | 0755));
+ ent = vfs_s_new_entry (me, path, ino);
+ if (VFS_SUBCLASS (me)->dir_load (me, ino, path) == -1)
+ {
+ vfs_s_free_entry (me, ent);
+ g_free (path);
+ return NULL;
+ }
+
+ vfs_s_insert_entry (me, root, ent);
+
+ iter = g_queue_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+ }
+ if (ent == NULL)
+ vfs_die ("find_linear: success but directory is not there\n");
+
+#if 0
+ if (vfs_s_resolve_symlink (me, ent, follow) == NULL)
+ {
+ g_free (path);
+ return NULL;
+ }
+#endif
+ g_free (path);
+ return ent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Ook, these were functions around directory entries / inodes */
+/* -------------------------------- superblock games -------------------------- */
+
+static struct vfs_s_super *
+vfs_s_new_super (struct vfs_class *me)
+{
+ struct vfs_s_super *super;
+
+ super = g_new0 (struct vfs_s_super, 1);
+ super->me = me;
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+vfs_s_insert_super (struct vfs_class *me, struct vfs_s_super *super)
+{
+ VFS_SUBCLASS (me)->supers = g_list_prepend (VFS_SUBCLASS (me)->supers, super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free_super (struct vfs_class *me, struct vfs_s_super *super)
+{
+ if (super->root != NULL)
+ {
+ vfs_s_free_inode (me, super->root);
+ super->root = NULL;
+ }
+
+#if 0
+ /* FIXME: We currently leak small amount of memory, sometimes. Fix it if you can. */
+ if (super->ino_usage != 0)
+ message (D_ERROR, "Direntry warning",
+ "Super ino_usage is %d, memory leak", super->ino_usage);
+
+ if (super->want_stale)
+ message (D_ERROR, "Direntry warning", "%s", "Super has want_stale set");
+#endif
+
+ VFS_SUBCLASS (me)->supers = g_list_remove (VFS_SUBCLASS (me)->supers, super);
+
+ CALL (free_archive) (me, super);
+#ifdef ENABLE_VFS_NET
+ vfs_path_element_free (super->path_element);
+#endif
+ g_free (super->name);
+ g_free (super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_file_handler_t *
+vfs_s_new_fh (struct vfs_s_inode *ino, gboolean changed)
+{
+ vfs_file_handler_t *fh;
+
+ fh = g_new0 (vfs_file_handler_t, 1);
+ vfs_s_init_fh (fh, ino, changed);
+
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free_fh (struct vfs_s_subclass *s, vfs_file_handler_t * fh)
+{
+ if (s->fh_free != NULL)
+ s->fh_free (fh);
+
+ g_free (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Support of archives */
+/* ------------------------ readdir & friends ----------------------------- */
+
+static struct vfs_s_inode *
+vfs_s_inode_from_path (const vfs_path_t * vpath, int flags)
+{
+ struct vfs_s_super *super;
+ struct vfs_s_inode *ino;
+ const char *q;
+ struct vfs_class *me;
+
+ q = vfs_s_get_path (vpath, &super, 0);
+ if (q == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ino =
+ vfs_s_find_inode (me, super, q,
+ (flags & FL_FOLLOW) != 0 ? LINK_FOLLOW : LINK_NO_FOLLOW,
+ flags & ~FL_FOLLOW);
+ if (ino == NULL && *q == '\0')
+ /* We are asking about / directory of ftp server: assume it exists */
+ ino =
+ vfs_s_find_inode (me, super, q,
+ (flags & FL_FOLLOW) != 0 ? LINK_FOLLOW : LINK_NO_FOLLOW,
+ FL_DIR | (flags & ~FL_FOLLOW));
+ return ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+vfs_s_opendir (const vfs_path_t * vpath)
+{
+ struct vfs_s_inode *dir;
+ struct dirhandle *info;
+ struct vfs_class *me;
+
+ dir = vfs_s_inode_from_path (vpath, FL_DIR | FL_FOLLOW);
+ if (dir == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ if (!S_ISDIR (dir->st.st_mode))
+ {
+ me->verrno = ENOTDIR;
+ return NULL;
+ }
+
+ dir->st.st_nlink++;
+#if 0
+ if (dir->subdir == NULL) /* This can actually happen if we allow empty directories */
+ {
+ me->verrno = EAGAIN;
+ return NULL;
+ }
+#endif
+ info = g_new (struct dirhandle, 1);
+ info->cur = g_queue_peek_head_link (dir->subdir);
+ info->dir = dir;
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+vfs_s_readdir (void *data)
+{
+ struct vfs_dirent *dir = NULL;
+ struct dirhandle *info = (struct dirhandle *) data;
+ const char *name;
+
+ if (info->cur == NULL || info->cur->data == NULL)
+ return NULL;
+
+ name = VFS_ENTRY (info->cur->data)->name;
+ if (name != NULL)
+ dir = vfs_dirent_init (NULL, name, 0);
+ else
+ vfs_die ("Null in structure-cannot happen");
+
+ info->cur = g_list_next (info->cur);
+
+ return dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_closedir (void *data)
+{
+ struct dirhandle *info = (struct dirhandle *) data;
+ struct vfs_s_inode *dir = info->dir;
+
+ vfs_s_free_inode (dir->super->me, dir);
+ g_free (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_chdir (const vfs_path_t * vpath)
+{
+ void *data;
+
+ data = vfs_s_opendir (vpath);
+ if (data == NULL)
+ return (-1);
+ vfs_s_closedir (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* --------------------------- stat and friends ---------------------------- */
+
+static int
+vfs_s_internal_stat (const vfs_path_t * vpath, struct stat *buf, int flag)
+{
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_inode_from_path (vpath, flag);
+ if (ino == NULL)
+ return (-1);
+ *buf = ino->st;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ struct vfs_s_inode *ino;
+ size_t len;
+ struct vfs_class *me;
+
+ ino = vfs_s_inode_from_path (vpath, 0);
+ if (ino == NULL)
+ return (-1);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ if (!S_ISLNK (ino->st.st_mode))
+ {
+ me->verrno = EINVAL;
+ return (-1);
+ }
+
+ if (ino->linkname == NULL)
+ {
+ me->verrno = EFAULT;
+ return (-1);
+ }
+
+ len = strlen (ino->linkname);
+ if (size < len)
+ len = size;
+ /* readlink() does not append a NUL character to buf */
+ memcpy (buf, ino->linkname, len);
+ return len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+vfs_s_read (void *fh, char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+
+ if (file->linear == LS_LINEAR_PREOPEN)
+ {
+ if (VFS_SUBCLASS (me)->linear_start (me, file, file->pos) == 0)
+ return (-1);
+ }
+
+ if (file->linear == LS_LINEAR_CLOSED)
+ vfs_die ("linear_start() did not set linear_state!");
+
+ if (file->linear == LS_LINEAR_OPEN)
+ return VFS_SUBCLASS (me)->linear_read (me, file, buffer, count);
+
+ if (file->handle != -1)
+ {
+ ssize_t n;
+
+ n = read (file->handle, buffer, count);
+ if (n < 0)
+ me->verrno = errno;
+ return n;
+ }
+ vfs_die ("vfs_s_read: This should not happen\n");
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+vfs_s_write (void *fh, const char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+
+ if (file->linear != LS_NOT_LINEAR)
+ vfs_die ("no writing to linear files, please");
+
+ file->changed = TRUE;
+ if (file->handle != -1)
+ {
+ ssize_t n;
+
+ n = write (file->handle, buffer, count);
+ if (n < 0)
+ me->verrno = errno;
+ return n;
+ }
+ vfs_die ("vfs_s_write: This should not happen\n");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+vfs_s_lseek (void *fh, off_t offset, int whence)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ off_t size = file->ino->st.st_size;
+
+ if (file->linear == LS_LINEAR_OPEN)
+ vfs_die ("cannot lseek() after linear_read!");
+
+ if (file->handle != -1)
+ { /* If we have local file opened, we want to work with it */
+ off_t retval;
+
+ retval = lseek (file->handle, offset, whence);
+ if (retval == -1)
+ VFS_FILE_HANDLER_SUPER (fh)->me->verrno = errno;
+ return retval;
+ }
+
+ switch (whence)
+ {
+ case SEEK_CUR:
+ offset += file->pos;
+ break;
+ case SEEK_END:
+ offset += size;
+ break;
+ default:
+ break;
+ }
+ if (offset < 0)
+ file->pos = 0;
+ else if (offset < size)
+ file->pos = offset;
+ else
+ file->pos = size;
+ return file->pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_close (void *fh)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ struct vfs_class *me = super->me;
+ struct vfs_s_subclass *sub = VFS_SUBCLASS (me);
+ int res = 0;
+
+ if (me == NULL)
+ return (-1);
+
+ super->fd_usage--;
+ if (super->fd_usage == 0)
+ vfs_stamp_create (me, VFS_FILE_HANDLER_SUPER (fh));
+
+ if (file->linear == LS_LINEAR_OPEN)
+ sub->linear_close (me, fh);
+ if (sub->fh_close != NULL)
+ res = sub->fh_close (me, fh);
+ if ((me->flags & VFSF_USETMP) != 0 && file->changed && sub->file_store != NULL)
+ {
+ char *s;
+
+ s = vfs_s_fullpath (me, file->ino);
+
+ if (s == NULL)
+ res = -1;
+ else
+ {
+ res = sub->file_store (me, fh, s, file->ino->localname);
+ g_free (s);
+ }
+ vfs_s_invalidate (me, super);
+ }
+
+ if (file->handle != -1)
+ {
+ close (file->handle);
+ file->handle = -1;
+ }
+
+ vfs_s_free_inode (me, file->ino);
+ vfs_s_free_fh (sub, fh);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_print_stats (const char *fs_name, const char *action,
+ const char *file_name, off_t have, off_t need)
+{
+ if (need != 0)
+ vfs_print_message (_("%s: %s: %s %3d%% (%lld) bytes transferred"), fs_name, action,
+ file_name, (int) ((double) have * 100 / need), (long long) have);
+ else
+ vfs_print_message (_("%s: %s: %s %lld bytes transferred"), fs_name, action, file_name,
+ (long long) have);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ------------------------------- mc support ---------------------------- */
+
+static void
+vfs_s_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ for (iter = VFS_SUBCLASS (me)->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+ char *name;
+
+ name = g_strconcat (super->name, PATH_SEP_STR, me->prefix, VFS_PATH_URL_DELIMITER,
+ /* super->current_dir->name, */ (char *) NULL);
+ func (name);
+ g_free (name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_ferrno (struct vfs_class *me)
+{
+ return me->verrno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get local copy of the given file. We reuse the existing file cache
+ * for remote filesystems. Archives use standard VFS facilities.
+ */
+
+static vfs_path_t *
+vfs_s_getlocalcopy (const vfs_path_t * vpath)
+{
+ vfs_file_handler_t *fh;
+ vfs_path_t *local = NULL;
+
+ if (vpath == NULL)
+ return NULL;
+
+ fh = vfs_s_open (vpath, O_RDONLY, 0);
+
+ if (fh != NULL)
+ {
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (vpath);
+ if ((me->flags & VFSF_USETMP) != 0 && fh->ino != NULL)
+ local = vfs_path_from_str_flags (fh->ino->localname, VPF_NO_CANON);
+
+ vfs_s_close (fh);
+ }
+
+ return local;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return the local copy. Since we are using our cache, we do nothing -
+ * the cache will be removed when the archive is closed.
+ */
+
+static int
+vfs_s_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ (void) vpath;
+ (void) local;
+ (void) has_changed;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ switch (ctlop)
+ {
+ case VFS_SETCTL_STALE_DATA:
+ {
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_inode_from_path (vpath, 0);
+ if (ino == NULL)
+ return 0;
+ if (arg != NULL)
+ ino->super->want_stale = TRUE;
+ else
+ {
+ ino->super->want_stale = FALSE;
+ vfs_s_invalidate (me, ino->super);
+ }
+ return 1;
+ }
+ case VFS_SETCTL_LOGFILE:
+ me->logfile = fopen ((char *) arg, "w");
+ return 1;
+ case VFS_SETCTL_FLUSH:
+ me->flush = TRUE;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------------------------- Stamping support -------------------------- */
+
+static vfsid
+vfs_s_getid (const vfs_path_t * vpath)
+{
+ struct vfs_s_super *archive = NULL;
+ const char *p;
+
+ p = vfs_s_get_path (vpath, &archive, FL_NO_OPEN);
+ if (p == NULL)
+ return NULL;
+
+ return (vfsid) archive;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_s_nothingisopen (vfsid id)
+{
+ return (VFS_SUPER (id)->fd_usage <= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free (vfsid id)
+{
+ vfs_s_free_super (VFS_SUPER (id)->me, VFS_SUPER (id));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_s_dir_uptodate (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ gint64 tim;
+
+ if (me->flush)
+ {
+ me->flush = FALSE;
+ return 0;
+ }
+
+ tim = g_get_monotonic_time ();
+
+ return (tim < ino->timestamp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_inode *
+vfs_s_new_inode (struct vfs_class *me, struct vfs_s_super *super, struct stat *initstat)
+{
+ struct vfs_s_inode *ino;
+
+ ino = g_try_new0 (struct vfs_s_inode, 1);
+ if (ino == NULL)
+ return NULL;
+
+ if (initstat != NULL)
+ ino->st = *initstat;
+ ino->super = super;
+ ino->subdir = g_queue_new ();
+ ino->st.st_nlink = 0;
+ ino->st.st_ino = VFS_SUBCLASS (me)->inode_counter++;
+ ino->st.st_dev = VFS_SUBCLASS (me)->rdev;
+
+ super->ino_usage++;
+
+ CALL (init_inode) (me, ino);
+
+ return ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ if (ino == NULL)
+ vfs_die ("Don't pass NULL to me");
+
+ /* ==0 can happen if freshly created entry is deleted */
+ if (ino->st.st_nlink > 1)
+ {
+ ino->st.st_nlink--;
+ return;
+ }
+
+ while (g_queue_get_length (ino->subdir) != 0)
+ {
+ struct vfs_s_entry *entry;
+
+ entry = VFS_ENTRY (g_queue_peek_head (ino->subdir));
+ vfs_s_free_entry (me, entry);
+ }
+
+ g_queue_free (ino->subdir);
+ ino->subdir = NULL;
+
+ CALL (free_inode) (me, ino);
+ g_free (ino->linkname);
+ if ((me->flags & VFSF_USETMP) != 0 && ino->localname != NULL)
+ {
+ unlink (ino->localname);
+ g_free (ino->localname);
+ }
+ ino->super->ino_usage--;
+ g_free (ino);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_entry *
+vfs_s_new_entry (struct vfs_class *me, const char *name, struct vfs_s_inode *inode)
+{
+ struct vfs_s_entry *entry;
+
+ entry = g_new0 (struct vfs_s_entry, 1);
+
+ entry->name = g_strdup (name);
+ entry->ino = inode;
+ entry->ino->ent = entry;
+ CALL (init_entry) (me, entry);
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_free_entry (struct vfs_class *me, struct vfs_s_entry *ent)
+{
+ if (ent->dir != NULL)
+ g_queue_remove (ent->dir->subdir, ent);
+
+ MC_PTR_FREE (ent->name);
+
+ if (ent->ino != NULL)
+ {
+ ent->ino->ent = NULL;
+ vfs_s_free_inode (me, ent->ino);
+ }
+
+ g_free (ent);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_insert_entry (struct vfs_class *me, struct vfs_s_inode *dir, struct vfs_s_entry *ent)
+{
+ (void) me;
+
+ ent->dir = dir;
+
+ ent->ino->st.st_nlink++;
+ g_queue_push_tail (dir->subdir, ent);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_entry_compare (const void *a, const void *b)
+{
+ const struct vfs_s_entry *e = (const struct vfs_s_entry *) a;
+ const char *name = (const char *) b;
+
+ return strcmp (e->name, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct stat *
+vfs_s_default_stat (struct vfs_class *me, mode_t mode)
+{
+ static struct stat st;
+ mode_t myumask;
+
+ (void) me;
+
+ myumask = umask (022);
+ umask (myumask);
+ mode &= ~myumask;
+
+ st.st_mode = mode;
+ st.st_ino = 0;
+ st.st_dev = 0;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = 0;
+#endif
+ st.st_uid = getuid ();
+ st.st_gid = getgid ();
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ st.st_blksize = 512;
+#endif
+ st.st_size = 0;
+
+ st.st_mtime = st.st_atime = st.st_ctime = time (NULL);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ st.st_atim.tv_nsec = st.st_mtim.tv_nsec = st.st_ctim.tv_nsec = 0;
+#endif
+
+ vfs_adjust_stat (&st);
+
+ return &st;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate number of st_blocks using st_size and st_blksize.
+ * In according to stat(2), st_blocks is the size in 512-byte units.
+ *
+ * @param s stat info
+ */
+
+void
+vfs_adjust_stat (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+ if (s->st_size == 0)
+ s->st_blocks = 0;
+ else
+ {
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ blkcnt_t ioblocks;
+ blksize_t ioblock_size;
+
+ /* 1. Calculate how many IO blocks are occupied */
+ ioblocks = 1 + (s->st_size - 1) / s->st_blksize;
+ /* 2. Calculate size of st_blksize in 512-byte units */
+ ioblock_size = 1 + (s->st_blksize - 1) / 512;
+ /* 3. Calculate number of blocks */
+ s->st_blocks = ioblocks * ioblock_size;
+#else
+ /* Let IO block size is 512 bytes */
+ s->st_blocks = 1 + (s->st_size - 1) / 512;
+#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
+ }
+#endif /* HAVE_STRUCT_STAT_ST_BLOCKS */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_entry *
+vfs_s_generate_entry (struct vfs_class *me, const char *name, struct vfs_s_inode *parent,
+ mode_t mode)
+{
+ struct vfs_s_inode *inode;
+ struct stat *st;
+
+ st = vfs_s_default_stat (me, mode);
+ inode = vfs_s_new_inode (me, parent->super, st);
+
+ return vfs_s_new_entry (me, name, inode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_inode *
+vfs_s_find_inode (struct vfs_class *me, const struct vfs_s_super *super,
+ const char *path, int follow, int flags)
+{
+ struct vfs_s_entry *ent;
+
+ if (((me->flags & VFSF_REMOTE) == 0) && (*path == '\0'))
+ return super->root;
+
+ ent = VFS_SUBCLASS (me)->find_entry (me, super->root, path, follow, flags);
+ return (ent != NULL ? ent->ino : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Ook, these were functions around directory entries / inodes */
+/* -------------------------------- superblock games -------------------------- */
+/**
+ * get superlock object by vpath
+ *
+ * @param vpath path
+ * @return superlock object or NULL if not found
+ */
+
+struct vfs_s_super *
+vfs_get_super_by_vpath (const vfs_path_t * vpath)
+{
+ GList *iter;
+ void *cookie = NULL;
+ const vfs_path_element_t *path_element;
+ struct vfs_s_subclass *subclass;
+ struct vfs_s_super *super = NULL;
+ vfs_path_t *vpath_archive;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+ subclass = VFS_SUBCLASS (path_element->class);
+
+ vpath_archive = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (vpath_archive, -1);
+
+ if (subclass->archive_check != NULL)
+ {
+ cookie = subclass->archive_check (vpath_archive);
+ if (cookie == NULL)
+ goto ret;
+ }
+
+ if (subclass->archive_same == NULL)
+ goto ret;
+
+ for (iter = subclass->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ int i;
+
+ super = VFS_SUPER (iter->data);
+
+ /* 0 == other, 1 == same, return it, 2 == other but stop scanning */
+ i = subclass->archive_same (path_element, super, vpath_archive, cookie);
+ if (i == 1)
+ goto ret;
+ if (i != 0)
+ break;
+
+ super = NULL;
+ }
+
+ ret:
+ vfs_path_free (vpath_archive, TRUE);
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * get path from last VFS-element and create corresponding superblock
+ *
+ * @param vpath source path object
+ * @param archive pointer to object for store newly created superblock
+ * @param flags flags
+ *
+ * @return path from last VFS-element
+ */
+const char *
+vfs_s_get_path (const vfs_path_t * vpath, struct vfs_s_super **archive, int flags)
+{
+ const char *retval = "";
+ int result = -1;
+ struct vfs_s_super *super;
+ const vfs_path_element_t *path_element;
+ struct vfs_s_subclass *subclass;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+
+ if (path_element->path != NULL)
+ retval = path_element->path;
+
+ super = vfs_get_super_by_vpath (vpath);
+ if (super != NULL)
+ goto return_success;
+
+ if ((flags & FL_NO_OPEN) != 0)
+ {
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+
+ subclass = VFS_SUBCLASS (path_element->class);
+
+ super = subclass->new_archive != NULL ?
+ subclass->new_archive (path_element->class) : vfs_s_new_super (path_element->class);
+
+ if (subclass->open_archive != NULL)
+ {
+ vfs_path_t *vpath_archive;
+
+ vpath_archive = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (vpath_archive, -1);
+
+ result = subclass->open_archive (super, vpath_archive, path_element);
+ vfs_path_free (vpath_archive, TRUE);
+ }
+ if (result == -1)
+ {
+ vfs_s_free_super (path_element->class, super);
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+ if (super->name == NULL)
+ vfs_die ("You have to fill name\n");
+ if (super->root == NULL)
+ vfs_die ("You have to fill root inode\n");
+
+ vfs_s_insert_super (path_element->class, super);
+ vfs_stamp_create (path_element->class, super);
+
+ return_success:
+ *archive = super;
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_invalidate (struct vfs_class *me, struct vfs_s_super *super)
+{
+ if (!super->want_stale)
+ {
+ vfs_s_free_inode (me, super->root);
+ super->root = vfs_s_new_inode (me, super, vfs_s_default_stat (me, S_IFDIR | 0755));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+vfs_s_fullpath (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ if (ino->ent == NULL)
+ ERRNOR (EAGAIN, NULL);
+
+ if ((me->flags & VFSF_USETMP) == 0)
+ {
+ /* archives */
+ char *path;
+
+ path = g_strdup (ino->ent->name);
+
+ while (TRUE)
+ {
+ char *newpath;
+
+ ino = ino->ent->dir;
+ if (ino == ino->super->root)
+ break;
+
+ newpath = g_strconcat (ino->ent->name, PATH_SEP_STR, path, (char *) NULL);
+ g_free (path);
+ path = newpath;
+ }
+ return path;
+ }
+
+ /* remote systems */
+ if (ino->ent->dir == NULL || ino->ent->dir->ent == NULL)
+ return g_strdup (ino->ent->name);
+
+ return g_strconcat (ino->ent->dir->ent->name, PATH_SEP_STR, ino->ent->name, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_init_fh (vfs_file_handler_t * fh, struct vfs_s_inode *ino, gboolean changed)
+{
+ fh->ino = ino;
+ fh->handle = -1;
+ fh->changed = changed;
+ fh->linear = LS_NOT_LINEAR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* --------------------------- stat and friends ---------------------------- */
+
+void *
+vfs_s_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ gboolean was_changed = FALSE;
+ vfs_file_handler_t *fh;
+ struct vfs_s_super *super;
+ const char *q;
+ struct vfs_s_inode *ino;
+ struct vfs_class *me;
+ struct vfs_s_subclass *s;
+
+ q = vfs_s_get_path (vpath, &super, 0);
+ if (q == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ino = vfs_s_find_inode (me, super, q, LINK_FOLLOW, FL_NONE);
+ if (ino != NULL && (flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ me->verrno = EEXIST;
+ return NULL;
+ }
+
+ s = VFS_SUBCLASS (me);
+
+ if (ino == NULL)
+ {
+ char *name;
+ struct vfs_s_entry *ent;
+ struct vfs_s_inode *dir;
+
+ /* If the filesystem is read-only, disable file creation */
+ if ((flags & O_CREAT) == 0 || me->write == NULL)
+ return NULL;
+
+ name = g_path_get_dirname (q);
+ dir = vfs_s_find_inode (me, super, name, LINK_FOLLOW, FL_DIR);
+ g_free (name);
+ if (dir == NULL)
+ return NULL;
+
+ name = g_path_get_basename (q);
+ ent = vfs_s_generate_entry (me, name, dir, 0755);
+ ino = ent->ino;
+ vfs_s_insert_entry (me, dir, ent);
+ if ((VFS_CLASS (s)->flags & VFSF_USETMP) != 0)
+ {
+ int tmp_handle;
+ vfs_path_t *tmp_vpath;
+
+ tmp_handle = vfs_mkstemps (&tmp_vpath, me->name, name);
+ ino->localname = vfs_path_free (tmp_vpath, FALSE);
+ if (tmp_handle == -1)
+ {
+ g_free (name);
+ return NULL;
+ }
+
+ close (tmp_handle);
+ }
+
+ g_free (name);
+ was_changed = TRUE;
+ }
+
+ if (S_ISDIR (ino->st.st_mode))
+ {
+ me->verrno = EISDIR;
+ return NULL;
+ }
+
+ fh = s->fh_new != NULL ? s->fh_new (ino, was_changed) : vfs_s_new_fh (ino, was_changed);
+
+ if (IS_LINEAR (flags))
+ {
+ if (s->linear_start != NULL)
+ {
+ vfs_print_message ("%s", _("Starting linear transfer..."));
+ fh->linear = LS_LINEAR_PREOPEN;
+ }
+ }
+ else
+ {
+ if (s->fh_open != NULL && s->fh_open (me, fh, flags, mode) != 0)
+ {
+ vfs_s_free_fh (s, fh);
+ return NULL;
+ }
+ }
+
+ if ((VFS_CLASS (s)->flags & VFSF_USETMP) != 0 && fh->ino->localname != NULL)
+ {
+ fh->handle = open (fh->ino->localname, NO_LINEAR (flags), mode);
+ if (fh->handle == -1)
+ {
+ vfs_s_free_fh (s, fh);
+ me->verrno = errno;
+ return NULL;
+ }
+ }
+
+ /* i.e. we had no open files and now we have one */
+ vfs_rmstamp (me, (vfsid) super);
+ super->fd_usage++;
+ fh->ino->st.st_nlink++;
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return vfs_s_internal_stat (vpath, buf, FL_FOLLOW);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return vfs_s_internal_stat (vpath, buf, FL_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_fstat (void *fh, struct stat *buf)
+{
+ *buf = VFS_FILE_HANDLER (fh)->ino->st;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_retrieve_file (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ /* If you want reget, you'll have to open file with O_LINEAR */
+ off_t total = 0;
+ char buffer[BUF_8K];
+ int handle;
+ ssize_t n;
+ off_t stat_size = ino->st.st_size;
+ vfs_file_handler_t *fh = NULL;
+ vfs_path_t *tmp_vpath;
+ struct vfs_s_subclass *s = VFS_SUBCLASS (me);
+
+ if ((me->flags & VFSF_USETMP) == 0)
+ return (-1);
+
+ handle = vfs_mkstemps (&tmp_vpath, me->name, ino->ent->name);
+ ino->localname = vfs_path_free (tmp_vpath, FALSE);
+ if (handle == -1)
+ {
+ me->verrno = errno;
+ goto error_4;
+ }
+
+ fh = s->fh_new != NULL ? s->fh_new (ino, FALSE) : vfs_s_new_fh (ino, FALSE);
+
+ if (s->linear_start (me, fh, 0) == 0)
+ goto error_3;
+
+ /* Clear the interrupt status */
+ tty_got_interrupt ();
+ tty_enable_interrupt_key ();
+
+ while ((n = s->linear_read (me, fh, buffer, sizeof (buffer))) != 0)
+ {
+ int t;
+
+ if (n < 0)
+ goto error_1;
+
+ total += n;
+ vfs_s_print_stats (me->name, _("Getting file"), ino->ent->name, total, stat_size);
+
+ if (tty_got_interrupt ())
+ goto error_1;
+
+ t = write (handle, buffer, n);
+ if (t != n)
+ {
+ if (t == -1)
+ me->verrno = errno;
+ goto error_1;
+ }
+ }
+ s->linear_close (me, fh);
+ close (handle);
+
+ tty_disable_interrupt_key ();
+ vfs_s_free_fh (s, fh);
+ return 0;
+
+ error_1:
+ s->linear_close (me, fh);
+ error_3:
+ tty_disable_interrupt_key ();
+ close (handle);
+ unlink (ino->localname);
+ error_4:
+ MC_PTR_FREE (ino->localname);
+ if (fh != NULL)
+ vfs_s_free_fh (s, fh);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------------------------- Stamping support -------------------------- */
+
+/* Initialize one of our subclasses - fill common functions */
+void
+vfs_init_class (struct vfs_class *vclass, const char *name, vfs_flags_t flags, const char *prefix)
+{
+ memset (vclass, 0, sizeof (struct vfs_class));
+
+ vclass->name = name;
+ vclass->flags = flags;
+ vclass->prefix = prefix;
+
+ vclass->fill_names = vfs_s_fill_names;
+ vclass->open = vfs_s_open;
+ vclass->close = vfs_s_close;
+ vclass->read = vfs_s_read;
+ if ((vclass->flags & VFSF_READONLY) == 0)
+ vclass->write = vfs_s_write;
+ vclass->opendir = vfs_s_opendir;
+ vclass->readdir = vfs_s_readdir;
+ vclass->closedir = vfs_s_closedir;
+ vclass->stat = vfs_s_stat;
+ vclass->lstat = vfs_s_lstat;
+ vclass->fstat = vfs_s_fstat;
+ vclass->readlink = vfs_s_readlink;
+ vclass->chdir = vfs_s_chdir;
+ vclass->ferrno = vfs_s_ferrno;
+ vclass->lseek = vfs_s_lseek;
+ vclass->getid = vfs_s_getid;
+ vclass->nothingisopen = vfs_s_nothingisopen;
+ vclass->free = vfs_s_free;
+ vclass->setctl = vfs_s_setctl;
+ if ((vclass->flags & VFSF_USETMP) != 0)
+ {
+ vclass->getlocalcopy = vfs_s_getlocalcopy;
+ vclass->ungetlocalcopy = vfs_s_ungetlocalcopy;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_subclass (struct vfs_s_subclass *sub, const char *name, vfs_flags_t flags,
+ const char *prefix)
+{
+ struct vfs_class *vclass = VFS_CLASS (sub);
+ size_t len;
+ char *start;
+
+ vfs_init_class (vclass, name, flags, prefix);
+
+ len = sizeof (struct vfs_s_subclass) - sizeof (struct vfs_class);
+ start = (char *) sub + sizeof (struct vfs_class);
+ memset (start, 0, len);
+
+ if ((vclass->flags & VFSF_USETMP) != 0)
+ sub->find_entry = vfs_s_find_entry_linear;
+ else if ((vclass->flags & VFSF_REMOTE) != 0)
+ sub->find_entry = vfs_s_find_entry_linear;
+ else
+ sub->find_entry = vfs_s_find_entry_tree;
+ sub->dir_uptodate = vfs_s_dir_uptodate;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find VFS id for given directory name */
+
+vfsid
+vfs_getid (const vfs_path_t * vpath)
+{
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (vpath);
+ if (me == NULL || me->getid == NULL)
+ return NULL;
+
+ return me->getid (vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------- Utility functions for networked filesystems -------------- */
+
+#ifdef ENABLE_VFS_NET
+int
+vfs_s_select_on_two (int fd1, int fd2)
+{
+ fd_set set;
+ struct timeval time_out;
+ int v;
+ int maxfd = MAX (fd1, fd2) + 1;
+
+ time_out.tv_sec = 1;
+ time_out.tv_usec = 0;
+ FD_ZERO (&set);
+ FD_SET (fd1, &set);
+ FD_SET (fd2, &set);
+
+ v = select (maxfd, &set, 0, 0, &time_out);
+ if (v <= 0)
+ return v;
+ if (FD_ISSET (fd1, &set))
+ return 1;
+ if (FD_ISSET (fd2, &set))
+ return 2;
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_get_line (struct vfs_class *me, int sock, char *buf, int buf_len, char term)
+{
+ FILE *logfile = me->logfile;
+ int i;
+ char c;
+
+ for (i = 0; i < buf_len - 1; i++, buf++)
+ {
+ if (read (sock, buf, sizeof (char)) <= 0)
+ return 0;
+
+ if (logfile != NULL)
+ {
+ size_t ret1;
+ int ret2;
+
+ ret1 = fwrite (buf, 1, 1, logfile);
+ ret2 = fflush (logfile);
+ (void) ret1;
+ (void) ret2;
+ }
+
+ if (*buf == term)
+ {
+ *buf = '\0';
+ return 1;
+ }
+ }
+
+ /* Line is too long - terminate buffer and discard the rest of line */
+ *buf = '\0';
+ while (read (sock, &c, sizeof (c)) > 0)
+ {
+ if (logfile != NULL)
+ {
+ size_t ret1;
+ int ret2;
+
+ ret1 = fwrite (&c, 1, 1, logfile);
+ ret2 = fflush (logfile);
+ (void) ret1;
+ (void) ret2;
+ }
+ if (c == '\n')
+ return 1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_get_line_interruptible (struct vfs_class *me, char *buffer, int size, int fd)
+{
+ int i;
+ int res = 0;
+
+ (void) me;
+
+ tty_enable_interrupt_key ();
+
+ for (i = 0; i < size - 1; i++)
+ {
+ ssize_t n;
+
+ n = read (fd, &buffer[i], 1);
+ if (n == -1 && errno == EINTR)
+ {
+ buffer[i] = '\0';
+ res = EINTR;
+ goto ret;
+ }
+ if (n == 0)
+ {
+ buffer[i] = '\0';
+ goto ret;
+ }
+ if (buffer[i] == '\n')
+ {
+ buffer[i] = '\0';
+ res = 1;
+ goto ret;
+ }
+ }
+
+ buffer[size - 1] = '\0';
+
+ ret:
+ tty_disable_interrupt_key ();
+
+ return res;
+}
+#endif /* ENABLE_VFS_NET */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Normalize filenames start position
+ */
+
+void
+vfs_s_normalize_filename_leading_spaces (struct vfs_s_inode *root_inode, size_t final_num_spaces)
+{
+ GList *iter;
+
+ for (iter = g_queue_peek_head_link (root_inode->subdir); iter != NULL;
+ iter = g_list_next (iter))
+ {
+ struct vfs_s_entry *entry = VFS_ENTRY (iter->data);
+
+ if ((size_t) entry->leading_spaces > final_num_spaces)
+ {
+ char *source_name, *spacer;
+
+ source_name = entry->name;
+ spacer = g_strnfill ((size_t) entry->leading_spaces - final_num_spaces, ' ');
+ entry->name = g_strconcat (spacer, source_name, (char *) NULL);
+ g_free (spacer);
+ g_free (source_name);
+ }
+
+ entry->leading_spaces = -1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/gc.c b/lib/vfs/gc.c
new file mode 100644
index 0000000..0914b75
--- /dev/null
+++ b/lib/vfs/gc.c
@@ -0,0 +1,335 @@
+/*
+ Virtual File System garbage collection code
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Pavel Roskin, 2003
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: garbage collection code
+ * \author Miguel de Icaza
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \author Pavel Roskin
+ * \date 1995, 1998, 2003
+ */
+
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/event.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "vfs.h"
+#include "utilvfs.h"
+
+#include "gc.h"
+
+/*
+ * The garbage collection mechanism is based on "stamps".
+ *
+ * A stamp is a record that says "I'm a filesystem which is no longer in
+ * use. Free me when you get a chance."
+ *
+ * This file contains a set of functions used for managing this stamp. You
+ * should use them when you write your own filesystem. Here are some rules
+ * of thumb:
+ *
+ * (1) When the last open file in your filesystem gets closed, conditionally
+ * create a stamp. You do this with vfs_stamp_create(). (The meaning
+ * of "conditionally" is explained below.)
+ *
+ * (2) When a file in your filesystem is opened, delete the stamp. You do
+ * this with vfs_rmstamp().
+ *
+ * (3) When a path inside your filesystem is invoked, call vfs_stamp() to
+ * postpone the free'ing of your filesystem a bit. (This simply updates
+ * a timestamp variable inside the stamp.)
+ *
+ * Additionally, when a user navigates to a new directory in a panel (or a
+ * programmer uses mc_chdir()), a stamp is conditionally created for the
+ * previous directory's filesystem. This ensures that that filesystem is
+ * free'ed. (see: _do_panel_cd() -> vfs_release_path(); mc_chdir()).
+ *
+ * We've spoken here of "conditionally creating" a stamp. What we mean is
+ * that vfs_stamp_create() is to be used: this function creates a stamp
+ * only if no directories are open (aka "active") in your filesystem. (If
+ * there _are_ directories open, it means that the filesystem is in use, in
+ * which case we don't want to free it.)
+ */
+
+/*** global variables ****************************************************************************/
+
+int vfs_timeout = 60; /* VFS timeout in seconds */
+
+/*** file scope macro definitions ****************************************************************/
+
+#define VFS_STAMPING(a) ((struct vfs_stamping *)(a))
+
+/*** file scope type declarations ****************************************************************/
+
+struct vfs_stamping
+{
+ struct vfs_class *v;
+ vfsid id;
+ gint64 time;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GSList *stamps = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gint
+vfs_stamp_compare (gconstpointer a, gconstpointer b)
+{
+ const struct vfs_stamping *vsa = (const struct vfs_stamping *) a;
+ const struct vfs_stamping *vsb = (const struct vfs_stamping *) b;
+
+ return (vsa == NULL || vsb == NULL || (vsa->v == vsb->v && vsa->id == vsb->id)) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_addstamp (struct vfs_class *v, vfsid id)
+{
+ if ((v->flags & VFSF_LOCAL) == 0 && id != NULL && !vfs_stamp (v, id))
+ {
+ struct vfs_stamping *stamp;
+
+ stamp = g_new (struct vfs_stamping, 1);
+ stamp->v = v;
+ stamp->id = id;
+ stamp->time = g_get_monotonic_time ();
+
+ stamps = g_slist_append (stamps, stamp);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_stamp (struct vfs_class *v, vfsid id)
+{
+ struct vfs_stamping what = {
+ .v = v,
+ .id = id
+ };
+ GSList *stamp;
+ gboolean ret = FALSE;
+
+ stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
+ if (stamp != NULL && stamp->data != NULL)
+ {
+ VFS_STAMPING (stamp->data)->time = g_get_monotonic_time ();
+ ret = TRUE;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_rmstamp (struct vfs_class *v, vfsid id)
+{
+ struct vfs_stamping what = {
+ .v = v,
+ .id = id
+ };
+ GSList *stamp;
+
+ stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
+ if (stamp != NULL)
+ {
+ g_free (stamp->data);
+ stamps = g_slist_delete_link (stamps, stamp);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_stamp_path (const vfs_path_t * vpath)
+{
+ vfsid id;
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ id = vfs_getid (vpath);
+ vfs_addstamp (me, id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create a new timestamp item by VFS class and VFS id.
+ */
+
+void
+vfs_stamp_create (struct vfs_class *vclass, vfsid id)
+{
+ vfsid nvfsid;
+
+ ev_vfs_stamp_create_t event_data = { vclass, id, FALSE };
+ const vfs_path_t *vpath;
+ struct vfs_class *me;
+
+ /* There are three directories we have to take care of: current_dir,
+ current_panel->cwd and other_panel->cwd. Although most of the time either
+ current_dir and current_panel->cwd or current_dir and other_panel->cwd are the
+ same, it's possible that all three are different -- Norbert */
+
+ if (!mc_event_present (MCEVENT_GROUP_CORE, "vfs_timestamp"))
+ return;
+
+ vpath = vfs_get_raw_current_dir ();
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ nvfsid = vfs_getid (vpath);
+ vfs_rmstamp (me, nvfsid);
+
+ if (!(id == NULL || (me == vclass && nvfsid == id)))
+ {
+ mc_event_raise (MCEVENT_GROUP_CORE, "vfs_timestamp", (gpointer) & event_data);
+
+ if (!event_data.ret && vclass != NULL && vclass->nothingisopen != NULL
+ && vclass->nothingisopen (id))
+ vfs_addstamp (vclass, id);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This is called from timeout handler with now = FALSE,
+ or can be called with now = TRUE to force freeing all filesystems */
+
+void
+vfs_expire (gboolean now)
+{
+ static gboolean locked = FALSE;
+ gint64 curr_time, exp_time;
+ GSList *stamp;
+
+ /* Avoid recursive invocation, e.g. when one of the free functions
+ calls message */
+ if (locked)
+ return;
+ locked = TRUE;
+
+ curr_time = g_get_monotonic_time ();
+ exp_time = curr_time - vfs_timeout * G_USEC_PER_SEC;
+
+ if (now)
+ {
+ /* reverse list to free nested VFSes at first */
+ stamps = g_slist_reverse (stamps);
+ }
+
+ /* NULLize stamps that point to expired VFS */
+ for (stamp = stamps; stamp != NULL; stamp = g_slist_next (stamp))
+ {
+ struct vfs_stamping *stamping = VFS_STAMPING (stamp->data);
+
+ if (now)
+ {
+ /* free VFS forced */
+ if (stamping->v->free != NULL)
+ stamping->v->free (stamping->id);
+ MC_PTR_FREE (stamp->data);
+ }
+ else if (stamping->time <= exp_time)
+ {
+ /* update timestamp of VFS that is in use, or free unused VFS */
+ if (stamping->v->nothingisopen != NULL && !stamping->v->nothingisopen (stamping->id))
+ stamping->time = curr_time;
+ else
+ {
+ if (stamping->v->free != NULL)
+ stamping->v->free (stamping->id);
+ MC_PTR_FREE (stamp->data);
+ }
+ }
+ }
+
+ /* then remove NULLized stamps */
+ stamps = g_slist_remove_all (stamps, NULL);
+
+ locked = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Return the number of seconds remaining to the vfs timeout.
+ * FIXME: The code should be improved to actually return the number of
+ * seconds until the next item times out.
+ */
+
+int
+vfs_timeouts (void)
+{
+ return stamps != NULL ? 10 : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_timeout_handler (void)
+{
+ vfs_expire (FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_release_path (const vfs_path_t * vpath)
+{
+ vfsid id;
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ id = vfs_getid (vpath);
+ vfs_stamp_create (me, id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Free all data */
+
+void
+vfs_gc_done (void)
+{
+ vfs_expire (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/gc.h b/lib/vfs/gc.h
new file mode 100644
index 0000000..59fa5ec
--- /dev/null
+++ b/lib/vfs/gc.h
@@ -0,0 +1,27 @@
+/**
+ * \file
+ * \brief Header: Virtual File System: garbage collection code
+ */
+
+#ifndef MC__VFS_GC_H
+#define MC__VFS_GC_H
+
+#include "vfs.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean vfs_stamp (struct vfs_class *vclass, vfsid id);
+void vfs_rmstamp (struct vfs_class *vclass, vfsid id);
+void vfs_stamp_create (struct vfs_class *vclass, vfsid id);
+void vfs_gc_done (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_VFS_GC_H */
diff --git a/lib/vfs/interface.c b/lib/vfs/interface.c
new file mode 100644
index 0000000..1b2de26
--- /dev/null
+++ b/lib/vfs/interface.c
@@ -0,0 +1,875 @@
+/*
+ Virtual File System: interface functions
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: path handlers
+ * \author Slava Zanko
+ * \date 2011
+ */
+
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h> /* For atol() */
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <ctype.h> /* is_digit() */
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "lib/global.h"
+
+#include "lib/widget.h" /* message() */
+#include "lib/strutil.h" /* str_crt_conv_from() */
+#include "lib/util.h"
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "path.h"
+#include "gc.h"
+#include "xdirentry.h"
+
+/* TODO: move it to separate private .h */
+extern GString *vfs_str_buffer;
+extern vfs_class *current_vfs;
+extern struct vfs_dirent *mc_readdir_result;
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+mc_def_getlocalcopy (const vfs_path_t * filename_vpath)
+{
+ vfs_path_t *tmp_vpath = NULL;
+ int fdin, fdout = -1;
+ ssize_t i;
+ char buffer[BUF_1K * 8];
+ struct stat mystat;
+
+ fdin = mc_open (filename_vpath, O_RDONLY | O_LINEAR);
+ if (fdin == -1)
+ goto fail;
+
+ fdout = vfs_mkstemps (&tmp_vpath, "vfs", vfs_path_get_last_path_str (filename_vpath));
+ if (fdout == -1)
+ goto fail;
+
+ while ((i = mc_read (fdin, buffer, sizeof (buffer))) > 0)
+ {
+ if (write (fdout, buffer, i) != i)
+ goto fail;
+ }
+ if (i == -1)
+ goto fail;
+ i = mc_close (fdin);
+ fdin = -1;
+ if (i == -1)
+ goto fail;
+
+ i = close (fdout);
+ fdout = -1;
+ if (i == -1)
+ goto fail;
+
+ if (mc_stat (filename_vpath, &mystat) != -1)
+ mc_chmod (tmp_vpath, mystat.st_mode);
+
+ return tmp_vpath;
+
+ fail:
+ vfs_path_free (tmp_vpath, TRUE);
+ if (fdout != -1)
+ close (fdout);
+ if (fdin != -1)
+ mc_close (fdin);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mc_def_ungetlocalcopy (const vfs_path_t * filename_vpath,
+ const vfs_path_t * local_vpath, gboolean has_changed)
+{
+ int fdin = -1, fdout = -1;
+ const char *local;
+
+ local = vfs_path_get_last_path_str (local_vpath);
+
+ if (has_changed)
+ {
+ char buffer[BUF_1K * 8];
+ ssize_t i;
+
+ if (vfs_path_get_last_path_vfs (filename_vpath)->write == NULL)
+ goto failed;
+
+ fdin = open (local, O_RDONLY);
+ if (fdin == -1)
+ goto failed;
+ fdout = mc_open (filename_vpath, O_WRONLY | O_TRUNC);
+ if (fdout == -1)
+ goto failed;
+ while ((i = read (fdin, buffer, sizeof (buffer))) > 0)
+ if (mc_write (fdout, buffer, (size_t) i) != i)
+ goto failed;
+ if (i == -1)
+ goto failed;
+
+ if (close (fdin) == -1)
+ {
+ fdin = -1;
+ goto failed;
+ }
+ fdin = -1;
+ if (mc_close (fdout) == -1)
+ {
+ fdout = -1;
+ goto failed;
+ }
+ }
+ unlink (local);
+ return 0;
+
+ failed:
+ message (D_ERROR, _("Changes to file lost"), "%s", vfs_path_get_last_path_str (filename_vpath));
+ if (fdout != -1)
+ mc_close (fdout);
+ if (fdin != -1)
+ close (fdin);
+ unlink (local);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_open (const vfs_path_t * vpath, int flags, ...)
+{
+ int result = -1;
+ mode_t mode = 0;
+ struct vfs_class *me;
+
+ if (vpath == NULL)
+ return (-1);
+
+ /* Get the mode flag */
+ if ((flags & O_CREAT) != 0)
+ {
+ va_list ap;
+
+ va_start (ap, flags);
+ /* We have to use PROMOTED_MODE_T instead of mode_t. Doing 'va_arg (ap, mode_t)'
+ * fails on systems where 'mode_t' is smaller than 'int' because of C's "default
+ * argument promotions". */
+ mode = va_arg (ap, PROMOTED_MODE_T);
+ va_end (ap);
+ }
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ if (me != NULL && me->open != NULL)
+ {
+ void *info;
+
+ /* open must be supported */
+ info = me->open (vpath, flags, mode);
+ if (info == NULL)
+ errno = vfs_ferrno (me);
+ else
+ result = vfs_new_handle (me, info);
+ }
+ else
+ errno = ENOTSUP;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+
+#define MC_NAMEOP(name, inarg, callarg) \
+int mc_##name inarg \
+{ \
+ int result; \
+ struct vfs_class *me; \
+\
+ if (vpath == NULL) \
+ return (-1); \
+\
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); \
+ if (me == NULL) \
+ return (-1); \
+\
+ result = me->name != NULL ? me->name callarg : -1; \
+ if (result == -1) \
+ errno = me->name != NULL ? vfs_ferrno (me) : ENOTSUP; \
+ return result; \
+}
+
+MC_NAMEOP (chmod, (const vfs_path_t *vpath, mode_t mode), (vpath, mode))
+MC_NAMEOP (chown, (const vfs_path_t *vpath, uid_t owner, gid_t group), (vpath, owner, group))
+MC_NAMEOP (fgetflags, (const vfs_path_t *vpath, unsigned long *flags), (vpath, flags))
+MC_NAMEOP (fsetflags, (const vfs_path_t *vpath, unsigned long flags), (vpath, flags))
+MC_NAMEOP (utime, (const vfs_path_t *vpath, mc_timesbuf_t * times), (vpath, times))
+MC_NAMEOP (readlink, (const vfs_path_t *vpath, char *buf, size_t bufsiz), (vpath, buf, bufsiz))
+MC_NAMEOP (unlink, (const vfs_path_t *vpath), (vpath))
+MC_NAMEOP (mkdir, (const vfs_path_t *vpath, mode_t mode), (vpath, mode))
+MC_NAMEOP (rmdir, (const vfs_path_t *vpath), (vpath))
+MC_NAMEOP (mknod, (const vfs_path_t *vpath, mode_t mode, dev_t dev), (vpath, mode, dev))
+
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int result = -1;
+
+ if (vpath1 != NULL && vpath2 != NULL)
+ {
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath2));
+ if (me != NULL)
+ {
+ result = me->symlink != NULL ? me->symlink (vpath1, vpath2) : -1;
+ if (result == -1)
+ errno = me->symlink != NULL ? vfs_ferrno (me) : ENOTSUP;
+ }
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+
+#define MC_HANDLEOP(rettype, name, inarg, callarg) \
+rettype mc_##name inarg \
+{ \
+ struct vfs_class *vfs; \
+ void *fsinfo = NULL; \
+ rettype result; \
+\
+ if (handle == -1) \
+ return (-1); \
+\
+ vfs = vfs_class_find_by_handle (handle, &fsinfo); \
+ if (vfs == NULL) \
+ return (-1); \
+\
+ result = vfs->name != NULL ? vfs->name callarg : -1; \
+ if (result == -1) \
+ errno = vfs->name != NULL ? vfs_ferrno (vfs) : ENOTSUP; \
+ return result; \
+}
+
+MC_HANDLEOP (ssize_t, read, (int handle, void *buf, size_t count), (fsinfo, buf, count))
+MC_HANDLEOP (ssize_t, write, (int handle, const void *buf, size_t count), (fsinfo, buf, count))
+MC_HANDLEOP (int, fstat, (int handle, struct stat *buf), (fsinfo, buf))
+
+/* --------------------------------------------------------------------------------------------- */
+
+#define MC_RENAMEOP(name) \
+int mc_##name (const vfs_path_t *vpath1, const vfs_path_t *vpath2) \
+{ \
+ int result; \
+ struct vfs_class *me1, *me2; \
+\
+ if (vpath1 == NULL || vpath2 == NULL) \
+ return (-1); \
+\
+ me1 = VFS_CLASS (vfs_path_get_last_path_vfs (vpath1)); \
+ me2 = VFS_CLASS (vfs_path_get_last_path_vfs (vpath2)); \
+\
+ if (me1 == NULL || me2 == NULL || me1 != me2) \
+ { \
+ errno = EXDEV; \
+ return (-1); \
+ } \
+\
+ result = me1->name != NULL ? me1->name (vpath1, vpath2) : -1; \
+ if (result == -1) \
+ errno = me1->name != NULL ? vfs_ferrno (me1) : ENOTSUP; \
+ return result; \
+}
+
+MC_RENAMEOP (link)
+MC_RENAMEOP (rename)
+
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_ctl (int handle, int ctlop, void *arg)
+{
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+
+ return (vfs == NULL || vfs->ctl == NULL) ? 0 : vfs->ctl (fsinfo, ctlop, arg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ int result = -1;
+ struct vfs_class *me;
+
+ if (vpath == NULL)
+ vfs_die ("You don't want to pass NULL to mc_setctl.");
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ if (me != NULL)
+ result = me->setctl != NULL ? me->setctl (vpath, ctlop, arg) : 0;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_close (int handle)
+{
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ int result;
+
+ if (handle == -1)
+ return (-1);
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+ if (vfs == NULL || fsinfo == NULL)
+ return (-1);
+
+ if (handle < 3)
+ return close (handle);
+
+ if (vfs->close == NULL)
+ vfs_die ("VFS must support close.\n");
+ result = vfs->close (fsinfo);
+ vfs_free_handle (handle);
+ if (result == -1)
+ errno = vfs_ferrno (vfs);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+DIR *
+mc_opendir (const vfs_path_t * vpath)
+{
+ int handle, *handlep;
+ void *info;
+ vfs_path_element_t *path_element;
+
+ if (vpath == NULL)
+ return NULL;
+
+ path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, -1);
+ if (!vfs_path_element_valid (path_element))
+ {
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ info = path_element->class->opendir ? path_element->class->opendir (vpath) : NULL;
+ if (info == NULL)
+ {
+ errno = path_element->class->opendir ? vfs_ferrno (path_element->class) : ENOTSUP;
+ return NULL;
+ }
+
+ path_element->dir.info = info;
+
+#ifdef HAVE_CHARSET
+ path_element->dir.converter = (path_element->encoding != NULL) ?
+ str_crt_conv_from (path_element->encoding) : str_cnv_from_term;
+ if (path_element->dir.converter == INVALID_CONV)
+ path_element->dir.converter = str_cnv_from_term;
+#endif
+
+ handle = vfs_new_handle (path_element->class, vfs_path_element_clone (path_element));
+
+ handlep = g_new (int, 1);
+ *handlep = handle;
+ return (DIR *) handlep;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_dirent *
+mc_readdir (DIR * dirp)
+{
+ int handle;
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ struct vfs_dirent *entry = NULL;
+ vfs_path_element_t *vfs_path_element;
+
+ if (dirp == NULL)
+ {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ handle = *(int *) dirp;
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+ if (vfs == NULL || fsinfo == NULL)
+ return NULL;
+
+ vfs_path_element = (vfs_path_element_t *) fsinfo;
+ if (vfs->readdir != NULL)
+ {
+ entry = vfs->readdir (vfs_path_element->dir.info);
+ if (entry == NULL)
+ return NULL;
+
+ g_string_set_size (vfs_str_buffer, 0);
+#ifdef HAVE_CHARSET
+ str_vfs_convert_from (vfs_path_element->dir.converter, entry->d_name, vfs_str_buffer);
+#else
+ g_string_assign (vfs_str_buffer, entry->d_name);
+#endif
+ vfs_dirent_assign (mc_readdir_result, vfs_str_buffer->str, entry->d_ino);
+ vfs_dirent_free (entry);
+ }
+ if (entry == NULL)
+ errno = vfs->readdir ? vfs_ferrno (vfs) : ENOTSUP;
+ return (entry != NULL) ? mc_readdir_result : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_closedir (DIR * dirp)
+{
+ int handle;
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ int result = -1;
+
+ if (dirp == NULL)
+ return result;
+
+ handle = *(int *) dirp;
+
+ vfs = vfs_class_find_by_handle (handle, &fsinfo);
+ if (vfs != NULL && fsinfo != NULL)
+ {
+ vfs_path_element_t *vfs_path_element = (vfs_path_element_t *) fsinfo;
+
+#ifdef HAVE_CHARSET
+ if (vfs_path_element->dir.converter != str_cnv_from_term)
+ {
+ str_close_conv (vfs_path_element->dir.converter);
+ vfs_path_element->dir.converter = INVALID_CONV;
+ }
+#endif
+
+ result = vfs->closedir ? (*vfs->closedir) (vfs_path_element->dir.info) : -1;
+ vfs_free_handle (handle);
+ vfs_path_element_free (vfs_path_element);
+ }
+ g_free (dirp);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+
+#define MC_STATOP(name) \
+int mc_##name (const vfs_path_t *vpath, struct stat *buf) \
+{ \
+ int result = -1; \
+ struct vfs_class *me; \
+\
+ if (vpath == NULL) \
+ return (-1); \
+\
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); \
+ if (me != NULL) \
+ { \
+ result = me->name ? me->name (vpath, buf) : -1; \
+ if (result == -1) \
+ errno = me->name ? vfs_ferrno (me) : ENOTSUP; \
+ } \
+\
+ return result; \
+}
+
+MC_STATOP (stat)
+MC_STATOP (lstat)
+
+/* --------------------------------------------------------------------------------------------- */
+
+vfs_path_t *
+mc_getlocalcopy (const vfs_path_t * pathname_vpath)
+{
+ vfs_path_t *result = NULL;
+ struct vfs_class *me;
+
+ if (pathname_vpath == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (pathname_vpath));
+ if (me != NULL)
+ {
+ result = me->getlocalcopy != NULL ?
+ me->getlocalcopy (pathname_vpath) : mc_def_getlocalcopy (pathname_vpath);
+ if (result == NULL)
+ errno = vfs_ferrno (me);
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mc_ungetlocalcopy (const vfs_path_t * pathname_vpath, const vfs_path_t * local_vpath,
+ gboolean has_changed)
+{
+ int result = -1;
+ const struct vfs_class *me;
+
+ if (pathname_vpath == NULL)
+ return (-1);
+
+ me = vfs_path_get_last_path_vfs (pathname_vpath);
+ if (me != NULL)
+ result = me->ungetlocalcopy != NULL ?
+ me->ungetlocalcopy (pathname_vpath, local_vpath, has_changed) :
+ mc_def_ungetlocalcopy (pathname_vpath, local_vpath, has_changed);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * VFS chdir.
+ *
+ * @param vpath VFS path.
+ * May be NULL. In this case NULL is returned and errno set to 0.
+ *
+ * @return 0 on success, -1 on failure.
+ */
+
+int
+mc_chdir (const vfs_path_t * vpath)
+{
+ struct vfs_class *old_vfs;
+ vfsid old_vfsid;
+ int result;
+ struct vfs_class *me;
+ const vfs_path_element_t *path_element;
+ vfs_path_t *cd_vpath;
+
+ if (vpath == NULL)
+ {
+ errno = 0;
+ return (-1);
+ }
+
+ if (vpath->relative)
+ cd_vpath = vfs_path_to_absolute (vpath);
+ else
+ cd_vpath = vfs_path_clone (vpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (cd_vpath));
+ if (me == NULL)
+ {
+ errno = EINVAL;
+ goto error_end;
+ }
+
+ if (me->chdir == NULL)
+ {
+ errno = ENOTSUP;
+ goto error_end;
+ }
+
+ result = me->chdir (cd_vpath);
+ if (result == -1)
+ {
+ errno = vfs_ferrno (me);
+ goto error_end;
+ }
+
+ old_vfsid = vfs_getid (vfs_get_raw_current_dir ());
+ old_vfs = current_vfs;
+
+ /* Actually change directory */
+ vfs_set_raw_current_dir (cd_vpath);
+ current_vfs = me;
+
+ /* This function uses the new current_dir implicitly */
+ vfs_stamp_create (old_vfs, old_vfsid);
+
+ /* Sometimes we assume no trailing slash on cwd */
+ path_element = vfs_path_get_by_index (vfs_get_raw_current_dir (), -1);
+ if (vfs_path_element_valid (path_element))
+ {
+ if (*path_element->path != '\0')
+ {
+ char *p;
+
+ p = strchr (path_element->path, 0) - 1;
+ if (IS_PATH_SEP (*p) && p > path_element->path)
+ *p = '\0';
+ }
+
+#ifdef ENABLE_VFS_NET
+ {
+ struct vfs_s_super *super;
+
+ super = vfs_get_super_by_vpath (vpath);
+ if (super != NULL && super->path_element != NULL)
+ {
+ g_free (super->path_element->path);
+ super->path_element->path = g_strdup (path_element->path);
+ }
+ }
+#endif /* ENABLE_VFS_NET */
+ }
+
+ return 0;
+
+ error_end:
+ vfs_path_free (cd_vpath, TRUE);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+mc_lseek (int fd, off_t offset, int whence)
+{
+ struct vfs_class *vfs;
+ void *fsinfo = NULL;
+ off_t result;
+
+ if (fd == -1)
+ return (-1);
+
+ vfs = vfs_class_find_by_handle (fd, &fsinfo);
+ if (vfs == NULL)
+ return (-1);
+
+ result = vfs->lseek ? vfs->lseek (fsinfo, offset, whence) : -1;
+ if (result == -1)
+ errno = vfs->lseek ? vfs_ferrno (vfs) : ENOTSUP;
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Following code heavily borrows from libiberty, mkstemps.c */
+/*
+ * Arguments:
+ * pname (output) - pointer to the name of the temp file (needs g_free).
+ * NULL if the function fails.
+ * prefix - part of the filename before the random part.
+ * Prepend $TMPDIR or /tmp if there are no path separators.
+ * suffix - if not NULL, part of the filename after the random part.
+ *
+ * Result:
+ * handle of the open file or -1 if couldn't open any.
+ */
+
+int
+mc_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *suffix)
+{
+ char *p1, *p2;
+ int fd;
+
+ if (strchr (prefix, PATH_SEP) != NULL)
+ p1 = g_strdup (prefix);
+ else
+ {
+ /* Add prefix first to find the position of XXXXXX */
+ p1 = g_build_filename (mc_tmpdir (), prefix, (char *) NULL);
+ }
+
+ p2 = g_strconcat (p1, "XXXXXX", suffix, (char *) NULL);
+ g_free (p1);
+
+ fd = g_mkstemp (p2);
+ if (fd >= 0)
+ *pname_vpath = vfs_path_from_str (p2);
+ else
+ {
+ *pname_vpath = NULL;
+ fd = -1;
+ }
+
+ g_free (p2);
+
+ return fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return the directory where mc should keep its temporary files.
+ * This directory is (in Bourne shell terms) "${TMPDIR=/tmp}/mc-$USER"
+ * When called the first time, the directory is created if needed.
+ * The first call should be done early, since we are using fprintf()
+ * and not message() to report possible problems.
+ */
+
+const char *
+mc_tmpdir (void)
+{
+ static char buffer[PATH_MAX];
+ static const char *tmpdir = NULL;
+ const char *sys_tmp;
+ struct passwd *pwd;
+ struct stat st;
+ const char *error = NULL;
+
+ /* Check if already correctly initialized */
+ if (tmpdir != NULL && lstat (tmpdir, &st) == 0 && S_ISDIR (st.st_mode) &&
+ st.st_uid == getuid () && (st.st_mode & 0777) == 0700)
+ return tmpdir;
+
+ sys_tmp = getenv ("MC_TMPDIR");
+ if (sys_tmp == NULL || !IS_PATH_SEP (sys_tmp[0]))
+ {
+ sys_tmp = getenv ("TMPDIR");
+ if (sys_tmp == NULL || !IS_PATH_SEP (sys_tmp[0]))
+ sys_tmp = TMPDIR_DEFAULT;
+ }
+
+ pwd = getpwuid (getuid ());
+ if (pwd != NULL)
+ g_snprintf (buffer, sizeof (buffer), "%s/mc-%s", sys_tmp, pwd->pw_name);
+ else
+ g_snprintf (buffer, sizeof (buffer), "%s/mc-%lu", sys_tmp, (unsigned long) getuid ());
+
+ canonicalize_pathname (buffer);
+
+ /* Try to create directory */
+ if (mkdir (buffer, S_IRWXU) != 0)
+ {
+ if (errno == EEXIST && lstat (buffer, &st) == 0)
+ {
+ /* Sanity check for existing directory */
+ if (!S_ISDIR (st.st_mode))
+ error = _("%s is not a directory\n");
+ else if (st.st_uid != getuid ())
+ error = _("Directory %s is not owned by you\n");
+ else if (((st.st_mode & 0777) != 0700) && (chmod (buffer, 0700) != 0))
+ error = _("Cannot set correct permissions for directory %s\n");
+ }
+ else
+ {
+ fprintf (stderr,
+ _("Cannot create temporary directory %s: %s\n"),
+ buffer, unix_error_string (errno));
+ error = "";
+ }
+ }
+
+ if (error != NULL)
+ {
+ int test_fd;
+ char *fallback_prefix;
+ gboolean fallback_ok = FALSE;
+ vfs_path_t *test_vpath;
+
+ if (*error != '\0')
+ fprintf (stderr, error, buffer);
+
+ /* Test if sys_tmp is suitable for temporary files */
+ fallback_prefix = g_strdup_printf ("%s/mctest", sys_tmp);
+ test_fd = mc_mkstemps (&test_vpath, fallback_prefix, NULL);
+ g_free (fallback_prefix);
+ if (test_fd != -1)
+ {
+ close (test_fd);
+ test_fd = open (vfs_path_as_str (test_vpath), O_RDONLY);
+ if (test_fd != -1)
+ {
+ close (test_fd);
+ unlink (vfs_path_as_str (test_vpath));
+ fallback_ok = TRUE;
+ }
+ }
+
+ if (fallback_ok)
+ {
+ fprintf (stderr, _("Temporary files will be created in %s\n"), sys_tmp);
+ g_snprintf (buffer, sizeof (buffer), "%s", sys_tmp);
+ error = NULL;
+ }
+ else
+ {
+ fprintf (stderr, _("Temporary files will not be created\n"));
+ g_snprintf (buffer, sizeof (buffer), "%s", "/dev/null/");
+ }
+
+ vfs_path_free (test_vpath, TRUE);
+ fprintf (stderr, "%s\n", _("Press any key to continue..."));
+ getc (stdin);
+ }
+
+ tmpdir = buffer;
+
+ if (error == NULL)
+ g_setenv ("MC_TMPDIR", tmpdir, TRUE);
+
+ return tmpdir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/netutil.c b/lib/vfs/netutil.c
new file mode 100644
index 0000000..1306879
--- /dev/null
+++ b/lib/vfs/netutil.c
@@ -0,0 +1,83 @@
+/*
+ Network utilities for the Midnight Commander Virtual File System.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: Network utilities
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h> /* memset() */
+
+#include "lib/global.h"
+
+#include "netutil.h"
+
+/*** global variables ****************************************************************************/
+
+SIG_ATOMIC_VOLATILE_T got_sigpipe = 0;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sig_pipe (int unused)
+{
+ (void) unused;
+ got_sigpipe = 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tcp_init (void)
+{
+ static gboolean initialized = FALSE;
+ struct sigaction sa;
+
+ if (initialized)
+ return;
+
+ got_sigpipe = 0;
+ memset (&sa, 0, sizeof (sa));
+ sa.sa_handler = sig_pipe;
+ sigemptyset (&sa.sa_mask);
+ sigaction (SIGPIPE, &sa, NULL);
+
+ initialized = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/netutil.h b/lib/vfs/netutil.h
new file mode 100644
index 0000000..9a12745
--- /dev/null
+++ b/lib/vfs/netutil.h
@@ -0,0 +1,26 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System: Network utilities
+ */
+
+#ifndef MC__VFS_NETUTIL_H
+#define MC__VFS_NETUTIL_H
+
+#include <signal.h>
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern SIG_ATOMIC_VOLATILE_T got_sigpipe;
+
+/*** declarations of public functions ************************************************************/
+
+void tcp_init (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC_VFS_NETUTIL_H */
diff --git a/lib/vfs/parse_ls_vga.c b/lib/vfs/parse_ls_vga.c
new file mode 100644
index 0000000..779792f
--- /dev/null
+++ b/lib/vfs/parse_ls_vga.c
@@ -0,0 +1,886 @@
+/*
+ Routines for parsing output from the 'ls' command.
+
+ Copyright (C) 1988-2023
+ Free Software Foundation, Inc.
+
+ Copyright (C) 1995, 1996 Miguel de Icaza
+
+ Written by:
+ Miguel de Icaza, 1995, 1996
+ Slava Zanko <slavazanko@gmail.com>, 2011
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Utilities for VFS modules
+ * \author Miguel de Icaza
+ * \date 1995, 1996
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/unixcompat.h" /* makedev */
+#include "lib/widget.h" /* message() */
+
+#include "utilvfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Parsing code is used by ftpfs, fish and extfs */
+#define MAXCOLS 30
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char *columns[MAXCOLS]; /* Points to the string in column n */
+static int column_ptr[MAXCOLS]; /* Index from 0 to the starting positions of the columns */
+static size_t vfs_parce_ls_final_num_spaces = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_num (int idx)
+{
+ char *column = columns[idx];
+
+ return (column != NULL && isdigit (column[0]));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return TRUE for MM-DD-YY and MM-DD-YYYY */
+
+static gboolean
+is_dos_date (const char *str)
+{
+ size_t len;
+
+ if (str == NULL)
+ return FALSE;
+
+ len = strlen (str);
+ if (len != 8 && len != 10)
+ return FALSE;
+
+ if (str[2] != str[5])
+ return FALSE;
+
+ return (strchr ("\\-/", (int) str[2]) != NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_week (const char *str, struct tm *tim)
+{
+ static const char *week = "SunMonTueWedThuFriSat";
+ const char *pos;
+
+ if (str == NULL)
+ return FALSE;
+
+ pos = strstr (week, str);
+ if (pos == NULL)
+ return FALSE;
+
+ if (tim != NULL)
+ tim->tm_wday = (pos - week) / 3;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check for possible locale's abbreviated month name (Jan..Dec).
+ * Any 3 bytes long string without digit, control and punctuation characters.
+ * isalpha() is locale specific, so it cannot be used if current
+ * locale is "C" and ftp server use Cyrillic.
+ * NB: It is assumed there are no whitespaces in month.
+ */
+static gboolean
+is_localized_month (const char *month)
+{
+ int i;
+
+ if (month == NULL)
+ return FALSE;
+
+ for (i = 0;
+ i < 3 && *month != '\0' && !isdigit ((unsigned char) *month)
+ && !iscntrl ((unsigned char) *month) && !ispunct ((unsigned char) *month); i++, month++)
+ ;
+
+ return (i == 3 && *month == '\0');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_time (const char *str, struct tm *tim)
+{
+ const char *p, *p2;
+
+ if (str == NULL)
+ return FALSE;
+
+ p = strchr (str, ':');
+ if (p == NULL)
+ return FALSE;
+
+ p2 = strrchr (str, ':');
+ if (p2 == NULL)
+ return FALSE;
+
+ if (p != p2)
+ {
+ if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
+ return FALSE;
+ }
+ else
+ {
+ if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_year (char *str, struct tm *tim)
+{
+ long year;
+
+ if (str == NULL)
+ return FALSE;
+
+ if (strchr (str, ':') != NULL)
+ return FALSE;
+
+ if (strlen (str) != 4)
+ return FALSE;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (str, "%ld", &year) != 1)
+ return FALSE;
+
+ if (year < 1900 || year > 3000)
+ return FALSE;
+
+ tim->tm_year = (int) (year - 1900);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_filetype (const char *s, size_t * ret_skipped, mode_t * ret_type)
+{
+ mode_t type;
+
+ switch (*s)
+ {
+ case 'd':
+ type = S_IFDIR;
+ break;
+ case 'b':
+ type = S_IFBLK;
+ break;
+ case 'c':
+ type = S_IFCHR;
+ break;
+ case 'l':
+ type = S_IFLNK;
+ break;
+#ifdef S_IFSOCK
+ case 's':
+ type = S_IFSOCK;
+ break;
+#else
+ case 's':
+ type = S_IFIFO;
+ break;
+#endif
+#ifdef S_IFDOOR /* Solaris door */
+ case 'D':
+ type = S_IFDOOR;
+ break;
+#else
+ case 'D':
+ type = S_IFIFO;
+ break;
+#endif
+ case 'p':
+ type = S_IFIFO;
+ break;
+#ifdef S_IFNAM /* Special named files */
+ case 'n':
+ type = S_IFNAM;
+ break;
+#else
+ case 'n':
+ type = S_IFREG;
+ break;
+#endif
+ case 'm': /* Don't know what these are :-) */
+ case '-':
+ case '?':
+ type = S_IFREG;
+ break;
+ default:
+ return FALSE;
+ }
+
+ *ret_type = type;
+
+ if (ret_skipped != NULL)
+ *ret_skipped = 1;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_fileperms (const char *s, size_t * ret_skipped, mode_t * ret_perms)
+{
+ const char *p = s;
+ mode_t perms = 0;
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'r':
+ perms |= S_IRUSR;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'w':
+ perms |= S_IWUSR;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'S':
+ perms |= S_ISUID;
+ break;
+ case 's':
+ perms |= S_IXUSR | S_ISUID;
+ break;
+ case 'x':
+ perms |= S_IXUSR;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'r':
+ perms |= S_IRGRP;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'w':
+ perms |= S_IWGRP;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'S':
+ perms |= S_ISGID;
+ break;
+ case 'l':
+ perms |= S_ISGID;
+ break; /* found on Solaris */
+ case 's':
+ perms |= S_IXGRP | S_ISGID;
+ break;
+ case 'x':
+ perms |= S_IXGRP;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'r':
+ perms |= S_IROTH;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'w':
+ perms |= S_IWOTH;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (*p++)
+ {
+ case '-':
+ break;
+ case 'T':
+ perms |= S_ISVTX;
+ break;
+ case 't':
+ perms |= S_IXOTH | S_ISVTX;
+ break;
+ case 'x':
+ perms |= S_IXOTH;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (*p == '+')
+ /* ACLs on Solaris, HP-UX and others */
+ p++;
+
+ if (ret_skipped != NULL)
+ *ret_skipped = p - s;
+ *ret_perms = perms;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
+{
+ const char *p = s;
+ mode_t type, perms;
+ size_t skipped;
+
+ if (!vfs_parse_filetype (p, &skipped, &type))
+ return FALSE;
+
+ p += skipped;
+ if (!vfs_parse_fileperms (p, &skipped, &perms))
+ return FALSE;
+
+ p += skipped;
+ *ret_skipped = p - s;
+ *ret_mode = type | perms;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_raw_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode)
+{
+ const char *p = s;
+ mode_t remote_type = 0, local_type, perms = 0;
+
+ /* isoctal */
+ for (; *p >= '0' && *p <= '7'; p++)
+ {
+ perms *= 010;
+ perms += (*p - '0');
+ }
+
+ if (*p++ != ' ')
+ return FALSE;
+
+ for (; *p >= '0' && *p <= '7'; p++)
+ {
+ remote_type *= 010;
+ remote_type += (*p - '0');
+ }
+
+ if (*p++ != ' ')
+ return FALSE;
+
+ /* generated with:
+ $ perl -e 'use Fcntl ":mode";
+ my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG);
+ foreach $t (@modes) { printf ("%o\n", $t); };'
+ TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os)
+ (see vfs_parse_filetype)
+ */
+
+ switch (remote_type)
+ {
+ case 020000:
+ local_type = S_IFCHR;
+ break;
+ case 040000:
+ local_type = S_IFDIR;
+ break;
+ case 060000:
+ local_type = S_IFBLK;
+ break;
+ case 0120000:
+ local_type = S_IFLNK;
+ break;
+ case 0100000:
+ default: /* don't know what is it */
+ local_type = S_IFREG;
+ break;
+ }
+
+ *ret_skipped = p - s;
+ *ret_mode = local_type | perms;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_month (const char *str, struct tm * tim)
+{
+ static const char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
+ const char *pos;
+
+ if (str == NULL)
+ return FALSE;
+
+ pos = strstr (month, str);
+ if (pos == NULL)
+ return FALSE;
+
+ if (tim != NULL)
+ tim->tm_mon = (pos - month) / 3;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This function parses from idx in the columns[] array */
+
+int
+vfs_parse_filedate (int idx, time_t * t)
+{
+ char *p;
+ struct tm tim;
+ int d[3];
+ gboolean got_year = FALSE;
+ gboolean l10n = FALSE; /* Locale's abbreviated month name */
+ time_t current_time;
+ struct tm *local_time;
+
+ /* Let's setup default time values */
+ current_time = time (NULL);
+ local_time = localtime (&current_time);
+ tim.tm_mday = local_time->tm_mday;
+ tim.tm_mon = local_time->tm_mon;
+ tim.tm_year = local_time->tm_year;
+
+ tim.tm_hour = 0;
+ tim.tm_min = 0;
+ tim.tm_sec = 0;
+ tim.tm_isdst = -1; /* Let mktime() try to guess correct dst offset */
+
+ p = columns[idx++];
+
+ /* We eat weekday name in case of extfs */
+ if (is_week (p, &tim))
+ p = columns[idx++];
+
+ /*
+ ALLOWED DATE FORMATS
+
+ We expect 3 fields max or we'll see oddities with certain file names.
+
+ Formats that contain either year or time (the default 'ls' formats):
+
+ * Mon DD hh:mm[:ss]
+ * Mon DD YYYY
+
+ Formats that contain both year and time, to make it easier to write
+ extfs scripts:
+
+ * MM-DD-YYYY hh:mm[:ss]
+ * MM-DD-YY hh:mm[:ss]
+
+ ('/' and '\' can be used instead of '-'.)
+
+ where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
+ YYYY four digit year, hh, mm, ss two digit hour, minute or second.
+
+ (As for the "3 fields max" restriction: this prevents, for example, a
+ file name "13:48" from being considered part of a "Sep 19 2016" date
+ string preceding it.)
+ */
+
+ /* Month name */
+ if (vfs_parse_month (p, &tim))
+ {
+ /* And we expect, it followed by day number */
+ if (!is_num (idx))
+ return 0; /* No day */
+
+ tim.tm_mday = (int) atol (columns[idx++]);
+
+ }
+ else if (is_dos_date (p))
+ {
+ /* Case with MM-DD-YY or MM-DD-YYYY */
+ p[2] = p[5] = '-';
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (p, "%2d-%2d-%d", &d[0], &d[1], &d[2]) != 3)
+ return 0; /* sscanf failed */
+
+ /* Months are zero based */
+ if (d[0] > 0)
+ d[0]--;
+
+ if (d[2] > 1900)
+ d[2] -= 1900;
+ else if (d[2] < 70)
+ /* Y2K madness */
+ d[2] += 100;
+
+ tim.tm_mon = d[0];
+ tim.tm_mday = d[1];
+ tim.tm_year = d[2];
+ got_year = TRUE;
+ }
+ else if (is_localized_month (p) && is_num (idx++))
+ /* Locale's abbreviated month name followed by day number */
+ l10n = TRUE;
+ else
+ return 0; /* unsupported format */
+
+ /* Here we expect to find time or year */
+ if (!is_num (idx)
+ || !(is_time (columns[idx], &tim) || (got_year = is_year (columns[idx], &tim))))
+ return 0; /* Neither time nor date */
+
+ idx++;
+
+ /*
+ * If the date is less than 6 months in the past, it is shown without year
+ * other dates in the past or future are shown with year but without time
+ * This does not check for years before 1900 ... I don't know, how
+ * to represent them at all
+ */
+ if (!got_year && local_time->tm_mon < 6 && local_time->tm_mon < tim.tm_mon
+ && tim.tm_mon - local_time->tm_mon >= 6)
+ tim.tm_year--;
+
+ *t = mktime (&tim);
+ if (l10n || (*t < 0))
+ *t = 0;
+
+ return idx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_split_text (char *p)
+{
+ char *original = p;
+ int numcols;
+
+ memset (columns, 0, sizeof (columns));
+
+ for (numcols = 0; *p != '\0' && numcols < MAXCOLS; numcols++)
+ {
+ for (; *p == ' ' || *p == '\r' || *p == '\n'; p++)
+ *p = '\0';
+
+ columns[numcols] = p;
+ column_ptr[numcols] = p - original;
+
+ for (; *p != '\0' && *p != ' ' && *p != '\r' && *p != '\n'; p++)
+ ;
+ }
+
+ return numcols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_parse_ls_lga_init (void)
+{
+ vfs_parce_ls_final_num_spaces = 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+size_t
+vfs_parse_ls_lga_get_final_spaces (void)
+{
+ return vfs_parce_ls_final_num_spaces;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_parse_ls_lga (const char *p, struct stat * s, char **filename, char **linkname,
+ size_t * num_spaces)
+{
+ int idx, idx2, num_cols;
+ int i;
+ char *p_copy = NULL;
+ char *t = NULL;
+ const char *line = p;
+ size_t skipped;
+
+ if (strncmp (p, "total", 5) == 0)
+ return FALSE;
+
+ if (!vfs_parse_filetype (p, &skipped, &s->st_mode))
+ goto error;
+
+ p += skipped;
+ if (*p == ' ') /* Notwell 4 */
+ p++;
+ if (*p == '[')
+ {
+ if (strlen (p) <= 8 || p[8] != ']')
+ goto error;
+
+ /* Should parse here the Notwell permissions :) */
+ if (S_ISDIR (s->st_mode))
+ s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
+ else
+ s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
+ p += 9;
+ }
+ else
+ {
+ size_t lc_skipped;
+ mode_t perms;
+
+ if (!vfs_parse_fileperms (p, &lc_skipped, &perms))
+ goto error;
+
+ p += lc_skipped;
+ s->st_mode |= perms;
+ }
+
+ p_copy = g_strdup (p);
+ num_cols = vfs_split_text (p_copy);
+
+ s->st_nlink = atol (columns[0]);
+ if (s->st_nlink <= 0)
+ goto error;
+
+ if (!is_num (1))
+ s->st_uid = vfs_finduid (columns[1]);
+ else
+ s->st_uid = (uid_t) atol (columns[1]);
+
+ /* Mhm, the ls -lg did not produce a group field */
+ for (idx = 3; idx <= 5; idx++)
+ if (vfs_parse_month (columns[idx], NULL) || is_week (columns[idx], NULL)
+ || is_dos_date (columns[idx]) || is_localized_month (columns[idx]))
+ break;
+
+ if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
+ goto error;
+
+ /* We don't have gid */
+ if (idx == 3 || (idx == 4 && (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))))
+ idx2 = 2;
+ else
+ {
+ /* We have gid field */
+ if (is_num (2))
+ s->st_gid = (gid_t) atol (columns[2]);
+ else
+ s->st_gid = vfs_findgid (columns[2]);
+ idx2 = 3;
+ }
+
+ /* This is device */
+ if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode))
+ {
+ int maj, min;
+
+ /* Corner case: there is no whitespace(s) between maj & min */
+ if (!is_num (idx2) && idx2 == 2)
+ {
+ /* cppcheck-suppress invalidscanf */
+ if (!is_num (++idx2) || sscanf (columns[idx2], " %d,%d", &maj, &min) != 2)
+ goto error;
+ }
+ else
+ {
+ /* cppcheck-suppress invalidscanf */
+ if (!is_num (idx2) || sscanf (columns[idx2], " %d,", &maj) != 1)
+ goto error;
+
+ /* cppcheck-suppress invalidscanf */
+ if (!is_num (++idx2) || sscanf (columns[idx2], " %d", &min) != 1)
+ goto error;
+ }
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ s->st_rdev = makedev (maj, min);
+#endif
+ s->st_size = 0;
+
+ }
+ else
+ {
+ /* Common file size */
+ if (!is_num (idx2))
+ goto error;
+
+ s->st_size = (off_t) g_ascii_strtoll (columns[idx2], NULL, 10);
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ s->st_rdev = 0;
+#endif
+ }
+
+ idx = vfs_parse_filedate (idx, &s->st_mtime);
+ if (idx == 0)
+ goto error;
+
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0;
+#endif
+
+ /* s->st_dev and s->st_ino must be initialized by vfs_s_new_inode () */
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ s->st_blksize = 512;
+#endif
+ vfs_adjust_stat (s);
+
+ if (num_spaces != NULL)
+ {
+ *num_spaces = column_ptr[idx] - column_ptr[idx - 1] - strlen (columns[idx - 1]);
+ if (DIR_IS_DOTDOT (columns[idx]))
+ vfs_parce_ls_final_num_spaces = *num_spaces;
+ }
+
+ for (i = idx + 1, idx2 = 0; i < num_cols; i++)
+ if (strcmp (columns[i], "->") == 0)
+ {
+ idx2 = i;
+ break;
+ }
+
+ if (((S_ISLNK (s->st_mode) || (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
+ && idx2 != 0)
+ {
+ if (filename != NULL)
+ *filename = g_strndup (p + column_ptr[idx], column_ptr[idx2] - column_ptr[idx] - 1);
+
+ if (linkname != NULL)
+ {
+ t = g_strdup (p + column_ptr[idx2 + 1]);
+ *linkname = t;
+ }
+ }
+ else
+ {
+ /* Extract the filename from the string copy, not from the columns
+ * this way we have a chance of entering hidden directories like ". ."
+ */
+ if (filename != NULL)
+ {
+ /* filename = g_strdup (columns [idx++]); */
+ t = g_strdup (p + column_ptr[idx]);
+ *filename = t;
+ }
+
+ if (linkname != NULL)
+ *linkname = NULL;
+ }
+
+ if (t != NULL)
+ {
+ size_t p2;
+
+ p2 = strlen (t);
+ if (--p2 > 0 && (t[p2] == '\r' || t[p2] == '\n'))
+ t[p2] = '\0';
+ if (--p2 > 0 && (t[p2] == '\r' || t[p2] == '\n'))
+ t[p2] = '\0';
+ }
+
+ g_free (p_copy);
+ return TRUE;
+
+ error:
+ {
+ static int errorcount = 0;
+
+ if (++errorcount < 5)
+ message (D_ERROR, _("Cannot parse:"), "%s",
+ (p_copy != NULL && *p_copy != '\0') ? p_copy : line);
+ else if (errorcount == 5)
+ message (D_ERROR, MSG_ERROR, _("More parsing errors will be ignored."));
+ }
+
+ g_free (p_copy);
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/path.c b/lib/vfs/path.c
new file mode 100644
index 0000000..c599e25
--- /dev/null
+++ b/lib/vfs/path.c
@@ -0,0 +1,1683 @@
+/*
+ Virtual File System path handlers
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: path handlers
+ * \author Slava Zanko
+ * \date 2011
+ */
+
+
+#include <config.h>
+
+#include <errno.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* mc_build_filename() */
+#include "lib/serialize.h"
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "xdirentry.h"
+#include "path.h"
+
+extern GPtrArray *vfs__classes_list;
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+path_magic (const char *path)
+{
+ struct stat buf;
+
+ return (stat (path, &buf) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Splits path extracting vfs part.
+ *
+ * Splits path
+ * \verbatim /p1#op/inpath \endverbatim
+ * into
+ * \verbatim inpath,op; \endverbatim
+ * returns which vfs it is.
+ * What is left in path is p1. You still want to g_free(path), you DON'T
+ * want to free neither *inpath nor *op
+ */
+
+static struct vfs_class *
+_vfs_split_with_semi_skip_count (char *path, const char **inpath, const char **op,
+ size_t skip_count)
+{
+ char *semi;
+ char *slash;
+ struct vfs_class *ret;
+
+ if (path == NULL)
+ vfs_die ("Cannot split NULL");
+
+ semi = strrstr_skip_count (path, "#", skip_count);
+
+ if ((semi == NULL) || (!path_magic (path)))
+ return NULL;
+
+ slash = strchr (semi, PATH_SEP);
+ *semi = '\0';
+
+ if (op != NULL)
+ *op = NULL;
+
+ if (inpath != NULL)
+ *inpath = NULL;
+
+ if (slash != NULL)
+ *slash = '\0';
+
+ ret = vfs_prefix_to_class (semi + 1);
+ if (ret != NULL)
+ {
+ if (op != NULL)
+ *op = semi + 1;
+ if (inpath != NULL)
+ *inpath = slash != NULL ? slash + 1 : NULL;
+ return ret;
+ }
+
+ if (slash != NULL)
+ *slash = PATH_SEP;
+
+ *semi = '#';
+ ret = _vfs_split_with_semi_skip_count (path, inpath, op, skip_count + 1);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * remove //, /./ and /../
+ *
+ * @return newly allocated string
+ */
+
+static char *
+vfs_canon (const char *path)
+{
+ char *result;
+
+ if (path == NULL)
+ vfs_die ("Cannot canonicalize NULL");
+
+ if (!IS_PATH_SEP (*path))
+ {
+ /* Relative to current directory */
+
+ char *local;
+
+ if (g_str_has_prefix (path, VFS_ENCODING_PREFIX))
+ {
+ /*
+ encoding prefix placed at start of string without the leading slash
+ should be autofixed by adding the leading slash
+ */
+ local = mc_build_filename (PATH_SEP_STR, path, (char *) NULL);
+ }
+ else
+ {
+ const char *curr_dir;
+
+ curr_dir = vfs_get_current_dir ();
+ local = mc_build_filename (curr_dir, path, (char *) NULL);
+ }
+ result = vfs_canon (local);
+ g_free (local);
+ }
+ else
+ {
+ /* Absolute path */
+
+ result = g_strdup (path);
+ canonicalize_pathname (result);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+/** get encoding after last #enc: or NULL, if part does not contain #enc:
+ *
+ * @param path null-terminated string
+ * @param len the maximum length of path, where #enc: should be searched
+ *
+ * @return newly allocated string.
+ */
+
+static char *
+vfs_get_encoding (const char *path, ssize_t len)
+{
+ char *semi;
+
+ /* try found #enc: */
+ semi = g_strrstr_len (path, len, VFS_ENCODING_PREFIX);
+ if (semi == NULL)
+ return NULL;
+
+ if (semi == path || IS_PATH_SEP (semi[-1]))
+ {
+ char *slash;
+
+ semi += strlen (VFS_ENCODING_PREFIX); /* skip "#enc:" */
+ slash = strchr (semi, PATH_SEP);
+ if (slash != NULL)
+ return g_strndup (semi, slash - semi);
+ return g_strdup (semi);
+ }
+
+ return vfs_get_encoding (path, semi - path);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the hostname and username from the path
+ *
+ * Format of the path is [user@]hostname:port/remote-dir, e.g.:
+ *
+ * ftp://sunsite.unc.edu/pub/linux
+ * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
+ * ftp://tsx-11.mit.edu:8192/
+ * ftp://joe@foo.edu:11321/private
+ * ftp://joe:password@foo.se
+ *
+ * @param path_element is an input string to be parsed
+ * @param path is an input string to be parsed
+ *
+ * @return g_malloc()ed url info.
+ * If the user is empty, e.g. ftp://@roxanne/private, and URL_USE_ANONYMOUS
+ * is not set, then the current login name is supplied.
+ * Return value is a g_malloc()ed structure with the pathname relative to the
+ * host.
+ */
+
+static void
+vfs_path_url_split (vfs_path_element_t * path_element, const char *path)
+{
+ char *pcopy;
+ char *colon, *at, *rest;
+
+ path_element->port = 0;
+
+ pcopy = g_strdup (path);
+
+ /* search for any possible user */
+ at = strrchr (pcopy, '@');
+
+ /* We have a username */
+ if (at == NULL)
+ rest = pcopy;
+ else
+ {
+ const char *pend;
+ char *inner_colon;
+
+ pend = strchr (at, '\0');
+ *at = '\0';
+
+ inner_colon = strchr (pcopy, ':');
+ if (inner_colon != NULL)
+ {
+ *inner_colon = '\0';
+ inner_colon++;
+ path_element->password = g_strdup (inner_colon);
+ }
+
+ if (*pcopy != '\0')
+ path_element->user = g_strdup (pcopy);
+
+ if (pend == at + 1)
+ rest = at;
+ else
+ rest = at + 1;
+ }
+
+ /* Check if the host comes with a port spec, if so, chop it */
+ if (*rest != '[')
+ colon = strchr (rest, ':');
+ else
+ {
+ colon = strchr (++rest, ']');
+ if (colon != NULL)
+ {
+ *colon = '\0';
+ colon++;
+ *colon = '\0';
+ path_element->ipv6 = TRUE;
+ }
+ }
+
+ if (colon != NULL)
+ {
+ *colon = '\0';
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (colon + 1, "%d", &path_element->port) == 1)
+ {
+ if (path_element->port <= 0 || path_element->port >= 65536)
+ path_element->port = 0;
+ }
+ else
+ while (*(++colon) != '\0')
+ {
+ switch (*colon)
+ {
+ case 'C':
+ path_element->port = 1;
+ break;
+ case 'r':
+ path_element->port = 2;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ path_element->host = g_strdup (rest);
+ g_free (pcopy);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * get VFS class for the given name
+ *
+ * @param class_name name of class
+ *
+ * @return pointer to class structure or NULL if class not found
+ */
+
+static struct vfs_class *
+vfs_get_class_by_name (const char *class_name)
+{
+ guint i;
+
+ if (class_name == NULL)
+ return NULL;
+
+ for (i = 0; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+ if ((vfs->name != NULL) && (strcmp (vfs->name, class_name) == 0))
+ return vfs;
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if path string contain URL-like elements
+ *
+ * @param path_str path
+ *
+ * @return TRUE if path is deprecated or FALSE otherwise
+ */
+
+static gboolean
+vfs_path_is_str_path_deprecated (const char *path_str)
+{
+ return strstr (path_str, VFS_PATH_URL_DELIMITER) == NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Split path string to path elements by deprecated algorithm.
+ *
+ * @param path_str VFS-path
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+*/
+
+static vfs_path_t *
+vfs_path_from_str_deprecated_parser (char *path)
+{
+ vfs_path_t *vpath;
+ vfs_path_element_t *element;
+ struct vfs_class *class;
+ const char *local, *op;
+
+ vpath = vfs_path_new (FALSE);
+
+ while ((class = _vfs_split_with_semi_skip_count (path, &local, &op, 0)) != NULL)
+ {
+ char *url_params;
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = class;
+ if (local == NULL)
+ local = "";
+ element->path = vfs_translate_path_n (local);
+
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (local, -1);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+
+ url_params = strchr (op, ':'); /* skip VFS prefix */
+ if (url_params != NULL)
+ {
+ *url_params = '\0';
+ url_params++;
+ vfs_path_url_split (element, url_params);
+ }
+
+ if (*op != '\0')
+ element->vfs_prefix = g_strdup (op);
+
+ g_array_prepend_val (vpath->path, element);
+ }
+ if (path[0] != '\0')
+ {
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = g_ptr_array_index (vfs__classes_list, 0);
+ element->path = vfs_translate_path_n (path);
+
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (path, -1);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+ g_array_prepend_val (vpath->path, element);
+ }
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Split path string to path elements by URL algorithm.
+ *
+ * @param path_str VFS-path
+ * @param flags flags for converter
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+*/
+
+static vfs_path_t *
+vfs_path_from_str_uri_parser (char *path)
+{
+ vfs_path_t *vpath;
+ vfs_path_element_t *element;
+ char *url_delimiter;
+
+ vpath = vfs_path_new (path != NULL && !IS_PATH_SEP (*path));
+
+ while ((url_delimiter = g_strrstr (path, VFS_PATH_URL_DELIMITER)) != NULL)
+ {
+ char *vfs_prefix_start;
+ char *real_vfs_prefix_start = url_delimiter;
+
+ while (real_vfs_prefix_start > path && !IS_PATH_SEP (*real_vfs_prefix_start))
+ real_vfs_prefix_start--;
+ vfs_prefix_start = real_vfs_prefix_start;
+
+ if (IS_PATH_SEP (*vfs_prefix_start))
+ vfs_prefix_start += 1;
+
+ *url_delimiter = '\0';
+
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = vfs_prefix_to_class (vfs_prefix_start);
+ element->vfs_prefix = g_strdup (vfs_prefix_start);
+
+ url_delimiter += strlen (VFS_PATH_URL_DELIMITER);
+
+ if (element->class != NULL && (element->class->flags & VFSF_REMOTE) != 0)
+ {
+ char *slash_pointer;
+
+ slash_pointer = strchr (url_delimiter, PATH_SEP);
+ if (slash_pointer == NULL)
+ {
+ element->path = g_strdup ("");
+ }
+ else
+ {
+ element->path = vfs_translate_path_n (slash_pointer + 1);
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (slash_pointer, -1);
+#endif
+ *slash_pointer = '\0';
+ }
+ vfs_path_url_split (element, url_delimiter);
+ }
+ else
+ {
+ element->path = vfs_translate_path_n (url_delimiter);
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (url_delimiter, -1);
+#endif
+ }
+#ifdef HAVE_CHARSET
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+ g_array_prepend_val (vpath->path, element);
+
+ if ((real_vfs_prefix_start > path && IS_PATH_SEP (*real_vfs_prefix_start)) ||
+ (real_vfs_prefix_start == path && !IS_PATH_SEP (*real_vfs_prefix_start)))
+ *real_vfs_prefix_start = '\0';
+ else
+ *(real_vfs_prefix_start + 1) = '\0';
+ }
+
+ if (path[0] != '\0')
+ {
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = g_ptr_array_index (vfs__classes_list, 0);
+ element->path = vfs_translate_path_n (path);
+#ifdef HAVE_CHARSET
+ element->encoding = vfs_get_encoding (path, -1);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+ g_array_prepend_val (vpath->path, element);
+ }
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Add element's class info to result string (such as VFS name, host, encoding etc)
+ * This function used as helper only in vfs_path_tokens_get() function
+ *
+ * @param element current path element
+ * @param ret_tokens total tikens for return
+ * @param element_tokens accumulated element-only tokens
+ */
+
+static void
+vfs_path_tokens_add_class_info (const vfs_path_element_t * element, GString * ret_tokens,
+ GString * element_tokens)
+{
+ if (((element->class->flags & VFSF_LOCAL) == 0 || ret_tokens->len > 0)
+ && element_tokens->len > 0)
+ {
+ GString *url_str;
+
+ if (ret_tokens->len > 0 && !IS_PATH_SEP (ret_tokens->str[ret_tokens->len - 1]))
+ g_string_append_c (ret_tokens, PATH_SEP);
+
+ g_string_append (ret_tokens, element->vfs_prefix);
+ g_string_append (ret_tokens, VFS_PATH_URL_DELIMITER);
+
+ url_str = vfs_path_build_url_params_str (element, TRUE);
+ if (url_str->len != 0)
+ {
+ g_string_append_len (ret_tokens, url_str->str, url_str->len);
+ g_string_append_c (ret_tokens, PATH_SEP);
+ }
+
+ g_string_free (url_str, TRUE);
+ }
+
+#ifdef HAVE_CHARSET
+ if (element->encoding != NULL)
+ {
+ if (ret_tokens->len > 0 && !IS_PATH_SEP (ret_tokens->str[ret_tokens->len - 1]))
+ g_string_append (ret_tokens, PATH_SEP_STR);
+ g_string_append (ret_tokens, VFS_ENCODING_PREFIX);
+ g_string_append (ret_tokens, element->encoding);
+ g_string_append (ret_tokens, PATH_SEP_STR);
+ }
+#endif
+
+ g_string_append (ret_tokens, element_tokens->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Strip path to home dir.
+ * @param dir pointer to string contains full path
+ */
+
+static char *
+vfs_path_strip_home (const char *dir)
+{
+ const char *home_dir = mc_config_get_home_dir ();
+
+ if (home_dir != NULL)
+ {
+ size_t len;
+
+ len = strlen (home_dir);
+
+ if (strncmp (dir, home_dir, len) == 0 && (IS_PATH_SEP (dir[len]) || dir[len] == '\0'))
+ return g_strdup_printf ("~%s", dir + len);
+ }
+
+ return g_strdup (dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#define vfs_append_from_path(appendfrom, is_relative) \
+{ \
+ if ((flags & VPF_STRIP_HOME) && element_index == 0 && \
+ (element->class->flags & VFSF_LOCAL) != 0) \
+ { \
+ char *stripped_home_str; \
+ stripped_home_str = vfs_path_strip_home (appendfrom); \
+ g_string_append (buffer, stripped_home_str); \
+ g_free (stripped_home_str); \
+ } \
+ else \
+ { \
+ if (!is_relative && !IS_PATH_SEP (*appendfrom) && *appendfrom != '\0' \
+ && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1]))) \
+ g_string_append_c (buffer, PATH_SEP); \
+ g_string_append (buffer, appendfrom); \
+ } \
+}
+
+/**
+ * Convert first elements_count elements from vfs_path_t to string representation with flags.
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param elements_count count of first elements for convert
+ * @param flags for converter
+ *
+ * @return pointer to newly created string.
+ */
+
+char *
+vfs_path_to_str_flags (const vfs_path_t * vpath, int elements_count, vfs_path_flag_t flags)
+{
+ int element_index;
+ GString *buffer;
+#ifdef HAVE_CHARSET
+ GString *recode_buffer = NULL;
+#endif
+
+ if (vpath == NULL)
+ return NULL;
+
+ if (elements_count == 0 || elements_count > vfs_path_elements_count (vpath))
+ elements_count = vfs_path_elements_count (vpath);
+
+ if (elements_count < 0)
+ elements_count = vfs_path_elements_count (vpath) + elements_count;
+
+ buffer = g_string_new ("");
+
+ for (element_index = 0; element_index < elements_count; element_index++)
+ {
+ const vfs_path_element_t *element;
+ gboolean is_relative = vpath->relative && (element_index == 0);
+
+ element = vfs_path_get_by_index (vpath, element_index);
+ if (element->vfs_prefix != NULL)
+ {
+ GString *url_str;
+
+ if (!is_relative && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1])))
+ g_string_append_c (buffer, PATH_SEP);
+
+ g_string_append (buffer, element->vfs_prefix);
+ g_string_append (buffer, VFS_PATH_URL_DELIMITER);
+
+ url_str = vfs_path_build_url_params_str (element, !(flags & VPF_STRIP_PASSWORD));
+ if (url_str->len != 0)
+ {
+ g_string_append_len (buffer, url_str->str, url_str->len);
+ g_string_append_c (buffer, PATH_SEP);
+ }
+
+ g_string_free (url_str, TRUE);
+ }
+
+#ifdef HAVE_CHARSET
+ if ((flags & VPF_RECODE) == 0 && vfs_path_element_need_cleanup_converter (element))
+ {
+ if ((flags & VPF_HIDE_CHARSET) == 0)
+ {
+ if ((!is_relative)
+ && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1])))
+ g_string_append (buffer, PATH_SEP_STR);
+ g_string_append (buffer, VFS_ENCODING_PREFIX);
+ g_string_append (buffer, element->encoding);
+ }
+
+ if (recode_buffer == NULL)
+ recode_buffer = g_string_sized_new (32);
+ else
+ g_string_set_size (recode_buffer, 0);
+
+ str_vfs_convert_from (element->dir.converter, element->path, recode_buffer);
+ vfs_append_from_path (recode_buffer->str, is_relative);
+ }
+ else
+#endif
+ {
+ vfs_append_from_path (element->path, is_relative);
+ }
+ }
+
+#ifdef HAVE_CHARSET
+ if (recode_buffer != NULL)
+ g_string_free (recode_buffer, TRUE);
+#endif
+
+ return g_string_free (buffer, FALSE);
+}
+
+#undef vfs_append_from_path
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert first elements_count elements from vfs_path_t to string representation.
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param elements_count count of first elements for convert
+ *
+ * @return pointer to newly created string.
+ */
+
+char *
+vfs_path_to_str_elements_count (const vfs_path_t * vpath, int elements_count)
+{
+ return vfs_path_to_str_flags (vpath, elements_count, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Split path string to path elements with flags for change parce process.
+ *
+ * @param path_str VFS-path
+ * @param flags flags for parser
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+ */
+
+vfs_path_t *
+vfs_path_from_str_flags (const char *path_str, vfs_path_flag_t flags)
+{
+ vfs_path_t *vpath;
+ char *path;
+
+ if (path_str == NULL)
+ return NULL;
+
+ if ((flags & VPF_NO_CANON) == 0)
+ path = vfs_canon (path_str);
+ else
+ path = g_strdup (path_str);
+
+ if (path == NULL)
+ return NULL;
+
+ if ((flags & VPF_USE_DEPRECATED_PARSER) != 0 && vfs_path_is_str_path_deprecated (path))
+ vpath = vfs_path_from_str_deprecated_parser (path);
+ else
+ vpath = vfs_path_from_str_uri_parser (path);
+
+ vpath->str = vfs_path_to_str_flags (vpath, 0, flags);
+ g_free (path);
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Split path string to path elements.
+ *
+ * @param path_str VFS-path
+ *
+ * @return pointer to newly created vfs_path_t object with filled path elements array.
+ */
+
+vfs_path_t *
+vfs_path_from_str (const char *path_str)
+{
+ return vfs_path_from_str_flags (path_str, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Create new vfs_path_t object.
+ *
+ * @return pointer to newly created vfs_path_t object.
+ */
+
+vfs_path_t *
+vfs_path_new (gboolean relative)
+{
+ vfs_path_t *vpath;
+
+ vpath = g_new0 (vfs_path_t, 1);
+ vpath->path = g_array_new (FALSE, TRUE, sizeof (vfs_path_element_t *));
+ vpath->relative = relative;
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Get count of path elements.
+ *
+ * @param vpath pointer to vfs_path_t object
+ *
+ * @return count of path elements.
+ */
+
+int
+vfs_path_elements_count (const vfs_path_t * vpath)
+{
+ return (vpath != NULL && vpath->path != NULL) ? vpath->path->len : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Add vfs_path_element_t object to end of list in vfs_path_t object
+ * @param vpath pointer to vfs_path_t object
+ * @param path_element pointer to vfs_path_element_t object
+ */
+
+void
+vfs_path_add_element (vfs_path_t * vpath, const vfs_path_element_t * path_element)
+{
+ g_array_append_val (vpath->path, path_element);
+ g_free (vpath->str);
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Get one path element by index.
+ *
+ * @param vpath pointer to vfs_path_t object.
+ * May be NULL. In this case NULL is returned and errno set to 0.
+ * @param element_index element index. May have negative value (in this case count was started at
+ * the end of list). If @element_index is out of range, NULL is returned and
+ * errno set to EINVAL.
+ *
+ * @return path element
+ */
+
+const vfs_path_element_t *
+vfs_path_get_by_index (const vfs_path_t * vpath, int element_index)
+{
+ int n;
+
+ if (vpath == NULL)
+ {
+ errno = 0;
+ return NULL;
+ }
+
+ n = vfs_path_elements_count (vpath);
+
+ if (element_index < 0)
+ element_index += n;
+
+ if (element_index < 0 || element_index > n)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ return g_array_index (vpath->path, vfs_path_element_t *, element_index);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Clone one path element
+ *
+ * @param element pointer to vfs_path_element_t object
+ *
+ * @return Newly allocated path element
+ */
+
+vfs_path_element_t *
+vfs_path_element_clone (const vfs_path_element_t * element)
+{
+ vfs_path_element_t *new_element = g_new (vfs_path_element_t, 1);
+
+ new_element->user = g_strdup (element->user);
+ new_element->password = g_strdup (element->password);
+ new_element->host = g_strdup (element->host);
+ new_element->ipv6 = element->ipv6;
+ new_element->port = element->port;
+ new_element->path = g_strdup (element->path);
+ new_element->class = element->class;
+ new_element->vfs_prefix = g_strdup (element->vfs_prefix);
+#ifdef HAVE_CHARSET
+ new_element->encoding = g_strdup (element->encoding);
+ if (vfs_path_element_need_cleanup_converter (element) && new_element->encoding != NULL)
+ new_element->dir.converter = str_crt_conv_from (new_element->encoding);
+ else
+ new_element->dir.converter = element->dir.converter;
+#endif
+ new_element->dir.info = element->dir.info;
+
+ return new_element;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Free one path element.
+ *
+ * @param element pointer to vfs_path_element_t object
+ *
+ */
+
+void
+vfs_path_element_free (vfs_path_element_t * element)
+{
+ if (element == NULL)
+ return;
+
+ g_free (element->user);
+ g_free (element->password);
+ g_free (element->host);
+ g_free (element->path);
+ g_free (element->vfs_prefix);
+
+#ifdef HAVE_CHARSET
+ g_free (element->encoding);
+
+ if (vfs_path_element_need_cleanup_converter (element))
+ str_close_conv (element->dir.converter);
+#endif
+
+ g_free (element);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Clone path
+ *
+ * @param vpath pointer to vfs_path_t object
+ *
+ * @return Newly allocated path object
+ */
+
+vfs_path_t *
+vfs_path_clone (const vfs_path_t * vpath)
+{
+ vfs_path_t *new_vpath;
+ int vpath_element_index;
+
+ if (vpath == NULL)
+ return NULL;
+
+ new_vpath = vfs_path_new (vpath->relative);
+
+ for (vpath_element_index = 0; vpath_element_index < vfs_path_elements_count (vpath);
+ vpath_element_index++)
+ {
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (vpath, vpath_element_index));
+ g_array_append_val (new_vpath->path, path_element);
+ }
+ new_vpath->str = g_strdup (vpath->str);
+
+ return new_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Free vfs_path_t object.
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param free_str if TRUE the string representation of vpath is freed as well
+ *
+ * @return the string representation of vpath (i.e. NULL if free_str is TRUE)
+ */
+
+char *
+vfs_path_free (vfs_path_t * vpath, gboolean free_str)
+{
+ int vpath_element_index;
+ char *ret;
+
+ if (vpath == NULL)
+ return NULL;
+
+ for (vpath_element_index = 0; vpath_element_index < vfs_path_elements_count (vpath);
+ vpath_element_index++)
+ {
+ vfs_path_element_t *path_element;
+
+ path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, vpath_element_index);
+ vfs_path_element_free (path_element);
+ }
+
+ g_array_free (vpath->path, TRUE);
+
+ if (!free_str)
+ ret = vpath->str;
+ else
+ {
+ g_free (vpath->str);
+ ret = NULL;
+ }
+
+ g_free (vpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Remove one path element by index
+ *
+ * @param vpath pointer to vfs_path_t object
+ * @param element_index element index. May have negative value (in this case count was started at the end of list).
+ *
+ */
+
+void
+vfs_path_remove_element_by_index (vfs_path_t * vpath, int element_index)
+{
+ vfs_path_element_t *element;
+
+ if ((vpath == NULL) || (vfs_path_elements_count (vpath) == 1))
+ return;
+
+ if (element_index < 0)
+ element_index = vfs_path_elements_count (vpath) + element_index;
+
+ element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, element_index);
+ vpath->path = g_array_remove_index (vpath->path, element_index);
+ vfs_path_element_free (element);
+ g_free (vpath->str);
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return VFS class for the given prefix */
+
+struct vfs_class *
+vfs_prefix_to_class (const char *prefix)
+{
+ guint i;
+
+ /* Avoid first class (localfs) that would accept any prefix */
+ for (i = 1; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs;
+
+ vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+ if (vfs->which != NULL)
+ {
+ if (vfs->which (vfs, prefix) == -1)
+ continue;
+ return vfs;
+ }
+
+ if (vfs->prefix != NULL && strncmp (prefix, vfs->prefix, strlen (vfs->prefix)) == 0)
+ return vfs;
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+
+/**
+ * Check if need cleanup charset converter for vfs_path_element_t
+ *
+ * @param element part of path
+ *
+ * @return TRUE if need cleanup converter or FALSE otherwise
+ */
+
+gboolean
+vfs_path_element_need_cleanup_converter (const vfs_path_element_t * element)
+{
+ return (element->dir.converter != str_cnv_from_term && element->dir.converter != INVALID_CONV);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change encoding for last part (vfs_path_element_t) of vpath
+ *
+ * @param vpath pointer to path structure
+ * encoding name of charset
+ *
+ * @return pointer to path structure (for use function in another functions)
+ */
+vfs_path_t *
+vfs_path_change_encoding (vfs_path_t * vpath, const char *encoding)
+{
+ vfs_path_element_t *path_element;
+
+ path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, -1);
+ /* don't add current encoding */
+ if ((path_element->encoding != NULL) && (strcmp (encoding, path_element->encoding) == 0))
+ return vpath;
+
+ g_free (path_element->encoding);
+ path_element->encoding = g_strdup (encoding);
+
+ if (vfs_path_element_need_cleanup_converter (path_element))
+ str_close_conv (path_element->dir.converter);
+
+ path_element->dir.converter = str_crt_conv_from (path_element->encoding);
+
+ g_free (vpath->str);
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+ return vpath;
+}
+
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Serialize vfs_path_t object to string
+ *
+ * @param vpath data for serialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return serialized vpath as newly allocated string
+ */
+
+char *
+vfs_path_serialize (const vfs_path_t * vpath, GError ** mcerror)
+{
+ mc_config_t *cpath;
+ ssize_t element_index;
+ char *ret_value;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if ((vpath == NULL) || (vfs_path_elements_count (vpath) == 0))
+ {
+ mc_propagate_error (mcerror, 0, "%s", "vpath object is empty");
+ return NULL;
+ }
+
+ cpath = mc_config_init (NULL, FALSE);
+
+ for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
+ {
+ char groupname[BUF_TINY];
+ const vfs_path_element_t *element;
+
+ g_snprintf (groupname, sizeof (groupname), "path-element-%zd", element_index);
+ element = vfs_path_get_by_index (vpath, element_index);
+ /* convert one element to config group */
+
+ mc_config_set_string_raw (cpath, groupname, "path", element->path);
+ mc_config_set_string_raw (cpath, groupname, "class-name", element->class->name);
+#ifdef HAVE_CHARSET
+ mc_config_set_string_raw (cpath, groupname, "encoding", element->encoding);
+#endif
+ mc_config_set_string_raw (cpath, groupname, "vfs_prefix", element->vfs_prefix);
+
+ mc_config_set_string_raw (cpath, groupname, "user", element->user);
+ mc_config_set_string_raw (cpath, groupname, "password", element->password);
+ mc_config_set_string_raw (cpath, groupname, "host", element->host);
+ if (element->port != 0)
+ mc_config_set_int (cpath, groupname, "port", element->port);
+ }
+
+ ret_value = mc_serialize_config (cpath, mcerror);
+ mc_config_deinit (cpath);
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deserialize string to vfs_path_t object
+ *
+ * @param data data for serialization
+ * @param error contain pointer to object for handle error code and message
+ *
+ * @return newly allocated vfs_path_t object
+ */
+
+vfs_path_t *
+vfs_path_deserialize (const char *data, GError ** mcerror)
+{
+ mc_config_t *cpath;
+ size_t element_index;
+ vfs_path_t *vpath;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ cpath = mc_deserialize_config (data, mcerror);
+ if (cpath == NULL)
+ return NULL;
+
+ vpath = vfs_path_new (FALSE);
+
+ for (element_index = 0;; element_index++)
+ {
+ struct vfs_class *eclass;
+ vfs_path_element_t *element;
+ char *cfg_value;
+ char groupname[BUF_TINY];
+
+ g_snprintf (groupname, sizeof (groupname), "path-element-%zu", element_index);
+ if (!mc_config_has_group (cpath, groupname))
+ break;
+
+ cfg_value = mc_config_get_string_raw (cpath, groupname, "class-name", NULL);
+ eclass = vfs_get_class_by_name (cfg_value);
+ if (eclass == NULL)
+ {
+ vfs_path_free (vpath, TRUE);
+ g_set_error (mcerror, MC_ERROR, 0, "Unable to find VFS class by name '%s'", cfg_value);
+ g_free (cfg_value);
+ mc_config_deinit (cpath);
+ return NULL;
+ }
+ g_free (cfg_value);
+
+ element = g_new0 (vfs_path_element_t, 1);
+ element->class = eclass;
+ element->path = mc_config_get_string_raw (cpath, groupname, "path", NULL);
+
+#ifdef HAVE_CHARSET
+ element->encoding = mc_config_get_string_raw (cpath, groupname, "encoding", NULL);
+ element->dir.converter =
+ (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
+#endif
+
+ element->vfs_prefix = mc_config_get_string_raw (cpath, groupname, "vfs_prefix", NULL);
+
+ element->user = mc_config_get_string_raw (cpath, groupname, "user", NULL);
+ element->password = mc_config_get_string_raw (cpath, groupname, "password", NULL);
+ element->host = mc_config_get_string_raw (cpath, groupname, "host", NULL);
+ element->port = mc_config_get_int (cpath, groupname, "port", 0);
+
+ vpath->path = g_array_append_val (vpath->path, element);
+ }
+
+ mc_config_deinit (cpath);
+ if (vfs_path_elements_count (vpath) == 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ g_set_error (mcerror, MC_ERROR, 0, "No any path elements found");
+ return NULL;
+ }
+ vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
+
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Build vfs_path_t object from arguments.
+ *
+ * @param first_element of path
+ * @param ... path tokens, terminated by NULL
+ *
+ * @return newly allocated vfs_path_t object
+ */
+
+vfs_path_t *
+vfs_path_build_filename (const char *first_element, ...)
+{
+ va_list args;
+ char *str_path;
+ vfs_path_t *vpath;
+
+ if (first_element == NULL)
+ return NULL;
+
+ va_start (args, first_element);
+ str_path = mc_build_filenamev (first_element, args);
+ va_end (args);
+ vpath = vfs_path_from_str (str_path);
+ g_free (str_path);
+ return vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Append tokens to path object
+ *
+ * @param vpath path object
+ * @param first_element of path
+ * @param ... NULL-terminated strings
+ *
+ * @return newly allocated path object
+ */
+
+vfs_path_t *
+vfs_path_append_new (const vfs_path_t * vpath, const char *first_element, ...)
+{
+ va_list args;
+ char *str_path;
+ const char *result_str;
+ vfs_path_t *ret_vpath;
+
+ if (vpath == NULL || first_element == NULL)
+ return NULL;
+
+ va_start (args, first_element);
+ str_path = mc_build_filenamev (first_element, args);
+ va_end (args);
+
+ result_str = vfs_path_as_str (vpath);
+ ret_vpath = vfs_path_build_filename (result_str, str_path, (char *) NULL);
+ g_free (str_path);
+
+ return ret_vpath;
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Append vpath_t tokens to path object
+ *
+ * @param first_vpath vpath objects
+ * @param ... NULL-terminated vpath objects
+ *
+ * @return newly allocated path object
+ */
+
+vfs_path_t *
+vfs_path_append_vpath_new (const vfs_path_t * first_vpath, ...)
+{
+ va_list args;
+ vfs_path_t *ret_vpath;
+ const vfs_path_t *current_vpath = first_vpath;
+
+ if (first_vpath == NULL)
+ return NULL;
+
+ ret_vpath = vfs_path_new (FALSE);
+
+ va_start (args, first_vpath);
+ do
+ {
+ int vindex;
+
+ for (vindex = 0; vindex < vfs_path_elements_count (current_vpath); vindex++)
+ {
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (current_vpath, vindex));
+ g_array_append_val (ret_vpath->path, path_element);
+ }
+ current_vpath = va_arg (args, const vfs_path_t *);
+ }
+ while (current_vpath != NULL);
+ va_end (args);
+
+ ret_vpath->str = vfs_path_to_str_flags (ret_vpath, 0, VPF_NONE);
+
+ return ret_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * get tockens count in path.
+ *
+ * @param vpath path object
+ *
+ * @return count of tokens
+ */
+
+size_t
+vfs_path_tokens_count (const vfs_path_t * vpath)
+{
+ size_t count_tokens = 0;
+ int element_index;
+
+ if (vpath == NULL)
+ return 0;
+
+ for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
+ {
+ const vfs_path_element_t *element;
+ const char *token, *prev_token;
+
+ element = vfs_path_get_by_index (vpath, element_index);
+
+ for (prev_token = element->path; (token = strchr (prev_token, PATH_SEP)) != NULL;
+ prev_token = token + 1)
+ {
+ /* skip empty substring */
+ if (token != prev_token)
+ count_tokens++;
+ }
+
+ if (*prev_token != '\0')
+ count_tokens++;
+ }
+
+ return count_tokens;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get subpath by tokens
+ *
+ * @param vpath path object
+ * @param start_position first token for got/ Started from 0.
+ * If negative, then position will be relative to end of path
+ * @param length count of tokens
+ *
+ * @return newly allocated string with path tokens separated by slash
+ */
+
+char *
+vfs_path_tokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length)
+{
+ GString *ret_tokens, *element_tokens;
+ int element_index;
+ size_t tokens_count = vfs_path_tokens_count (vpath);
+
+ if (vpath == NULL)
+ return NULL;
+
+ if (length == 0)
+ length = tokens_count;
+
+ if (length < 0)
+ length = tokens_count + length;
+
+ if (start_position < 0)
+ start_position = (ssize_t) tokens_count + start_position;
+
+ if (start_position < 0)
+ return NULL;
+
+ if (start_position >= (ssize_t) tokens_count)
+ return NULL;
+
+ if (start_position + (ssize_t) length > (ssize_t) tokens_count)
+ length = tokens_count - start_position;
+
+ ret_tokens = g_string_sized_new (32);
+ element_tokens = g_string_sized_new (32);
+
+ for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
+ {
+ const vfs_path_element_t *element;
+ char **path_tokens, **iterator;
+
+ g_string_assign (element_tokens, "");
+ element = vfs_path_get_by_index (vpath, element_index);
+ path_tokens = g_strsplit (element->path, PATH_SEP_STR, -1);
+
+ for (iterator = path_tokens; *iterator != NULL; iterator++)
+ {
+ if (**iterator != '\0')
+ {
+ if (start_position == 0)
+ {
+ if (length == 0)
+ {
+ vfs_path_tokens_add_class_info (element, ret_tokens, element_tokens);
+ g_string_free (element_tokens, TRUE);
+ g_strfreev (path_tokens);
+ return g_string_free (ret_tokens, FALSE);
+ }
+ length--;
+ if (element_tokens->len != 0)
+ g_string_append_c (element_tokens, PATH_SEP);
+ g_string_append (element_tokens, *iterator);
+ }
+ else
+ start_position--;
+ }
+ }
+ g_strfreev (path_tokens);
+ vfs_path_tokens_add_class_info (element, ret_tokens, element_tokens);
+ }
+
+ g_string_free (element_tokens, TRUE);
+ return g_string_free (ret_tokens, !(start_position == 0 && length == 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get subpath by tokens
+ *
+ * @param vpath path object
+ * @param start_position first token for got/ Started from 0.
+ * If negative, then position will be relative to end of path
+ * @param length count of tokens
+ *
+ * @return newly allocated path object with path tokens separated by slash
+ */
+
+vfs_path_t *
+vfs_path_vtokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length)
+{
+ char *str_tokens;
+ vfs_path_t *ret_vpath = NULL;
+
+ str_tokens = vfs_path_tokens_get (vpath, start_position, length);
+ if (str_tokens != NULL)
+ {
+ ret_vpath = vfs_path_from_str_flags (str_tokens, VPF_NO_CANON);
+ g_free (str_tokens);
+ }
+ return ret_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Build URL parameters (such as user:pass @ host:port) from one path element object
+ *
+ * @param element path element
+ * @param keep_password TRUE or FALSE
+ *
+ * @return newly allocated string
+ */
+
+GString *
+vfs_path_build_url_params_str (const vfs_path_element_t * element, gboolean keep_password)
+{
+ GString *buffer;
+
+ if (element == NULL)
+ return NULL;
+
+ buffer = g_string_sized_new (64);
+
+ if (element->user != NULL)
+ g_string_append (buffer, element->user);
+
+ if (element->password != NULL && keep_password)
+ {
+ g_string_append_c (buffer, ':');
+ g_string_append (buffer, element->password);
+ }
+
+ if (element->host != NULL)
+ {
+ if ((element->user != NULL) || (element->password != NULL))
+ g_string_append_c (buffer, '@');
+ if (element->ipv6)
+ g_string_append_c (buffer, '[');
+ g_string_append (buffer, element->host);
+ if (element->ipv6)
+ g_string_append_c (buffer, ']');
+ }
+
+ if ((element->port) != 0 && (element->host != NULL))
+ {
+ g_string_append_c (buffer, ':');
+ g_string_append_printf (buffer, "%d", element->port);
+ }
+
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Build pretty string representation of one path_element_t object
+ *
+ * @param element path element
+ *
+ * @return newly allocated string
+ */
+
+GString *
+vfs_path_element_build_pretty_path_str (const vfs_path_element_t * element)
+{
+ GString *url_params, *pretty_path;
+
+ pretty_path = g_string_new (element->class->prefix);
+ g_string_append (pretty_path, VFS_PATH_URL_DELIMITER);
+
+ url_params = vfs_path_build_url_params_str (element, FALSE);
+ g_string_append_len (pretty_path, url_params->str, url_params->len);
+ g_string_free (url_params, TRUE);
+
+ if (!IS_PATH_SEP (*element->path))
+ g_string_append_c (pretty_path, PATH_SEP);
+
+ g_string_append (pretty_path, element->path);
+ return pretty_path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Compare two path objects as strings
+ *
+ * @param vpath1 first path object
+ * @param vpath2 second vpath object
+ *
+ * @return integer value like to strcmp.
+ */
+
+gboolean
+vfs_path_equal (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+ gboolean ret_val;
+
+ if (vpath1 == NULL || vpath2 == NULL)
+ return FALSE;
+
+ path1 = vfs_path_as_str (vpath1);
+ path2 = vfs_path_as_str (vpath2);
+
+ ret_val = strcmp (path1, path2) == 0;
+
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Compare two path objects as strings
+ *
+ * @param vpath1 first path object
+ * @param vpath2 second vpath object
+ * @param len number of first 'len' characters
+ *
+ * @return integer value like to strcmp.
+ */
+
+gboolean
+vfs_path_equal_len (const vfs_path_t * vpath1, const vfs_path_t * vpath2, size_t len)
+{
+ const char *path1, *path2;
+ gboolean ret_val;
+
+ if (vpath1 == NULL || vpath2 == NULL)
+ return FALSE;
+
+ path1 = vfs_path_as_str (vpath1);
+ path2 = vfs_path_as_str (vpath2);
+
+ ret_val = strncmp (path1, path2, len) == 0;
+
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate path length in string representation
+ *
+ * @param vpath path object
+ *
+ * @return length of path
+ */
+
+size_t
+vfs_path_len (const vfs_path_t * vpath)
+{
+ if (vpath == NULL)
+ return 0;
+
+ return strlen (vpath->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert relative vpath object to absolute
+ *
+ * @param vpath path object
+ *
+ * @return absolute path object
+ */
+
+vfs_path_t *
+vfs_path_to_absolute (const vfs_path_t * vpath)
+{
+ vfs_path_t *absolute_vpath;
+ const char *path_str;
+
+ if (!vpath->relative)
+ return vfs_path_clone (vpath);
+
+ path_str = vfs_path_as_str (vpath);
+ absolute_vpath = vfs_path_from_str (path_str);
+ return absolute_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/path.h b/lib/vfs/path.h
new file mode 100644
index 0000000..0887111
--- /dev/null
+++ b/lib/vfs/path.h
@@ -0,0 +1,149 @@
+#ifndef MC__VFS_PATH_H
+#define MC__VFS_PATH_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define VFS_PATH_URL_DELIMITER "://"
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ VPF_NONE = 0,
+ VPF_NO_CANON = 1 << 0,
+ VPF_USE_DEPRECATED_PARSER = 1 << 1,
+ VPF_RECODE = 1 << 2,
+ VPF_STRIP_HOME = 1 << 3,
+ VPF_STRIP_PASSWORD = 1 << 4,
+ VPF_HIDE_CHARSET = 1 << 5
+} vfs_path_flag_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct vfs_class;
+struct vfs_url_struct;
+
+typedef struct
+{
+ gboolean relative;
+ GArray *path;
+ char *str;
+} vfs_path_t;
+
+typedef struct
+{
+ char *user;
+ char *password;
+ char *host;
+ gboolean ipv6;
+ int port;
+ char *path;
+ struct vfs_class *class;
+#ifdef HAVE_CHARSET
+ char *encoding;
+#endif
+ char *vfs_prefix;
+
+ struct
+ {
+#ifdef HAVE_CHARSET
+ GIConv converter;
+#endif
+ DIR *info;
+ } dir;
+} vfs_path_element_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+vfs_path_t *vfs_path_new (gboolean relative);
+vfs_path_t *vfs_path_clone (const vfs_path_t * vpath);
+void vfs_path_remove_element_by_index (vfs_path_t * vpath, int element_index);
+char *vfs_path_free (vfs_path_t * path, gboolean free_str);
+int vfs_path_elements_count (const vfs_path_t * path);
+
+char *vfs_path_to_str_elements_count (const vfs_path_t * path, int elements_count);
+char *vfs_path_to_str_flags (const vfs_path_t * vpath, int elements_count, vfs_path_flag_t flags);
+vfs_path_t *vfs_path_from_str (const char *path_str);
+vfs_path_t *vfs_path_from_str_flags (const char *path_str, vfs_path_flag_t flags);
+vfs_path_t *vfs_path_build_filename (const char *first_element, ...);
+vfs_path_t *vfs_path_append_new (const vfs_path_t * vpath, const char *first_element, ...);
+vfs_path_t *vfs_path_append_vpath_new (const vfs_path_t * first_vpath, ...);
+size_t vfs_path_tokens_count (const vfs_path_t * vpath);
+char *vfs_path_tokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length);
+vfs_path_t *vfs_path_vtokens_get (const vfs_path_t * vpath, ssize_t start_position, ssize_t length);
+
+void vfs_path_add_element (vfs_path_t * vpath, const vfs_path_element_t * path_element);
+const vfs_path_element_t *vfs_path_get_by_index (const vfs_path_t * path, int element_index);
+vfs_path_element_t *vfs_path_element_clone (const vfs_path_element_t * element);
+void vfs_path_element_free (vfs_path_element_t * element);
+
+struct vfs_class *vfs_prefix_to_class (const char *prefix);
+
+#ifdef HAVE_CHARSET
+gboolean vfs_path_element_need_cleanup_converter (const vfs_path_element_t * element);
+vfs_path_t *vfs_path_change_encoding (vfs_path_t * vpath, const char *encoding);
+#endif
+
+char *vfs_path_serialize (const vfs_path_t * vpath, GError ** error);
+vfs_path_t *vfs_path_deserialize (const char *data, GError ** error);
+
+GString *vfs_path_build_url_params_str (const vfs_path_element_t * element, gboolean keep_password);
+GString *vfs_path_element_build_pretty_path_str (const vfs_path_element_t * element);
+
+size_t vfs_path_len (const vfs_path_t * vpath);
+gboolean vfs_path_equal (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+gboolean vfs_path_equal_len (const vfs_path_t * vpath1, const vfs_path_t * vpath2, size_t len);
+vfs_path_t *vfs_path_to_absolute (const vfs_path_t * vpath);
+
+/*** inline functions ****************************************************************************/
+
+static inline gboolean
+vfs_path_element_valid (const vfs_path_element_t * element)
+{
+ return (element != NULL && element->class != NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline const char *
+vfs_path_get_last_path_str (const vfs_path_t * vpath)
+{
+ const vfs_path_element_t *element;
+ if (vpath == NULL)
+ return NULL;
+ element = vfs_path_get_by_index (vpath, -1);
+ return (element != NULL) ? element->path : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline const struct vfs_class *
+vfs_path_get_last_path_vfs (const vfs_path_t * vpath)
+{
+ const vfs_path_element_t *element;
+ if (vpath == NULL)
+ return NULL;
+ element = vfs_path_get_by_index (vpath, -1);
+ return (element != NULL) ? element->class : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert vfs_path_t to string representation.
+ *
+ * @param vpath pointer to vfs_path_t object
+ *
+ * @return pointer to constant string
+ */
+
+static inline const char *
+vfs_path_as_str (const vfs_path_t * vpath)
+{
+ return (vpath == NULL ? NULL : vpath->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif
diff --git a/lib/vfs/utilvfs.c b/lib/vfs/utilvfs.c
new file mode 100644
index 0000000..162eb4c
--- /dev/null
+++ b/lib/vfs/utilvfs.c
@@ -0,0 +1,374 @@
+/*
+ Utilities for VFS modules.
+
+ Copyright (C) 1988-2023
+ Free Software Foundation, Inc.
+
+ Copyright (C) 1995, 1996 Miguel de Icaza
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Utilities for VFS modules
+ * \author Miguel de Icaza
+ * \date 1995, 1996
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/unixcompat.h"
+#include "lib/widget.h" /* message() */
+#include "lib/strutil.h" /* INVALID_CONV */
+
+#include "vfs.h"
+#include "utilvfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef TUNMLEN
+#define TUNMLEN 256
+#endif
+#ifndef TGNMLEN
+#define TGNMLEN 256
+#endif
+
+#define MC_HISTORY_VFS_PASSWORD "mc.vfs.password"
+
+/*
+ * FIXME2, the "-993" is to reduce the chance of a hit on the first lookup.
+ */
+#define GUID_DEFAULT_CONST -993
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Get current username
+ *
+ * @return g_malloc()ed string with the name of the currently logged in
+ * user ("anonymous" if uid is not registered in the system)
+ */
+
+char *
+vfs_get_local_username (void)
+{
+ struct passwd *p_i;
+
+ p_i = getpwuid (geteuid ());
+
+ /* Unknown UID, strange */
+ return (p_i != NULL && p_i->pw_name != NULL) ? g_strdup (p_i->pw_name) : g_strdup ("anonymous");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Look up a user or group name from a uid/gid, maintaining a cache.
+ * FIXME, for now it's a one-entry cache.
+ * This file should be modified for non-unix systems to do something
+ * reasonable.
+ */
+
+int
+vfs_finduid (const char *uname)
+{
+ static int saveuid = GUID_DEFAULT_CONST;
+ static char saveuname[TUNMLEN] = "\0";
+
+ size_t uname_len;
+
+ uname_len = strlen (uname);
+
+ if (uname[0] != saveuname[0] /* Quick test w/o proc call */
+ || strncmp (uname, saveuname, MIN (uname_len, TUNMLEN - 1)) != 0)
+ {
+ struct passwd *pw;
+
+ g_strlcpy (saveuname, uname, TUNMLEN);
+ pw = getpwnam (uname);
+ if (pw != NULL)
+ saveuid = pw->pw_uid;
+ else
+ {
+ static int my_uid = GUID_DEFAULT_CONST;
+
+ if (my_uid < 0)
+ my_uid = getuid ();
+
+ saveuid = my_uid;
+ }
+ }
+
+ return saveuid;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_findgid (const char *gname)
+{
+ static int savegid = GUID_DEFAULT_CONST;
+ static char savegname[TGNMLEN] = "\0";
+
+ size_t gname_len;
+
+ gname_len = strlen (gname);
+
+ if (gname[0] != savegname[0] /* Quick test w/o proc call */
+ || strncmp (gname, savegname, MIN (gname_len, TGNMLEN - 1)) != 0)
+ {
+ struct group *gr;
+
+ g_strlcpy (savegname, gname, TGNMLEN);
+ gr = getgrnam (gname);
+ if (gr != NULL)
+ savegid = gr->gr_gid;
+ else
+ {
+ static int my_gid = GUID_DEFAULT_CONST;
+
+ if (my_gid < 0)
+ my_gid = getgid ();
+
+ savegid = my_gid;
+ }
+ }
+
+ return savegid;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create a temporary file with a name resembling the original.
+ * This is needed e.g. for local copies requested by extfs.
+ * Some extfs scripts may look at the extension.
+ * We also protect stupid scripts against dangerous names.
+ */
+
+int
+vfs_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *param_basename)
+{
+ const char *p;
+ GString *suffix;
+ int shift;
+ int fd;
+
+ /* Strip directories */
+ p = strrchr (param_basename, PATH_SEP);
+ if (p == NULL)
+ p = param_basename;
+ else
+ p++;
+
+ /* Protection against very long names */
+ shift = strlen (p) - (MC_MAXPATHLEN - 16);
+ if (shift > 0)
+ p += shift;
+
+ suffix = g_string_sized_new (32);
+
+ /* Protection against unusual characters */
+ for (; *p != '\0' && *p != '#'; p++)
+ if (strchr (".-_@", *p) != NULL || g_ascii_isalnum (*p))
+ g_string_append_c (suffix, *p);
+
+ fd = mc_mkstemps (pname_vpath, prefix, suffix->str);
+ g_string_free (suffix, TRUE);
+
+ return fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the hostname and username from the path
+ *
+ * Format of the path is [user@]hostname:port/remote-dir, e.g.:
+ *
+ * ftp://sunsite.unc.edu/pub/linux
+ * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
+ * ftp://tsx-11.mit.edu:8192/
+ * ftp://joe@foo.edu:11321/private
+ * ftp://joe:password@foo.se
+ *
+ * @param path is an input string to be parsed
+ * @param default_port is an input default port
+ * @param flags are parsing modifier flags (@see vfs_url_flags_t)
+ *
+ * @return g_malloc()ed url info.
+ * If the user is empty, e.g. ftp://@roxanne/private, and URL_USE_ANONYMOUS
+ * is not set, then the current login name is supplied.
+ * Return value is a g_malloc()ed structure with the pathname relative to the
+ * host.
+ */
+
+vfs_path_element_t *
+vfs_url_split (const char *path, int default_port, vfs_url_flags_t flags)
+{
+ vfs_path_element_t *path_element;
+
+ char *pcopy;
+ size_t pcopy_len;
+ const char *pend;
+ char *colon, *at, *rest;
+
+ path_element = g_new0 (vfs_path_element_t, 1);
+ path_element->port = default_port;
+
+ pcopy_len = strlen (path);
+ pcopy = g_strndup (path, pcopy_len);
+ pend = pcopy + pcopy_len;
+
+ if ((flags & URL_NOSLASH) == 0)
+ {
+ char *dir;
+
+ /* locate path component */
+ dir = strchr (pcopy, PATH_SEP);
+
+ if (dir == NULL)
+ path_element->path = g_strdup (PATH_SEP_STR);
+ else
+ {
+ path_element->path = g_strndup (dir, pcopy_len - (size_t) (dir - pcopy));
+ *dir = '\0';
+ }
+ }
+
+ /* search for any possible user */
+ at = strrchr (pcopy, '@');
+
+ /* We have a username */
+ if (at == NULL)
+ rest = pcopy;
+ else
+ {
+ char *inner_colon;
+
+ *at = '\0';
+ inner_colon = strchr (pcopy, ':');
+ if (inner_colon != NULL)
+ {
+ *inner_colon = '\0';
+ inner_colon++;
+ path_element->password = g_strdup (inner_colon);
+ }
+
+ if (*pcopy != '\0')
+ path_element->user = g_strdup (pcopy);
+
+ if (pend == at + 1)
+ rest = at;
+ else
+ rest = at + 1;
+ }
+
+ if ((flags & URL_USE_ANONYMOUS) == 0)
+ {
+ g_free (path_element->user);
+ path_element->user = vfs_get_local_username ();
+ }
+ /* Check if the host comes with a port spec, if so, chop it */
+ if (*rest != '[')
+ colon = strchr (rest, ':');
+ else
+ {
+ colon = strchr (++rest, ']');
+ if (colon != NULL)
+ {
+ colon[0] = '\0';
+ colon[1] = '\0';
+ colon++;
+ }
+ else
+ {
+ vfs_path_element_free (path_element);
+ g_free (pcopy);
+ return NULL;
+ }
+ }
+
+ if (colon != NULL)
+ {
+ *colon = '\0';
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (colon + 1, "%d", &path_element->port) == 1)
+ {
+ if (path_element->port <= 0 || path_element->port >= 65536)
+ path_element->port = default_port;
+ }
+ else
+ while (*(++colon) != '\0')
+ {
+ switch (*colon)
+ {
+ case 'C':
+ path_element->port = 1;
+ break;
+ case 'r':
+ path_element->port = 2;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ path_element->host = g_strdup (rest);
+ g_free (pcopy);
+#ifdef HAVE_CHARSET
+ path_element->dir.converter = INVALID_CONV;
+#endif
+
+ return path_element;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void __attribute__ ((noreturn)) vfs_die (const char *m)
+{
+ message (D_ERROR, _("Internal error:"), "%s", m);
+ exit (EXIT_FAILURE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+vfs_get_password (const char *msg)
+{
+ return input_dialog (msg, _("Password:"), MC_HISTORY_VFS_PASSWORD, INPUT_PASSWORD,
+ INPUT_COMPLETE_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/utilvfs.h b/lib/vfs/utilvfs.h
new file mode 100644
index 0000000..d50d4b6
--- /dev/null
+++ b/lib/vfs/utilvfs.h
@@ -0,0 +1,64 @@
+
+/**
+ * \file
+ * \brief Header: Utilities for VFS modules
+ * \author Miguel de Icaza
+ * \date 1995, 1996
+ */
+
+#ifndef MC_VFS_UTILVFS_H
+#define MC_VFS_UTILVFS_H
+
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "path.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/** Bit flags for vfs_url_split()
+ *
+ * Modify parsing parameters according to flag meaning.
+ * @see vfs_url_split()
+ */
+typedef enum
+{
+ URL_FLAGS_NONE = 0,
+ URL_USE_ANONYMOUS = 1, /**< if set, empty *user will contain NULL instead of current */
+ URL_NOSLASH = 2 /**< if set, 'proto://' part in url is not searched */
+} vfs_url_flags_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int vfs_finduid (const char *name);
+int vfs_findgid (const char *name);
+
+vfs_path_element_t *vfs_url_split (const char *path, int default_port, vfs_url_flags_t flags);
+int vfs_split_text (char *p);
+
+int vfs_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *basename);
+void vfs_die (const char *msg);
+char *vfs_get_password (const char *msg);
+
+char *vfs_get_local_username (void);
+
+gboolean vfs_parse_filetype (const char *s, size_t * ret_skipped, mode_t * ret_type);
+gboolean vfs_parse_fileperms (const char *s, size_t * ret_skipped, mode_t * ret_perms);
+gboolean vfs_parse_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode);
+gboolean vfs_parse_raw_filemode (const char *s, size_t * ret_skipped, mode_t * ret_mode);
+
+void vfs_parse_ls_lga_init (void);
+gboolean vfs_parse_ls_lga (const char *p, struct stat *s, char **filename, char **linkname,
+ size_t * filename_pos);
+size_t vfs_parse_ls_lga_get_final_spaces (void);
+gboolean vfs_parse_month (const char *str, struct tm *tim);
+int vfs_parse_filedate (int idx, time_t * t);
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/lib/vfs/vfs.c b/lib/vfs/vfs.c
new file mode 100644
index 0000000..ad57189
--- /dev/null
+++ b/lib/vfs/vfs.c
@@ -0,0 +1,775 @@
+/*
+ Virtual File System switch code
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by: 1995 Miguel de Icaza
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2011-2013
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System switch code
+ * \author Miguel de Icaza
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \date 1995, 1998
+ * \warning functions like extfs_lstat() have right to destroy any
+ * strings you pass to them. This is actually ok as you g_strdup what
+ * you are passing to them, anyway; still, beware.
+ *
+ * Namespace: exports *many* functions with vfs_ prefix; exports
+ * parse_ls_lga and friends which do not have that prefix.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#ifdef __linux__
+#ifdef HAVE_LINUX_FS_H
+#include <linux/fs.h>
+#endif /* HAVE_LINUX_FS_H */
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif /* HAVE_SYS_IOCTL_H */
+#endif /* __linux__ */
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+#include "lib/event.h"
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "gc.h"
+
+/* TODO: move it to the separate .h */
+extern struct vfs_dirent *mc_readdir_result;
+extern GPtrArray *vfs__classes_list;
+extern GString *vfs_str_buffer;
+extern vfs_class *current_vfs;
+
+/*** global variables ****************************************************************************/
+
+struct vfs_dirent *mc_readdir_result = NULL;
+GPtrArray *vfs__classes_list = NULL;
+GString *vfs_str_buffer = NULL;
+vfs_class *current_vfs = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define VFS_FIRST_HANDLE 100
+
+/*** file scope type declarations ****************************************************************/
+
+struct vfs_openfile
+{
+ int handle;
+ vfs_class *vclass;
+ void *fsinfo;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/** They keep track of the current directory */
+static vfs_path_t *current_path = NULL;
+
+static GPtrArray *vfs_openfiles = NULL;
+static long vfs_free_handle_list = -1;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/* now used only by vfs_translate_path, but could be used in other vfs
+ * plugin to automatic detect encoding
+ * path - path to translate
+ * size - how many bytes from path translate
+ * defcnv - converter, that is used as default, when path does not contain any
+ * #enc: substring
+ * buffer - used to store result of translation
+ */
+
+static estr_t
+_vfs_translate_path (const char *path, int size, GIConv defcnv, GString * buffer)
+{
+ estr_t state = ESTR_SUCCESS;
+#ifdef HAVE_CHARSET
+ const char *semi;
+
+ if (size == 0)
+ return ESTR_SUCCESS;
+
+ size = (size > 0) ? size : (signed int) strlen (path);
+
+ /* try found /#enc: */
+ semi = g_strrstr_len (path, size, VFS_ENCODING_PREFIX);
+ if (semi != NULL && (semi == path || IS_PATH_SEP (semi[-1])))
+ {
+ char encoding[16];
+ const char *slash;
+ GIConv coder = INVALID_CONV;
+ int ms;
+
+ /* first must be translated part before #enc: */
+ ms = semi - path;
+
+ state = _vfs_translate_path (path, ms, defcnv, buffer);
+
+ if (state != ESTR_SUCCESS)
+ return state;
+
+ /* now can be translated part after #enc: */
+ semi += strlen (VFS_ENCODING_PREFIX); /* skip "#enc:" */
+ slash = strchr (semi, PATH_SEP);
+ /* ignore slashes after size; */
+ if (slash - path >= size)
+ slash = NULL;
+
+ ms = (slash != NULL) ? slash - semi : (int) strlen (semi);
+ ms = MIN ((unsigned int) ms, sizeof (encoding) - 1);
+ /* limit encoding size (ms) to path size (size) */
+ if (semi + ms > path + size)
+ ms = path + size - semi;
+ memcpy (encoding, semi, ms);
+ encoding[ms] = '\0';
+
+ if (is_supported_encoding (encoding))
+ coder = str_crt_conv_to (encoding);
+
+ if (coder != INVALID_CONV)
+ {
+ if (slash != NULL)
+ state = str_vfs_convert_to (coder, slash + 1, path + size - slash - 1, buffer);
+ str_close_conv (coder);
+ return state;
+ }
+
+ errno = EINVAL;
+ state = ESTR_FAILURE;
+ }
+ else
+ {
+ /* path can be translated whole at once */
+ state = str_vfs_convert_to (defcnv, path, size, buffer);
+ }
+#else
+ (void) size;
+ (void) defcnv;
+
+ g_string_assign (buffer, path);
+#endif /* HAVE_CHARSET */
+
+ return state;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_openfile *
+vfs_get_openfile (int handle)
+{
+ struct vfs_openfile *h;
+
+ if (handle < VFS_FIRST_HANDLE || (guint) (handle - VFS_FIRST_HANDLE) >= vfs_openfiles->len)
+ return NULL;
+
+ h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, handle - VFS_FIRST_HANDLE);
+ if (h == NULL)
+ return NULL;
+
+ g_assert (h->handle == handle);
+
+ return h;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_test_current_dir (const vfs_path_t * vpath)
+{
+ struct stat my_stat, my_stat2;
+
+ return (mc_global.vfs.cd_symlinks && mc_stat (vpath, &my_stat) == 0
+ && mc_stat (vfs_get_raw_current_dir (), &my_stat2) == 0
+ && my_stat.st_ino == my_stat2.st_ino && my_stat.st_dev == my_stat2.st_dev);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Free open file data for given file handle */
+
+void
+vfs_free_handle (int handle)
+{
+ const int idx = handle - VFS_FIRST_HANDLE;
+
+ if (handle >= VFS_FIRST_HANDLE && (guint) idx < vfs_openfiles->len)
+ {
+ struct vfs_openfile *h;
+
+ h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, idx);
+ g_free (h);
+ g_ptr_array_index (vfs_openfiles, idx) = (void *) vfs_free_handle_list;
+ vfs_free_handle_list = idx;
+ }
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find VFS class by file handle */
+
+struct vfs_class *
+vfs_class_find_by_handle (int handle, void **fsinfo)
+{
+ struct vfs_openfile *h;
+
+ h = vfs_get_openfile (handle);
+
+ if (h == NULL)
+ return NULL;
+
+ if (fsinfo != NULL)
+ *fsinfo = h->fsinfo;
+
+ return h->vclass;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Create new VFS handle and put it to the list
+ */
+
+int
+vfs_new_handle (struct vfs_class *vclass, void *fsinfo)
+{
+ struct vfs_openfile *h;
+
+ h = g_new (struct vfs_openfile, 1);
+ h->fsinfo = fsinfo;
+ h->vclass = vclass;
+
+ /* Allocate the first free handle */
+ h->handle = vfs_free_handle_list;
+ if (h->handle == -1)
+ {
+ /* No free allocated handles, allocate one */
+ h->handle = vfs_openfiles->len;
+ g_ptr_array_add (vfs_openfiles, h);
+ }
+ else
+ {
+ vfs_free_handle_list = (long) g_ptr_array_index (vfs_openfiles, vfs_free_handle_list);
+ g_ptr_array_index (vfs_openfiles, h->handle) = h;
+ }
+
+ h->handle += VFS_FIRST_HANDLE;
+ return h->handle;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_ferrno (struct vfs_class *vfs)
+{
+ return vfs->ferrno ? (*vfs->ferrno) (vfs) : E_UNKNOWN;
+ /* Hope that error message is obscure enough ;-) */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_register_class (struct vfs_class * vfs)
+{
+ if (vfs->init != NULL) /* vfs has own initialization function */
+ if (!vfs->init (vfs)) /* but it failed */
+ return FALSE;
+
+ g_ptr_array_add (vfs__classes_list, vfs);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_unregister_class (struct vfs_class *vfs)
+{
+ if (vfs->done != NULL)
+ vfs->done (vfs);
+
+ g_ptr_array_remove (vfs__classes_list, vfs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Strip known vfs suffixes from a filename (possible improvement: strip
+ * suffix from last path component).
+ * \return a malloced string which has to be freed.
+ */
+
+char *
+vfs_strip_suffix_from_filename (const char *filename)
+{
+ char *semi, *p;
+
+ if (filename == NULL)
+ vfs_die ("vfs_strip_suffix_from_path got NULL: impossible");
+
+ p = g_strdup (filename);
+ semi = g_strrstr (p, VFS_PATH_URL_DELIMITER);
+ if (semi != NULL)
+ {
+ char *vfs_prefix;
+
+ *semi = '\0';
+ vfs_prefix = strrchr (p, PATH_SEP);
+ if (vfs_prefix == NULL)
+ *semi = *VFS_PATH_URL_DELIMITER;
+ else
+ *vfs_prefix = '\0';
+ }
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+vfs_translate_path (const char *path)
+{
+ estr_t state;
+
+ g_string_set_size (vfs_str_buffer, 0);
+ state = _vfs_translate_path (path, -1, str_cnv_from_term, vfs_str_buffer);
+ return (state != ESTR_FAILURE) ? vfs_str_buffer->str : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+vfs_translate_path_n (const char *path)
+{
+ const char *result;
+
+ result = vfs_translate_path (path);
+ return g_strdup (result);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get current directory without any OS calls.
+ *
+ * @return string contains current path
+ */
+
+const char *
+vfs_get_current_dir (void)
+{
+ return current_path->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get current directory without any OS calls.
+ *
+ * @return newly allocated string contains current path
+ */
+
+char *
+vfs_get_current_dir_n (void)
+{
+ return g_strdup (current_path->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get raw current directory object without any OS calls.
+ *
+ * @return object contain current path
+ */
+
+const vfs_path_t *
+vfs_get_raw_current_dir (void)
+{
+ return current_path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set current directory object.
+ *
+ * @param vpath new path
+ */
+void
+vfs_set_raw_current_dir (const vfs_path_t * vpath)
+{
+ vfs_path_free (current_path, TRUE);
+ current_path = (vfs_path_t *) vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return TRUE is the current VFS class is local */
+
+gboolean
+vfs_current_is_local (void)
+{
+ return (current_vfs->flags & VFSF_LOCAL) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return flags of the VFS class of the given filename */
+
+vfs_flags_t
+vfs_file_class_flags (const vfs_path_t * vpath)
+{
+ const vfs_path_element_t *path_element;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+ if (!vfs_path_element_valid (path_element))
+ return VFSF_UNKNOWN;
+
+ return path_element->class->flags;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init (void)
+{
+ /* create the VFS handle arrays */
+ vfs__classes_list = g_ptr_array_new ();
+
+ /* create the VFS handle array */
+ vfs_openfiles = g_ptr_array_new ();
+
+ vfs_str_buffer = g_string_new ("");
+
+ mc_readdir_result = vfs_dirent_init (NULL, "", -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_setup_work_dir (void)
+{
+ vfs_setup_cwd ();
+
+ /* FIXME: is we really need for this check? */
+ /*
+ if (strlen (current_dir) > MC_MAXPATHLEN - 2)
+ vfs_die ("Current dir too long.\n");
+ */
+
+ current_vfs = VFS_CLASS (vfs_path_get_last_path_vfs (current_path));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_shut (void)
+{
+ guint i;
+
+ vfs_gc_done ();
+
+ vfs_set_raw_current_dir (NULL);
+
+ for (i = 0; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+
+ if (vfs->done != NULL)
+ vfs->done (vfs);
+ }
+
+ /* NULL-ize pointers to make unit tests happy */
+ g_ptr_array_free (vfs_openfiles, TRUE);
+ vfs_openfiles = NULL;
+ g_ptr_array_free (vfs__classes_list, TRUE);
+ vfs__classes_list = NULL;
+ g_string_free (vfs_str_buffer, TRUE);
+ vfs_str_buffer = NULL;
+ current_vfs = NULL;
+ vfs_free_handle_list = -1;
+ vfs_dirent_free (mc_readdir_result);
+ mc_readdir_result = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Init or create vfs_dirent structure
+ *
+ * @d vfs_dirent structure to init. If NULL, new structure is created.
+ * @fname file name
+ * @ino file inode number
+ *
+ * @return pointer to d if d isn't NULL, or pointer to newly created structure.
+ */
+
+struct vfs_dirent *
+vfs_dirent_init (struct vfs_dirent *d, const char *fname, ino_t ino)
+{
+ struct vfs_dirent *ret = d;
+
+ if (ret == NULL)
+ ret = g_new0 (struct vfs_dirent, 1);
+
+ if (ret->d_name_str == NULL)
+ ret->d_name_str = g_string_sized_new (MC_MAXFILENAMELEN);
+
+ vfs_dirent_assign (ret, fname, ino);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Assign members of vfs_dirent structure
+ *
+ * @d vfs_dirent structure for assignment
+ * @fname file name
+ * @ino file inode number
+ */
+
+void
+vfs_dirent_assign (struct vfs_dirent *d, const char *fname, ino_t ino)
+{
+ g_string_assign (d->d_name_str, fname);
+ d->d_name = d->d_name_str->str;
+ d->d_ino = ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy vfs_dirent structure
+ *
+ * @d vfs_dirent structure to destroy.
+ */
+
+void
+vfs_dirent_free (struct vfs_dirent *d)
+{
+ g_string_free (d->d_name_str, TRUE);
+ g_free (d);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * These ones grab information from the VFS
+ * and handles them to an upper layer
+ */
+
+void
+vfs_fill_names (fill_names_f func)
+{
+ guint i;
+
+ for (i = 0; i < vfs__classes_list->len; i++)
+ {
+ struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
+
+ if (vfs->fill_names != NULL)
+ vfs->fill_names (vfs, func);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+vfs_file_is_local (const vfs_path_t * vpath)
+{
+ return (vfs_file_class_flags (vpath) & VFSF_LOCAL) != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_print_message (const char *msg, ...)
+{
+ ev_vfs_print_message_t event_data;
+ va_list ap;
+
+ va_start (ap, msg);
+ event_data.msg = g_strdup_vprintf (msg, ap);
+ va_end (ap);
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "vfs_print_message", (gpointer) & event_data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * If it's local, reread the current directory
+ * from the OS.
+ */
+
+void
+vfs_setup_cwd (void)
+{
+ char *current_dir;
+ vfs_path_t *tmp_vpath;
+ const struct vfs_class *me;
+
+ if (vfs_get_raw_current_dir () == NULL)
+ {
+ current_dir = g_get_current_dir ();
+ vfs_set_raw_current_dir (vfs_path_from_str (current_dir));
+ g_free (current_dir);
+
+ current_dir = getenv ("PWD");
+ tmp_vpath = vfs_path_from_str (current_dir);
+
+ if (tmp_vpath != NULL)
+ {
+ if (vfs_test_current_dir (tmp_vpath))
+ vfs_set_raw_current_dir (tmp_vpath);
+ else
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+
+ me = vfs_path_get_last_path_vfs (vfs_get_raw_current_dir ());
+ if ((me->flags & VFSF_LOCAL) != 0)
+ {
+ current_dir = g_get_current_dir ();
+ tmp_vpath = vfs_path_from_str (current_dir);
+ g_free (current_dir);
+
+ if (tmp_vpath != NULL)
+ {
+ /* One of directories in the path is not readable */
+
+ /* Check if it is O.K. to use the current_dir */
+ if (!vfs_test_current_dir (tmp_vpath))
+ vfs_set_raw_current_dir (tmp_vpath);
+ else
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return current directory. If it's local, reread the current directory
+ * from the OS.
+ */
+
+char *
+vfs_get_cwd (void)
+{
+ vfs_setup_cwd ();
+ return vfs_get_current_dir_n ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Preallocate space for file in new place for ensure that file
+ * will be fully copied with less fragmentation.
+ *
+ * @param dest_vfs_fd mc VFS file handler
+ * @param src_fsize source file size
+ * @param dest_fsize destination file size (if destination exists, otherwise should be 0)
+ *
+ * @return 0 if success and non-zero otherwise.
+ * Note: function doesn't touch errno global variable.
+ */
+
+int
+vfs_preallocate (int dest_vfs_fd, off_t src_fsize, off_t dest_fsize)
+{
+#ifndef HAVE_POSIX_FALLOCATE
+ (void) dest_vfs_fd;
+ (void) src_fsize;
+ (void) dest_fsize;
+ return 0;
+
+#else /* HAVE_POSIX_FALLOCATE */
+ void *dest_fd = NULL;
+ struct vfs_class *dest_class;
+
+ if (src_fsize == 0)
+ return 0;
+
+ dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
+ if ((dest_class->flags & VFSF_LOCAL) == 0 || dest_fd == NULL)
+ return 0;
+
+ return posix_fallocate (*(int *) dest_fd, dest_fsize, src_fsize - dest_fsize);
+
+#endif /* HAVE_POSIX_FALLOCATE */
+}
+
+ /* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
+{
+#ifdef FICLONE
+ void *dest_fd = NULL;
+ void *src_fd = NULL;
+ struct vfs_class *dest_class;
+ struct vfs_class *src_class;
+
+ dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
+ if ((dest_class->flags & VFSF_LOCAL) == 0)
+ {
+ errno = ENOTSUP;
+ return (-1);
+ }
+ if (dest_fd == NULL)
+ {
+ errno = EBADF;
+ return (-1);
+ }
+
+ src_class = vfs_class_find_by_handle (src_vfs_fd, &src_fd);
+ if ((src_class->flags & VFSF_LOCAL) == 0)
+ {
+ errno = ENOTSUP;
+ return (-1);
+ }
+ if (src_fd == NULL)
+ {
+ errno = EBADF;
+ return (-1);
+ }
+
+ return ioctl (*(int *) dest_fd, FICLONE, *(int *) src_fd);
+#else
+ (void) dest_vfs_fd;
+ (void) src_vfs_fd;
+ errno = ENOTSUP;
+ return (-1);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/vfs/vfs.h b/lib/vfs/vfs.h
new file mode 100644
index 0000000..ee78ff5
--- /dev/null
+++ b/lib/vfs/vfs.h
@@ -0,0 +1,343 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System switch code
+ */
+
+#ifndef MC__VFS_VFS_H
+#define MC__VFS_VFS_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h> /* DIR */
+#ifdef HAVE_UTIMENSAT
+#include <sys/time.h>
+#elif defined (HAVE_UTIME_H)
+#include <utime.h>
+#endif
+#include <stdio.h>
+#include <unistd.h>
+#include <stddef.h>
+
+#include "lib/global.h"
+
+#include "path.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define VFS_CLASS(a) ((struct vfs_class *) (a))
+
+#define VFS_ENCODING_PREFIX "#enc:"
+
+#define O_ALL (O_CREAT | O_EXCL | O_NOCTTY | O_NDELAY | O_SYNC | O_WRONLY | O_RDWR | O_RDONLY)
+/* Midnight commander code should _not_ use other flags than those
+ listed above and O_APPEND */
+
+#if (O_ALL & O_APPEND)
+#warning "Unexpected problem with flags, O_LINEAR disabled, contact pavel@ucw.cz"
+#define O_LINEAR 0
+#define IS_LINEAR(a) 0
+#define NO_LINEAR(a) a
+#else
+#define O_LINEAR O_APPEND
+#define IS_LINEAR(a) ((a) == (O_RDONLY | O_LINEAR)) /* Return only 0 and 1 ! */
+#define NO_LINEAR(a) (((a) == (O_RDONLY | O_LINEAR)) ? O_RDONLY : (a))
+#endif
+
+/* O_LINEAR is strange beast, be careful. If you open file asserting
+ * O_RDONLY | O_LINEAR, you promise:
+ *
+ * a) to read file linearly from beginning to the end
+ * b) not to open another file before you close this one
+ * (this will likely go away in future)
+ * as a special gift, you may
+ * c) lseek() immediately after open(), giving ftpfs chance to
+ * reget. Be warned that this lseek() can fail, and you _have_
+ * to handle that gratefully.
+ *
+ * O_LINEAR allows filesystems not to create temporary file in some
+ * cases (ftp transfer). -- pavel@ucw.cz
+ */
+
+/* And now some defines for our errors. */
+
+#ifdef ENOMSG
+#define E_UNKNOWN ENOMSG /* if we do not know what error happened */
+#else
+#define E_UNKNOWN EIO /* if we do not know what error happened */
+#endif
+
+#ifdef EREMOTEIO
+#define E_REMOTE EREMOTEIO /* if other side of ftp/fish reports error */
+#else
+#define E_REMOTE ENETUNREACH /* :-( there's no EREMOTEIO on some systems */
+#endif
+
+#ifdef EPROTO
+#define E_PROTO EPROTO /* if other side fails to follow protocol */
+#else
+#define E_PROTO EIO
+#endif
+
+/**
+ * This is the type of callback function passed to vfs_fill_names.
+ * It gets the name of the virtual file system as its first argument.
+ * See also:
+ * vfs_fill_names().
+ */
+typedef void (*fill_names_f) (const char *);
+
+typedef void *vfsid;
+
+#ifdef HAVE_UTIMENSAT
+typedef struct timespec mc_timesbuf_t[2];
+#else
+typedef struct utimbuf mc_timesbuf_t;
+#endif
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ VFSF_UNKNOWN = 0,
+ VFSF_LOCAL = 1 << 0, /* Class is local (not virtual) filesystem */
+ VFSF_NOLINKS = 1 << 1, /* Hard links not supported */
+
+ VFSF_REMOTE = 1 << 2,
+ VFSF_READONLY = 1 << 3,
+ VFSF_USETMP = 1 << 4
+} vfs_flags_t;
+
+/* Operations for mc_ctl - on open file */
+enum
+{
+ VFS_CTL_IS_NOTREADY
+};
+
+/* Operations for mc_setctl - on path */
+enum
+{
+ VFS_SETCTL_FORGET,
+ VFS_SETCTL_RUN,
+ VFS_SETCTL_LOGFILE,
+ VFS_SETCTL_FLUSH, /* invalidate directory cache */
+
+ /* Setting this makes vfs layer give out potentially incorrect data,
+ but it also makes some operations much faster. Use with caution. */
+ VFS_SETCTL_STALE_DATA
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct vfs_class
+{
+ const char *name; /* "FIles over SHell" */
+ vfs_flags_t flags;
+ const char *prefix; /* "fish:" */
+ int verrno; /* can't use errno because glibc2 might define errno as function */
+ gboolean flush; /* if set to TRUE, invalidate directory cache */
+ FILE *logfile;
+
+ /* *INDENT-OFF* */
+ int (*init) (struct vfs_class * me);
+ void (*done) (struct vfs_class * me);
+
+ /**
+ * The fill_names method shall call the callback function for every
+ * filesystem name that this vfs module supports.
+ */
+ void (*fill_names) (struct vfs_class * me, fill_names_f);
+
+ /**
+ * The which() method shall return the index of the vfs subsystem
+ * or -1 if this vfs cannot handle the given pathname.
+ */
+ int (*which) (struct vfs_class * me, const char *path);
+
+ void *(*open) (const vfs_path_t * vpath, int flags, mode_t mode);
+ int (*close) (void *vfs_info);
+ ssize_t (*read) (void *vfs_info, char *buffer, size_t count);
+ ssize_t (*write) (void *vfs_info, const char *buf, size_t count);
+
+ void *(*opendir) (const vfs_path_t * vpath);
+ struct vfs_dirent *(*readdir) (void *vfs_info);
+ int (*closedir) (void *vfs_info);
+
+ int (*stat) (const vfs_path_t * vpath, struct stat * buf);
+ int (*lstat) (const vfs_path_t * vpath, struct stat * buf);
+ int (*fstat) (void *vfs_info, struct stat * buf);
+
+ int (*chmod) (const vfs_path_t * vpath, mode_t mode);
+ int (*chown) (const vfs_path_t * vpath, uid_t owner, gid_t group);
+
+ int (*fgetflags) (const vfs_path_t * vpath, unsigned long *flags);
+ int (*fsetflags) (const vfs_path_t * vpath, unsigned long flags);
+
+ int (*utime) (const vfs_path_t * vpath, mc_timesbuf_t * times);
+
+ int (*readlink) (const vfs_path_t * vpath, char *buf, size_t size);
+ int (*symlink) (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+ int (*link) (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+ int (*unlink) (const vfs_path_t * vpath);
+ int (*rename) (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+ int (*chdir) (const vfs_path_t * vpath);
+ int (*ferrno) (struct vfs_class * me);
+ off_t (*lseek) (void *vfs_info, off_t offset, int whence);
+ int (*mknod) (const vfs_path_t * vpath, mode_t mode, dev_t dev);
+
+ vfsid (*getid) (const vfs_path_t * vpath);
+
+ gboolean (*nothingisopen) (vfsid id);
+ void (*free) (vfsid id);
+
+ vfs_path_t *(*getlocalcopy) (const vfs_path_t * vpath);
+ int (*ungetlocalcopy) (const vfs_path_t * vpath, const vfs_path_t * local_vpath,
+ gboolean has_changed);
+
+ int (*mkdir) (const vfs_path_t * vpath, mode_t mode);
+ int (*rmdir) (const vfs_path_t * vpath);
+
+ int (*ctl) (void *vfs_info, int ctlop, void *arg);
+ int (*setctl) (const vfs_path_t * vpath, int ctlop, void *arg);
+ /* *INDENT-ON* */
+} vfs_class;
+
+/*
+ * This struct is used instead of standard dirent to hold file name of any length
+ * (not limited to NAME_MAX).
+ */
+struct vfs_dirent
+{
+ /* private */
+ GString *d_name_str;
+
+ /* public */
+ ino_t d_ino;
+ char *d_name; /* Alias of d_name_str->str */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int vfs_timeout;
+
+#ifdef ENABLE_VFS_NET
+extern int use_netrc;
+#endif
+
+/*** declarations of public functions ************************************************************/
+
+/* lib/vfs/direntry.c: */
+void vfs_init_class (struct vfs_class *vclass, const char *name, vfs_flags_t flags,
+ const char *prefix);
+
+void *vfs_s_open (const vfs_path_t * vpath, int flags, mode_t mode);
+int vfs_s_stat (const vfs_path_t * vpath, struct stat *buf);
+int vfs_s_lstat (const vfs_path_t * vpath, struct stat *buf);
+int vfs_s_fstat (void *fh, struct stat *buf);
+
+void vfs_adjust_stat (struct stat *s);
+
+vfsid vfs_getid (const vfs_path_t * vpath);
+
+void vfs_init (void);
+void vfs_shut (void);
+/* Register a file system class */
+gboolean vfs_register_class (struct vfs_class *vfs);
+void vfs_unregister_class (struct vfs_class *vfs);
+
+void vfs_setup_work_dir (void);
+
+void vfs_timeout_handler (void);
+int vfs_timeouts (void);
+void vfs_expire (gboolean now);
+
+const char *vfs_get_current_dir (void);
+char *vfs_get_current_dir_n (void);
+const vfs_path_t *vfs_get_raw_current_dir (void);
+void vfs_set_raw_current_dir (const vfs_path_t * vpath);
+
+gboolean vfs_current_is_local (void);
+gboolean vfs_file_is_local (const vfs_path_t * vpath);
+
+char *vfs_strip_suffix_from_filename (const char *filename);
+
+vfs_flags_t vfs_file_class_flags (const vfs_path_t * vpath);
+
+/* translate path back to terminal encoding, remove all #enc:
+ * every invalid character is replaced with question mark
+ * return static buffer */
+const char *vfs_translate_path (const char *path);
+/* return new string */
+char *vfs_translate_path_n (const char *path);
+
+void vfs_stamp_path (const vfs_path_t * path);
+
+void vfs_release_path (const vfs_path_t * vpath);
+
+struct vfs_dirent *vfs_dirent_init (struct vfs_dirent *d, const char *fname, ino_t ino);
+void vfs_dirent_assign (struct vfs_dirent *d, const char *fname, ino_t ino);
+void vfs_dirent_free (struct vfs_dirent *d);
+
+void vfs_fill_names (fill_names_f);
+
+/* *INDENT-OFF* */
+void vfs_print_message (const char *msg, ...) G_GNUC_PRINTF (1, 2);
+/* *INDENT-ON* */
+
+int vfs_ferrno (struct vfs_class *vfs);
+
+int vfs_new_handle (struct vfs_class *vclass, void *fsinfo);
+
+struct vfs_class *vfs_class_find_by_handle (int handle, void **fsinfo);
+
+void vfs_free_handle (int handle);
+
+void vfs_setup_cwd (void);
+char *vfs_get_cwd (void);
+
+int vfs_preallocate (int dest_desc, off_t src_fsize, off_t dest_fsize);
+
+int vfs_clone_file (int dest_vfs_fd, int src_vfs_fd);
+
+/**
+ * Interface functions described in interface.c
+ */
+ssize_t mc_read (int handle, void *buffer, size_t count);
+ssize_t mc_write (int handle, const void *buffer, size_t count);
+int mc_utime (const vfs_path_t * vpath, mc_timesbuf_t * times);
+int mc_readlink (const vfs_path_t * vpath, char *buf, size_t bufsiz);
+int mc_close (int handle);
+off_t mc_lseek (int fd, off_t offset, int whence);
+DIR *mc_opendir (const vfs_path_t * vpath);
+struct vfs_dirent *mc_readdir (DIR * dirp);
+int mc_closedir (DIR * dir);
+int mc_stat (const vfs_path_t * vpath, struct stat *buf);
+int mc_mknod (const vfs_path_t * vpath, mode_t mode, dev_t dev);
+int mc_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+int mc_mkdir (const vfs_path_t * vpath, mode_t mode);
+int mc_rmdir (const vfs_path_t * vpath);
+int mc_fstat (int fd, struct stat *buf);
+int mc_lstat (const vfs_path_t * vpath, struct stat *buf);
+int mc_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+int mc_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2);
+int mc_chmod (const vfs_path_t * vpath, mode_t mode);
+int mc_chown (const vfs_path_t * vpath, uid_t owner, gid_t group);
+int mc_fgetflags (const vfs_path_t * vpath, unsigned long *flags);
+int mc_fsetflags (const vfs_path_t * vpath, unsigned long flags);
+int mc_chdir (const vfs_path_t * vpath);
+int mc_unlink (const vfs_path_t * vpath);
+int mc_ctl (int fd, int ctlop, void *arg);
+int mc_setctl (const vfs_path_t * vpath, int ctlop, void *arg);
+int mc_open (const vfs_path_t * vpath, int flags, ...);
+vfs_path_t *mc_getlocalcopy (const vfs_path_t * pathname_vpath);
+int mc_ungetlocalcopy (const vfs_path_t * pathname_vpath, const vfs_path_t * local_vpath,
+ gboolean has_changed);
+int mc_mkstemps (vfs_path_t ** pname_vpath, const char *prefix, const char *suffix);
+
+/* Creating temporary files safely */
+const char *mc_tmpdir (void);
+
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_VFS_VFS_H */
diff --git a/lib/vfs/xdirentry.h b/lib/vfs/xdirentry.h
new file mode 100644
index 0000000..e1244cb
--- /dev/null
+++ b/lib/vfs/xdirentry.h
@@ -0,0 +1,205 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System directory structure
+ */
+
+
+#ifndef MC__VFS_XDIRENTRY_H
+#define MC__VFS_XDIRENTRY_H
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "lib/global.h" /* GList */
+#include "lib/vfs/path.h" /* vfs_path_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LINK_FOLLOW 15
+#define LINK_NO_FOLLOW -1
+
+/* For vfs_s_find_entry and vfs_s_find_inode */
+#define FL_NONE 0
+#define FL_MKDIR 1
+#define FL_MKFILE 2
+#define FL_DIR 4
+
+/* For open_super */
+#define FL_NO_OPEN 1
+
+/* For vfs_s_entry_from_path */
+#define FL_FOLLOW 1
+#define FL_DIR 4
+
+#define ERRNOR(a, b) do { me->verrno = a; return b; } while (0)
+
+#define VFS_SUBCLASS(a) ((struct vfs_s_subclass *) (a))
+
+#define VFS_SUPER(a) ((struct vfs_s_super *) (a))
+#define CONST_VFS_SUPER(a) ((const struct vfs_s_super *) (a))
+#define VFS_ENTRY(a) ((struct vfs_s_entry *) (a))
+#define VFS_INODE(a) ((struct vfs_s_inode *) (a))
+
+#define VFS_FILE_HANDLER(a) ((vfs_file_handler_t *) a)
+#define VFS_FILE_HANDLER_SUPER(a) VFS_FILE_HANDLER (a)->ino->super
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ LS_NOT_LINEAR = 0,
+ LS_LINEAR_CLOSED = 1,
+ LS_LINEAR_OPEN = 2,
+ LS_LINEAR_PREOPEN = 3
+} vfs_linear_state_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Single connection or archive */
+struct vfs_s_super
+{
+ struct vfs_class *me;
+ struct vfs_s_inode *root;
+ char *name; /* My name, whatever it means */
+ int fd_usage; /* Number of open files */
+ int ino_usage; /* Usage count of this superblock */
+ gboolean want_stale; /* If set, we do not flush cache properly */
+#ifdef ENABLE_VFS_NET
+ vfs_path_element_t *path_element;
+#endif /* ENABLE_VFS_NET */
+};
+
+/*
+ * Single virtual file - directory entry. The same inode can have many
+ * entries (i.e. hard links), but usually has only one.
+ */
+struct vfs_s_entry
+{
+ struct vfs_s_inode *dir; /* Directory we are in, i.e. our parent */
+ char *name; /* Name of this entry */
+ struct vfs_s_inode *ino; /* ... and its inode */
+ ssize_t leading_spaces; /* number of leading spases in the file name */
+};
+
+/* Single virtual file - inode */
+struct vfs_s_inode
+{
+ struct vfs_s_super *super; /* Archive the file is on */
+ struct vfs_s_entry *ent; /* Our entry in the parent directory -
+ use only for directories because they
+ cannot be hardlinked */
+ GQueue *subdir; /* If this is a directory, its entry. List of vfs_s_entry */
+ struct stat st; /* Parameters of this inode */
+ char *linkname; /* Symlink's contents */
+ char *localname; /* Filename of local file, if we have one */
+ gint64 timestamp; /* Subclass specific */
+ off_t data_offset; /* Subclass specific */
+ void *user_data; /* Subclass specific */
+};
+
+/* Data associated with an open file */
+typedef struct
+{
+ struct vfs_s_inode *ino;
+ off_t pos; /* This is for module's use */
+ int handle; /* This is for module's use, but if != -1, will be mc_close()d */
+ gboolean changed; /* Did this file change? */
+ vfs_linear_state_t linear; /* Is that file open with O_LINEAR? */
+} vfs_file_handler_t;
+
+/*
+ * One of our subclasses (tar, cpio, fish, ftpfs) with data and methods.
+ * Extends vfs_class.
+ */
+struct vfs_s_subclass
+{
+ struct vfs_class base; /* base class */
+
+ GList *supers;
+ int inode_counter;
+ dev_t rdev;
+
+ /* *INDENT-OFF* */
+ int (*init_inode) (struct vfs_class * me, struct vfs_s_inode * ino); /* optional */
+ void (*free_inode) (struct vfs_class * me, struct vfs_s_inode * ino); /* optional */
+ int (*init_entry) (struct vfs_class * me, struct vfs_s_entry * entry); /* optional */
+
+ void *(*archive_check) (const vfs_path_t * vpath); /* optional */
+ int (*archive_same) (const vfs_path_element_t * vpath_element, struct vfs_s_super * psup,
+ const vfs_path_t * vpath, void *cookie);
+ struct vfs_s_super *(*new_archive) (struct vfs_class * me);
+ int (*open_archive) (struct vfs_s_super * psup,
+ const vfs_path_t * vpath, const vfs_path_element_t * vpath_element);
+ void (*free_archive) (struct vfs_class * me, struct vfs_s_super * psup);
+
+ vfs_file_handler_t *(*fh_new) (struct vfs_s_inode * ino, gboolean changed);
+ int (*fh_open) (struct vfs_class * me, vfs_file_handler_t * fh, int flags, mode_t mode);
+ int (*fh_close) (struct vfs_class * me, vfs_file_handler_t * fh);
+ void (*fh_free) (vfs_file_handler_t * fh);
+
+ struct vfs_s_entry *(*find_entry) (struct vfs_class * me,
+ struct vfs_s_inode * root,
+ const char *path, int follow, int flags);
+ int (*dir_load) (struct vfs_class * me, struct vfs_s_inode * ino, const char *path);
+ gboolean (*dir_uptodate) (struct vfs_class * me, struct vfs_s_inode * ino);
+ int (*file_store) (struct vfs_class * me, vfs_file_handler_t * fh, char *path, char *localname);
+
+ int (*linear_start) (struct vfs_class * me, vfs_file_handler_t * fh, off_t from);
+ ssize_t (*linear_read) (struct vfs_class * me, vfs_file_handler_t * fh, void *buf, size_t len);
+ void (*linear_close) (struct vfs_class * me, vfs_file_handler_t * fh);
+ /* *INDENT-ON* */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* entries and inodes */
+struct vfs_s_inode *vfs_s_new_inode (struct vfs_class *me,
+ struct vfs_s_super *super, struct stat *initstat);
+void vfs_s_free_inode (struct vfs_class *me, struct vfs_s_inode *ino);
+
+struct vfs_s_entry *vfs_s_new_entry (struct vfs_class *me, const char *name,
+ struct vfs_s_inode *inode);
+void vfs_s_free_entry (struct vfs_class *me, struct vfs_s_entry *ent);
+void vfs_s_insert_entry (struct vfs_class *me, struct vfs_s_inode *dir, struct vfs_s_entry *ent);
+int vfs_s_entry_compare (const void *a, const void *b);
+struct stat *vfs_s_default_stat (struct vfs_class *me, mode_t mode);
+
+struct vfs_s_entry *vfs_s_generate_entry (struct vfs_class *me, const char *name,
+ struct vfs_s_inode *parent, mode_t mode);
+struct vfs_s_inode *vfs_s_find_inode (struct vfs_class *me,
+ const struct vfs_s_super *super,
+ const char *path, int follow, int flags);
+struct vfs_s_inode *vfs_s_find_root (struct vfs_class *me, struct vfs_s_entry *entry);
+
+/* outside interface */
+void vfs_init_subclass (struct vfs_s_subclass *sub, const char *name, vfs_flags_t flags,
+ const char *prefix);
+const char *vfs_s_get_path (const vfs_path_t * vpath, struct vfs_s_super **archive, int flags);
+struct vfs_s_super *vfs_get_super_by_vpath (const vfs_path_t * vpath);
+
+void vfs_s_invalidate (struct vfs_class *me, struct vfs_s_super *super);
+char *vfs_s_fullpath (struct vfs_class *me, struct vfs_s_inode *ino);
+
+void vfs_s_init_fh (vfs_file_handler_t * fh, struct vfs_s_inode *ino, gboolean changed);
+
+/* network filesystems support */
+int vfs_s_select_on_two (int fd1, int fd2);
+int vfs_s_get_line (struct vfs_class *me, int sock, char *buf, int buf_len, char term);
+int vfs_s_get_line_interruptible (struct vfs_class *me, char *buffer, int size, int fd);
+/* misc */
+int vfs_s_retrieve_file (struct vfs_class *me, struct vfs_s_inode *ino);
+
+void vfs_s_normalize_filename_leading_spaces (struct vfs_s_inode *root_inode, size_t final_filepos);
+
+/*** inline functions ****************************************************************************/
+
+static inline void
+vfs_s_store_filename_leading_spaces (struct vfs_s_entry *entry, size_t position)
+{
+ entry->leading_spaces = (ssize_t) position;
+}
+
+#endif
diff --git a/lib/widget.h b/lib/widget.h
new file mode 100644
index 0000000..e3bb5ca
--- /dev/null
+++ b/lib/widget.h
@@ -0,0 +1,53 @@
+/** \file widget.h
+ * \brief Header: MC widget and dialog manager: main include file.
+ */
+#ifndef MC__WIDGET_H
+#define MC__WIDGET_H
+
+#include "lib/global.h" /* GLib */
+
+/* main forward declarations */
+struct Widget;
+typedef struct Widget Widget;
+struct WGroup;
+typedef struct WGroup WGroup;
+
+/* Please note that the first element in all the widgets is a */
+/* widget variable of type Widget. We abuse this fact everywhere */
+
+#include "lib/widget/rect.h"
+#include "lib/widget/widget-common.h"
+#include "lib/widget/group.h"
+#include "lib/widget/background.h"
+#include "lib/widget/frame.h"
+#include "lib/widget/dialog.h"
+#include "lib/widget/history.h"
+#include "lib/widget/button.h"
+#include "lib/widget/buttonbar.h"
+#include "lib/widget/check.h"
+#include "lib/widget/hline.h"
+#include "lib/widget/gauge.h"
+#include "lib/widget/groupbox.h"
+#include "lib/widget/label.h"
+#include "lib/widget/listbox.h"
+#include "lib/widget/menu.h"
+#include "lib/widget/radio.h"
+#include "lib/widget/input.h"
+#include "lib/widget/listbox-window.h"
+#include "lib/widget/quick.h"
+#include "lib/widget/wtools.h"
+#include "lib/widget/dialog-switch.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_H */
diff --git a/lib/widget/Makefile.am b/lib/widget/Makefile.am
new file mode 100644
index 0000000..90f023b
--- /dev/null
+++ b/lib/widget/Makefile.am
@@ -0,0 +1,30 @@
+
+noinst_LTLIBRARIES = libmcwidget.la
+
+libmcwidget_la_SOURCES = \
+ background.c background.h \
+ button.c button.h \
+ buttonbar.c buttonbar.h \
+ check.c check.h \
+ dialog.c dialog.h \
+ dialog-switch.c dialog-switch.h \
+ frame.c frame.h \
+ gauge.c gauge.h \
+ group.c group.h \
+ groupbox.c groupbox.h \
+ hline.c hline.h \
+ history.c history.h \
+ input.c input.h \
+ input_complete.c \
+ listbox-window.c listbox-window.h \
+ listbox.c listbox.h \
+ label.c label.h \
+ menu.c menu.h \
+ mouse.c mouse.h \
+ quick.c quick.h \
+ radio.c radio.h \
+ rect.c rect.h \
+ widget-common.c widget-common.h \
+ wtools.c wtools.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/lib/widget/Makefile.in b/lib/widget/Makefile.in
new file mode 100644
index 0000000..9353dec
--- /dev/null
+++ b/lib/widget/Makefile.in
@@ -0,0 +1,843 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lib/widget
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcwidget_la_LIBADD =
+am_libmcwidget_la_OBJECTS = background.lo button.lo buttonbar.lo \
+ check.lo dialog.lo dialog-switch.lo frame.lo gauge.lo group.lo \
+ groupbox.lo hline.lo history.lo input.lo input_complete.lo \
+ listbox-window.lo listbox.lo label.lo menu.lo mouse.lo \
+ quick.lo radio.lo rect.lo widget-common.lo wtools.lo
+libmcwidget_la_OBJECTS = $(am_libmcwidget_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/background.Plo \
+ ./$(DEPDIR)/button.Plo ./$(DEPDIR)/buttonbar.Plo \
+ ./$(DEPDIR)/check.Plo ./$(DEPDIR)/dialog-switch.Plo \
+ ./$(DEPDIR)/dialog.Plo ./$(DEPDIR)/frame.Plo \
+ ./$(DEPDIR)/gauge.Plo ./$(DEPDIR)/group.Plo \
+ ./$(DEPDIR)/groupbox.Plo ./$(DEPDIR)/history.Plo \
+ ./$(DEPDIR)/hline.Plo ./$(DEPDIR)/input.Plo \
+ ./$(DEPDIR)/input_complete.Plo ./$(DEPDIR)/label.Plo \
+ ./$(DEPDIR)/listbox-window.Plo ./$(DEPDIR)/listbox.Plo \
+ ./$(DEPDIR)/menu.Plo ./$(DEPDIR)/mouse.Plo \
+ ./$(DEPDIR)/quick.Plo ./$(DEPDIR)/radio.Plo \
+ ./$(DEPDIR)/rect.Plo ./$(DEPDIR)/widget-common.Plo \
+ ./$(DEPDIR)/wtools.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcwidget_la_SOURCES)
+DIST_SOURCES = $(libmcwidget_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcwidget.la
+libmcwidget_la_SOURCES = \
+ background.c background.h \
+ button.c button.h \
+ buttonbar.c buttonbar.h \
+ check.c check.h \
+ dialog.c dialog.h \
+ dialog-switch.c dialog-switch.h \
+ frame.c frame.h \
+ gauge.c gauge.h \
+ group.c group.h \
+ groupbox.c groupbox.h \
+ hline.c hline.h \
+ history.c history.h \
+ input.c input.h \
+ input_complete.c \
+ listbox-window.c listbox-window.h \
+ listbox.c listbox.h \
+ label.c label.h \
+ menu.c menu.h \
+ mouse.c mouse.h \
+ quick.c quick.h \
+ radio.c radio.h \
+ rect.c rect.h \
+ widget-common.c widget-common.h \
+ wtools.c wtools.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/widget/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/widget/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcwidget.la: $(libmcwidget_la_OBJECTS) $(libmcwidget_la_DEPENDENCIES) $(EXTRA_libmcwidget_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcwidget_la_OBJECTS) $(libmcwidget_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/background.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/button.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buttonbar.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/check.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dialog-switch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dialog.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/frame.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gauge.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/group.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/groupbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/history.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hline.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input_complete.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/label.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listbox-window.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/menu.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mouse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quick.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/radio.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rect.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/widget-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wtools.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/background.Plo
+ -rm -f ./$(DEPDIR)/button.Plo
+ -rm -f ./$(DEPDIR)/buttonbar.Plo
+ -rm -f ./$(DEPDIR)/check.Plo
+ -rm -f ./$(DEPDIR)/dialog-switch.Plo
+ -rm -f ./$(DEPDIR)/dialog.Plo
+ -rm -f ./$(DEPDIR)/frame.Plo
+ -rm -f ./$(DEPDIR)/gauge.Plo
+ -rm -f ./$(DEPDIR)/group.Plo
+ -rm -f ./$(DEPDIR)/groupbox.Plo
+ -rm -f ./$(DEPDIR)/history.Plo
+ -rm -f ./$(DEPDIR)/hline.Plo
+ -rm -f ./$(DEPDIR)/input.Plo
+ -rm -f ./$(DEPDIR)/input_complete.Plo
+ -rm -f ./$(DEPDIR)/label.Plo
+ -rm -f ./$(DEPDIR)/listbox-window.Plo
+ -rm -f ./$(DEPDIR)/listbox.Plo
+ -rm -f ./$(DEPDIR)/menu.Plo
+ -rm -f ./$(DEPDIR)/mouse.Plo
+ -rm -f ./$(DEPDIR)/quick.Plo
+ -rm -f ./$(DEPDIR)/radio.Plo
+ -rm -f ./$(DEPDIR)/rect.Plo
+ -rm -f ./$(DEPDIR)/widget-common.Plo
+ -rm -f ./$(DEPDIR)/wtools.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/background.Plo
+ -rm -f ./$(DEPDIR)/button.Plo
+ -rm -f ./$(DEPDIR)/buttonbar.Plo
+ -rm -f ./$(DEPDIR)/check.Plo
+ -rm -f ./$(DEPDIR)/dialog-switch.Plo
+ -rm -f ./$(DEPDIR)/dialog.Plo
+ -rm -f ./$(DEPDIR)/frame.Plo
+ -rm -f ./$(DEPDIR)/gauge.Plo
+ -rm -f ./$(DEPDIR)/group.Plo
+ -rm -f ./$(DEPDIR)/groupbox.Plo
+ -rm -f ./$(DEPDIR)/history.Plo
+ -rm -f ./$(DEPDIR)/hline.Plo
+ -rm -f ./$(DEPDIR)/input.Plo
+ -rm -f ./$(DEPDIR)/input_complete.Plo
+ -rm -f ./$(DEPDIR)/label.Plo
+ -rm -f ./$(DEPDIR)/listbox-window.Plo
+ -rm -f ./$(DEPDIR)/listbox.Plo
+ -rm -f ./$(DEPDIR)/menu.Plo
+ -rm -f ./$(DEPDIR)/mouse.Plo
+ -rm -f ./$(DEPDIR)/quick.Plo
+ -rm -f ./$(DEPDIR)/radio.Plo
+ -rm -f ./$(DEPDIR)/rect.Plo
+ -rm -f ./$(DEPDIR)/widget-common.Plo
+ -rm -f ./$(DEPDIR)/wtools.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/widget/background.c b/lib/widget/background.c
new file mode 100644
index 0000000..1965dee
--- /dev/null
+++ b/lib/widget/background.c
@@ -0,0 +1,126 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 2020-2023
+ The Free Software Foundation, Inc.
+
+ Authors:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file background.c
+ * \brief Source: WBackground widget (background area of dialog)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static const int *
+background_get_colors (const Widget * w)
+{
+ return &(CONST_BACKGROUND (w)->color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+background_adjust (WBackground * b)
+{
+ Widget *w = WIDGET (b);
+
+ w->rect = WIDGET (w->owner)->rect;
+ w->pos_flags |= WPOS_KEEP_ALL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+background_draw (const WBackground * b)
+{
+ const Widget *w = CONST_WIDGET (b);
+
+ tty_setcolor (b->color);
+ tty_fill_region (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, b->pattern);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+background_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WBackground *b = BACKGROUND (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ background_adjust (b);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ background_draw (b);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WBackground *
+background_new (int y, int x, int lines, int cols, int color, unsigned char pattern,
+ widget_cb_fn callback)
+{
+ WRect r = { y, x, lines, cols };
+ WBackground *b;
+ Widget *w;
+
+ b = g_new (WBackground, 1);
+ w = WIDGET (b);
+ widget_init (w, &r, callback != NULL ? callback : background_callback, NULL);
+ w->get_colors = background_get_colors;
+
+ b->color = color;
+ b->pattern = pattern;
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/background.h b/lib/widget/background.h
new file mode 100644
index 0000000..b9a0b2c
--- /dev/null
+++ b/lib/widget/background.h
@@ -0,0 +1,36 @@
+
+/** \file background.h
+ * \brief Header: WBackground widget
+ */
+
+#ifndef MC__WIDGET_BACKGROUND_H
+#define MC__WIDGET_BACKGROUND_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define BACKGROUND(x) ((WBackground *)(x))
+#define CONST_BACKGROUND(x) ((const WBackground *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+
+ int color; /* Color to fill area */
+ unsigned char pattern; /* Symbol to fill area */
+} WBackground;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WBackground *background_new (int y, int x, int lines, int cols, int color, unsigned char pattern,
+ widget_cb_fn callback);
+cb_ret_t background_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_BACKGROUND_H */
diff --git a/lib/widget/button.c b/lib/widget/button.c
new file mode 100644
index 0000000..9f0bfa5
--- /dev/null
+++ b/lib/widget/button.c
@@ -0,0 +1,284 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file button.c
+ * \brief Source: WButton widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+button_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WButton *b = BUTTON (w);
+ WGroup *g = w->owner;
+ WDialog *h = DIALOG (g);
+ int off = 0;
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ /*
+ * Don't let the default button steal Enter from the current
+ * button. This is a workaround for the flawed event model
+ * when hotkeys are sent to all widgets before the key is
+ * handled by the current widget.
+ */
+ if (parm == '\n' && WIDGET (g->current->data) == w)
+ {
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+
+ if (parm == '\n' && b->flags == DEFPUSH_BUTTON)
+ {
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+
+ if (b->text.hotkey != NULL && g_ascii_tolower ((gchar) b->text.hotkey[0]) == parm)
+ {
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ if (parm != ' ' && parm != '\n')
+ return MSG_NOT_HANDLED;
+
+ h->ret_value = b->action;
+ if (b->callback == NULL || b->callback (b, b->action) != 0)
+ dlg_close (h);
+
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ off = 3;
+ break;
+ case NORMAL_BUTTON:
+ off = 2;
+ break;
+ case NARROW_BUTTON:
+ off = 1;
+ break;
+ case HIDDEN_BUTTON:
+ default:
+ off = 0;
+ break;
+ }
+ widget_gotoyx (w, 0, b->hotpos + off);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ {
+ gboolean focused;
+
+ focused = widget_get_state (w, WST_FOCUSED);
+
+ widget_selectcolor (w, focused, FALSE);
+ widget_gotoyx (w, 0, 0);
+
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ tty_print_string ("[< ");
+ break;
+ case NORMAL_BUTTON:
+ tty_print_string ("[ ");
+ break;
+ case NARROW_BUTTON:
+ tty_print_string ("[");
+ break;
+ case HIDDEN_BUTTON:
+ default:
+ return MSG_HANDLED;
+ }
+
+ hotkey_draw (w, b->text, focused);
+
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ tty_print_string (" >]");
+ break;
+ case NORMAL_BUTTON:
+ tty_print_string (" ]");
+ break;
+ case NARROW_BUTTON:
+ tty_print_string ("]");
+ break;
+ default:
+ break;
+ }
+
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ hotkey_free (b->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+button_mouse_default_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ (void) event;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ send_message (w, NULL, MSG_KEY, ' ', NULL);
+ send_message (w->owner, w, MSG_POST_KEY, ' ', NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WButton *
+button_new (int y, int x, int action, button_flags_t flags, const char *text, bcback_fn callback)
+{
+ WRect r = { y, x, 1, 1 };
+ WButton *b;
+ Widget *w;
+
+ b = g_new (WButton, 1);
+ w = WIDGET (b);
+
+ b->action = action;
+ b->flags = flags;
+ b->text = hotkey_new (text);
+ r.cols = button_get_len (b);
+ widget_init (w, &r, button_default_callback, button_mouse_default_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR | WOP_WANT_HOTKEY;
+ b->callback = callback;
+ b->hotpos = (b->text.hotkey != NULL) ? str_term_width1 (b->text.start) : -1;
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+button_get_text (const WButton * b)
+{
+ return hotkey_get_text (b->text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+button_set_text (WButton * b, const char *text)
+{
+ Widget *w = WIDGET (b);
+ hotkey_t hk;
+
+ hk = hotkey_new (text);
+ if (hotkey_equal (b->text, hk))
+ {
+ hotkey_free (hk);
+ return;
+ }
+
+ hotkey_free (b->text);
+ b->text = hk;
+ b->hotpos = (b->text.hotkey != NULL) ? str_term_width1 (b->text.start) : -1;
+ w->rect.cols = button_get_len (b);
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+button_get_len (const WButton * b)
+{
+ int ret = hotkey_width (b->text);
+
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ ret += 6;
+ break;
+ case NORMAL_BUTTON:
+ ret += 4;
+ break;
+ case NARROW_BUTTON:
+ ret += 2;
+ break;
+ case HIDDEN_BUTTON:
+ default:
+ return 0;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/button.h b/lib/widget/button.h
new file mode 100644
index 0000000..5f21e1e
--- /dev/null
+++ b/lib/widget/button.h
@@ -0,0 +1,58 @@
+
+/** \file button.h
+ * \brief Header: WButton widget
+ */
+
+#ifndef MC__WIDGET_BUTTON_H
+#define MC__WIDGET_BUTTON_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define BUTTON(x) ((WButton *)(x))
+
+struct WButton;
+
+/* button callback */
+/* return 0 to continue work with dialog, non-zero to close */
+typedef int (*bcback_fn) (struct WButton * button, int action);
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ HIDDEN_BUTTON = 0,
+ NARROW_BUTTON = 1,
+ NORMAL_BUTTON = 2,
+ DEFPUSH_BUTTON = 3
+} button_flags_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WButton
+{
+ Widget widget;
+ int action; /* what to do when pressed */
+
+ button_flags_t flags; /* button flags */
+ hotkey_t text; /* text of button, contain hotkey too */
+ int hotpos; /* offset hot KEY char in text */
+ bcback_fn callback; /* callback function */
+} WButton;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WButton *button_new (int y, int x, int action, button_flags_t flags, const char *text,
+ bcback_fn callback);
+char *button_get_text (const WButton * b);
+void button_set_text (WButton * b, const char *text);
+int button_get_len (const WButton * b);
+
+cb_ret_t button_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+void button_mouse_default_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_BUTTON_H */
diff --git a/lib/widget/buttonbar.c b/lib/widget/buttonbar.c
new file mode 100644
index 0000000..4522000
--- /dev/null
+++ b/lib/widget/buttonbar.c
@@ -0,0 +1,290 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file buttonbar.c
+ * \brief Source: WButtonBar widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* calculate positions of buttons; width is never less than 7 */
+static void
+buttonbar_init_button_positions (WButtonBar * bb)
+{
+ int i;
+ int pos = 0;
+
+ if (COLS < BUTTONBAR_LABELS_NUM * 7)
+ {
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ {
+ if (pos + 7 <= COLS)
+ pos += 7;
+
+ bb->labels[i].end_coord = pos;
+ }
+ }
+ else
+ {
+ /* Distribute the extra width in a way that the middle vertical line
+ (between F5 and F6) aligns with the two panels. The extra width
+ is distributed in this order: F10, F5, F9, F4, ..., F6, F1. */
+ int dv, md;
+
+ dv = COLS / BUTTONBAR_LABELS_NUM;
+ md = COLS % BUTTONBAR_LABELS_NUM;
+
+ for (i = 0; i < BUTTONBAR_LABELS_NUM / 2; i++)
+ {
+ pos += dv;
+ if (BUTTONBAR_LABELS_NUM / 2 - 1 - i < md / 2)
+ pos++;
+
+ bb->labels[i].end_coord = pos;
+ }
+
+ for (; i < BUTTONBAR_LABELS_NUM; i++)
+ {
+ pos += dv;
+ if (BUTTONBAR_LABELS_NUM - 1 - i < (md + 1) / 2)
+ pos++;
+
+ bb->labels[i].end_coord = pos;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* return width of one button */
+static int
+buttonbar_get_button_width (const WButtonBar * bb, int i)
+{
+ if (i == 0)
+ return bb->labels[0].end_coord;
+ return bb->labels[i].end_coord - bb->labels[i - 1].end_coord;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+buttonbar_get_button_by_x_coord (const WButtonBar * bb, int x)
+{
+ int i;
+
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ if (bb->labels[i].end_coord > x)
+ return i;
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_label_text (WButtonBar * bb, int idx, const char *text)
+{
+ g_free (bb->labels[idx - 1].text);
+ bb->labels[idx - 1].text = g_strdup (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* returns TRUE if a function has been called, FALSE otherwise. */
+static gboolean
+buttonbar_call (WButtonBar * bb, int i)
+{
+ cb_ret_t ret = MSG_NOT_HANDLED;
+ Widget *w = WIDGET (bb);
+ Widget *target;
+
+ if ((bb != NULL) && (bb->labels[i].command != CK_IgnoreKey))
+ {
+ target = (bb->labels[i].receiver != NULL) ? bb->labels[i].receiver : WIDGET (w->owner);
+ ret = send_message (target, w, MSG_ACTION, bb->labels[i].command, NULL);
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+buttonbar_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WButtonBar *bb = BUTTONBAR (w);
+ int i;
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ if (parm == KEY_F (i + 1) && buttonbar_call (bb, i))
+ return MSG_HANDLED;
+ return MSG_NOT_HANDLED;
+
+ case MSG_DRAW:
+ if (widget_get_state (w, WST_VISIBLE))
+ {
+ buttonbar_init_button_positions (bb);
+ widget_gotoyx (w, 0, 0);
+ tty_setcolor (DEFAULT_COLOR);
+ tty_printf ("%-*s", w->rect.cols, "");
+ widget_gotoyx (w, 0, 0);
+
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ {
+ int width;
+ const char *text;
+
+ width = buttonbar_get_button_width (bb, i);
+ if (width <= 0)
+ break;
+
+ tty_setcolor (BUTTONBAR_HOTKEY_COLOR);
+ tty_printf ("%2d", i + 1);
+
+ tty_setcolor (BUTTONBAR_BUTTON_COLOR);
+ text = (bb->labels[i].text != NULL) ? bb->labels[i].text : "";
+ tty_print_string (str_fit_to_term (text, width - 2, J_LEFT_FIT));
+ }
+ }
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ g_free (bb->labels[i].text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+buttonbar_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_CLICK:
+ {
+ WButtonBar *bb = BUTTONBAR (w);
+ int button;
+
+ button = buttonbar_get_button_by_x_coord (bb, event->x);
+ if (button >= 0)
+ buttonbar_call (bb, button);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WButtonBar *
+buttonbar_new (void)
+{
+ WRect r = { LINES - 1, 0, 1, COLS };
+ WButtonBar *bb;
+ Widget *w;
+
+ bb = g_new0 (WButtonBar, 1);
+ w = WIDGET (bb);
+ widget_init (w, &r, buttonbar_callback, buttonbar_mouse_callback);
+
+ w->pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_BOTTOM;
+ w->options |= WOP_WANT_HOTKEY;
+
+ return bb;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+buttonbar_set_label (WButtonBar * bb, int idx, const char *text, const global_keymap_t * keymap,
+ Widget * receiver)
+{
+ if ((bb != NULL) && (idx >= 1) && (idx <= BUTTONBAR_LABELS_NUM))
+ {
+ long command = CK_IgnoreKey;
+
+ if (keymap != NULL)
+ command = keybind_lookup_keymap_command (keymap, KEY_F (idx));
+
+ if ((text == NULL) || (text[0] == '\0'))
+ set_label_text (bb, idx, "");
+ else
+ set_label_text (bb, idx, text);
+
+ bb->labels[idx - 1].command = command;
+ bb->labels[idx - 1].receiver = WIDGET (receiver);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Find ButtonBar widget in the dialog */
+WButtonBar *
+buttonbar_find (const WDialog * h)
+{
+ return BUTTONBAR (widget_find_by_type (CONST_WIDGET (h), buttonbar_callback));
+}
diff --git a/lib/widget/buttonbar.h b/lib/widget/buttonbar.h
new file mode 100644
index 0000000..af9249c
--- /dev/null
+++ b/lib/widget/buttonbar.h
@@ -0,0 +1,46 @@
+
+/** \file buttonbar.h
+ * \brief Header: WButtonBar widget
+ */
+
+#ifndef MC__WIDGET_BUTTONBAR_H
+#define MC__WIDGET_BUTTONBAR_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define BUTTONBAR(x) ((WButtonBar *)(x))
+
+/* number of bttons in buttonbar */
+#define BUTTONBAR_LABELS_NUM 10
+
+#define buttonbar_clear_label(bb, idx, recv) buttonbar_set_label (bb, idx, "", NULL, recv)
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WButtonBar
+{
+ Widget widget;
+
+ struct
+ {
+ char *text;
+ long command;
+ Widget *receiver;
+ int end_coord; /* cumulative width of buttons so far */
+ } labels[BUTTONBAR_LABELS_NUM];
+} WButtonBar;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WButtonBar *buttonbar_new (void);
+void buttonbar_set_label (WButtonBar * bb, int idx, const char *text,
+ const global_keymap_t * keymap, Widget * receiver);
+WButtonBar *buttonbar_find (const WDialog * h);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_BUTTONBAR_H */
diff --git a/lib/widget/check.c b/lib/widget/check.c
new file mode 100644
index 0000000..63c55e3
--- /dev/null
+++ b/lib/widget/check.c
@@ -0,0 +1,182 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file check.c
+ * \brief Source: WCheck widget (checkbutton)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+check_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WCheck *c = CHECK (w);
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ if (c->text.hotkey != NULL)
+ {
+ if (g_ascii_tolower ((gchar) c->text.hotkey[0]) == parm)
+ {
+ /* make action */
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ if (parm != ' ')
+ return MSG_NOT_HANDLED;
+ c->state = !c->state;
+ widget_draw (w);
+ send_message (w->owner, w, MSG_NOTIFY, 0, NULL);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ widget_gotoyx (w, 0, 1);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ {
+ gboolean focused;
+
+ focused = widget_get_state (w, WST_FOCUSED);
+ widget_selectcolor (w, focused, FALSE);
+ widget_gotoyx (w, 0, 0);
+ tty_print_string (c->state ? "[x] " : "[ ] ");
+ hotkey_draw (w, c->text, focused);
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ hotkey_free (c->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ (void) event;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ send_message (w, NULL, MSG_KEY, ' ', NULL);
+ send_message (w->owner, w, MSG_POST_KEY, ' ', NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WCheck *
+check_new (int y, int x, gboolean state, const char *text)
+{
+ WRect r = { y, x, 1, 1 };
+ WCheck *c;
+ Widget *w;
+
+ c = g_new (WCheck, 1);
+ w = WIDGET (c);
+ c->text = hotkey_new (text);
+ /* 4 is width of "[X] " */
+ r.cols = 4 + hotkey_width (c->text);
+ widget_init (w, &r, check_callback, check_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR | WOP_WANT_HOTKEY;
+ c->state = state;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+check_set_text (WCheck * check, const char *text)
+{
+ Widget *w = WIDGET (check);
+ hotkey_t hk;
+
+ hk = hotkey_new (text);
+ if (hotkey_equal (check->text, hk))
+ {
+ hotkey_free (hk);
+ return;
+ }
+
+ hotkey_free (check->text);
+ check->text = hk;
+
+ if (check->text.start[0] == '\0' && check->text.hotkey == NULL && check->text.end == NULL)
+ w->rect.cols = 3; /* "[ ]" */
+ else
+ w->rect.cols = 4 + hotkey_width (check->text); /* "[ ] text" */
+
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/check.h b/lib/widget/check.h
new file mode 100644
index 0000000..9840a24
--- /dev/null
+++ b/lib/widget/check.h
@@ -0,0 +1,33 @@
+
+/** \file check.h
+ * \brief Header: WCheck widget
+ */
+
+#ifndef MC__WIDGET_CHECK_H
+#define MC__WIDGET_CHECK_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define CHECK(x) ((WCheck *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WCheck
+{
+ Widget widget;
+ gboolean state; /* check button state */
+ hotkey_t text; /* text of check button */
+} WCheck;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WCheck *check_new (int y, int x, gboolean state, const char *text);
+void check_set_text (WCheck * check, const char *text);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_CHECK_H */
diff --git a/lib/widget/dialog-switch.c b/lib/widget/dialog-switch.c
new file mode 100644
index 0000000..959cbf9
--- /dev/null
+++ b/lib/widget/dialog-switch.c
@@ -0,0 +1,409 @@
+/*
+ Support of multiply editors and viewers.
+
+ Original idea and code: Oleg "Olegarch" Konovalov <olegarch@linuxinside.com>
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Daniel Borca <dborca@yahoo.com>, 2007
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file dialog-switch.c
+ * \brief Source: support of multiply editors and viewers.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/tty/color.h" /* tty_set_normal_attrs() */
+#include "lib/widget.h"
+#include "lib/event.h"
+
+/*** global variables ****************************************************************************/
+
+/* Primitive way to check if the the current dialog is our dialog */
+/* This is needed by async routines like load_prompt */
+GList *top_dlg = NULL;
+
+/* If set then dialogs just clean the screen when refreshing, else */
+/* they do a complete refresh, refreshing all the parts of the program */
+gboolean fast_refresh = FALSE;
+
+WDialog *filemanager = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* List of dialogs: filemanagers, editors, viewers */
+static GList *mc_dialogs = NULL;
+/* Currently active dialog */
+static GList *mc_current = NULL;
+/* Is there any dialogs that we have to run after returning to the manager from another dialog */
+static gboolean dialog_switch_pending = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned char
+get_hotkey (int n)
+{
+ return (n <= 9) ? '0' + n : 'a' + n - 10;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dialog_switch_suspend (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (data != mc_current->data)
+ widget_set_state (WIDGET (data), WST_SUSPENDED, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dialog_switch_goto (GList * dlg)
+{
+ if (mc_current != dlg)
+ {
+ WDialog *old = DIALOG (mc_current->data);
+
+ mc_current = dlg;
+
+ if (old == filemanager)
+ {
+ /* switch from panels to another dialog (editor, viewer, etc) */
+ dialog_switch_pending = TRUE;
+ dialog_switch_process_pending ();
+ }
+ else
+ {
+ /* switch from editor, viewer, etc to another dialog */
+ widget_set_state (WIDGET (old), WST_SUSPENDED, TRUE);
+
+ if (DIALOG (dlg->data) != filemanager)
+ /* switch to another editor, viewer, etc */
+ /* return to panels before run the required dialog */
+ dialog_switch_pending = TRUE;
+ else
+ {
+ /* switch to panels */
+ widget_set_state (WIDGET (filemanager), WST_ACTIVE, TRUE);
+ do_refresh ();
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dialog_switch_resize (WDialog * d)
+{
+ if (widget_get_state (WIDGET (d), WST_ACTIVE))
+ send_message (d, NULL, MSG_RESIZE, 0, NULL);
+ else
+ GROUP (d)->winch_pending = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_add (WDialog * h)
+{
+ GList *dlg;
+
+ dlg = g_list_find (mc_dialogs, h);
+
+ if (dlg != NULL)
+ mc_current = dlg;
+ else
+ {
+ mc_dialogs = g_list_prepend (mc_dialogs, h);
+ mc_current = mc_dialogs;
+ }
+
+ /* suspend forced all other screens */
+ g_list_foreach (mc_dialogs, dialog_switch_suspend, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_remove (WDialog * h)
+{
+ GList *this;
+
+ if (DIALOG (mc_current->data) == h)
+ this = mc_current;
+ else
+ this = g_list_find (mc_dialogs, h);
+
+ mc_dialogs = g_list_delete_link (mc_dialogs, this);
+
+ /* adjust current dialog */
+ if (top_dlg != NULL)
+ mc_current = g_list_find (mc_dialogs, DIALOG (top_dlg->data));
+ else
+ mc_current = mc_dialogs;
+
+ /* resume forced the current screen */
+ if (mc_current != NULL)
+ widget_set_state (WIDGET (mc_current->data), WST_ACTIVE, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+size_t
+dialog_switch_num (void)
+{
+ return g_list_length (mc_dialogs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_next (void)
+{
+ GList *next;
+
+ if (mc_global.midnight_shutdown || mc_current == NULL)
+ return;
+
+ next = g_list_next (mc_current);
+ if (next == NULL)
+ next = mc_dialogs;
+
+ dialog_switch_goto (next);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_prev (void)
+{
+ GList *prev;
+
+ if (mc_global.midnight_shutdown || mc_current == NULL)
+ return;
+
+ prev = g_list_previous (mc_current);
+ if (prev == NULL)
+ prev = g_list_last (mc_dialogs);
+
+ dialog_switch_goto (prev);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_list (void)
+{
+ const size_t dlg_num = g_list_length (mc_dialogs);
+ int lines, cols;
+ Listbox *listbox;
+ GList *h, *selected;
+ int i = 0;
+
+ if (mc_global.midnight_shutdown || mc_current == NULL)
+ return;
+
+ lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
+ cols = COLS * 2 / 3;
+
+ listbox = listbox_window_new (lines, cols, _("Screens"), "[Screen selector]");
+
+ for (h = mc_dialogs; h != NULL; h = g_list_next (h))
+ {
+ WDialog *dlg = DIALOG (h->data);
+ char *title;
+
+ if (dlg->get_title != NULL)
+ title = dlg->get_title (dlg, WIDGET (listbox->list)->rect.cols - 2);
+ else
+ title = g_strdup ("");
+
+ listbox_add_item (listbox->list, LISTBOX_APPEND_BEFORE, get_hotkey (i++), title, h, FALSE);
+
+ g_free (title);
+ }
+
+ selected = listbox_run_with_data (listbox, mc_current);
+ if (selected != NULL)
+ dialog_switch_goto (selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+dialog_switch_process_pending (void)
+{
+ int ret = 0;
+
+ while (dialog_switch_pending)
+ {
+ WDialog *h = DIALOG (mc_current->data);
+ Widget *wh = WIDGET (h);
+
+ dialog_switch_pending = FALSE;
+ widget_set_state (wh, WST_SUSPENDED, TRUE);
+ ret = dlg_run (h);
+ if (widget_get_state (wh, WST_CLOSED))
+ {
+ widget_destroy (wh);
+
+ /* return to panels */
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ {
+ mc_current = g_list_find (mc_dialogs, filemanager);
+ mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "update_panels", NULL);
+ }
+ }
+ }
+
+ repaint_screen ();
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_got_winch (void)
+{
+ GList *dlg;
+
+ for (dlg = mc_dialogs; dlg != NULL; dlg = g_list_next (dlg))
+ if (dlg != mc_current)
+ GROUP (dlg->data)->winch_pending = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_shutdown (void)
+{
+ while (mc_dialogs != NULL)
+ {
+ WDialog *dlg = DIALOG (mc_dialogs->data);
+
+ dlg_run (dlg);
+ widget_destroy (WIDGET (dlg));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+do_refresh (void)
+{
+ GList *d = top_dlg;
+
+ if (fast_refresh)
+ {
+ if (d != NULL)
+ widget_draw (WIDGET (d->data));
+ }
+ else
+ {
+ /* Search first fullscreen dialog */
+ for (; d != NULL; d = g_list_next (d))
+ if ((WIDGET (d->data)->pos_flags & WPOS_FULLSCREEN) != 0)
+ break;
+
+ /* when small dialog (i.e. error message) is created first,
+ there is no fullscreen dialog in the stack */
+ if (d == NULL)
+ d = g_list_last (top_dlg);
+
+ /* back to top dialog */
+ for (; d != NULL; d = g_list_previous (d))
+ widget_draw (WIDGET (d->data));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+repaint_screen (void)
+{
+ do_refresh ();
+ tty_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_refresh (void)
+{
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ return;
+#endif /* ENABLE_BACKGROUND */
+
+ if (!tty_got_winch ())
+ tty_refresh ();
+ else
+ {
+ /* if winch was caugth, we should do not only redraw screen, but
+ reposition/resize all */
+ dialog_change_screen_size ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_change_screen_size (void)
+{
+ GList *d;
+
+ tty_flush_winch ();
+ tty_change_screen_size ();
+
+#ifdef HAVE_SLANG
+ tty_keypad (TRUE);
+ tty_nodelay (FALSE);
+#endif
+
+ /* Inform all suspending dialogs */
+ dialog_switch_got_winch ();
+
+ /* Inform all running dialogs from first to last */
+ for (d = g_list_last (top_dlg); d != NULL; d = g_list_previous (d))
+ dialog_switch_resize (DIALOG (d->data));
+
+ /* Now, force the redraw */
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/dialog-switch.h b/lib/widget/dialog-switch.h
new file mode 100644
index 0000000..5163095
--- /dev/null
+++ b/lib/widget/dialog-switch.h
@@ -0,0 +1,44 @@
+
+#ifndef MC__DIALOG_SWITCH_H
+#define MC__DIALOG_SWITCH_H
+
+#include <sys/types.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern GList *top_dlg;
+
+extern gboolean fast_refresh;
+
+extern WDialog *filemanager;
+
+/*** declarations of public functions ************************************************************/
+
+void dialog_switch_add (WDialog * h);
+void dialog_switch_remove (WDialog * h);
+size_t dialog_switch_num (void);
+
+void dialog_switch_next (void);
+void dialog_switch_prev (void);
+void dialog_switch_list (void);
+
+int dialog_switch_process_pending (void);
+void dialog_switch_got_winch (void);
+void dialog_switch_shutdown (void);
+
+/* Redraw all dialogs */
+void do_refresh (void);
+
+void repaint_screen (void);
+void mc_refresh (void);
+void dialog_change_screen_size (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__DIALOG_SWITCH_H */
diff --git a/lib/widget/dialog.c b/lib/widget/dialog.c
new file mode 100644
index 0000000..3ab2191
--- /dev/null
+++ b/lib/widget/dialog.c
@@ -0,0 +1,626 @@
+/*
+ Dialog box features module for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file dialog.c
+ * \brief Source: dialog box features module
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/tty/key.h"
+#include "lib/strutil.h"
+#include "lib/fileloc.h" /* MC_HISTORY_FILE */
+#include "lib/event.h" /* mc_event_raise() */
+#include "lib/util.h" /* MC_PTR_FREE */
+#include "lib/mcconfig.h" /* num_history_items_recorded */
+
+#include "lib/widget.h"
+#include "lib/widget/mouse.h"
+
+/*** global variables ****************************************************************************/
+
+/* Color styles for normal and error dialogs */
+dlg_colors_t dialog_colors;
+dlg_colors_t alarm_colors;
+dlg_colors_t listbox_colors;
+
+/* A hook list for idle events */
+hook_t *idle_hook = NULL;
+
+/* left click outside of dialog closes it */
+gboolean mouse_close_dialog = FALSE;
+
+const global_keymap_t *dialog_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static const int *
+dlg_default_get_colors (const Widget * w)
+{
+ return CONST_DIALOG (w)->colors;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read histories from the ${XDG_CACHE_HOME}/mc/history file
+ */
+static void
+dlg_read_history (WDialog * h)
+{
+ char *profile;
+ ev_history_load_save_t event_data;
+
+ if (num_history_items_recorded == 0) /* this is how to disable */
+ return;
+
+ profile = mc_config_get_full_path (MC_HISTORY_FILE);
+ event_data.cfg = mc_config_init (profile, TRUE);
+ event_data.receiver = NULL;
+
+ /* create all histories in dialog */
+ mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data);
+
+ mc_config_deinit (event_data.cfg);
+ g_free (profile);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+refresh_cmd (void)
+{
+#ifdef HAVE_SLANG
+ tty_touch_screen ();
+ mc_refresh ();
+#else
+ /* Use this if the refreshes fail */
+ tty_clear_screen ();
+ repaint_screen ();
+#endif /* HAVE_SLANG */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dlg_execute_cmd (WDialog * h, long command)
+{
+ WGroup *g = GROUP (h);
+ cb_ret_t ret = MSG_HANDLED;
+
+ if (send_message (h, NULL, MSG_ACTION, command, NULL) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Ok:
+ h->ret_value = B_ENTER;
+ dlg_close (h);
+ break;
+ case CK_Cancel:
+ h->ret_value = B_CANCEL;
+ dlg_close (h);
+ break;
+
+ case CK_Up:
+ case CK_Left:
+ group_select_prev_widget (g);
+ break;
+ case CK_Down:
+ case CK_Right:
+ group_select_next_widget (g);
+ break;
+
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, h->help_ctx };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ }
+ break;
+
+ case CK_Suspend:
+ mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
+ refresh_cmd ();
+ break;
+ case CK_Refresh:
+ refresh_cmd ();
+ break;
+
+ case CK_ScreenList:
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_list ();
+ else
+ ret = MSG_NOT_HANDLED;
+ break;
+ case CK_ScreenNext:
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_next ();
+ else
+ ret = MSG_NOT_HANDLED;
+ break;
+ case CK_ScreenPrev:
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_prev ();
+ else
+ ret = MSG_NOT_HANDLED;
+ break;
+
+ default:
+ ret = MSG_NOT_HANDLED;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dlg_handle_key (WDialog * h, int d_key)
+{
+ long command;
+
+ command = widget_lookup_key (WIDGET (h), d_key);
+ if (command == CK_IgnoreKey)
+ command = keybind_lookup_keymap_command (dialog_map, d_key);
+ if (command != CK_IgnoreKey)
+ return dlg_execute_cmd (h, command);
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dlg_key_event (WDialog * h, int d_key)
+{
+ Widget *w = WIDGET (h);
+ WGroup *g = GROUP (h);
+ cb_ret_t handled;
+
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ /* TAB used to cycle */
+ if (!widget_get_options (w, WOP_WANT_TAB))
+ {
+ if (d_key == '\t')
+ {
+ group_select_next_widget (g);
+ return;
+ }
+ else if ((d_key & ~(KEY_M_SHIFT | KEY_M_CTRL)) == '\t')
+ {
+ group_select_prev_widget (g);
+ return;
+ }
+ }
+
+ /* first can dlalog handle the key itself */
+ handled = send_message (h, NULL, MSG_KEY, d_key, NULL);
+
+ if (handled == MSG_NOT_HANDLED)
+ handled = group_default_callback (w, NULL, MSG_KEY, d_key, NULL);
+
+ if (handled == MSG_NOT_HANDLED)
+ handled = dlg_handle_key (h, d_key);
+
+ (void) handled;
+ send_message (h, NULL, MSG_POST_KEY, d_key, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+dlg_handle_mouse_event (Widget * w, Gpm_Event * event)
+{
+ if (w->mouse_callback != NULL)
+ {
+ int mou;
+
+ mou = mouse_handle_event (w, event);
+ if (mou != MOU_UNHANDLED)
+ return mou;
+ }
+
+ return group_handle_mouse_event (w, event);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+frontend_dlg_run (WDialog * h)
+{
+ Widget *wh = WIDGET (h);
+ Gpm_Event event;
+
+ event.x = -1;
+
+ /* close opened editors, viewers, etc */
+ if (!widget_get_state (wh, WST_MODAL) && mc_global.midnight_shutdown)
+ {
+ send_message (h, NULL, MSG_VALIDATE, 0, NULL);
+ return;
+ }
+
+ while (widget_get_state (wh, WST_ACTIVE))
+ {
+ int d_key;
+
+ if (tty_got_winch ())
+ dialog_change_screen_size ();
+
+ if (is_idle ())
+ {
+ if (idle_hook)
+ execute_hooks (idle_hook);
+
+ while (widget_get_state (wh, WST_IDLE) && is_idle ())
+ send_message (wh, NULL, MSG_IDLE, 0, NULL);
+
+ /* Allow terminating the dialog from the idle handler */
+ if (!widget_get_state (wh, WST_ACTIVE))
+ break;
+ }
+
+ widget_update_cursor (wh);
+
+ /* Clear interrupt flag */
+ tty_got_interrupt ();
+ d_key = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, TRUE);
+
+ dlg_process_event (h, d_key, &event);
+
+ if (widget_get_state (wh, WST_CLOSED))
+ send_message (h, NULL, MSG_VALIDATE, 0, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dlg_default_destroy (Widget * w)
+{
+ WDialog *h = DIALOG (w);
+
+ /* if some widgets have history, save all histories at one moment here */
+ dlg_save_history (h);
+ group_default_callback (w, NULL, MSG_DESTROY, 0, NULL);
+ send_message (w, NULL, MSG_DESTROY, 0, NULL);
+ mc_event_group_del (h->event_group);
+ g_free (h->event_group);
+ g_free (h);
+
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Default dialog callback */
+
+cb_ret_t
+dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+ /* nothing to init in dialog itself */
+ return MSG_HANDLED;
+
+ case MSG_IDLE:
+ /* we don't want endless loop */
+ widget_idle (w, FALSE);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ /* nothing to deinit in dialog itself */
+ return MSG_HANDLED;
+
+ default:
+ return group_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_CLICK:
+ if (event->y < 0 || event->y >= w->rect.lines || event->x < 0 || event->x >= w->rect.cols)
+ {
+ DIALOG (w)->ret_value = B_CANCEL;
+ dlg_close (DIALOG (w));
+ }
+ break;
+
+ default:
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WDialog *
+dlg_create (gboolean modal, int y1, int x1, int lines, int cols, widget_pos_flags_t pos_flags,
+ gboolean compact, const int *colors, widget_cb_fn callback,
+ widget_mouse_cb_fn mouse_callback, const char *help_ctx, const char *title)
+{
+ WRect r = { y1, x1, lines, cols };
+ WDialog *new_d;
+ Widget *w;
+ WGroup *g;
+
+ new_d = g_new0 (WDialog, 1);
+ w = WIDGET (new_d);
+ g = GROUP (new_d);
+ widget_adjust_position (pos_flags, &r);
+ group_init (g, &r, callback != NULL ? callback : dlg_default_callback,
+ mouse_callback != NULL ? mouse_callback : dlg_default_mouse_callback);
+
+ w->pos_flags = pos_flags;
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->state |= WST_FOCUSED;
+ /* Temporary hack: dialog doesn't have an owner, own itself. */
+ w->owner = g;
+
+ w->keymap = dialog_map;
+
+ w->mouse_handler = dlg_handle_mouse_event;
+ w->mouse.forced_capture = mouse_close_dialog && (w->pos_flags & WPOS_FULLSCREEN) == 0;
+
+ w->destroy = dlg_default_destroy;
+ w->get_colors = dlg_default_get_colors;
+
+ new_d->colors = colors;
+ new_d->help_ctx = help_ctx;
+ new_d->compact = compact;
+ new_d->data.p = NULL;
+
+ if (modal)
+ {
+ w->state |= WST_MODAL;
+
+ new_d->bg =
+ WIDGET (frame_new (0, 0, w->rect.lines, w->rect.cols, title, FALSE, new_d->compact));
+ group_add_widget (g, new_d->bg);
+ frame_set_title (FRAME (new_d->bg), title);
+ }
+
+ /* unique name of event group for this dialog */
+ new_d->event_group = g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG, (void *) new_d);
+
+ return new_d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_set_default_colors (void)
+{
+ dialog_colors[DLG_COLOR_NORMAL] = COLOR_NORMAL;
+ dialog_colors[DLG_COLOR_FOCUS] = COLOR_FOCUS;
+ dialog_colors[DLG_COLOR_HOT_NORMAL] = COLOR_HOT_NORMAL;
+ dialog_colors[DLG_COLOR_HOT_FOCUS] = COLOR_HOT_FOCUS;
+ dialog_colors[DLG_COLOR_TITLE] = COLOR_TITLE;
+
+ alarm_colors[DLG_COLOR_NORMAL] = ERROR_COLOR;
+ alarm_colors[DLG_COLOR_FOCUS] = ERROR_FOCUS;
+ alarm_colors[DLG_COLOR_HOT_NORMAL] = ERROR_HOT_NORMAL;
+ alarm_colors[DLG_COLOR_HOT_FOCUS] = ERROR_HOT_FOCUS;
+ alarm_colors[DLG_COLOR_TITLE] = ERROR_TITLE;
+
+ listbox_colors[DLG_COLOR_NORMAL] = PMENU_ENTRY_COLOR;
+ listbox_colors[DLG_COLOR_FOCUS] = PMENU_SELECTED_COLOR;
+ listbox_colors[DLG_COLOR_HOT_NORMAL] = PMENU_ENTRY_COLOR;
+ listbox_colors[DLG_COLOR_HOT_FOCUS] = PMENU_SELECTED_COLOR;
+ listbox_colors[DLG_COLOR_TITLE] = PMENU_TITLE_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_close (WDialog * h)
+{
+ widget_set_state (WIDGET (h), WST_CLOSED, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Init the process */
+
+void
+dlg_init (WDialog * h)
+{
+ WGroup *g = GROUP (h);
+ Widget *wh = WIDGET (h);
+
+ if (top_dlg != NULL && widget_get_state (WIDGET (top_dlg->data), WST_MODAL))
+ widget_set_state (wh, WST_MODAL, TRUE);
+
+ /* add dialog to the stack */
+ top_dlg = g_list_prepend (top_dlg, h);
+
+ /* Initialize dialog manager and widgets */
+ if (widget_get_state (wh, WST_CONSTRUCT))
+ {
+ if (!widget_get_state (wh, WST_MODAL))
+ dialog_switch_add (h);
+
+ send_message (h, NULL, MSG_INIT, 0, NULL);
+ group_default_callback (wh, NULL, MSG_INIT, 0, NULL);
+ dlg_read_history (h);
+ }
+
+ /* Select the first widget that takes focus */
+ while (g->current != NULL && !widget_is_focusable (g->current->data))
+ group_set_current_widget_next (g);
+
+ widget_set_state (wh, WST_ACTIVE, TRUE);
+ widget_draw (wh);
+
+ h->ret_value = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_process_event (WDialog * h, int key, Gpm_Event * event)
+{
+ switch (key)
+ {
+ case EV_NONE:
+ if (tty_got_interrupt ())
+ dlg_execute_cmd (h, CK_Cancel);
+ break;
+
+ case EV_MOUSE:
+ {
+ Widget *w = WIDGET (h);
+
+ GROUP (h)->mouse_status = w->mouse_handler (w, event);
+ break;
+ }
+
+ default:
+ dlg_key_event (h, key);
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Shutdown the dlg_run */
+
+void
+dlg_run_done (WDialog * h)
+{
+ top_dlg = g_list_remove (top_dlg, h);
+
+ if (widget_get_state (WIDGET (h), WST_CLOSED))
+ {
+ send_message (h, GROUP (h)->current == NULL ? NULL : WIDGET (GROUP (h)->current->data),
+ MSG_END, 0, NULL);
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_remove (h);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Standard run dialog routine
+ * We have to keep this routine small so that we can duplicate it's
+ * behavior on complex routines like the file routines, this way,
+ * they can call the dlg_process_event without rewriting all the code
+ */
+
+int
+dlg_run (WDialog * h)
+{
+ dlg_init (h);
+ frontend_dlg_run (h);
+ dlg_run_done (h);
+ return h->ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Write history to the ${XDG_CACHE_HOME}/mc/history file
+ */
+void
+dlg_save_history (WDialog * h)
+{
+ char *profile;
+ int i;
+
+ if (num_history_items_recorded == 0) /* this is how to disable */
+ return;
+
+ profile = mc_config_get_full_path (MC_HISTORY_FILE);
+ i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if (i != -1)
+ close (i);
+
+ /* Make sure the history is only readable by the user */
+ if (chmod (profile, S_IRUSR | S_IWUSR) != -1 || errno == ENOENT)
+ {
+ ev_history_load_save_t event_data;
+
+ event_data.cfg = mc_config_init (profile, FALSE);
+ event_data.receiver = NULL;
+
+ /* get all histories in dialog */
+ mc_event_raise (h->event_group, MCEVENT_HISTORY_SAVE, &event_data);
+
+ mc_config_save_file (event_data.cfg, NULL);
+ mc_config_deinit (event_data.cfg);
+ }
+
+ g_free (profile);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+dlg_get_title (const WDialog * h, size_t len)
+{
+ char *t;
+
+ if (h == NULL)
+ abort ();
+
+ if (h->get_title != NULL)
+ t = h->get_title (h, len);
+ else
+ t = g_strdup ("");
+
+ return t;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/dialog.h b/lib/widget/dialog.h
new file mode 100644
index 0000000..93c4638
--- /dev/null
+++ b/lib/widget/dialog.h
@@ -0,0 +1,129 @@
+/*
+ Dialog box features module for the Midnight Commander
+ */
+
+/** \file dialog.h
+ * \brief Header: dialog box features module
+ */
+
+#ifndef MC__DIALOG_H
+#define MC__DIALOG_H
+
+#include <sys/types.h> /* size_t */
+
+#include "lib/global.h"
+#include "lib/hook.h" /* hook_t */
+#include "lib/keybind.h" /* global_keymap_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define DIALOG(x) ((WDialog *)(x))
+#define CONST_DIALOG(x) ((const WDialog *)(x))
+
+/* Common return values */
+/* ATTENTION: avoid overlapping with FileProgressStatus values */
+#define B_EXIT 0
+#define B_CANCEL 1
+#define B_ENTER 2
+#define B_HELP 3
+#define B_USER 100
+
+/*** enums ***************************************************************************************/
+
+/* Dialog color constants */
+typedef enum
+{
+ DLG_COLOR_NORMAL,
+ DLG_COLOR_FOCUS,
+ DLG_COLOR_HOT_NORMAL,
+ DLG_COLOR_HOT_FOCUS,
+ DLG_COLOR_TITLE,
+ DLG_COLOR_COUNT
+} dlg_colors_enum_t;
+
+/*** typedefs(not structures) ********************************************************************/
+
+typedef struct WDialog WDialog;
+
+/* get string representation of shortcut assigned with command */
+/* as menu is a widget of dialog, ask dialog about shortcut string */
+typedef char *(*dlg_shortcut_str) (long command);
+
+/* get dialog name to show in dialog list */
+typedef char *(*dlg_title_str) (const WDialog * h, size_t len);
+
+typedef int dlg_colors_t[DLG_COLOR_COUNT];
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WDialog
+{
+ WGroup group; /* base class */
+
+ /* Set by the user */
+ gboolean compact; /* Suppress spaces around the frame */
+ const char *help_ctx; /* Name of the help entry */
+ const int *colors; /* Color set. Unused in viewer and editor */
+
+ /* Set and received by the user */
+ int ret_value; /* Result of dlg_run() */
+
+ /* Internal variables */
+ char *event_group; /* Name of event group for this dialog */
+ Widget *bg; /* WFrame or WBackground */
+
+ /* Data can be passed to dialog */
+ union
+ {
+ void *p;
+ int i;
+ } data;
+
+ dlg_shortcut_str get_shortcut; /* Shortcut string */
+ dlg_title_str get_title; /* useless for modal dialogs */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/* Color styles for normal and error dialogs */
+extern dlg_colors_t dialog_colors;
+extern dlg_colors_t alarm_colors;
+extern dlg_colors_t listbox_colors;
+
+/* A hook list for idle events */
+extern hook_t *idle_hook;
+
+extern gboolean mouse_close_dialog;
+
+extern const global_keymap_t *dialog_map;
+
+/*** declarations of public functions ************************************************************/
+
+/* Creates a dialog head */
+WDialog *dlg_create (gboolean modal, int y1, int x1, int lines, int cols,
+ widget_pos_flags_t pos_flags, gboolean compact,
+ const int *colors, widget_cb_fn callback, widget_mouse_cb_fn mouse_callback,
+ const char *help_ctx, const char *title);
+
+void dlg_set_default_colors (void);
+
+void dlg_init (WDialog * h);
+int dlg_run (WDialog * d);
+
+void dlg_run_done (WDialog * h);
+void dlg_save_history (WDialog * h);
+void dlg_process_event (WDialog * h, int key, Gpm_Event * event);
+
+char *dlg_get_title (const WDialog * h, size_t len);
+
+/* Default callbacks for dialogs */
+cb_ret_t dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+void dlg_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event);
+
+void dlg_close (WDialog * h);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__DIALOG_H */
diff --git a/lib/widget/frame.c b/lib/widget/frame.c
new file mode 100644
index 0000000..31127ab
--- /dev/null
+++ b/lib/widget/frame.c
@@ -0,0 +1,164 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 2020-2023
+ The Free Software Foundation, Inc.
+
+ Authors:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file frame.c
+ * \brief Source: WFrame widget (frame of dialogs)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+frame_adjust (WFrame * f)
+{
+ Widget *w = WIDGET (f);
+
+ w->rect = WIDGET (w->owner)->rect;
+ w->pos_flags |= WPOS_KEEP_ALL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+frame_draw (const WFrame * f)
+{
+ const Widget *wf = CONST_WIDGET (f);
+ const WRect *w = &wf->rect;
+ int d = f->compact ? 0 : 1;
+ const int *colors;
+
+ colors = widget_get_colors (wf);
+
+ if (mc_global.tty.shadows)
+ tty_draw_box_shadow (w->y, w->x, w->lines, w->cols, SHADOW_COLOR);
+
+ tty_setcolor (colors[FRAME_COLOR_NORMAL]);
+ tty_fill_region (w->y, w->x, w->lines, w->cols, ' ');
+ tty_draw_box (w->y + d, w->x + d, w->lines - 2 * d, w->cols - 2 * d, f->single);
+
+ if (f->title != NULL)
+ {
+ /* TODO: truncate long title */
+ tty_setcolor (colors[FRAME_COLOR_TITLE]);
+ widget_gotoyx (f, d, (w->cols - str_term_width1 (f->title)) / 2);
+ tty_print_string (f->title);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WFrame *
+frame_new (int y, int x, int lines, int cols, const char *title, gboolean single, gboolean compact)
+{
+ WRect r = { y, x, lines, cols };
+ WFrame *f;
+ Widget *w;
+
+ f = g_new (WFrame, 1);
+ w = WIDGET (f);
+ widget_init (w, &r, frame_callback, NULL);
+
+ f->single = single;
+ f->compact = compact;
+
+ f->title = NULL;
+ frame_set_title (f, title);
+
+ return f;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+frame_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WFrame *f = FRAME (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ frame_adjust (f);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ frame_draw (f);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ g_free (f->title);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+frame_set_title (WFrame * f, const char *title)
+{
+ MC_PTR_FREE (f->title);
+
+ /* Strip existing spaces, add one space before and after the title */
+ if (title != NULL && *title != '\0')
+ {
+ char *t;
+
+ t = g_strstrip (g_strdup (title));
+ if (*t != '\0')
+ f->title = g_strdup_printf (" %s ", t);
+ g_free (t);
+ }
+
+ widget_draw (WIDGET (f));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/frame.h b/lib/widget/frame.h
new file mode 100644
index 0000000..83e314e
--- /dev/null
+++ b/lib/widget/frame.h
@@ -0,0 +1,43 @@
+
+/** \file frame.h
+ * \brief Header: WFrame widget
+ */
+
+#ifndef MC__WIDGET_FRAME_H
+#define MC__WIDGET_FRAME_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define FRAME(x) ((WFrame *)(x))
+#define CONST_FRAME(x) ((const WFrame *)(x))
+
+#define FRAME_COLOR_NORMAL DLG_COLOR_NORMAL
+#define FRAME_COLOR_TITLE DLG_COLOR_TITLE
+
+/*** enums ***************************************************************************************/
+
+/*** typedefs(not structures) ********************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+
+ char *title;
+ gboolean single;
+ gboolean compact;
+} WFrame;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WFrame *frame_new (int y, int x, int lines, int cols, const char *title, gboolean single,
+ gboolean compact);
+cb_ret_t frame_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+void frame_set_title (WFrame * f, const char *title);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_FRAME_H */
diff --git a/lib/widget/gauge.c b/lib/widget/gauge.c
new file mode 100644
index 0000000..5eebb11
--- /dev/null
+++ b/lib/widget/gauge.c
@@ -0,0 +1,178 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file gauge.c
+ * \brief Source: WGauge widget (progress indicator)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+gauge_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGauge *g = GAUGE (w);
+ const int *colors;
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ colors = widget_get_colors (w);
+ widget_gotoyx (w, 0, 0);
+ if (!g->shown)
+ {
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("%*s", w->rect.cols, "");
+ }
+ else
+ {
+ int gauge_len;
+ int percentage, columns;
+ int total = g->max;
+ int done = g->current;
+
+ if (total <= 0 || done < 0)
+ {
+ done = 0;
+ total = 100;
+ }
+ if (done > total)
+ done = total;
+ while (total > 65535)
+ {
+ total /= 256;
+ done /= 256;
+ }
+
+ gauge_len = w->rect.cols - 7; /* 7 positions for percentage */
+
+ percentage = (200 * done / total + 1) / 2;
+ columns = (2 * gauge_len * done / total + 1) / 2;
+ tty_print_char ('[');
+ if (g->from_left_to_right)
+ {
+ tty_setcolor (GAUGE_COLOR);
+ tty_printf ("%*s", columns, "");
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("%*s] %3d%%", gauge_len - columns, "", percentage);
+ }
+ else
+ {
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("%*s", gauge_len - columns, "");
+ tty_setcolor (GAUGE_COLOR);
+ tty_printf ("%*s", columns, "");
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("] %3d%%", percentage);
+ }
+ }
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WGauge *
+gauge_new (int y, int x, int cols, gboolean shown, int max, int current)
+{
+ WRect r = { y, x, 1, cols };
+ WGauge *g;
+ Widget *w;
+
+ g = g_new (WGauge, 1);
+ w = WIDGET (g);
+ widget_init (w, &r, gauge_callback, NULL);
+
+ g->shown = shown;
+ if (max == 0)
+ max = 1; /* I do not like division by zero :) */
+ g->max = max;
+ g->current = current;
+ g->from_left_to_right = TRUE;
+
+ return g;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+gauge_set_value (WGauge * g, int max, int current)
+{
+ if (g->current == current && g->max == max)
+ return; /* Do not flicker */
+
+ if (max == 0)
+ max = 1; /* I do not like division by zero :) */
+ g->current = current;
+ g->max = max;
+ widget_draw (WIDGET (g));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+gauge_show (WGauge * g, gboolean shown)
+{
+ if (g->shown != shown)
+ {
+ g->shown = shown;
+ widget_draw (WIDGET (g));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/gauge.h b/lib/widget/gauge.h
new file mode 100644
index 0000000..e1f5d2c
--- /dev/null
+++ b/lib/widget/gauge.h
@@ -0,0 +1,36 @@
+
+/** \file gauge.h
+ * \brief Header: WGauge widget
+ */
+
+#ifndef MC__WIDGET_GAUGE_H
+#define MC__WIDGET_GAUGE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define GAUGE(x) ((WGauge *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WGauge
+{
+ Widget widget;
+ gboolean shown;
+ int max;
+ int current;
+ gboolean from_left_to_right;
+} WGauge;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WGauge *gauge_new (int y, int x, int cols, gboolean shown, int max, int current);
+void gauge_set_value (WGauge * g, int max, int current);
+void gauge_show (WGauge * g, gboolean shown);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_GAUGE_H */
diff --git a/lib/widget/group.c b/lib/widget/group.c
new file mode 100644
index 0000000..eb6ba1e
--- /dev/null
+++ b/lib/widget/group.c
@@ -0,0 +1,970 @@
+/*
+ Widget group features module for the Midnight Commander
+
+ Copyright (C) 2020-2023
+ The Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file group.c
+ * \brief Source: widget group features module
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/key.h" /* ALT() */
+
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/* Control widget positions in a group */
+typedef struct
+{
+ int shift_x;
+ int scale_x;
+ int shift_y;
+ int scale_y;
+} widget_shift_scale_t;
+
+typedef struct
+{
+ widget_state_t state;
+ gboolean enable;
+} widget_state_info_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_widget_init (void *data, void *user_data)
+{
+ (void) user_data;
+
+ send_message (WIDGET (data), NULL, MSG_INIT, 0, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+group_get_next_or_prev_of (GList * list, gboolean next)
+{
+ GList *l = NULL;
+
+ if (list != NULL)
+ {
+ WGroup *owner = WIDGET (list->data)->owner;
+
+ if (owner != NULL)
+ {
+ if (next)
+ {
+ l = g_list_next (list);
+ if (l == NULL)
+ l = owner->widgets;
+ }
+ else
+ {
+ l = g_list_previous (list);
+ if (l == NULL)
+ l = g_list_last (owner->widgets);
+ }
+ }
+ }
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_select_next_or_prev (WGroup * g, gboolean next)
+{
+ if (g->widgets != NULL && g->current != NULL)
+ {
+ GList *l = g->current;
+
+ do
+ {
+ l = group_get_next_or_prev_of (l, next);
+ }
+ while (!widget_is_focusable (l->data) && l != g->current);
+
+ widget_select (l->data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_widget_set_state (gpointer data, gpointer user_data)
+{
+ widget_state_info_t *state = (widget_state_info_t *) user_data;
+
+ widget_set_state (WIDGET (data), state->state, state->enable);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Send broadcast message to all widgets in the group that have specified options.
+ *
+ * @param g WGroup object
+ * @param msg message sent to widgets
+ * @param reverse if TRUE, send message in reverse order, FALSE -- in direct one.
+ * @param options if WOP_DEFAULT, the message is sent to all widgets. Else message is sent to widgets
+ * that have specified options.
+ */
+
+static void
+group_send_broadcast_msg_custom (WGroup * g, widget_msg_t msg, gboolean reverse,
+ widget_options_t options)
+{
+ GList *p, *first;
+
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ p = group_get_next_or_prev_of (g->current, !reverse);
+ first = p;
+
+ do
+ {
+ Widget *w = WIDGET (p->data);
+
+ p = group_get_next_or_prev_of (p, !reverse);
+
+ if (options == WOP_DEFAULT || (options & w->options) != 0)
+ /* special case: don't draw invisible widgets */
+ if (msg != MSG_DRAW || widget_get_state (w, WST_VISIBLE))
+ send_message (w, NULL, msg, 0, NULL);
+ }
+ while (first != p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback to convert group coordinates from local (relative to owner) to global
+ * (relative to screen).
+ *
+ * @param w widget
+ */
+
+static void
+group_default_make_global (Widget * w, const WRect * delta)
+{
+ GList *iter;
+
+ if (delta != NULL)
+ {
+ /* change own coordinates */
+ widget_default_make_global (w, delta);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_global (WIDGET (iter->data), delta);
+ }
+ else if (w->owner != NULL)
+ {
+ WRect r = WIDGET (w->owner)->rect;
+
+ r.lines = 0;
+ r.cols = 0;
+ /* change own coordinates */
+ widget_default_make_global (w, &r);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_global (WIDGET (iter->data), &r);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback to convert group coordinates from global (relative to screen) to local
+ * (relative to owner).
+ *
+ * @param w widget
+ */
+
+static void
+group_default_make_local (Widget * w, const WRect * delta)
+{
+ GList *iter;
+
+ if (delta != NULL)
+ {
+ /* change own coordinates */
+ widget_default_make_local (w, delta);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_local (WIDGET (iter->data), delta);
+ }
+ else if (w->owner != NULL)
+ {
+ WRect r = WIDGET (w->owner)->rect;
+
+ r.lines = 0;
+ r.cols = 0;
+ /* change own coordinates */
+ widget_default_make_local (w, &r);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_local (WIDGET (iter->data), &r);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback function to find widget in the group.
+ *
+ * @param w WGroup object
+ * @param what widget to find
+ *
+ * @return holder of @what if found, NULL otherwise
+ */
+
+static GList *
+group_default_find (const Widget * w, const Widget * what)
+{
+ GList *w0;
+
+ w0 = widget_default_find (w, what);
+ if (w0 == NULL)
+ {
+ GList *iter;
+
+ for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ {
+ w0 = widget_find (WIDGET (iter->data), what);
+ if (w0 != NULL)
+ break;
+ }
+ }
+
+ return w0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback function to find widget in the group using widget callback.
+ *
+ * @param w WGroup object
+ * @param cb widget callback
+ *
+ * @return widget object if found, NULL otherwise
+ */
+
+static Widget *
+group_default_find_by_type (const Widget * w, widget_cb_fn cb)
+{
+ Widget *w0;
+
+ w0 = widget_default_find_by_type (w, cb);
+ if (w0 == NULL)
+ {
+ GList *iter;
+
+ for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ {
+ w0 = widget_find_by_type (WIDGET (iter->data), cb);
+ if (w0 != NULL)
+ break;
+ }
+ }
+
+ return w0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback function to find widget by widget ID in the group.
+ *
+ * @param w WGroup object
+ * @param id widget ID
+ *
+ * @return widget object if widget with specified id is found in group, NULL otherwise
+ */
+
+static Widget *
+group_default_find_by_id (const Widget * w, unsigned long id)
+{
+ Widget *w0;
+
+ w0 = widget_default_find_by_id (w, id);
+ if (w0 == NULL)
+ {
+ GList *iter;
+
+ for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ {
+ w0 = widget_find_by_id (WIDGET (iter->data), id);
+ if (w0 != NULL)
+ break;
+ }
+ }
+
+ return w0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Update cursor position in the active widget of the group.
+ *
+ * @param g WGroup object
+ *
+ * @return MSG_HANDLED if cursor was updated in the specified group, MSG_NOT_HANDLED otherwise
+ */
+
+static cb_ret_t
+group_update_cursor (WGroup * g)
+{
+ GList *p = g->current;
+
+ if (p != NULL && widget_get_state (WIDGET (g), WST_ACTIVE))
+ do
+ {
+ Widget *w = WIDGET (p->data);
+
+ /* Don't use widget_is_selectable() here.
+ If WOP_SELECTABLE option is not set, widget can handle mouse events.
+ For example, commandl line in file manager */
+ if (widget_get_options (w, WOP_WANT_CURSOR) && widget_get_state (w, WST_VISIBLE)
+ && !widget_get_state (w, WST_DISABLED) && widget_update_cursor (WIDGET (p->data)))
+ return MSG_HANDLED;
+
+ p = group_get_widget_next_of (p);
+ }
+ while (p != g->current);
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_widget_set_position (gpointer data, gpointer user_data)
+{
+ /* there are, mainly, 2 generally possible situations:
+ * 1. control sticks to one side - it should be moved
+ * 2. control sticks to two sides of one direction - it should be sized
+ */
+
+ Widget *c = WIDGET (data);
+ const WRect *g = &CONST_WIDGET (c->owner)->rect;
+ const widget_shift_scale_t *wss = (const widget_shift_scale_t *) user_data;
+ WRect r = c->rect;
+
+ if ((c->pos_flags & WPOS_CENTER_HORZ) != 0)
+ r.x = g->x + (g->cols - c->rect.cols) / 2;
+ else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0 && (c->pos_flags & WPOS_KEEP_RIGHT) != 0)
+ {
+ r.x += wss->shift_x;
+ r.cols += wss->scale_x;
+ }
+ else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0)
+ r.x += wss->shift_x;
+ else if ((c->pos_flags & WPOS_KEEP_RIGHT) != 0)
+ r.x += wss->shift_x + wss->scale_x;
+
+ if ((c->pos_flags & WPOS_CENTER_VERT) != 0)
+ r.y = g->y + (g->lines - c->rect.lines) / 2;
+ else if ((c->pos_flags & WPOS_KEEP_TOP) != 0 && (c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
+ {
+ r.y += wss->shift_y;
+ r.lines += wss->scale_y;
+ }
+ else if ((c->pos_flags & WPOS_KEEP_TOP) != 0)
+ r.y += wss->shift_y;
+ else if ((c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
+ r.y += wss->shift_y + wss->scale_y;
+
+ send_message (c, NULL, MSG_RESIZE, 0, &r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_set_position (WGroup * g, const WRect * r)
+{
+ WRect *w = &WIDGET (g)->rect;
+ widget_shift_scale_t wss;
+ /* save old positions, will be used to reposition childs */
+ WRect or = *w;
+
+ *w = *r;
+
+ /* dialog is empty */
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ /* values by which controls should be moved */
+ wss.shift_x = w->x - or.x;
+ wss.scale_x = w->cols - or.cols;
+ wss.shift_y = w->y - or.y;
+ wss.scale_y = w->lines - or.lines;
+
+ if (wss.shift_x != 0 || wss.shift_y != 0 || wss.scale_x != 0 || wss.scale_y != 0)
+ g_list_foreach (g->widgets, group_widget_set_position, &wss);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_default_resize (WGroup * g, WRect * r)
+{
+ /* This is default resizing mechanism.
+ * The main idea of this code is to resize dialog according to flags
+ * (if any of flags require automatic resizing, like WPOS_CENTER,
+ * end after that reposition controls in dialog according to flags of widget)
+ */
+
+ Widget *w = WIDGET (g);
+ WRect r0;
+
+ r0 = r != NULL ? *r : w->rect;
+ widget_adjust_position (w->pos_flags, &r0);
+ group_set_position (g, &r0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_draw (WGroup * g)
+{
+ Widget *wg = WIDGET (g);
+
+ /* draw all widgets in Z-order, from first to last */
+ if (widget_get_state (wg, WST_ACTIVE))
+ {
+ GList *p;
+
+ if (g->winch_pending)
+ {
+ g->winch_pending = FALSE;
+ send_message (wg, NULL, MSG_RESIZE, 0, NULL);
+ }
+
+ for (p = g->widgets; p != NULL; p = g_list_next (p))
+ widget_draw (WIDGET (p->data));
+
+ widget_update_cursor (wg);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+group_handle_key (WGroup * g, int key)
+{
+ cb_ret_t handled;
+
+ /* first try the hotkey */
+ handled = send_message (g, NULL, MSG_HOTKEY, key, NULL);
+
+ /* not used - then try widget_callback */
+ if (handled == MSG_NOT_HANDLED)
+ handled = send_message (g->current->data, NULL, MSG_KEY, key, NULL);
+
+ /* not used - try to use the unhandled case */
+ if (handled == MSG_NOT_HANDLED)
+ handled = send_message (g, g->current->data, MSG_UNHANDLED_KEY, key, NULL);
+
+ return handled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+group_handle_hotkey (WGroup * g, int key)
+{
+ GList *current;
+ Widget *w;
+ cb_ret_t handled = MSG_NOT_HANDLED;
+ int c;
+
+ if (g->widgets == NULL)
+ return MSG_NOT_HANDLED;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ w = WIDGET (g->current->data);
+
+ if (!widget_get_state (w, WST_VISIBLE) || widget_get_state (w, WST_DISABLED))
+ return MSG_NOT_HANDLED;
+
+ /* Explanation: we don't send letter hotkeys to other widgets
+ * if the currently selected widget is an input line */
+ if (widget_get_options (w, WOP_IS_INPUT))
+ {
+ /* skip ascii control characters, anything else can valid character in some encoding */
+ if (key >= 32 && key < 256)
+ return MSG_NOT_HANDLED;
+ }
+
+ /* If it's an alt key, send the message */
+ c = key & ~ALT (0);
+ if (key & ALT (0) && g_ascii_isalpha (c))
+ key = g_ascii_tolower (c);
+
+ if (widget_get_options (w, WOP_WANT_HOTKEY))
+ handled = send_message (w, NULL, MSG_HOTKEY, key, NULL);
+
+ /* If not used, send hotkey to other widgets */
+ if (handled == MSG_HANDLED)
+ return MSG_HANDLED;
+
+ current = group_get_widget_next_of (g->current);
+
+ /* send it to all widgets */
+ while (g->current != current && handled == MSG_NOT_HANDLED)
+ {
+ w = WIDGET (current->data);
+
+ if (widget_get_options (w, WOP_WANT_HOTKEY) && !widget_get_state (w, WST_DISABLED))
+ handled = send_message (w, NULL, MSG_HOTKEY, key, NULL);
+
+ if (handled == MSG_NOT_HANDLED)
+ current = group_get_widget_next_of (current);
+ }
+
+ if (handled == MSG_HANDLED)
+ {
+ w = WIDGET (current->data);
+ widget_select (w);
+ send_message (g, w, MSG_HOTKEY_HANDLED, 0, NULL);
+ }
+
+ return handled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Initialize group.
+ *
+ * @param g WGroup widget
+ * @param y1 y-coordinate of top-left corner
+ * @param x1 x-coordinate of top-left corner
+ * @param lines group height
+ * @param cols group width
+ * @param callback group callback
+ * @param mouse_callback group mouse handler
+ */
+
+void
+group_init (WGroup * g, const WRect * r, widget_cb_fn callback, widget_mouse_cb_fn mouse_callback)
+{
+ Widget *w = WIDGET (g);
+
+ widget_init (w, r, callback != NULL ? callback : group_default_callback, mouse_callback);
+
+ w->mouse_handler = group_handle_mouse_event;
+
+ w->make_global = group_default_make_global;
+ w->make_local = group_default_make_local;
+
+ w->find = group_default_find;
+ w->find_by_type = group_default_find_by_type;
+ w->find_by_id = group_default_find_by_id;
+
+ w->set_state = group_default_set_state;
+
+ g->mouse_status = MOU_UNHANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+group_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ g_list_foreach (g->widgets, group_widget_init, NULL);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ group_draw (g);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ return group_handle_key (g, parm);
+
+ case MSG_HOTKEY:
+ return group_handle_hotkey (g, parm);
+
+ case MSG_CURSOR:
+ return group_update_cursor (g);
+
+ case MSG_RESIZE:
+ group_default_resize (g, RECT (data));
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ g_list_foreach (g->widgets, (GFunc) widget_destroy, NULL);
+ g_list_free (g->widgets);
+ g->widgets = NULL;
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Change state of group.
+ *
+ * @param w group
+ * @param state widget state flag to modify
+ * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
+ * Only one flag per call can be modified.
+ * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
+ */
+cb_ret_t
+group_default_set_state (Widget * w, widget_state_t state, gboolean enable)
+{
+ gboolean ret = MSG_HANDLED;
+ WGroup *g = GROUP (w);
+ widget_state_info_t st = {
+ .state = state,
+ .enable = enable
+ };
+
+ ret = widget_default_set_state (w, state, enable);
+
+ if (state == WST_ACTIVE || state == WST_SUSPENDED || state == WST_CLOSED)
+ /* inform all child widgets */
+ g_list_foreach (g->widgets, group_widget_set_state, &st);
+
+ if ((w->state & WST_ACTIVE) != 0)
+ {
+ if ((w->state & WST_FOCUSED) != 0)
+ {
+ /* update current widget */
+ if (g->current != NULL)
+ widget_set_state (WIDGET (g->current->data), WST_FOCUSED, enable);
+ }
+ else
+ /* inform all child widgets */
+ g_list_foreach (g->widgets, group_widget_set_state, &st);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handling mouse events.
+ *
+ * @param g WGroup object
+ * @param event GPM mouse event
+ *
+ * @return result of mouse event handling
+ */
+int
+group_handle_mouse_event (Widget * w, Gpm_Event * event)
+{
+ WGroup *g = GROUP (w);
+
+ if (g->widgets != NULL)
+ {
+ GList *p;
+
+ /* send the event to widgets in reverse Z-order */
+ p = g_list_last (g->widgets);
+ do
+ {
+ Widget *wp = WIDGET (p->data);
+
+ /* Don't use widget_is_selectable() here.
+ If WOP_SELECTABLE option is not set, widget can handle mouse events.
+ For example, commandl line in file manager */
+ if (widget_get_state (w, WST_VISIBLE) && !widget_get_state (wp, WST_DISABLED))
+ {
+ /* put global cursor position to the widget */
+ int ret;
+
+ ret = wp->mouse_handler (wp, event);
+ if (ret != MOU_UNHANDLED)
+ return ret;
+ }
+
+ p = g_list_previous (p);
+ }
+ while (p != NULL);
+ }
+
+ return MOU_UNHANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Insert widget to group before specified widget with specified positioning.
+ * Make the inserted widget current.
+ *
+ * @param g WGroup object
+ * @param w widget to be added
+ * @pos positioning flags
+ * @param before add @w before this widget
+ *
+ * @return widget ID
+ */
+
+unsigned long
+group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags, const void *before)
+{
+ Widget *wg = WIDGET (g);
+ Widget *ww = WIDGET (w);
+ GList *new_current;
+
+ /* Don't accept NULL widget. This shouldn't happen */
+ assert (ww != NULL);
+
+ if ((pos_flags & WPOS_CENTER_HORZ) != 0)
+ ww->rect.x = (wg->rect.cols - ww->rect.cols) / 2;
+
+ if ((pos_flags & WPOS_CENTER_VERT) != 0)
+ ww->rect.y = (wg->rect.lines - ww->rect.lines) / 2;
+
+ ww->owner = g;
+ ww->pos_flags = pos_flags;
+ widget_make_global (ww);
+
+ if (g->widgets == NULL || before == NULL)
+ {
+ g->widgets = g_list_append (g->widgets, ww);
+ new_current = g_list_last (g->widgets);
+ }
+ else
+ {
+ GList *b;
+
+ b = g_list_find (g->widgets, before);
+
+ /* don't accept widget not from group. This shouldn't happen */
+ assert (b != NULL);
+
+ b = g_list_next (b);
+ g->widgets = g_list_insert_before (g->widgets, b, ww);
+ if (b != NULL)
+ new_current = g_list_previous (b);
+ else
+ new_current = g_list_last (g->widgets);
+ }
+
+ /* widget has been added at runtime */
+ if (widget_get_state (wg, WST_ACTIVE))
+ {
+ group_widget_init (ww, NULL);
+ widget_select (ww);
+ }
+ else
+ g->current = new_current;
+
+ return ww->id;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Remove widget from group.
+ *
+ * @param w Widget object
+ */
+void
+group_remove_widget (void *w)
+{
+ Widget *ww = WIDGET (w);
+ WGroup *g;
+ GList *d;
+
+ /* Don't accept NULL widget. This shouldn't happen */
+ assert (w != NULL);
+
+ g = ww->owner;
+
+ d = g_list_find (g->widgets, ww);
+ if (d == g->current)
+ group_set_current_widget_next (g);
+
+ g->widgets = g_list_delete_link (g->widgets, d);
+ if (g->widgets == NULL)
+ g->current = NULL;
+
+ /* widget has been deleted at runtime */
+ if (widget_get_state (WIDGET (g), WST_ACTIVE))
+ {
+ group_draw (g);
+ group_select_current_widget (g);
+ }
+
+ widget_make_local (ww);
+ ww->owner = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Switch current widget to widget after current in group.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_set_current_widget_next (WGroup * g)
+{
+ g->current = group_get_next_or_prev_of (g->current, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Switch current widget to widget before current in group.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_set_current_widget_prev (WGroup * g)
+{
+ g->current = group_get_next_or_prev_of (g->current, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get widget that is after specified widget in group.
+ *
+ * @param w widget holder
+ *
+ * @return widget that is after "w" or NULL if "w" is NULL or widget doesn't have owner
+ */
+
+GList *
+group_get_widget_next_of (GList * w)
+{
+ return group_get_next_or_prev_of (w, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get widget that is before specified widget in group.
+ *
+ * @param w widget holder
+ *
+ * @return widget that is before "w" or NULL if "w" is NULL or widget doesn't have owner
+ */
+
+GList *
+group_get_widget_prev_of (GList * w)
+{
+ return group_get_next_or_prev_of (w, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Try to select next widget in the Z order.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_select_next_widget (WGroup * g)
+{
+ group_select_next_or_prev (g, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Try to select previous widget in the Z order.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_select_prev_widget (WGroup * g)
+{
+ group_select_next_or_prev (g, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find the widget with the specified ID in the group and select it
+ *
+ * @param g WGroup object
+ * @param id widget ID
+ */
+
+void
+group_select_widget_by_id (const WGroup * g, unsigned long id)
+{
+ Widget *w;
+
+ w = widget_find_by_id (CONST_WIDGET (g), id);
+ if (w != NULL)
+ widget_select (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Send broadcast message to all widgets in the group.
+ *
+ * @param g WGroup object
+ * @param msg message sent to widgets
+ */
+
+void
+group_send_broadcast_msg (WGroup * g, widget_msg_t msg)
+{
+ group_send_broadcast_msg_custom (g, msg, FALSE, WOP_DEFAULT);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/group.h b/lib/widget/group.h
new file mode 100644
index 0000000..3155d8f
--- /dev/null
+++ b/lib/widget/group.h
@@ -0,0 +1,125 @@
+/*
+ * Widget group features module for Midnight Commander
+ */
+
+/** \file group.h
+ * \brief Header: widget group features module
+ */
+
+#ifndef MC__GROUP_H
+#define MC__GROUP_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define GROUP(x) ((WGroup *)(x))
+#define CONST_GROUP(x) ((const WGroup *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** typedefs(not structures) ********************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WGroup
+{
+ Widget widget;
+
+ /* Group members */
+ GList *widgets; /* widgets list */
+ GList *current; /* Currently active widget */
+
+ gboolean winch_pending; /* SIGWINCH signal has been got. Resize group after rise */
+ int mouse_status; /* For the autorepeat status of the mouse */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void group_init (WGroup * g, const WRect * r, widget_cb_fn callback,
+ widget_mouse_cb_fn mouse_callback);
+/* Default callback for groups */
+cb_ret_t group_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+cb_ret_t group_default_set_state (Widget * w, widget_state_t state, gboolean enable);
+int group_handle_mouse_event (Widget * w, Gpm_Event * event);
+
+unsigned long group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags,
+ const void *before);
+void group_remove_widget (void *w);
+
+void group_set_current_widget_next (WGroup * g);
+void group_set_current_widget_prev (WGroup * g);
+
+GList *group_get_widget_next_of (GList * w);
+GList *group_get_widget_prev_of (GList * w);
+
+void group_select_next_widget (WGroup * g);
+void group_select_prev_widget (WGroup * g);
+
+void group_select_widget_by_id (const WGroup * g, unsigned long id);
+
+void group_send_broadcast_msg (WGroup * g, widget_msg_t message);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Add widget to group before current widget.
+ *
+ * @param g WGroup object
+ * @param w widget to be added
+ *
+ * @return widget ID
+ */
+
+static inline unsigned long
+group_add_widget (WGroup * g, void *w)
+{
+ return group_add_widget_autopos (g, w, WPOS_KEEP_DEFAULT,
+ g->current != NULL ? g->current->data : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Add widget to group before specified widget.
+ *
+ * @param g WGroup object
+ * @param w widget to be added
+ * @param before add @w before this widget
+ *
+ * @return widget ID
+ */
+
+static inline unsigned long
+group_add_widget_before (WGroup * g, void *w, void *before)
+{
+ return group_add_widget_autopos (g, w, WPOS_KEEP_DEFAULT, before);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Select current widget in the Dialog.
+ *
+ * @param h WDialog object
+ */
+
+static inline void
+group_select_current_widget (WGroup * g)
+{
+ if (g->current != NULL)
+ widget_select (WIDGET (g->current->data));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline unsigned long
+group_get_current_widget_id (const WGroup * g)
+{
+ return WIDGET (g->current->data)->id;
+}
+
+#endif /* MC__GROUP_H */
diff --git a/lib/widget/groupbox.c b/lib/widget/groupbox.c
new file mode 100644
index 0000000..49cf7b0
--- /dev/null
+++ b/lib/widget/groupbox.c
@@ -0,0 +1,136 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file groupbox.c
+ * \brief Source: WGroupbox widget
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+groupbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroupbox *g = GROUPBOX (w);
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ {
+ gboolean disabled;
+ const int *colors;
+
+ colors = widget_get_colors (w);
+
+ disabled = widget_get_state (w, WST_DISABLED);
+ tty_setcolor (disabled ? DISABLED_COLOR : colors[DLG_COLOR_NORMAL]);
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, TRUE);
+
+ if (g->title != NULL)
+ {
+ tty_setcolor (disabled ? DISABLED_COLOR : colors[DLG_COLOR_TITLE]);
+ widget_gotoyx (w, 0, 1);
+ tty_print_string (g->title);
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ g_free (g->title);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WGroupbox *
+groupbox_new (int y, int x, int height, int width, const char *title)
+{
+ WRect r = { y, x, height, width };
+ WGroupbox *g;
+ Widget *w;
+
+ g = g_new (WGroupbox, 1);
+ w = WIDGET (g);
+ widget_init (w, &r, groupbox_callback, NULL);
+
+ g->title = NULL;
+ groupbox_set_title (g, title);
+
+ return g;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+groupbox_set_title (WGroupbox * g, const char *title)
+{
+ MC_PTR_FREE (g->title);
+
+ /* Strip existing spaces, add one space before and after the title */
+ if (title != NULL && *title != '\0')
+ {
+ char *t;
+
+ t = g_strstrip (g_strdup (title));
+ g->title = g_strconcat (" ", t, " ", (char *) NULL);
+ g_free (t);
+ }
+
+ widget_draw (WIDGET (g));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/groupbox.h b/lib/widget/groupbox.h
new file mode 100644
index 0000000..06fb0d3
--- /dev/null
+++ b/lib/widget/groupbox.h
@@ -0,0 +1,32 @@
+
+/** \file groupbox.h
+ * \brief Header: WGroupbox widget
+ */
+
+#ifndef MC__WIDGET_GROUPBOX_H
+#define MC__WIDGET_GROUPBOX_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define GROUPBOX(x) ((WGroupbox *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WGroupbox
+{
+ Widget widget;
+ char *title;
+} WGroupbox;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WGroupbox *groupbox_new (int y, int x, int height, int width, const char *title);
+void groupbox_set_title (WGroupbox * g, const char *title);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_GROUPBOX_H */
diff --git a/lib/widget/history.c b/lib/widget/history.c
new file mode 100644
index 0000000..8197db8
--- /dev/null
+++ b/lib/widget/history.c
@@ -0,0 +1,302 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file history.c
+ * \brief Source: show history
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/keybind.h" /* CK_* */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_VIEW (B_USER + 1)
+#define B_EDIT (B_USER + 2)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ int y;
+ int x;
+ size_t count;
+ size_t max_width;
+} history_dlg_data;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+history_dlg_reposition (WDialog * dlg_head)
+{
+ history_dlg_data *data;
+ int x = 0, y, he, wi;
+ WRect r;
+
+ /* guard checks */
+ if (dlg_head == NULL || dlg_head->data.p == NULL)
+ return MSG_NOT_HANDLED;
+
+ data = (history_dlg_data *) dlg_head->data.p;
+
+ y = data->y;
+ he = data->count + 2;
+
+ if (he <= y || y > (LINES - 6))
+ {
+ he = MIN (he, y - 1);
+ y -= he;
+ }
+ else
+ {
+ y++;
+ he = MIN (he, LINES - y);
+ }
+
+ if (data->x > 2)
+ x = data->x - 2;
+
+ wi = data->max_width + 4;
+
+ if ((wi + x) > COLS)
+ {
+ wi = MIN (wi, COLS);
+ x = COLS - wi;
+ }
+
+ rect_init (&r, y, x, he, wi);
+
+ return dlg_default_callback (WIDGET (dlg_head), NULL, MSG_RESIZE, 0, &r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+history_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ return history_dlg_reposition (DIALOG (w));
+
+ case MSG_NOTIFY:
+ {
+ /* message from listbox */
+ WDialog *d = DIALOG (w);
+
+ switch (parm)
+ {
+ case CK_View:
+ d->ret_value = B_VIEW;
+ break;
+ case CK_Edit:
+ d->ret_value = B_EDIT;
+ break;
+ case CK_Enter:
+ d->ret_value = B_ENTER;
+ break;
+ default:
+ return MSG_NOT_HANDLED;
+ }
+
+ dlg_close (d);
+ return MSG_HANDLED;
+ }
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+history_create_item (history_descriptor_t * hd, void *data)
+{
+ char *text = (char *) data;
+ size_t width;
+
+ width = str_term_width1 (text);
+ hd->max_width = MAX (width, hd->max_width);
+
+ listbox_add_item (hd->listbox, LISTBOX_APPEND_AT_END, 0, text, NULL, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+history_release_item (history_descriptor_t * hd, WLEntry * le)
+{
+ void *text;
+
+ (void) hd;
+
+ text = le->text;
+ le->text = NULL;
+
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+history_descriptor_init (history_descriptor_t * hd, int y, int x, GList * history, int current)
+{
+ hd->list = history;
+ hd->y = y;
+ hd->x = x;
+ hd->current = current;
+ hd->action = CK_IgnoreKey;
+ hd->text = NULL;
+ hd->max_width = 0;
+ hd->listbox = listbox_new (1, 1, 2, 2, TRUE, NULL);
+ /* in most cases history list contains string only and no any other data */
+ hd->create = history_create_item;
+ hd->release = history_release_item;
+ hd->free = g_free;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+history_show (history_descriptor_t * hd)
+{
+ GList *z, *hi;
+ size_t count;
+ WDialog *query_dlg;
+ history_dlg_data hist_data;
+ int dlg_ret;
+
+ if (hd == NULL || hd->list == NULL)
+ return;
+
+ hd->max_width = str_term_width1 (_("History")) + 2;
+
+ for (z = hd->list; z != NULL; z = g_list_previous (z))
+ hd->create (hd, z->data);
+ /* after this, the order of history items is following: recent at begin, oldest at end */
+
+ count = listbox_get_length (hd->listbox);
+
+ hist_data.y = hd->y;
+ hist_data.x = hd->x;
+ hist_data.count = count;
+ hist_data.max_width = hd->max_width;
+
+ query_dlg =
+ dlg_create (TRUE, 0, 0, 4, 4, WPOS_KEEP_DEFAULT, TRUE, dialog_colors, history_dlg_callback,
+ NULL, "[History-query]", _("History"));
+ query_dlg->data.p = &hist_data;
+
+ /* this call makes list stick to all sides of dialog, effectively make
+ it be resized with dialog */
+ group_add_widget_autopos (GROUP (query_dlg), hd->listbox, WPOS_KEEP_ALL, NULL);
+
+ /* to avoid diplicating of (calculating sizes in two places)
+ code, call history_dlg_callback function here, to set dialog and
+ controls positions.
+ The main idea - create 4x4 dialog and add 2x2 list in
+ center of it, and let dialog function resize it to needed size. */
+ send_message (query_dlg, NULL, MSG_RESIZE, 0, NULL);
+
+ if (WIDGET (query_dlg)->rect.y < hd->y)
+ {
+ /* history is above base widget -- revert order to place recent item at bottom */
+ /* revert history direction */
+ g_queue_reverse (hd->listbox->list);
+ if (hd->current < 0 || (size_t) hd->current >= count)
+ listbox_select_last (hd->listbox);
+ else
+ listbox_set_current (hd->listbox, count - 1 - (size_t) hd->current);
+ }
+ else
+ {
+ /* history is below base widget -- keep order to place recent item on top */
+ if (hd->current > 0)
+ listbox_set_current (hd->listbox, hd->current);
+ }
+
+ dlg_ret = dlg_run (query_dlg);
+ if (dlg_ret != B_CANCEL)
+ {
+ char *q;
+
+ switch (dlg_ret)
+ {
+ case B_EDIT:
+ hd->action = CK_Edit;
+ break;
+ case B_VIEW:
+ hd->action = CK_View;
+ break;
+ default:
+ hd->action = CK_Enter;
+ }
+
+ listbox_get_current (hd->listbox, &q, NULL);
+ hd->text = g_strdup (q);
+ }
+
+ /* get modified history from dialog */
+ z = NULL;
+ for (hi = listbox_get_first_link (hd->listbox); hi != NULL; hi = g_list_next (hi))
+ /* history is being reverted here again */
+ z = g_list_prepend (z, hd->release (hd, LENTRY (hi->data)));
+
+ /* restore history direction */
+ if (WIDGET (query_dlg)->rect.y < hd->y)
+ z = g_list_reverse (z);
+
+ widget_destroy (WIDGET (query_dlg));
+
+ hd->list = g_list_first (hd->list);
+ g_list_free_full (hd->list, hd->free);
+ hd->list = g_list_last (z);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/history.h b/lib/widget/history.h
new file mode 100644
index 0000000..03f764f
--- /dev/null
+++ b/lib/widget/history.h
@@ -0,0 +1,51 @@
+
+/** \file lib/widget/history.h
+ * \brief Header: show history
+ */
+
+#ifndef MC__WIDGET_HISTORY_H
+#define MC__WIDGET_HISTORY_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* forward declarations */
+struct history_descriptor_t;
+struct WLEntry;
+struct WListbox;
+
+typedef void (*history_create_item_func) (struct history_descriptor_t * hd, void *data);
+typedef void *(*history_release_item_func) (struct history_descriptor_t * hd, struct WLEntry * le);
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct history_descriptor_t
+{
+ GList *list; /**< list with history items */
+ int y; /**< y-coordinate to place history window */
+ int x; /**< x-coordinate to place history window */
+ int current; /**< initially selected item in the history */
+ int action; /**< return action in the history */
+ char *text; /**< return text of selected item */
+
+ size_t max_width; /**< maximum width of string in history */
+ struct WListbox *listbox; /**< listbox widget to draw history */
+
+ history_create_item_func create; /**< function to create item of @list */
+ history_release_item_func release; /**< function to release item of @list */
+ GDestroyNotify free; /**< function to destroy element of @list */
+} history_descriptor_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void history_descriptor_init (history_descriptor_t * hd, int y, int x, GList * history,
+ int current);
+
+void history_show (history_descriptor_t * hd);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_HISTORY_H */
diff --git a/lib/widget/hline.c b/lib/widget/hline.c
new file mode 100644
index 0000000..73e261a
--- /dev/null
+++ b/lib/widget/hline.c
@@ -0,0 +1,194 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file hline.c
+ * \brief Source: WHLine widget (horizontal line)
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hline_adjust_cols (WHLine * l)
+{
+ if (l->auto_adjust_cols)
+ {
+ Widget *wl = WIDGET (l);
+ const Widget *o = CONST_WIDGET (wl->owner);
+ WRect *w = &wl->rect;
+ const WRect *wo = &o->rect;
+
+ if (CONST_DIALOG (o)->compact)
+ {
+ w->x = wo->x;
+ w->cols = wo->cols;
+ }
+ else
+ {
+ w->x = wo->x + 1;
+ w->cols = wo->cols - 2;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+hline_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WHLine *l = HLINE (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ hline_adjust_cols (l);
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ hline_adjust_cols (l);
+ w->rect.y = RECT (data)->y;
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ if (l->transparent)
+ tty_setcolor (DEFAULT_COLOR);
+ else
+ {
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ }
+
+ tty_draw_hline (w->rect.y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+
+ if (l->auto_adjust_cols)
+ {
+ widget_gotoyx (w, 0, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, 0, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ }
+
+ if (l->text != NULL)
+ {
+ int text_width;
+
+ text_width = str_term_width1 (l->text);
+ widget_gotoyx (w, 0, (w->rect.cols - text_width) / 2);
+ tty_print_string (l->text);
+ }
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ g_free (l->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WHLine *
+hline_new (int y, int x, int width)
+{
+ WRect r = { y, x, 1, width };
+ WHLine *l;
+ Widget *w;
+
+ l = g_new (WHLine, 1);
+ w = WIDGET (l);
+ r.cols = width < 0 ? 1 : width;
+ widget_init (w, &r, hline_callback, NULL);
+ l->text = NULL;
+ l->auto_adjust_cols = (width < 0);
+ l->transparent = FALSE;
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hline_set_text (WHLine * l, const char *text)
+{
+ g_free (l->text);
+
+ if (text == NULL || *text == '\0')
+ l->text = NULL;
+ else
+ l->text = g_strdup (text);
+
+ widget_draw (WIDGET (l));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hline_set_textv (WHLine * l, const char *format, ...)
+{
+ va_list args;
+ char buf[BUF_1K]; /* FIXME: is it enough? */
+
+ va_start (args, format);
+ g_vsnprintf (buf, sizeof (buf), format, args);
+ va_end (args);
+
+ hline_set_text (l, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/hline.h b/lib/widget/hline.h
new file mode 100644
index 0000000..4a84bf8
--- /dev/null
+++ b/lib/widget/hline.h
@@ -0,0 +1,37 @@
+
+/** \file hline.h
+ * \brief Header: WHLine widget
+ */
+
+#ifndef MC__WIDGET_HLINE_H
+#define MC__WIDGET_HLINE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define HLINE(x) ((WHLine *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+ char *text;
+ gboolean auto_adjust_cols; /* Compute widget.cols from parent width? */
+ gboolean transparent; /* Paint in the default color fg/bg */
+} WHLine;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WHLine *hline_new (int y, int x, int width);
+void hline_set_text (WHLine * l, const char *text);
+/* *INDENT-OFF* */
+void hline_set_textv (WHLine * l, const char *format, ...) G_GNUC_PRINTF (2, 3);
+/* *INDENT-ON* */
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_HLINE_H */
diff --git a/lib/widget/input.c b/lib/widget/input.c
new file mode 100644
index 0000000..3a67b49
--- /dev/null
+++ b/lib/widget/input.c
@@ -0,0 +1,1323 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file input.c
+ * \brief Source: WInput widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/fileloc.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+#include "lib/mcconfig.h" /* mc_config_history_*() */
+
+/*** global variables ****************************************************************************/
+
+gboolean quote = FALSE;
+
+const global_keymap_t *input_map = NULL;
+
+/* Color styles for input widgets */
+input_colors_t input_colors;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define LARGE_HISTORY_BUTTON 1
+
+#ifdef LARGE_HISTORY_BUTTON
+#define HISTORY_BUTTON_WIDTH 3
+#else
+#define HISTORY_BUTTON_WIDTH 1
+#endif
+
+#define should_show_history_button(in) \
+ (in->history.list != NULL && WIDGET (in)->rect.cols > HISTORY_BUTTON_WIDTH * 2 + 1 \
+ && WIDGET (in)->owner != NULL)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Input widgets have a global kill ring */
+/* Pointer to killed data */
+static char *kill_buffer = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static size_t
+get_history_length (GList * history)
+{
+ size_t len = 0;
+
+ for (; history != NULL; history = g_list_previous (history))
+ len++;
+
+ return len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+draw_history_button (WInput * in)
+{
+ char c;
+ gboolean disabled;
+
+ if (g_list_next (in->history.current) == NULL)
+ c = '^';
+ else if (g_list_previous (in->history.current) == NULL)
+ c = 'v';
+ else
+ c = '|';
+
+ widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH);
+ disabled = widget_get_state (WIDGET (in), WST_DISABLED);
+ tty_setcolor (disabled ? DISABLED_COLOR : in->color[WINPUTC_HISTORY]);
+
+#ifdef LARGE_HISTORY_BUTTON
+ tty_print_string ("[ ]");
+ widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH + 1);
+#endif
+
+ tty_print_char (c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_mark_cmd (WInput * in, gboolean mark)
+{
+ in->mark = mark ? in->point : -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+input_eval_marks (WInput * in, long *start_mark, long *end_mark)
+{
+ if (in->mark >= 0)
+ {
+ *start_mark = MIN (in->mark, in->point);
+ *end_mark = MAX (in->mark, in->point);
+ return TRUE;
+ }
+
+ *start_mark = *end_mark = -1;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_show_hist (WInput * in)
+{
+ size_t len;
+ history_descriptor_t hd;
+
+ len = get_history_length (in->history.list);
+
+ history_descriptor_init (&hd, WIDGET (in)->rect.y, WIDGET (in)->rect.x, in->history.list,
+ g_list_position (in->history.list, in->history.list));
+ history_show (&hd);
+
+ /* in->history.list was destroyed in history_show().
+ * Apply new history and current position to avoid use-after-free. */
+ in->history.list = hd.list;
+ in->history.current = in->history.list;
+ if (hd.text != NULL)
+ {
+ input_assign_text (in, hd.text);
+ g_free (hd.text);
+ }
+
+ /* Has history cleaned up or not? */
+ if (len != get_history_length (in->history.list))
+ in->history.changed = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Strip password from incomplete url (just user:pass@host without VFS prefix).
+ *
+ * @param url partial URL
+ * @return newly allocated string without password
+ */
+
+static char *
+input_history_strip_password (char *url)
+{
+ char *at, *delim, *colon;
+
+ at = strrchr (url, '@');
+ if (at == NULL)
+ return g_strdup (url);
+
+ /* TODO: handle ':' and '@' in password */
+
+ delim = strstr (url, VFS_PATH_URL_DELIMITER);
+ if (delim != NULL)
+ colon = strchr (delim + strlen (VFS_PATH_URL_DELIMITER), ':');
+ else
+ colon = strchr (url, ':');
+
+ /* if 'colon' before 'at', 'colon' delimits user and password: user:password@host */
+ /* if 'colon' after 'at', 'colon' delimits host and port: user@host:port */
+ if (colon != NULL && colon > at)
+ colon = NULL;
+
+ if (colon == NULL)
+ return g_strdup (url);
+ *colon = '\0';
+
+ return g_strconcat (url, at, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_push_history (WInput * in)
+{
+ char *t;
+ gboolean empty;
+
+ t = g_strstrip (input_get_text (in));
+ empty = *t == '\0';
+ if (!empty)
+ {
+ g_free (t);
+ t = input_get_text (in);
+
+ if (in->history.name != NULL && in->strip_password)
+ {
+ /*
+ We got string user:pass@host without any VFS prefixes
+ and vfs_path_to_str_flags (t, VPF_STRIP_PASSWORD) doesn't work.
+ Therefore we want to strip password in separate algorithm
+ */
+ char *url_with_stripped_password;
+
+ url_with_stripped_password = input_history_strip_password (t);
+ g_free (t);
+ t = url_with_stripped_password;
+ }
+ }
+
+ if (in->history.list == NULL || in->history.list->data == NULL
+ || strcmp (in->history.list->data, t) != 0 || in->history.changed)
+ {
+ in->history.list = list_append_unique (in->history.list, t);
+ in->history.current = in->history.list;
+ in->history.changed = TRUE;
+ }
+ else
+ g_free (t);
+
+ in->need_push = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_buffer_backward (WInput * in, int start, int end)
+{
+ int str_len;
+
+ str_len = str_length (in->buffer->str);
+ if (start >= str_len || end > str_len + 1)
+ return;
+
+ start = str_offset_to_pos (in->buffer->str, start);
+ end = str_offset_to_pos (in->buffer->str, end);
+ g_string_erase (in->buffer, start, end - start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+beginning_of_line (WInput * in)
+{
+ in->point = 0;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+end_of_line (WInput * in)
+{
+ in->point = str_length (in->buffer->str);
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+backward_char (WInput * in)
+{
+ if (in->point > 0)
+ {
+ const char *act;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ in->point -= str_cprev_noncomb_char (&act, in->buffer->str);
+ }
+
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+forward_char (WInput * in)
+{
+ const char *act;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ if (act[0] != '\0')
+ in->point += str_cnext_noncomb_char (&act);
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+forward_word (WInput * in)
+{
+ const char *p;
+
+ p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+
+ for (; p[0] != '\0' && (str_isspace (p) || str_ispunct (p)); in->point++)
+ str_cnext_char (&p);
+
+ for (; p[0] != '\0' && !str_isspace (p) && !str_ispunct (p); in->point++)
+ str_cnext_char (&p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+backward_word (WInput * in)
+{
+ const char *p;
+
+ p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+
+ for (; p != in->buffer->str; in->point--)
+ {
+ const char *p_tmp;
+
+ p_tmp = p;
+ str_cprev_char (&p);
+ if (!str_isspace (p) && !str_ispunct (p))
+ {
+ p = p_tmp;
+ break;
+ }
+ }
+
+ for (; p != in->buffer->str; in->point--)
+ {
+ str_cprev_char (&p);
+ if (str_isspace (p) || str_ispunct (p))
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+backward_delete (WInput * in)
+{
+ const char *act;
+ int start;
+
+ if (in->point == 0)
+ return;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ start = in->point - str_cprev_noncomb_char (&act, in->buffer->str);
+ move_buffer_backward (in, start, in->point);
+ in->charpoint = 0;
+ in->need_push = TRUE;
+ in->point = start;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+copy_region (WInput * in, int start, int end)
+{
+ int first = MIN (start, end);
+ int last = MAX (start, end);
+
+ if (last == first)
+ {
+ /* Copy selected files to clipboard */
+ mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file", NULL);
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+ return;
+ }
+
+ g_free (kill_buffer);
+
+ first = str_offset_to_pos (in->buffer->str, first);
+ last = str_offset_to_pos (in->buffer->str, last);
+
+ kill_buffer = g_strndup (in->buffer->str + first, last - first);
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", kill_buffer);
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+delete_region (WInput * in, int start, int end)
+{
+ int first = MIN (start, end);
+ int last = MAX (start, end);
+
+ input_mark_cmd (in, FALSE);
+ in->point = first;
+ move_buffer_backward (in, first, last);
+ in->charpoint = 0;
+ in->need_push = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+insert_char (WInput * in, int c_code)
+{
+ int res;
+ long m1, m2;
+ size_t ins_point;
+
+ if (input_eval_marks (in, &m1, &m2))
+ delete_region (in, m1, m2);
+
+ if (c_code == -1)
+ return MSG_NOT_HANDLED;
+
+ if (in->charpoint >= MB_LEN_MAX)
+ return MSG_HANDLED;
+
+ in->charbuf[in->charpoint] = c_code;
+ in->charpoint++;
+
+ res = str_is_valid_char (in->charbuf, in->charpoint);
+ if (res < 0)
+ {
+ if (res != -2)
+ in->charpoint = 0; /* broken multibyte char, skip */
+ return MSG_HANDLED;
+ }
+
+ in->need_push = TRUE;
+ ins_point = str_offset_to_pos (in->buffer->str, in->point);
+ g_string_insert_len (in->buffer, ins_point, in->charbuf, in->charpoint);
+ in->point++;
+ in->charpoint = 0;
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+delete_char (WInput * in)
+{
+ const char *act;
+ int end;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ end = in->point + str_cnext_noncomb_char (&act);
+ move_buffer_backward (in, in->point, end);
+ in->charpoint = 0;
+ in->need_push = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+kill_word (WInput * in)
+{
+ int old_point = in->point;
+ int new_point;
+
+ forward_word (in);
+ new_point = in->point;
+ in->point = old_point;
+
+ delete_region (in, old_point, new_point);
+ in->need_push = TRUE;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+back_kill_word (WInput * in)
+{
+ int old_point = in->point;
+ int new_point;
+
+ backward_word (in);
+ new_point = in->point;
+ in->point = old_point;
+
+ delete_region (in, old_point, new_point);
+ in->need_push = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+yank (WInput * in)
+{
+ if (kill_buffer != NULL)
+ {
+ char *p;
+
+ in->charpoint = 0;
+ for (p = kill_buffer; *p != '\0'; p++)
+ insert_char (in, *p);
+ in->charpoint = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+kill_line (WInput * in)
+{
+ int chp;
+
+ chp = str_offset_to_pos (in->buffer->str, in->point);
+ g_free (kill_buffer);
+ kill_buffer = g_strndup (in->buffer->str + chp, in->buffer->len - chp);
+ g_string_set_size (in->buffer, chp);
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+clear_line (WInput * in)
+{
+ in->need_push = TRUE;
+ g_string_set_size (in->buffer, 0);
+ in->point = 0;
+ in->mark = -1;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ins_from_clip (WInput * in)
+{
+ char *p = NULL;
+ ev_clipboard_text_from_file_t event_data = { NULL, FALSE };
+
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL);
+
+ event_data.text = &p;
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_from_file", &event_data);
+ if (event_data.ret)
+ {
+ char *pp;
+
+ for (pp = p; *pp != '\0'; pp++)
+ insert_char (in, *pp);
+
+ g_free (p);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hist_prev (WInput * in)
+{
+ GList *prev;
+
+ if (in->history.list == NULL)
+ return;
+
+ if (in->need_push)
+ input_push_history (in);
+
+ prev = g_list_previous (in->history.current);
+ if (prev != NULL)
+ {
+ input_assign_text (in, (char *) prev->data);
+ in->history.current = prev;
+ in->history.changed = TRUE;
+ in->need_push = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hist_next (WInput * in)
+{
+ GList *next;
+
+ if (in->need_push)
+ {
+ input_push_history (in);
+ input_assign_text (in, "");
+ return;
+ }
+
+ if (in->history.list == NULL)
+ return;
+
+ next = g_list_next (in->history.current);
+ if (next == NULL)
+ {
+ input_assign_text (in, "");
+ in->history.current = in->history.list;
+ }
+ else
+ {
+ input_assign_text (in, (char *) next->data);
+ in->history.current = next;
+ in->history.changed = TRUE;
+ in->need_push = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+port_region_marked_for_delete (WInput * in)
+{
+ g_string_set_size (in->buffer, 0);
+ in->point = 0;
+ in->first = FALSE;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+input_execute_cmd (WInput * in, long command)
+{
+ cb_ret_t res = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkToHome:
+ case CK_MarkToEnd:
+ /* a highlight command like shift-arrow */
+ if (in->mark < 0)
+ {
+ input_mark_cmd (in, FALSE); /* clear */
+ input_mark_cmd (in, TRUE); /* marking on */
+ }
+ break;
+ case CK_WordRight:
+ case CK_WordLeft:
+ case CK_Right:
+ case CK_Left:
+ if (in->mark >= 0)
+ input_mark_cmd (in, FALSE);
+ break;
+ default:
+ break;
+ }
+
+ switch (command)
+ {
+ case CK_Home:
+ case CK_MarkToHome:
+ beginning_of_line (in);
+ break;
+ case CK_End:
+ case CK_MarkToEnd:
+ end_of_line (in);
+ break;
+ case CK_Left:
+ case CK_MarkLeft:
+ backward_char (in);
+ break;
+ case CK_WordLeft:
+ case CK_MarkToWordBegin:
+ backward_word (in);
+ break;
+ case CK_Right:
+ case CK_MarkRight:
+ forward_char (in);
+ break;
+ case CK_WordRight:
+ case CK_MarkToWordEnd:
+ forward_word (in);
+ break;
+ case CK_BackSpace:
+ {
+ long m1, m2;
+
+ if (input_eval_marks (in, &m1, &m2))
+ delete_region (in, m1, m2);
+ else
+ backward_delete (in);
+ }
+ break;
+ case CK_Delete:
+ if (in->first)
+ port_region_marked_for_delete (in);
+ else
+ {
+ long m1, m2;
+
+ if (input_eval_marks (in, &m1, &m2))
+ delete_region (in, m1, m2);
+ else
+ delete_char (in);
+ }
+ break;
+ case CK_DeleteToWordEnd:
+ kill_word (in);
+ break;
+ case CK_DeleteToWordBegin:
+ back_kill_word (in);
+ break;
+ case CK_Mark:
+ input_mark_cmd (in, TRUE);
+ break;
+ case CK_Remove:
+ delete_region (in, in->point, MAX (in->mark, 0));
+ break;
+ case CK_DeleteToEnd:
+ kill_line (in);
+ break;
+ case CK_Clear:
+ clear_line (in);
+ break;
+ case CK_Store:
+ copy_region (in, MAX (in->mark, 0), in->point);
+ break;
+ case CK_Cut:
+ {
+ long m;
+
+ m = MAX (in->mark, 0);
+ copy_region (in, m, in->point);
+ delete_region (in, in->point, m);
+ }
+ break;
+ case CK_Yank:
+ yank (in);
+ break;
+ case CK_Paste:
+ ins_from_clip (in);
+ break;
+ case CK_HistoryPrev:
+ hist_prev (in);
+ break;
+ case CK_HistoryNext:
+ hist_next (in);
+ break;
+ case CK_History:
+ do_show_hist (in);
+ break;
+ case CK_Complete:
+ input_complete (in);
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+
+ switch (command)
+ {
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkToHome:
+ case CK_MarkToEnd:
+ /* do nothing */
+ break;
+ default:
+ in->mark = -1;
+ break;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_load" event handler */
+static gboolean
+input_load_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WInput *in = INPUT (init_data);
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+
+ in->history.list = mc_config_history_load (ev->cfg, in->history.name);
+ in->history.current = in->history.list;
+
+ if (in->init_from_history)
+ {
+ const char *def_text = "";
+
+ if (in->history.list != NULL && in->history.list->data != NULL)
+ def_text = (const char *) in->history.list->data;
+
+ input_assign_text (in, def_text);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_save" event handler */
+static gboolean
+input_save_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WInput *in = INPUT (init_data);
+
+ (void) event_group_name;
+ (void) event_name;
+
+ if (!in->is_password && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL))
+ {
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ input_push_history (in);
+ if (in->history.changed)
+ mc_config_history_save (ev->cfg, in->history.name, in->history.list);
+ in->history.changed = FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_destroy (WInput * in)
+{
+ input_complete_free (in);
+
+ /* clean history */
+ if (in->history.list != NULL)
+ {
+ /* history is already saved before this moment */
+ in->history.list = g_list_first (in->history.list);
+ g_list_free_full (in->history.list, g_free);
+ }
+ g_free (in->history.name);
+ g_string_free (in->buffer, TRUE);
+ MC_PTR_FREE (kill_buffer);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Calculates the buffer index (aka "point") corresponding to some screen coordinate.
+ */
+static int
+input_screen_to_point (const WInput * in, int x)
+{
+ x += in->term_first_shown;
+
+ if (x < 0)
+ return 0;
+
+ if (x < str_term_width1 (in->buffer->str))
+ return str_column_to_pos (in->buffer->str, x);
+
+ return str_length (in->buffer->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ /* save point between MSG_MOUSE_DOWN and MSG_MOUSE_DRAG */
+ static int prev_point = 0;
+ WInput *in = INPUT (w);
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+
+ if (event->x >= w->rect.cols - HISTORY_BUTTON_WIDTH && should_show_history_button (in))
+ do_show_hist (in);
+ else
+ {
+ in->first = FALSE;
+ input_mark_cmd (in, FALSE);
+ input_set_point (in, input_screen_to_point (in, event->x));
+ /* save point for the possible following MSG_MOUSE_DRAG action */
+ prev_point = in->point;
+ }
+ break;
+
+ case MSG_MOUSE_DRAG:
+ /* start point: set marker using point before first MSG_MOUSE_DRAG action */
+ if (in->mark < 0)
+ in->mark = prev_point;
+
+ input_set_point (in, input_screen_to_point (in, event->x));
+ break;
+
+ default:
+ /* don't create highlight region of 0 length */
+ if (in->mark == in->point)
+ input_mark_cmd (in, FALSE);
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Create new instance of WInput object.
+ * @param y Y coordinate
+ * @param x X coordinate
+ * @param input_colors Array of used colors
+ * @param width Widget width
+ * @param def_text Default text filled in widget
+ * @param histname Name of history
+ * @param completion_flags Flags for specify type of completions
+ * @return WInput object
+ */
+WInput *
+input_new (int y, int x, const int *colors, int width, const char *def_text,
+ const char *histname, input_complete_t completion_flags)
+{
+ WRect r = { y, x, 1, width };
+ WInput *in;
+ Widget *w;
+
+ in = g_new (WInput, 1);
+ w = WIDGET (in);
+ widget_init (w, &r, input_callback, input_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_IS_INPUT | WOP_WANT_CURSOR;
+ w->keymap = input_map;
+
+ in->color = colors;
+ in->first = TRUE;
+ in->mark = -1;
+ in->term_first_shown = 0;
+ in->disable_update = 0;
+ in->is_password = FALSE;
+ in->strip_password = FALSE;
+
+ /* in->buffer will be corrected in "history_load" event handler */
+ in->buffer = g_string_sized_new (width);
+
+ /* init completions before input_assign_text() call */
+ in->completions = NULL;
+ in->completion_flags = completion_flags;
+
+ in->init_from_history = (def_text == INPUT_LAST_TEXT);
+
+ if (in->init_from_history || def_text == NULL)
+ def_text = "";
+
+ input_assign_text (in, def_text);
+
+ /* prepare to history setup */
+ in->history.list = NULL;
+ in->history.current = NULL;
+ in->history.changed = FALSE;
+ in->history.name = NULL;
+ if ((histname != NULL) && (*histname != '\0'))
+ in->history.name = g_strdup (histname);
+ /* history will be loaded later */
+
+ in->label = NULL;
+
+ return in;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WInput *in = INPUT (w);
+ WDialog *h = DIALOG (w->owner);
+ cb_ret_t v;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ /* subscribe to "history_load" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL);
+ /* subscribe to "history_save" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL);
+ if (in->label != NULL)
+ widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED));
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ if (parm == XCTRL ('q'))
+ {
+ quote = TRUE;
+ v = input_handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
+ quote = FALSE;
+ return v;
+ }
+
+ /* Keys we want others to handle */
+ if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
+ || parm == KEY_F (10) || parm == '\n')
+ return MSG_NOT_HANDLED;
+
+ /* When pasting multiline text, insert literal Enter */
+ if ((parm & ~KEY_M_MASK) == '\n')
+ {
+ quote = TRUE;
+ v = input_handle_char (in, '\n');
+ quote = FALSE;
+ return v;
+ }
+
+ return input_handle_char (in, parm);
+
+ case MSG_ACTION:
+ return input_execute_cmd (in, parm);
+
+ case MSG_DRAW:
+ input_update (in, FALSE);
+ return MSG_HANDLED;
+
+ case MSG_ENABLE:
+ case MSG_DISABLE:
+ if (in->label != NULL)
+ widget_set_state (WIDGET (in->label), WST_DISABLED, msg == MSG_DISABLE);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ widget_gotoyx (in, 0, str_term_width2 (in->buffer->str, in->point) - in->term_first_shown);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ /* unsubscribe from "history_load" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w);
+ /* unsubscribe from "history_save" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w);
+ input_destroy (in);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_set_default_colors (void)
+{
+ input_colors[WINPUTC_MAIN] = INPUT_COLOR;
+ input_colors[WINPUTC_MARK] = INPUT_MARK_COLOR;
+ input_colors[WINPUTC_UNCHANGED] = INPUT_UNCHANGED_COLOR;
+ input_colors[WINPUTC_HISTORY] = INPUT_HISTORY_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+input_handle_char (WInput * in, int key)
+{
+ cb_ret_t v;
+ long command;
+
+ if (quote)
+ {
+ input_complete_free (in);
+ v = insert_char (in, key);
+ input_update (in, TRUE);
+ quote = FALSE;
+ return v;
+ }
+
+ command = widget_lookup_key (WIDGET (in), key);
+ if (command == CK_IgnoreKey)
+ {
+ if (key > 255)
+ return MSG_NOT_HANDLED;
+ if (in->first)
+ port_region_marked_for_delete (in);
+ input_complete_free (in);
+ v = insert_char (in, key);
+ input_update (in, TRUE);
+ }
+ else
+ {
+ gboolean keep_first;
+
+ if (command != CK_Complete)
+ input_complete_free (in);
+ input_execute_cmd (in, command);
+ v = MSG_HANDLED;
+ /* if in->first == TRUE and history or completion window was cancelled,
+ keep "first" state */
+ keep_first = in->first && (command == CK_History || command == CK_Complete);
+ input_update (in, !keep_first);
+ }
+
+ return v;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_assign_text (WInput * in, const char *text)
+{
+ if (text == NULL)
+ text = "";
+
+ input_complete_free (in);
+ in->mark = -1;
+ in->need_push = TRUE;
+ in->charpoint = 0;
+ g_string_assign (in->buffer, text);
+ in->point = str_length (in->buffer->str);
+ input_update (in, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Inserts text in input line */
+void
+input_insert (WInput * in, const char *text, gboolean insert_extra_space)
+{
+ input_disable_update (in);
+ while (*text != '\0')
+ input_handle_char (in, (unsigned char) *text++); /* unsigned extension char->int */
+ if (insert_extra_space)
+ input_handle_char (in, ' ');
+ input_enable_update (in);
+ input_update (in, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_set_point (WInput * in, int pos)
+{
+ int max_pos;
+
+ max_pos = str_length (in->buffer->str);
+ pos = MIN (pos, max_pos);
+ if (pos != in->point)
+ input_complete_free (in);
+ in->point = pos;
+ in->charpoint = 0;
+ input_update (in, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_update (WInput * in, gboolean clear_first)
+{
+ Widget *wi = WIDGET (in);
+ const WRect *w = &wi->rect;
+ int has_history = 0;
+ int buf_len;
+ const char *cp;
+ int pw;
+
+ if (in->disable_update != 0)
+ return;
+
+ /* don't draw widget not put into dialog */
+ if (wi->owner == NULL || !widget_get_state (WIDGET (wi->owner), WST_ACTIVE))
+ return;
+
+ if (clear_first)
+ in->first = FALSE;
+
+ if (should_show_history_button (in))
+ has_history = HISTORY_BUTTON_WIDTH;
+
+ buf_len = str_length (in->buffer->str);
+
+ /* Adjust the mark */
+ in->mark = MIN (in->mark, buf_len);
+
+ pw = str_term_width2 (in->buffer->str, in->point);
+
+ /* Make the point visible */
+ if ((pw < in->term_first_shown) || (pw >= in->term_first_shown + w->cols - has_history))
+ {
+ in->term_first_shown = pw - (w->cols / 3);
+ if (in->term_first_shown < 0)
+ in->term_first_shown = 0;
+ }
+
+ if (has_history != 0)
+ draw_history_button (in);
+
+ if (widget_get_state (wi, WST_DISABLED))
+ tty_setcolor (DISABLED_COLOR);
+ else if (in->first)
+ tty_setcolor (in->color[WINPUTC_UNCHANGED]);
+ else
+ tty_setcolor (in->color[WINPUTC_MAIN]);
+
+ widget_gotoyx (in, 0, 0);
+
+ if (!in->is_password)
+ {
+ if (in->mark < 0)
+ tty_print_string (str_term_substring (in->buffer->str, in->term_first_shown,
+ w->cols - has_history));
+ else
+ {
+ long m1, m2;
+
+ if (input_eval_marks (in, &m1, &m2))
+ {
+ tty_setcolor (in->color[WINPUTC_MAIN]);
+ cp = str_term_substring (in->buffer->str, in->term_first_shown,
+ w->cols - has_history);
+ tty_print_string (cp);
+ tty_setcolor (in->color[WINPUTC_MARK]);
+ if (m1 < in->term_first_shown)
+ {
+ widget_gotoyx (in, 0, 0);
+ m1 = in->term_first_shown;
+ m2 -= m1;
+ }
+ else
+ {
+ int buf_width;
+
+ widget_gotoyx (in, 0, m1 - in->term_first_shown);
+ buf_width = str_term_width2 (in->buffer->str, m1);
+ m2 = MIN (m2 - m1,
+ (w->cols - has_history) - (buf_width - in->term_first_shown));
+ }
+
+ tty_print_string (str_term_substring (in->buffer->str, m1, m2));
+ }
+ }
+ }
+ else
+ {
+ int i;
+
+ cp = str_term_substring (in->buffer->str, in->term_first_shown, w->cols - has_history);
+ tty_setcolor (in->color[WINPUTC_MAIN]);
+ for (i = 0; i < w->cols - has_history; i++)
+ {
+ if (i < (buf_len - in->term_first_shown) && cp[0] != '\0')
+ tty_print_char ('*');
+ else
+ tty_print_char (' ');
+ if (cp[0] != '\0')
+ str_cnext_char (&cp);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_enable_update (WInput * in)
+{
+ in->disable_update--;
+ input_update (in, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_disable_update (WInput * in)
+{
+ in->disable_update++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Cleans the input line and adds the current text to the history
+ *
+ * @param in the input line
+ */
+void
+input_clean (WInput * in)
+{
+ input_push_history (in);
+ in->need_push = TRUE;
+ g_string_set_size (in->buffer, 0);
+ in->point = 0;
+ in->charpoint = 0;
+ in->mark = -1;
+ input_complete_free (in);
+ input_update (in, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/input.h b/lib/widget/input.h
new file mode 100644
index 0000000..bd7aa26
--- /dev/null
+++ b/lib/widget/input.h
@@ -0,0 +1,155 @@
+
+/** \file input.h
+ * \brief Header: WInput widget
+ */
+
+#ifndef MC__WIDGET_INPUT_H
+#define MC__WIDGET_INPUT_H
+
+#include <limits.h> /* MB_LEN_MAX */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define INPUT(x) ((WInput *)(x))
+
+/* For history load-save functions */
+#define INPUT_LAST_TEXT ((char *) 2)
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ WINPUTC_MAIN, /* color used */
+ WINPUTC_MARK, /* color for marked text */
+ WINPUTC_UNCHANGED, /* color for inactive text (Is first keystroke) */
+ WINPUTC_HISTORY, /* color for history list */
+ WINPUTC_COUNT_COLORS /* count of used colors */
+} input_colors_enum_t;
+
+/* completion flags */
+typedef enum
+{
+ INPUT_COMPLETE_NONE = 0,
+ INPUT_COMPLETE_FILENAMES = 1 << 0,
+ INPUT_COMPLETE_HOSTNAMES = 1 << 1,
+ INPUT_COMPLETE_COMMANDS = 1 << 2,
+ INPUT_COMPLETE_VARIABLES = 1 << 3,
+ INPUT_COMPLETE_USERNAMES = 1 << 4,
+ INPUT_COMPLETE_CD = 1 << 5,
+ INPUT_COMPLETE_SHELL_ESC = 1 << 6,
+} input_complete_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef int input_colors_t[WINPUTC_COUNT_COLORS];
+
+typedef struct
+{
+ Widget widget;
+
+ GString *buffer;
+ const int *color;
+ int point; /* cursor position in the input line in characters */
+ int mark; /* the mark position in characters; negative value means no marked text */
+ int term_first_shown; /* column of the first shown character */
+ gboolean first; /* is first keystroke? */
+ int disable_update; /* do we want to skip updates? */
+ gboolean is_password; /* is this a password input line? */
+ gboolean init_from_history; /* init text will be get from history */
+ gboolean need_push; /* need to push the current Input on hist? */
+ gboolean strip_password; /* need to strip password before placing string to history */
+ char **completions; /* possible completions array */
+ input_complete_t completion_flags;
+ char charbuf[MB_LEN_MAX]; /* buffer for multibytes characters */
+ size_t charpoint; /* point to end of mulibyte sequence in charbuf */
+ WLabel *label; /* label associated with this input line */
+ struct input_history_t
+ {
+ char *name; /* name of history for loading and saving */
+ GList *list; /* the history */
+ GList *current; /* current history item */
+ gboolean changed; /* the history has changed */
+ } history;
+} WInput;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int quote;
+
+extern const global_keymap_t *input_map;
+
+/* Color styles for normal and command line input widgets */
+extern input_colors_t input_colors;
+
+/*** declarations of public functions ************************************************************/
+
+WInput *input_new (int y, int x, const int *colors,
+ int len, const char *text, const char *histname,
+ input_complete_t completion_flags);
+/* callback is public; needed for command line */
+cb_ret_t input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+void input_set_default_colors (void);
+cb_ret_t input_handle_char (WInput * in, int key);
+void input_assign_text (WInput * in, const char *text);
+void input_insert (WInput * in, const char *text, gboolean insert_extra_space);
+void input_set_point (WInput * in, int pos);
+void input_update (WInput * in, gboolean clear_first);
+void input_enable_update (WInput * in);
+void input_disable_update (WInput * in);
+void input_clean (WInput * in);
+
+/* input_complete.c */
+void input_complete (WInput * in);
+void input_complete_free (WInput * in);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get text of input line.
+ *
+ * @param in input line
+ *
+ * @return newly allocated string that contains a copy of @in's text.
+ */
+static inline char *
+input_get_text (const WInput * in)
+{
+ return g_strndup (in->buffer->str, in->buffer->len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get pointer to input line buffer.
+ *
+ * @param in input line
+ *
+ * @return pointer to @in->buffer->str.
+ */
+static inline const char *
+input_get_ctext (const WInput * in)
+{
+ return in->buffer->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Is input line empty or not.
+ *
+ * @param in input line
+ *
+ * @return TRUE if buffer of @in is empty, FALSE otherwise.
+ */
+static inline gboolean
+input_is_empty (const WInput * in)
+{
+ return (in->buffer->len == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+
+#endif /* MC__WIDGET_INPUT_H */
diff --git a/lib/widget/input_complete.c b/lib/widget/input_complete.c
new file mode 100644
index 0000000..94a4c3b
--- /dev/null
+++ b/lib/widget/input_complete.c
@@ -0,0 +1,1484 @@
+/*
+ Input line filename/username/hostname/variable/command completion.
+ (Let mc type for you...)
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file lib/widget/input_complete.c
+ * \brief Source: Input line filename/username/hostname/variable/command completion
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <limits.h> /* MB_LEN_MAX */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/vfs/vfs.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/* Linux declares environ in <unistd.h>, so don't repeat it here. */
+#if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
+extern char **environ;
+#endif
+
+/*** file scope macro definitions ****************************************************************/
+
+/* #define DO_COMPLETION_DEBUG */
+#ifdef DO_COMPLETION_DEBUG
+#define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
+#else
+#define SHOW_C_CTX(func)
+#endif /* DO_CMPLETION_DEBUG */
+
+#define DO_INSERTION 1
+#define DO_QUERY 2
+
+/*** file scope type declarations ****************************************************************/
+
+typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
+
+typedef struct
+{
+ size_t in_command_position;
+ char *word;
+ char *p;
+ char *q;
+ char *r;
+ gboolean is_cd;
+ input_complete_t flags;
+} try_complete_automation_state_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
+void complete_engine_fill_completions (WInput * in);
+
+/*** file scope variables ************************************************************************/
+
+static char **hosts = NULL;
+static char **hosts_p = NULL;
+static int hosts_alloclen = 0;
+
+static int complete_height, complete_width;
+static WInput *input;
+static int min_end;
+static int start = 0;
+static int end = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef DO_COMPLETION_DEBUG
+/**
+ * Useful to print/debug completion flags
+ */
+static const char *
+show_c_flags (input_complete_t flags)
+{
+ static char s_cf[] = "FHCVUDS";
+
+ s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
+ s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
+ s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
+ s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
+ s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
+ s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
+ s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
+
+ return s_cf;
+}
+#endif /* DO_CMPLETION_DEBUG */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+filename_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static DIR *directory = NULL;
+ static char *filename = NULL;
+ static char *dirname = NULL;
+ static char *users_dirname = NULL;
+ static size_t filename_len = 0;
+ static vfs_path_t *dirname_vpath = NULL;
+
+ gboolean isdir = TRUE, isexec = FALSE;
+ struct vfs_dirent *entry = NULL;
+
+ SHOW_C_CTX ("filename_completion_function");
+
+ if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
+ {
+ char *u_text;
+ char *result;
+ char *e_result;
+
+ u_text = strutils_shell_unescape (text);
+
+ result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
+ g_free (u_text);
+
+ e_result = strutils_shell_escape (result);
+ g_free (result);
+
+ return e_result;
+ }
+
+ /* If we're starting the match process, initialize us a bit. */
+ if (state == 0)
+ {
+ const char *temp;
+
+ g_free (dirname);
+ g_free (filename);
+ g_free (users_dirname);
+ vfs_path_free (dirname_vpath, TRUE);
+
+ if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
+ {
+ filename = g_strdup (++temp);
+ dirname = g_strndup (text, temp - text);
+ }
+ else
+ {
+ dirname = g_strdup (".");
+ filename = g_strdup (text);
+ }
+
+ /* We aren't done yet. We also support the "~user" syntax. */
+
+ /* Save the version of the directory that the user typed. */
+ users_dirname = dirname;
+ dirname = tilde_expand (dirname);
+ canonicalize_pathname (dirname);
+ dirname_vpath = vfs_path_from_str (dirname);
+
+ /* Here we should do something with variable expansion
+ and `command`.
+ Maybe a dream - UNIMPLEMENTED yet. */
+
+ directory = mc_opendir (dirname_vpath);
+ filename_len = strlen (filename);
+ }
+
+ /* Now that we have some state, we can read the directory. */
+
+ while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
+ {
+ if (!str_is_valid_string (entry->d_name))
+ continue;
+
+ /* Special case for no filename.
+ All entries except "." and ".." match. */
+ if (filename_len == 0)
+ {
+ if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
+ continue;
+ }
+ else
+ {
+ /* Otherwise, if these match up to the length of filename, then
+ it may be a match. */
+ if ((entry->d_name[0] != filename[0]) ||
+ ((NLENGTH (entry)) < filename_len) ||
+ strncmp (filename, entry->d_name, filename_len) != 0)
+ continue;
+ }
+
+ isdir = TRUE;
+ isexec = FALSE;
+
+ {
+ struct stat tempstat;
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
+
+ /* Unix version */
+ if (mc_stat (tmp_vpath, &tempstat) == 0)
+ {
+ uid_t my_uid;
+ gid_t my_gid;
+
+ my_uid = getuid ();
+ my_gid = getgid ();
+
+ if (!S_ISDIR (tempstat.st_mode))
+ {
+ isdir = FALSE;
+
+ if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0) ||
+ (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0) ||
+ (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0) ||
+ (tempstat.st_mode & 0001) != 0)
+ isexec = TRUE;
+ }
+ }
+ else
+ {
+ /* stat failed, strange. not a dir in any case */
+ isdir = FALSE;
+ }
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
+ break;
+ if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
+ break;
+ if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
+ break;
+ }
+
+ if (entry == NULL)
+ {
+ if (directory != NULL)
+ {
+ mc_closedir (directory);
+ directory = NULL;
+ }
+ MC_PTR_FREE (dirname);
+ vfs_path_free (dirname_vpath, TRUE);
+ dirname_vpath = NULL;
+ MC_PTR_FREE (filename);
+ MC_PTR_FREE (users_dirname);
+ return NULL;
+ }
+
+ {
+ GString *temp;
+
+ temp = g_string_sized_new (16);
+
+ if (users_dirname != NULL && (users_dirname[0] != '.' || users_dirname[1] != '\0'))
+ {
+ g_string_append (temp, users_dirname);
+
+ /* We need a '/' at the end. */
+ if (!IS_PATH_SEP (temp->str[temp->len - 1]))
+ g_string_append_c (temp, PATH_SEP);
+ }
+ g_string_append (temp, entry->d_name);
+ if (isdir)
+ g_string_append_c (temp, PATH_SEP);
+
+ return g_string_free (temp, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** We assume here that text[0] == '~' , if you want to call it in another way,
+ you have to change the code */
+
+static char *
+username_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static struct passwd *entry = NULL;
+ static size_t userlen = 0;
+
+ (void) flags;
+ SHOW_C_CTX ("username_completion_function");
+
+ if (text[0] == '\\' && text[1] == '~')
+ text++;
+ if (state == 0)
+ { /* Initialization stuff */
+ setpwent ();
+ userlen = strlen (text + 1);
+ }
+
+ while ((entry = getpwent ()) != NULL)
+ {
+ /* Null usernames should result in all users as possible completions. */
+ if (userlen == 0)
+ break;
+ if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
+ break;
+ }
+
+ if (entry != NULL)
+ return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
+
+ endpwent ();
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** We assume text [0] == '$' and want to have a look at text [1], if it is
+ equal to '{', so that we should append '}' at the end */
+
+static char *
+variable_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static char **env_p = NULL;
+ static gboolean isbrace = FALSE;
+ static size_t varlen = 0;
+ const char *p = NULL;
+
+ (void) flags;
+ SHOW_C_CTX ("variable_completion_function");
+
+ if (state == 0)
+ { /* Initialization stuff */
+ isbrace = (text[1] == '{');
+ varlen = strlen (text + 1 + isbrace);
+ env_p = environ;
+ }
+
+ while (*env_p != NULL)
+ {
+ p = strchr (*env_p, '=');
+ if (p != NULL && ((size_t) (p - *env_p) >= varlen)
+ && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
+ break;
+ env_p++;
+ }
+
+ if (*env_p == NULL)
+ return NULL;
+
+ {
+ GString *temp;
+
+ temp = g_string_new_len (*env_p, p - *env_p);
+
+ if (isbrace)
+ {
+ g_string_prepend_c (temp, '{');
+ g_string_append_c (temp, '}');
+ }
+ g_string_prepend_c (temp, '$');
+
+ env_p++;
+
+ return g_string_free (temp, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fetch_hosts (const char *filename)
+{
+ FILE *file;
+ char buffer[256];
+ char *name;
+ char *lc_start;
+ char *bi;
+
+ file = fopen (filename, "r");
+ if (file == NULL)
+ return;
+
+ while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
+ {
+ /* Skip to first character. */
+ for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
+ ;
+
+ /* Ignore comments... */
+ if (bi[0] == '#')
+ continue;
+
+ /* Handle $include. */
+ if (strncmp (bi, "$include ", 9) == 0)
+ {
+ char *includefile, *t;
+
+ /* Find start of filename. */
+ includefile = bi + 9;
+ while (*includefile != '\0' && whitespace (*includefile))
+ includefile++;
+ t = includefile;
+
+ /* Find end of filename. */
+ while (t[0] != '\0' && !str_isspace (t))
+ str_next_char (&t);
+ *t = '\0';
+
+ fetch_hosts (includefile);
+ continue;
+ }
+
+ /* Skip IP #s. */
+ while (bi[0] != '\0' && !str_isspace (bi))
+ str_next_char (&bi);
+
+ /* Get the host names separated by white space. */
+ while (bi[0] != '\0' && bi[0] != '#')
+ {
+ while (bi[0] != '\0' && str_isspace (bi))
+ str_next_char (&bi);
+ if (bi[0] == '#')
+ continue;
+ for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
+ ;
+
+ if (bi == lc_start)
+ continue;
+
+ name = g_strndup (lc_start, bi - lc_start);
+
+ {
+ char **host_p;
+ int j;
+
+ j = hosts_p - hosts;
+
+ if (j >= hosts_alloclen)
+ {
+ hosts_alloclen += 30;
+ hosts = g_renew (char *, hosts, hosts_alloclen + 1);
+ hosts_p = hosts + j;
+ }
+
+ for (host_p = hosts; host_p < hosts_p; host_p++)
+ if (strcmp (name, *host_p) == 0)
+ break; /* We do not want any duplicates */
+
+ if (host_p == hosts_p)
+ {
+ *(hosts_p++) = name;
+ *hosts_p = NULL;
+ }
+ else
+ g_free (name);
+ }
+ }
+ }
+
+ fclose (file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+hostname_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static char **host_p = NULL;
+ static size_t textstart = 0;
+ static size_t textlen = 0;
+
+ (void) flags;
+ SHOW_C_CTX ("hostname_completion_function");
+
+ if (state == 0)
+ { /* Initialization stuff */
+ const char *p;
+
+ g_strfreev (hosts);
+ hosts_alloclen = 30;
+ hosts = g_new (char *, hosts_alloclen + 1);
+ *hosts = NULL;
+ hosts_p = hosts;
+ p = getenv ("HOSTFILE");
+ fetch_hosts (p != NULL ? p : "/etc/hosts");
+ host_p = hosts;
+ textstart = (*text == '@') ? 1 : 0;
+ textlen = strlen (text + textstart);
+ }
+
+ for (; *host_p != NULL; host_p++)
+ {
+ if (textlen == 0)
+ break; /* Match all of them */
+ if (strncmp (text + textstart, *host_p, textlen) == 0)
+ break;
+ }
+
+ if (*host_p == NULL)
+ {
+ g_strfreev (hosts);
+ hosts = NULL;
+ return NULL;
+ }
+
+ {
+ GString *temp;
+
+ temp = g_string_sized_new (8);
+
+ if (textstart != 0)
+ g_string_append_c (temp, '@');
+ g_string_append (temp, *host_p);
+ host_p++;
+
+ return g_string_free (temp, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This is the function to call when the word to complete is in a position
+ * where a command word can be found. It looks around $PATH, looking for
+ * commands that match. It also scans aliases, function names, and the
+ * table of shell built-ins.
+ */
+
+static char *
+command_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static const char *path_end = NULL;
+ static gboolean isabsolute = FALSE;
+ static int phase = 0;
+ static size_t text_len = 0;
+ static const char *const *words = NULL;
+ static char *path = NULL;
+ static char *cur_path = NULL;
+ static char *cur_word = NULL;
+ static int init_state = 0;
+ static const char *const bash_reserved[] = {
+ "if", "then", "else", "elif", "fi", "case", "esac", "for",
+ "select", "while", "until", "do", "done", "in", "function", 0
+ };
+ static const char *const bash_builtins[] = {
+ "alias", "bg", "bind", "break", "builtin", "cd", "command",
+ "continue", "declare", "dirs", "echo", "enable", "eval",
+ "exec", "exit", "export", "fc", "fg", "getopts", "hash",
+ "help", "history", "jobs", "kill", "let", "local", "logout",
+ "popd", "pushd", "pwd", "read", "readonly", "return", "set",
+ "shift", "source", "suspend", "test", "times", "trap", "type",
+ "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
+ };
+
+ char *u_text;
+ char *p, *found;
+
+ SHOW_C_CTX ("command_completion_function");
+
+ if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
+ return NULL;
+
+ u_text = strutils_shell_unescape (text);
+ flags &= ~INPUT_COMPLETE_SHELL_ESC;
+
+ if (state == 0)
+ { /* Initialize us a little bit */
+ isabsolute = strchr (u_text, PATH_SEP) != NULL;
+ if (!isabsolute)
+ {
+ words = bash_reserved;
+ phase = 0;
+ text_len = strlen (u_text);
+
+ if (path == NULL)
+ {
+ path = g_strdup (getenv ("PATH"));
+ if (path != NULL)
+ {
+ p = path;
+ path_end = strchr (p, '\0');
+ while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
+ *p++ = '\0';
+ }
+ }
+ }
+ }
+
+ if (isabsolute)
+ {
+ p = filename_completion_function (u_text, state, flags);
+
+ if (p != NULL)
+ {
+ char *temp_p = p;
+
+ p = strutils_shell_escape (p);
+ g_free (temp_p);
+ }
+
+ g_free (u_text);
+ return p;
+ }
+
+ found = NULL;
+ switch (phase)
+ {
+ case 0: /* Reserved words */
+ for (; *words != NULL; words++)
+ if (strncmp (*words, u_text, text_len) == 0)
+ {
+ g_free (u_text);
+ return g_strdup (*(words++));
+ }
+ phase++;
+ words = bash_builtins;
+ MC_FALLTHROUGH;
+ case 1: /* Builtin commands */
+ for (; *words != NULL; words++)
+ if (strncmp (*words, u_text, text_len) == 0)
+ {
+ g_free (u_text);
+ return g_strdup (*(words++));
+ }
+ phase++;
+ if (path == NULL)
+ break;
+ cur_path = path;
+ cur_word = NULL;
+ MC_FALLTHROUGH;
+ case 2: /* And looking through the $PATH */
+ while (found == NULL)
+ {
+ if (cur_word == NULL)
+ {
+ char *expanded;
+
+ if (cur_path >= path_end)
+ break;
+ expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
+ cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
+ g_free (expanded);
+ cur_path = strchr (cur_path, '\0') + 1;
+ init_state = state;
+ }
+ found = filename_completion_function (cur_word, state - init_state, flags);
+ if (found == NULL)
+ MC_PTR_FREE (cur_word);
+ }
+ MC_FALLTHROUGH;
+ default:
+ break;
+ }
+
+ if (found == NULL)
+ MC_PTR_FREE (path);
+ else
+ {
+ p = strrchr (found, PATH_SEP);
+ if (p != NULL)
+ {
+ char *tmp = found;
+
+ found = strutils_shell_escape (p + 1);
+ g_free (tmp);
+ }
+ }
+
+ g_free (u_text);
+ return found;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+match_compare (const void *a, const void *b)
+{
+ return strcmp (*(char *const *) a, *(char *const *) b);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns an array of char * matches with the longest common denominator
+ in the 1st entry. Then a NULL terminated list of different possible
+ completions follows.
+ You have to supply your own CompletionFunction with the word you
+ want to complete as the first argument and an count of previous matches
+ as the second.
+ In case no matches were found we return NULL. */
+
+static char **
+completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
+{
+ /* Number of slots in match_list. */
+ size_t match_list_size = 30;
+ /* The list of matches. */
+ char **match_list;
+ /* Number of matches actually found. */
+ size_t matches = 0;
+
+ /* Temporary string binder. */
+ char *string;
+
+ match_list = g_new (char *, match_list_size + 1);
+ match_list[1] = NULL;
+
+ while ((string = (*entry_function) (text, matches, flags)) != NULL)
+ {
+ if (matches + 1 == match_list_size)
+ {
+ match_list_size += 30;
+ match_list = (char **) g_renew (char *, match_list, match_list_size + 1);
+ }
+ match_list[++matches] = string;
+ match_list[matches + 1] = NULL;
+ }
+
+ /* If there were any matches, then look through them finding out the
+ lowest common denominator. That then becomes match_list[0]. */
+ if (matches == 0)
+ MC_PTR_FREE (match_list); /* There were no matches. */
+ else
+ {
+ /* If only one match, just use that. */
+ if (matches == 1)
+ {
+ match_list[0] = match_list[1];
+ match_list[1] = NULL;
+ }
+ else
+ {
+ size_t i = 1;
+ int low = 4096; /* Count of max-matched characters. */
+ size_t j;
+
+ qsort (match_list + 1, matches, sizeof (char *), match_compare);
+
+ /* And compare each member of the list with
+ the next, finding out where they stop matching.
+ If we find two equal strings, we have to put one away... */
+
+ j = i + 1;
+ while (j < matches + 1)
+ {
+ char *si, *sj;
+ char *ni, *nj;
+
+ for (si = match_list[i], sj = match_list[j]; si[0] != '\0' && sj[0] != '\0';)
+ {
+
+ ni = str_get_next_char (si);
+ nj = str_get_next_char (sj);
+
+ if (ni - si != nj - sj)
+ break;
+ if (strncmp (si, sj, ni - si) != 0)
+ break;
+
+ si = ni;
+ sj = nj;
+ }
+
+ if (si[0] == '\0' && sj[0] == '\0')
+ { /* Two equal strings */
+ g_free (match_list[j]);
+ j++;
+ if (j > matches)
+ break;
+ continue; /* Look for a run of equal strings */
+ }
+ else if (low > si - match_list[i])
+ low = si - match_list[i];
+ if (i + 1 != j) /* So there's some gap */
+ match_list[i + 1] = match_list[j];
+ i++;
+ j++;
+ }
+ matches = i;
+ match_list[matches + 1] = NULL;
+ match_list[0] = g_strndup (match_list[1], low);
+ }
+ }
+
+ return match_list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Check if directory completion is needed */
+static gboolean
+check_is_cd (const char *text, int lc_start, input_complete_t flags)
+{
+ const char *p, *q;
+
+ SHOW_C_CTX ("check_is_cd");
+
+ if ((flags & INPUT_COMPLETE_CD) == 0)
+ return FALSE;
+
+ /* Skip initial spaces */
+ p = text;
+ q = text + lc_start;
+ while (p < q && p[0] != '\0' && str_isspace (p))
+ str_cnext_char (&p);
+
+ /* Check if the command is "cd" and the cursor is after it */
+ return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+try_complete_commands_prepare (try_complete_automation_state_t * state, char *text, int *lc_start)
+{
+ const char *command_separator_chars = ";|&{(`";
+ char *ti;
+
+ if (*lc_start == 0)
+ ti = text;
+ else
+ {
+ ti = str_get_prev_char (&text[*lc_start]);
+ while (ti > text && whitespace (ti[0]))
+ str_prev_char (&ti);
+ }
+
+ if (ti == text)
+ state->in_command_position++;
+ else if (strchr (command_separator_chars, ti[0]) != NULL)
+ {
+ state->in_command_position++;
+ if (ti != text)
+ {
+ int this_char, prev_char;
+
+ /* Handle the two character tokens '>&', '<&', and '>|'.
+ We are not in a command position after one of these. */
+ this_char = ti[0];
+ prev_char = str_get_prev_char (ti)[0];
+
+ /* Quoted */
+ if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
+ || (this_char == '|' && prev_char == '>') || (ti != text
+ && str_get_prev_char (ti)[0] == '\\'))
+ state->in_command_position = 0;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+try_complete_find_start_sign (try_complete_automation_state_t * state)
+{
+ if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
+ state->p = strrchr (state->word, '`');
+ if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
+ {
+ state->q = strrchr (state->word, '$');
+
+ /* don't substitute variable in \$ case */
+ if (strutils_is_char_escaped (state->word, state->q))
+ {
+ /* drop '\\' */
+ str_move (state->q - 1, state->q);
+ /* adjust flags */
+ state->flags &= ~INPUT_COMPLETE_VARIABLES;
+ state->q = NULL;
+ }
+ }
+ if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
+ state->r = strrchr (state->word, '@');
+ if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
+ {
+ if (state->q > state->p)
+ state->p = str_get_next_char (state->q);
+ state->q = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char **
+try_complete_all_possible (try_complete_automation_state_t * state, char *text, int *lc_start)
+{
+ char **matches = NULL;
+
+ if (state->in_command_position != 0)
+ {
+ SHOW_C_CTX ("try_complete:cmd_subst");
+ matches =
+ completion_matches (state->word, command_completion_function,
+ state->flags & (~INPUT_COMPLETE_FILENAMES));
+ }
+ else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
+ {
+ if (state->is_cd)
+ state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
+ SHOW_C_CTX ("try_complete:filename_subst_1");
+ matches = completion_matches (state->word, filename_completion_function, state->flags);
+
+ if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
+ {
+ state->q = text + *lc_start;
+ for (state->p = text;
+ *state->p != '\0' && state->p < state->q && whitespace (*state->p);
+ str_next_char (&state->p))
+ ;
+ if (strncmp (state->p, "cd", 2) == 0)
+ for (state->p += 2;
+ *state->p != '\0' && state->p < state->q && whitespace (*state->p);
+ str_next_char (&state->p))
+ ;
+ if (state->p == state->q)
+ {
+ char *cdpath_ref, *cdpath;
+ char c;
+
+ cdpath_ref = g_strdup (getenv ("CDPATH"));
+ cdpath = cdpath_ref;
+ c = (cdpath == NULL) ? '\0' : ':';
+
+ while (matches == NULL && c == ':')
+ {
+ char *s;
+
+ s = strchr (cdpath, ':');
+ /* cppcheck-suppress nullPointer */
+ if (s == NULL)
+ s = strchr (cdpath, '\0');
+ c = *s;
+ *s = '\0';
+ if (*cdpath != '\0')
+ {
+ state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
+ SHOW_C_CTX ("try_complete:filename_subst_2");
+ matches =
+ completion_matches (state->r, filename_completion_function,
+ state->flags);
+ g_free (state->r);
+ }
+ *s = c;
+ cdpath = str_get_next_char (s);
+ }
+ g_free (cdpath_ref);
+ }
+ }
+ }
+ return matches;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+insert_text (WInput * in, char *text, ssize_t size)
+{
+ size_t text_len;
+ int buff_len;
+ ssize_t new_size;
+
+ text_len = strlen (text);
+ buff_len = str_length (in->buffer->str);
+ if (size < 0)
+ size = (ssize_t) text_len;
+ else
+ size = MIN (size, (ssize_t) text_len);
+
+ new_size = size + start - end;
+ if (new_size != 0)
+ {
+ /* make a hole within buffer */
+
+ size_t tail_len;
+
+ tail_len = in->buffer->len - end;
+ if (tail_len != 0)
+ {
+ char *tail;
+ size_t hole_end;
+
+ tail = g_strndup (in->buffer->str + end, tail_len);
+
+ hole_end = end + new_size;
+ if (in->buffer->len < hole_end)
+ g_string_set_size (in->buffer, hole_end + tail_len);
+
+ g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
+
+ g_free (tail);
+ }
+ }
+
+ g_string_overwrite_len (in->buffer, start, text, size);
+
+ in->point += str_length (in->buffer->str) - buff_len;
+ input_update (in, TRUE);
+ end += new_size;
+
+ return new_size != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ static int bl = 0;
+
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_KEY:
+ switch (parm)
+ {
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ bl = 0;
+ h->ret_value = 0;
+ dlg_close (h);
+ return MSG_HANDLED;
+
+ case KEY_BACKSPACE:
+ bl = 0;
+ /* exit from completion list if input line is empty */
+ if (end == 0)
+ {
+ h->ret_value = 0;
+ dlg_close (h);
+ }
+ /* Refill the list box and start again */
+ else if (end == min_end)
+ {
+ end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
+ input_handle_char (input, parm);
+ h->ret_value = B_USER;
+ dlg_close (h);
+ }
+ else
+ {
+ int new_end;
+ int i;
+ GList *e;
+
+ new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
+
+ for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
+ e != NULL; i++, e = g_list_next (e))
+ {
+ WLEntry *le = LENTRY (e->data);
+
+ if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
+ {
+ listbox_set_current (LISTBOX (g->current->data), i);
+ end = new_end;
+ input_handle_char (input, parm);
+ widget_draw (WIDGET (g->current->data));
+ break;
+ }
+ }
+ }
+ return MSG_HANDLED;
+
+ default:
+ if (parm < 32 || parm > 255)
+ {
+ bl = 0;
+ if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
+ return MSG_NOT_HANDLED;
+
+ if (end == min_end)
+ return MSG_HANDLED;
+
+ /* This means we want to refill the list box and start again */
+ h->ret_value = B_USER;
+ dlg_close (h);
+ }
+ else
+ {
+ static char buff[MB_LEN_MAX] = "";
+ GList *e;
+ int i;
+ int need_redraw = 0;
+ int low = 4096;
+ char *last_text = NULL;
+
+ buff[bl++] = (char) parm;
+ buff[bl] = '\0';
+
+ switch (str_is_valid_char (buff, bl))
+ {
+ case -1:
+ bl = 0;
+ MC_FALLTHROUGH;
+ case -2:
+ return MSG_HANDLED;
+ default:
+ break;
+ }
+
+ for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
+ e != NULL; i++, e = g_list_next (e))
+ {
+ WLEntry *le = LENTRY (e->data);
+
+ if (strncmp (input->buffer->str + start, le->text, end - start) == 0
+ && strncmp (le->text + end - start, buff, bl) == 0)
+ {
+ if (need_redraw == 0)
+ {
+ need_redraw = 1;
+ listbox_set_current (LISTBOX (g->current->data), i);
+ last_text = le->text;
+ }
+ else
+ {
+ char *si, *sl;
+ int si_num = 0;
+ int sl_num = 0;
+
+ /* count symbols between start and end */
+ for (si = le->text + start; si < le->text + end;
+ str_next_char (&si), si_num++)
+ ;
+ for (sl = last_text + start; sl < last_text + end;
+ str_next_char (&sl), sl_num++)
+ ;
+
+ /* pointers to next symbols */
+ si = &le->text[str_offset_to_pos (le->text, ++si_num)];
+ sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
+
+ while (si[0] != '\0' && sl[0] != '\0')
+ {
+ char *nexti, *nextl;
+
+ nexti = str_get_next_char (si);
+ nextl = str_get_next_char (sl);
+
+ if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
+ break;
+
+ si = nexti;
+ sl = nextl;
+
+ si_num++;
+ }
+
+ last_text = le->text;
+
+ si = &last_text[str_offset_to_pos (last_text, si_num)];
+ if (low > si - last_text)
+ low = si - last_text;
+
+ need_redraw = 2;
+ }
+ }
+ }
+
+ if (need_redraw == 2)
+ {
+ insert_text (input, last_text, low);
+ widget_draw (WIDGET (g->current->data));
+ }
+ else if (need_redraw == 1)
+ {
+ h->ret_value = B_ENTER;
+ dlg_close (h);
+ }
+ bl = 0;
+ }
+ }
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Returns TRUE if the user would like to see us again */
+static gboolean
+complete_engine (WInput * in, int what_to_do)
+{
+ if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
+ input_complete_free (in);
+
+ if (in->completions == NULL)
+ complete_engine_fill_completions (in);
+
+ if (in->completions == NULL)
+ tty_beep ();
+ else
+ {
+ if ((what_to_do & DO_INSERTION) != 0
+ || ((what_to_do & DO_QUERY) != 0 && in->completions[1] == NULL))
+ {
+ char *lc_complete = in->completions[0];
+
+ if (!insert_text (in, lc_complete, -1) || in->completions[1] != NULL)
+ tty_beep ();
+ else
+ input_complete_free (in);
+ }
+
+ if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions[1] != NULL)
+ {
+ int maxlen = 0, count = 0, i;
+ int x, y, w, h;
+ int start_x, start_y;
+ char **p, *q;
+ WDialog *complete_dlg;
+ WListbox *complete_list;
+
+ for (p = in->completions + 1; *p != NULL; count++, p++)
+ {
+ i = str_term_width1 (*p);
+ if (i > maxlen)
+ maxlen = i;
+ }
+
+ start_x = WIDGET (in)->rect.x;
+ start_y = WIDGET (in)->rect.y;
+ if (start_y - 2 >= count)
+ {
+ y = start_y - 2 - count;
+ h = 2 + count;
+ }
+ else if (start_y >= LINES - start_y - 1)
+ {
+ y = 0;
+ h = start_y;
+ }
+ else
+ {
+ y = start_y + 1;
+ h = LINES - start_y - 1;
+ }
+ x = start - in->term_first_shown - 2 + start_x;
+ w = maxlen + 4;
+ if (x + w > COLS)
+ x = COLS - w;
+ if (x < 0)
+ x = 0;
+ if (x + w > COLS)
+ w = COLS;
+
+ input = in;
+ min_end = end;
+ complete_height = h;
+ complete_width = w;
+
+ complete_dlg =
+ dlg_create (TRUE, y, x, complete_height, complete_width, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, complete_callback, NULL, "[Completion]", NULL);
+ complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
+ group_add_widget (GROUP (complete_dlg), complete_list);
+
+ for (p = in->completions + 1; *p != NULL; p++)
+ listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
+
+ i = dlg_run (complete_dlg);
+ q = NULL;
+ if (i == B_ENTER)
+ {
+ listbox_get_current (complete_list, &q, NULL);
+ if (q != NULL)
+ insert_text (in, q, -1);
+ }
+ if (q != NULL || end != min_end)
+ input_complete_free (in);
+ widget_destroy (WIDGET (complete_dlg));
+
+ /* B_USER if user wants to start over again */
+ return (i == B_USER);
+ }
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Returns an array of matches, or NULL if none. */
+char **
+try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
+{
+ try_complete_automation_state_t state;
+ char **matches = NULL;
+
+ memset (&state, 0, sizeof (state));
+ state.flags = flags;
+
+ SHOW_C_CTX ("try_complete");
+ state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
+
+ state.is_cd = check_is_cd (text, *lc_start, state.flags);
+
+ /* Determine if this could be a command word. It is if it appears at
+ the start of the line (ignoring preceding whitespace), or if it
+ appears after a character that separates commands. And we have to
+ be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
+ if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
+ try_complete_commands_prepare (&state, text, lc_start);
+
+ try_complete_find_start_sign (&state);
+
+ /* Command substitution? */
+ if (state.p > state.q && state.p > state.r)
+ {
+ SHOW_C_CTX ("try_complete:cmd_backq_subst");
+ matches = completion_matches (str_cget_next_char (state.p),
+ command_completion_function,
+ state.flags & (~INPUT_COMPLETE_FILENAMES));
+ if (matches != NULL)
+ *lc_start += str_get_next_char (state.p) - state.word;
+ }
+
+ /* Variable name? */
+ else if (state.q > state.p && state.q > state.r)
+ {
+ SHOW_C_CTX ("try_complete:var_subst");
+ matches = completion_matches (state.q, variable_completion_function, state.flags);
+ if (matches != NULL)
+ *lc_start += state.q - state.word;
+ }
+
+ /* Starts with '@', then look through the known hostnames for
+ completion first. */
+ else if (state.r > state.p && state.r > state.q)
+ {
+ SHOW_C_CTX ("try_complete:host_subst");
+ matches = completion_matches (state.r, hostname_completion_function, state.flags);
+ if (matches != NULL)
+ *lc_start += state.r - state.word;
+ }
+
+ /* Starts with '~' and there is no slash in the word, then
+ try completing this word as a username. */
+ if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
+ && strchr (state.word, PATH_SEP) == NULL)
+ {
+ SHOW_C_CTX ("try_complete:user_subst");
+ matches = completion_matches (state.word, username_completion_function, state.flags);
+ }
+
+ /* If this word is in a command position, then
+ complete over possible command names, including aliases, functions,
+ and command names. */
+ if (matches == NULL)
+ matches = try_complete_all_possible (&state, text, lc_start);
+
+ /* And finally if nothing found, try complete directory name */
+ if (matches == NULL)
+ {
+ state.in_command_position = 0;
+ matches = try_complete_all_possible (&state, text, lc_start);
+ }
+
+ g_free (state.word);
+
+ if (matches != NULL &&
+ (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
+ (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
+ {
+ /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
+ char **m;
+
+ for (m = matches; *m != NULL; m++)
+ {
+ char *p;
+
+ p = *m;
+ *m = strutils_shell_escape (*m);
+ g_free (p);
+ }
+ }
+
+ return matches;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+complete_engine_fill_completions (WInput * in)
+{
+ char *s;
+ const char *word_separators;
+
+ word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
+
+ end = str_offset_to_pos (in->buffer->str, in->point);
+
+ s = in->buffer->str;
+ if (in->point != 0)
+ {
+ /* get symbol before in->point */
+ size_t i;
+
+ for (i = in->point - 1; i > 0; i--)
+ str_next_char (&s);
+ }
+
+ for (; s >= in->buffer->str; str_prev_char (&s))
+ {
+ start = s - in->buffer->str;
+ if (strchr (word_separators, *s) != NULL && !strutils_is_char_escaped (in->buffer->str, s))
+ break;
+ }
+
+ if (start < end)
+ {
+ str_next_char (&s);
+ start = s - in->buffer->str;
+ }
+
+ in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* declared in lib/widget/input.h */
+void
+input_complete (WInput * in)
+{
+ int engine_flags;
+
+ if (!str_is_valid_string (in->buffer->str))
+ return;
+
+ if (in->completions != NULL)
+ engine_flags = DO_QUERY;
+ else
+ {
+ engine_flags = DO_INSERTION;
+
+ if (mc_global.widget.show_all_if_ambiguous)
+ engine_flags |= DO_QUERY;
+ }
+
+ while (complete_engine (in, engine_flags))
+ ;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_complete_free (WInput * in)
+{
+ g_strfreev (in->completions);
+ in->completions = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/label.c b/lib/widget/label.c
new file mode 100644
index 0000000..5a04a0f
--- /dev/null
+++ b/lib/widget/label.c
@@ -0,0 +1,201 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file label.c
+ * \brief Source: WLabel widget
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+label_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WLabel *l = LABEL (w);
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ {
+ char *p = l->text;
+ int y = 0;
+ gboolean disabled;
+ align_crt_t align;
+
+ if (l->text == NULL)
+ return MSG_HANDLED;
+
+ disabled = widget_get_state (w, WST_DISABLED);
+
+ if (l->transparent)
+ tty_setcolor (disabled ? DISABLED_COLOR : DEFAULT_COLOR);
+ else
+ {
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (disabled ? DISABLED_COLOR : colors[DLG_COLOR_NORMAL]);
+ }
+
+ align = (w->pos_flags & WPOS_CENTER_HORZ) != 0 ? J_CENTER_LEFT : J_LEFT;
+
+ while (TRUE)
+ {
+ char *q;
+ char c = '\0';
+
+
+ q = strchr (p, '\n');
+ if (q != NULL)
+ {
+ c = q[0];
+ q[0] = '\0';
+ }
+
+ widget_gotoyx (w, y, 0);
+ tty_print_string (str_fit_to_term (p, w->rect.cols, align));
+
+ if (q == NULL)
+ break;
+
+ q[0] = c;
+ p = q + 1;
+ y++;
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ g_free (l->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WLabel *
+label_new (int y, int x, const char *text)
+{
+ WRect r = { y, x, 1, 1 };
+ WLabel *l;
+ Widget *w;
+
+ if (text != NULL)
+ str_msg_term_size (text, &r.lines, &r.cols);
+
+ l = g_new (WLabel, 1);
+ w = WIDGET (l);
+ widget_init (w, &r, label_callback, NULL);
+
+ l->text = g_strdup (text);
+ l->auto_adjust_cols = TRUE;
+ l->transparent = FALSE;
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+label_set_text (WLabel * label, const char *text)
+{
+ Widget *w = WIDGET (label);
+ int newcols = w->rect.cols;
+ int newlines;
+
+ if (label->text != NULL && text != NULL && strcmp (label->text, text) == 0)
+ return; /* Flickering is not nice */
+
+ g_free (label->text);
+
+ if (text == NULL)
+ label->text = NULL;
+ else
+ {
+ label->text = g_strdup (text);
+ if (label->auto_adjust_cols)
+ {
+ str_msg_term_size (text, &newlines, &newcols);
+ w->rect.cols = MAX (newcols, w->rect.cols);
+ w->rect.lines = MAX (newlines, w->rect.lines);
+ }
+ }
+
+ widget_draw (w);
+
+ w->rect.cols = MIN (newcols, w->rect.cols);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+label_set_textv (WLabel * label, const char *format, ...)
+{
+ va_list args;
+ char buf[BUF_1K]; /* FIXME: is it enough? */
+
+ va_start (args, format);
+ g_vsnprintf (buf, sizeof (buf), format, args);
+ va_end (args);
+
+ label_set_text (label, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/label.h b/lib/widget/label.h
new file mode 100644
index 0000000..6d1607f
--- /dev/null
+++ b/lib/widget/label.h
@@ -0,0 +1,37 @@
+
+/** \file label.h
+ * \brief Header: WLabel widget
+ */
+
+#ifndef MC__WIDGET_LABEL_H
+#define MC__WIDGET_LABEL_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LABEL(x) ((WLabel *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+ gboolean auto_adjust_cols; /* compute widget.cols from strlen(text)? */
+ char *text;
+ gboolean transparent; /* Paint in the default color fg/bg */
+} WLabel;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WLabel *label_new (int y, int x, const char *text);
+void label_set_text (WLabel * label, const char *text);
+/* *INDENT-OFF* */
+void label_set_textv (WLabel * label, const char *format, ...) G_GNUC_PRINTF (2, 3);
+/* *INDENT-ON* */
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_LABEL_H */
diff --git a/lib/widget/listbox-window.c b/lib/widget/listbox-window.c
new file mode 100644
index 0000000..47d7f8b
--- /dev/null
+++ b/lib/widget/listbox-window.c
@@ -0,0 +1,176 @@
+/*
+ Widget based utility functions.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Miguel de Icaza, 1994, 1995, 1996
+ Radek Doulik, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2009, 2010, 2013
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file listbox-window.c
+ * \brief Source: Listbox widget, a listbox within dialog window
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* COLS */
+#include "lib/skin.h"
+#include "lib/strutil.h" /* str_term_width1() */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+Listbox *
+listbox_window_centered_new (int center_y, int center_x, int lines, int cols,
+ const char *title, const char *help)
+{
+ const int space = 4;
+
+ int xpos = 0, ypos = 0;
+ Listbox *listbox;
+ widget_pos_flags_t pos_flags = WPOS_TRYUP;
+
+ /* Adjust sizes */
+ lines = MIN (lines, LINES - 6);
+
+ if (title != NULL)
+ {
+ int len;
+
+ len = str_term_width1 (title) + 4;
+ cols = MAX (cols, len);
+ }
+
+ cols = MIN (cols, COLS - 6);
+
+ /* adjust position */
+ if ((center_y < 0) || (center_x < 0))
+ pos_flags |= WPOS_CENTER;
+ else
+ {
+ /* Actually, this this is not used in MC. */
+
+ ypos = center_y;
+ xpos = center_x;
+
+ ypos -= lines / 2;
+ xpos -= cols / 2;
+
+ if (ypos + lines >= LINES)
+ ypos = LINES - lines - space;
+ if (ypos < 0)
+ ypos = 0;
+
+ if (xpos + cols >= COLS)
+ xpos = COLS - cols - space;
+ if (xpos < 0)
+ xpos = 0;
+ }
+
+ listbox = g_new (Listbox, 1);
+
+ listbox->dlg =
+ dlg_create (TRUE, ypos, xpos, lines + space, cols + space, pos_flags, FALSE, listbox_colors,
+ NULL, NULL, help, title);
+
+ listbox->list = listbox_new (2, 2, lines, cols, FALSE, NULL);
+ group_add_widget (GROUP (listbox->dlg), listbox->list);
+
+ return listbox;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+Listbox *
+listbox_window_new (int lines, int cols, const char *title, const char *help)
+{
+ return listbox_window_centered_new (-1, -1, lines, cols, title, help);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Returns the number of the item selected */
+int
+listbox_run (Listbox * l)
+{
+ int val = -1;
+
+ if (dlg_run (l->dlg) != B_CANCEL)
+ val = l->list->current;
+ widget_destroy (WIDGET (l->dlg));
+ g_free (l);
+ return val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * A variant of listbox_run() which is more convenient to use when we
+ * need to select arbitrary 'data'.
+ *
+ * @param select the item to select initially, by its 'data'. Optional.
+ * @return the 'data' of the item selected, or NULL if none selected.
+ */
+void *
+listbox_run_with_data (Listbox * l, const void *select)
+{
+ void *val = NULL;
+
+ if (select != NULL)
+ listbox_set_current (l->list, listbox_search_data (l->list, select));
+
+ if (dlg_run (l->dlg) != B_CANCEL)
+ {
+ WLEntry *e;
+
+ e = listbox_get_nth_entry (l->list, l->list->current);
+ if (e != NULL)
+ {
+ /* The assert guards against returning a soon-to-be deallocated
+ * pointer (as in listbox_add_item(..., TRUE)). */
+ g_assert (!e->free_data);
+ val = e->data;
+ }
+ }
+
+ widget_destroy (WIDGET (l->dlg));
+ g_free (l);
+ return val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/listbox-window.h b/lib/widget/listbox-window.h
new file mode 100644
index 0000000..b9bb1e8
--- /dev/null
+++ b/lib/widget/listbox-window.h
@@ -0,0 +1,36 @@
+/** \file listbox-window.h
+ * \brief Header: Listbox widget, a listbox within dialog window
+ */
+
+#ifndef MC__LISTBOX_DIALOG_H
+#define MC__LISTBOX_DIALOG_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LISTBOX_APPEND_TEXT(l,h,t,d,f) \
+ listbox_add_item (l->list, LISTBOX_APPEND_AT_END, h, t, d, f)
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ WDialog *dlg;
+ WListbox *list;
+} Listbox;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Listbox utility functions */
+Listbox *listbox_window_centered_new (int center_y, int center_x, int lines, int cols,
+ const char *title, const char *help);
+Listbox *listbox_window_new (int lines, int cols, const char *title, const char *help);
+int listbox_run (Listbox * l);
+void *listbox_run_with_data (Listbox * l, const void *select);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__LISTBOX_DIALOG_H */
diff --git a/lib/widget/listbox.c b/lib/widget/listbox.c
new file mode 100644
index 0000000..9f25487
--- /dev/null
+++ b/lib/widget/listbox.c
@@ -0,0 +1,832 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file listbox.c
+ * \brief Source: WListbox widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* Q_() */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+const global_keymap_t *listbox_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Gives the position of the last item. */
+#define LISTBOX_LAST(l) (listbox_is_empty (l) ? 0 : (int) g_queue_get_length ((l)->list) - 1)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+listbox_entry_cmp (const void *a, const void *b, void *user_data)
+{
+ const WLEntry *ea = (const WLEntry *) a;
+ const WLEntry *eb = (const WLEntry *) b;
+
+ (void) user_data;
+
+ return strcmp (ea->text, eb->text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_entry_free (void *data)
+{
+ WLEntry *e = data;
+
+ g_free (e->text);
+ if (e->free_data)
+ g_free (e->data);
+ g_free (e);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_drawscroll (const WListbox * l)
+{
+ const WRect *w = &CONST_WIDGET (l)->rect;
+ int max_line = w->lines - 1;
+ int line = 0;
+ int i;
+ int length;
+
+ /* Are we at the top? */
+ widget_gotoyx (l, 0, w->cols);
+ if (l->top == 0)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('^');
+
+ length = g_queue_get_length (l->list);
+
+ /* Are we at the bottom? */
+ widget_gotoyx (w, max_line, w->cols);
+ if (l->top + w->lines == length || w->lines >= length)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('v');
+
+ /* Now draw the nice relative pointer */
+ if (!g_queue_is_empty (l->list))
+ line = 1 + ((l->current * (w->lines - 2)) / length);
+
+ for (i = 1; i < max_line; i++)
+ {
+ widget_gotoyx (l, i, w->cols);
+ if (i != line)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('*');
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_draw (WListbox * l, gboolean focused)
+{
+ Widget *wl = WIDGET (l);
+ const WRect *w = &CONST_WIDGET (l)->rect;
+ const int *colors;
+ gboolean disabled;
+ int normalc, selc;
+ int length = 0;
+ GList *le = NULL;
+ int pos;
+ int i;
+ int sel_line = -1;
+
+ colors = widget_get_colors (wl);
+
+ disabled = widget_get_state (wl, WST_DISABLED);
+ normalc = disabled ? DISABLED_COLOR : colors[DLG_COLOR_NORMAL];
+ selc = disabled ? DISABLED_COLOR : colors[focused ? DLG_COLOR_HOT_FOCUS : DLG_COLOR_FOCUS];
+
+ if (l->list != NULL)
+ {
+ length = g_queue_get_length (l->list);
+ le = g_queue_peek_nth_link (l->list, (guint) l->top);
+ }
+
+ /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
+ pos = (le == NULL) ? 0 : l->top;
+
+ for (i = 0; i < w->lines; i++)
+ {
+ const char *text = "";
+
+ /* Display the entry */
+ if (pos == l->current && sel_line == -1)
+ {
+ sel_line = i;
+ tty_setcolor (selc);
+ }
+ else
+ tty_setcolor (normalc);
+
+ widget_gotoyx (l, i, 1);
+
+ if (l->list != NULL && le != NULL && (i == 0 || pos < length))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ text = e->text;
+ le = g_list_next (le);
+ pos++;
+ }
+
+ tty_print_string (str_fit_to_term (text, w->cols - 2, J_LEFT_FIT));
+ }
+
+ l->cursor_y = sel_line;
+
+ if (l->scrollbar && length > w->lines)
+ {
+ tty_setcolor (normalc);
+ listbox_drawscroll (l);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+listbox_check_hotkey (WListbox * l, int key)
+{
+ if (!listbox_is_empty (l))
+ {
+ int i;
+ GList *le;
+
+ for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ if (e->hotkey == key)
+ return i;
+ }
+ }
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Calculates the item displayed at screen row 'y' (y==0 being the widget's 1st row). */
+static int
+listbox_y_pos (WListbox * l, int y)
+{
+ return MIN (l->top + y, LISTBOX_LAST (l));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_fwd (WListbox * l, gboolean wrap)
+{
+ if (!listbox_is_empty (l))
+ {
+ if ((guint) l->current + 1 < g_queue_get_length (l->list))
+ listbox_set_current (l, l->current + 1);
+ else if (wrap)
+ listbox_select_first (l);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_fwd_n (WListbox * l, int n)
+{
+ listbox_set_current (l, MIN (l->current + n, LISTBOX_LAST (l)));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_back (WListbox * l, gboolean wrap)
+{
+ if (!listbox_is_empty (l))
+ {
+ if (l->current > 0)
+ listbox_set_current (l, l->current - 1);
+ else if (wrap)
+ listbox_select_last (l);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_back_n (WListbox * l, int n)
+{
+ listbox_set_current (l, MAX (l->current - n, 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+listbox_execute_cmd (WListbox * l, long command)
+{
+ cb_ret_t ret = MSG_HANDLED;
+ const WRect *w = &CONST_WIDGET (l)->rect;
+
+ if (l->list == NULL || g_queue_is_empty (l->list))
+ return MSG_NOT_HANDLED;
+
+ switch (command)
+ {
+ case CK_Up:
+ listbox_back (l, TRUE);
+ break;
+ case CK_Down:
+ listbox_fwd (l, TRUE);
+ break;
+ case CK_Top:
+ listbox_select_first (l);
+ break;
+ case CK_Bottom:
+ listbox_select_last (l);
+ break;
+ case CK_PageUp:
+ listbox_back_n (l, w->lines - 1);
+ break;
+ case CK_PageDown:
+ listbox_fwd_n (l, w->lines - 1);
+ break;
+ case CK_Delete:
+ if (l->deletable)
+ {
+ gboolean is_last, is_more;
+ int length;
+
+ length = g_queue_get_length (l->list);
+
+ is_last = (l->current + 1 >= length);
+ is_more = (l->top + w->lines >= length);
+
+ listbox_remove_current (l);
+ if ((l->top > 0) && (is_last || is_more))
+ l->top--;
+ }
+ break;
+ case CK_Clear:
+ if (l->deletable && mc_global.widget.confirm_history_cleanup
+ /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
+ && (query_dialog (Q_ ("DialogTitle|History cleanup"),
+ _("Do you want clean this history?"),
+ D_ERROR, 2, _("&Yes"), _("&No")) == 0))
+ listbox_remove_list (l);
+ break;
+ case CK_View:
+ case CK_Edit:
+ case CK_Enter:
+ ret = send_message (WIDGET (l)->owner, l, MSG_NOTIFY, command, NULL);
+ break;
+ default:
+ ret = MSG_NOT_HANDLED;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return MSG_HANDLED if we want a redraw */
+static cb_ret_t
+listbox_key (WListbox * l, int key)
+{
+ long command;
+
+ if (l->list == NULL)
+ return MSG_NOT_HANDLED;
+
+ /* focus on listbox item N by '0'..'9' keys */
+ if (key >= '0' && key <= '9')
+ {
+ listbox_set_current (l, key - '0');
+ return MSG_HANDLED;
+ }
+
+ command = widget_lookup_key (WIDGET (l), key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+ return listbox_execute_cmd (l, command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Listbox item adding function */
+static inline void
+listbox_add_entry (WListbox * l, WLEntry * e, listbox_append_t pos)
+{
+ if (l->list == NULL)
+ {
+ l->list = g_queue_new ();
+ pos = LISTBOX_APPEND_AT_END;
+ }
+
+ switch (pos)
+ {
+ case LISTBOX_APPEND_AT_END:
+ g_queue_push_tail (l->list, e);
+ break;
+
+ case LISTBOX_APPEND_BEFORE:
+ g_queue_insert_before (l->list, g_queue_peek_nth_link (l->list, (guint) l->current), e);
+ break;
+
+ case LISTBOX_APPEND_AFTER:
+ g_queue_insert_after (l->list, g_queue_peek_nth_link (l->list, (guint) l->current), e);
+ break;
+
+ case LISTBOX_APPEND_SORTED:
+ g_queue_insert_sorted (l->list, e, (GCompareDataFunc) listbox_entry_cmp, NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Call this whenever the user changes the selected item. */
+static void
+listbox_on_change (WListbox * l)
+{
+ listbox_draw (l, TRUE);
+ send_message (WIDGET (l)->owner, l, MSG_NOTIFY, 0, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_do_action (WListbox * l)
+{
+ int action;
+
+ if (listbox_is_empty (l))
+ return;
+
+ if (l->callback != NULL)
+ action = l->callback (l);
+ else
+ action = LISTBOX_DONE;
+
+ if (action == LISTBOX_DONE)
+ {
+ WDialog *h = DIALOG (WIDGET (l)->owner);
+
+ h->ret_value = B_ENTER;
+ dlg_close (h);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_run_hotkey (WListbox * l, int pos)
+{
+ listbox_set_current (l, pos);
+ listbox_on_change (l);
+ listbox_do_action (l);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+listbox_destroy (WListbox * l)
+{
+ listbox_remove_list (l);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WListbox *l = LISTBOX (w);
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ {
+ int pos;
+
+ pos = listbox_check_hotkey (l, parm);
+ if (pos < 0)
+ return MSG_NOT_HANDLED;
+
+ listbox_run_hotkey (l, pos);
+
+ return MSG_HANDLED;
+ }
+
+ case MSG_KEY:
+ {
+ cb_ret_t ret_code;
+
+ ret_code = listbox_key (l, parm);
+ if (ret_code != MSG_NOT_HANDLED)
+ listbox_on_change (l);
+ return ret_code;
+ }
+
+ case MSG_ACTION:
+ return listbox_execute_cmd (l, parm);
+
+ case MSG_CURSOR:
+ widget_gotoyx (l, l->cursor_y, 0);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ listbox_draw (l, widget_get_state (w, WST_FOCUSED));
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ listbox_destroy (l);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WListbox *l = LISTBOX (w);
+ int old_current;
+
+ old_current = l->current;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ listbox_set_current (l, listbox_y_pos (l, event->y));
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ listbox_back (l, FALSE);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ listbox_fwd (l, FALSE);
+ break;
+
+ case MSG_MOUSE_DRAG:
+ event->result.repeat = TRUE; /* It'd be functional even without this. */
+ listbox_set_current (l, listbox_y_pos (l, event->y));
+ break;
+
+ case MSG_MOUSE_CLICK:
+ /* We don't call listbox_set_current() here: MSG_MOUSE_DOWN/DRAG did this already. */
+ if (event->count == GPM_DOUBLE) /* Double click */
+ listbox_do_action (l);
+ break;
+
+ default:
+ break;
+ }
+
+ /* If the selection has changed, we redraw the widget and notify the dialog. */
+ if (l->current != old_current)
+ listbox_on_change (l);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WListbox *
+listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
+{
+ WRect r = { y, x, 1, width };
+ WListbox *l;
+ Widget *w;
+
+ l = g_new (WListbox, 1);
+ w = WIDGET (l);
+ r.lines = height > 0 ? height : 1;
+ widget_init (w, &r, listbox_callback, listbox_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_HOTKEY;
+ w->keymap = listbox_map;
+
+ l->list = NULL;
+ l->top = l->current = 0;
+ l->deletable = deletable;
+ l->callback = callback;
+ l->allow_duplicates = TRUE;
+ l->scrollbar = !mc_global.tty.slow_terminal;
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Finds item by its label.
+ */
+int
+listbox_search_text (WListbox * l, const char *text)
+{
+ if (!listbox_is_empty (l))
+ {
+ int i;
+ GList *le;
+
+ for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ if (strcmp (e->text, text) == 0)
+ return i;
+ }
+ }
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Finds item by its 'data' slot.
+ */
+int
+listbox_search_data (WListbox * l, const void *data)
+{
+ if (!listbox_is_empty (l))
+ {
+ int i;
+ GList *le;
+
+ for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ if (e->data == data)
+ return i;
+ }
+ }
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Select the first entry and scrolls the list to the top */
+void
+listbox_select_first (WListbox * l)
+{
+ l->current = l->top = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Selects the last entry and scrolls the list to the bottom */
+void
+listbox_select_last (WListbox * l)
+{
+ int lines = WIDGET (l)->rect.lines;
+ int length;
+
+ length = listbox_get_length (l);
+
+ l->current = DOZ (length, 1);
+ l->top = DOZ (length, lines);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listbox_set_current (WListbox * l, int dest)
+{
+ GList *le;
+ int pos;
+ gboolean top_seen = FALSE;
+
+ if (listbox_is_empty (l) || dest < 0)
+ return;
+
+ /* Special case */
+ for (pos = 0, le = g_queue_peek_head_link (l->list); le != NULL; pos++, le = g_list_next (le))
+ {
+ if (pos == l->top)
+ top_seen = TRUE;
+
+ if (pos == dest)
+ {
+ l->current = dest;
+ if (!top_seen)
+ l->top = l->current;
+ else
+ {
+ int lines = WIDGET (l)->rect.lines;
+
+ if (l->current - l->top >= lines)
+ l->top = l->current - lines + 1;
+ }
+ return;
+ }
+ }
+
+ /* If we are unable to find it, set decent values */
+ l->current = l->top = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+listbox_get_length (const WListbox * l)
+{
+ return listbox_is_empty (l) ? 0 : (int) g_queue_get_length (l->list);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Returns the current string text as well as the associated extra data */
+void
+listbox_get_current (WListbox * l, char **string, void **extra)
+{
+ WLEntry *e = NULL;
+ gboolean ok;
+
+ if (l != NULL)
+ e = listbox_get_nth_entry (l, l->current);
+
+ ok = (e != NULL);
+
+ if (string != NULL)
+ *string = ok ? e->text : NULL;
+
+ if (extra != NULL)
+ *extra = ok ? e->data : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WLEntry *
+listbox_get_nth_entry (const WListbox * l, int pos)
+{
+ if (!listbox_is_empty (l) && pos >= 0)
+ {
+ GList *item;
+
+ item = g_queue_peek_nth_link (l->list, (guint) pos);
+ if (item != NULL)
+ return LENTRY (item->data);
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GList *
+listbox_get_first_link (const WListbox * l)
+{
+ return (l == NULL || l->list == NULL) ? NULL : g_queue_peek_head_link (l->list);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listbox_remove_current (WListbox * l)
+{
+ if (!listbox_is_empty (l))
+ {
+ GList *current;
+ int length;
+
+ current = g_queue_peek_nth_link (l->list, (guint) l->current);
+ listbox_entry_free (current->data);
+ g_queue_delete_link (l->list, current);
+
+ length = g_queue_get_length (l->list);
+
+ if (length == 0)
+ l->top = l->current = 0;
+ else if (l->current >= length)
+ l->current = length - 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+listbox_is_empty (const WListbox * l)
+{
+ return (l == NULL || l->list == NULL || g_queue_is_empty (l->list));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Set new listbox items list.
+ *
+ * @param l WListbox object
+ * @param list list of WLEntry objects
+ */
+void
+listbox_set_list (WListbox * l, GQueue * list)
+{
+ listbox_remove_list (l);
+
+ if (l != NULL)
+ l->list = list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listbox_remove_list (WListbox * l)
+{
+ if (l != NULL)
+ {
+ if (l->list != NULL)
+ {
+ g_queue_free_full (l->list, (GDestroyNotify) listbox_entry_free);
+ l->list = NULL;
+ }
+
+ l->current = l->top = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data,
+ gboolean free_data)
+{
+ WLEntry *entry;
+
+ if (l == NULL)
+ return NULL;
+
+ if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
+ return NULL;
+
+ entry = g_new (WLEntry, 1);
+ entry->text = g_strdup (text);
+ entry->data = data;
+ entry->free_data = free_data;
+ entry->hotkey = hotkey;
+
+ listbox_add_entry (l, entry, pos);
+
+ return entry->text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/listbox.h b/lib/widget/listbox.h
new file mode 100644
index 0000000..0a62dfd
--- /dev/null
+++ b/lib/widget/listbox.h
@@ -0,0 +1,82 @@
+
+/** \file listbox.h
+ * \brief Header: WListbox widget
+ */
+
+#ifndef MC__WIDGET_LISTBOX_H
+#define MC__WIDGET_LISTBOX_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LISTBOX(x) ((WListbox *)(x))
+#define LENTRY(x) ((WLEntry *)(x))
+
+/*** enums ***************************************************************************************/
+
+/* callback should return one of the following values */
+typedef enum
+{
+ LISTBOX_CONT, /* continue */
+ LISTBOX_DONE /* finish dialog */
+} lcback_ret_t;
+
+typedef enum
+{
+ LISTBOX_APPEND_AT_END = 0, /* append at the end */
+ LISTBOX_APPEND_BEFORE, /* insert before current */
+ LISTBOX_APPEND_AFTER, /* insert after current */
+ LISTBOX_APPEND_SORTED /* insert alphabetically */
+} listbox_append_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WListbox;
+typedef lcback_ret_t (*lcback_fn) (struct WListbox * l);
+
+typedef struct WLEntry
+{
+ char *text; /* Text to display */
+ int hotkey;
+ void *data; /* Client information */
+ gboolean free_data; /* Whether to free the data on entry's removal */
+} WLEntry;
+
+typedef struct WListbox
+{
+ Widget widget;
+ GQueue *list; /* Pointer to the list of WLEntry */
+ int top; /* The first element displayed */
+ int current; /* The current element displayed */
+ gboolean allow_duplicates; /* Do we allow duplicates on the list? */
+ gboolean scrollbar; /* Draw a scrollbar? */
+ gboolean deletable; /* Can list entries be deleted? */
+ lcback_fn callback; /* The callback function */
+ int cursor_x, cursor_y; /* Cache the values */
+} WListbox;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const global_keymap_t *listbox_map;
+
+/*** declarations of public functions ************************************************************/
+
+WListbox *listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback);
+int listbox_search_text (WListbox * l, const char *text);
+int listbox_search_data (WListbox * l, const void *data);
+void listbox_select_first (WListbox * l);
+void listbox_select_last (WListbox * l);
+void listbox_set_current (WListbox * l, int dest);
+int listbox_get_length (const WListbox * l);
+void listbox_get_current (WListbox * l, char **string, void **extra);
+WLEntry *listbox_get_nth_entry (const WListbox * l, int pos);
+GList *listbox_get_first_link (const WListbox * l);
+void listbox_remove_current (WListbox * l);
+gboolean listbox_is_empty (const WListbox * l);
+void listbox_set_list (WListbox * l, GQueue * list);
+void listbox_remove_list (WListbox * l);
+char *listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text,
+ void *data, gboolean free_data);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_LISTBOX_H */
diff --git a/lib/widget/menu.c b/lib/widget/menu.c
new file mode 100644
index 0000000..4a30c02
--- /dev/null
+++ b/lib/widget/menu.c
@@ -0,0 +1,1092 @@
+/*
+ Pulldown menu code
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file menu.c
+ * \brief Source: pulldown menu code
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/tty/key.h" /* key macros */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+
+/*** global variables ****************************************************************************/
+
+const global_keymap_t *menu_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MENUENTRY(x) ((menu_entry_t *)(x))
+#define MENU(x) ((menu_t *)(x))
+
+/*** file scope type declarations ****************************************************************/
+
+struct menu_entry_t
+{
+ unsigned char first_letter;
+ hotkey_t text;
+ long command;
+ char *shortcut;
+};
+
+struct menu_t
+{
+ int start_x; /* position relative to menubar start */
+ hotkey_t text;
+ GList *entries;
+ size_t max_entry_len; /* cached max length of entry texts (text + shortcut) */
+ size_t max_hotkey_len; /* cached max length of shortcuts */
+ unsigned int current; /* pointer to current menu entry */
+ char *help_node;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menu_arrange (menu_t * menu, dlg_shortcut_str get_shortcut)
+{
+ if (menu != NULL)
+ {
+ GList *i;
+ size_t max_shortcut_len = 0;
+
+ menu->max_entry_len = 1;
+ menu->max_hotkey_len = 1;
+
+ for (i = menu->entries; i != NULL; i = g_list_next (i))
+ {
+ menu_entry_t *entry = MENUENTRY (i->data);
+
+ if (entry != NULL)
+ {
+ size_t len;
+
+ len = (size_t) hotkey_width (entry->text);
+ menu->max_hotkey_len = MAX (menu->max_hotkey_len, len);
+
+ if (get_shortcut != NULL)
+ entry->shortcut = get_shortcut (entry->command);
+
+ if (entry->shortcut != NULL)
+ {
+ len = (size_t) str_term_width1 (entry->shortcut);
+ max_shortcut_len = MAX (max_shortcut_len, len);
+ }
+ }
+ }
+
+ menu->max_entry_len = menu->max_hotkey_len + max_shortcut_len;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_paint_idx (const WMenuBar * menubar, unsigned int idx, int color)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ const menu_entry_t *entry = MENUENTRY (g_list_nth_data (menu->entries, idx));
+ const int y = 2 + idx;
+ int x = menu->start_x;
+
+ if (x + menu->max_entry_len + 4 > (gsize) w->cols)
+ x = w->cols - menu->max_entry_len - 4;
+
+ if (entry == NULL)
+ {
+ /* menu separator */
+ tty_setcolor (MENU_ENTRY_COLOR);
+
+ widget_gotoyx (menubar, y, x - 1);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ tty_draw_hline (w->y + y, w->x + x, ACS_HLINE, menu->max_entry_len + 3);
+ widget_gotoyx (menubar, y, x + menu->max_entry_len + 3);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ }
+ else
+ {
+ int yt, xt;
+
+ /* menu text */
+ tty_setcolor (color);
+ widget_gotoyx (menubar, y, x);
+ tty_print_char ((unsigned char) entry->first_letter);
+ tty_getyx (&yt, &xt);
+ tty_draw_hline (yt, xt, ' ', menu->max_entry_len + 2); /* clear line */
+ tty_print_string (entry->text.start);
+
+ if (entry->text.hotkey != NULL)
+ {
+ tty_setcolor (color == MENU_SELECTED_COLOR ? MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
+ tty_print_string (entry->text.hotkey);
+ tty_setcolor (color);
+ }
+
+ if (entry->text.end != NULL)
+ tty_print_string (entry->text.end);
+
+ if (entry->shortcut != NULL)
+ {
+ widget_gotoyx (menubar, y, x + menu->max_hotkey_len + 3);
+ tty_print_string (entry->shortcut);
+ }
+
+ /* move cursor to the start of entry text */
+ widget_gotoyx (menubar, y, x + 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_draw_drop (const WMenuBar * menubar)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ const unsigned int count = g_list_length (menu->entries);
+ int column = menu->start_x - 1;
+ unsigned int i;
+
+ if (column + menu->max_entry_len + 5 > (gsize) w->cols)
+ column = w->cols - menu->max_entry_len - 5;
+
+ if (mc_global.tty.shadows)
+ tty_draw_box_shadow (w->y + 1, w->x + column, count + 2, menu->max_entry_len + 5,
+ SHADOW_COLOR);
+
+ tty_setcolor (MENU_ENTRY_COLOR);
+ tty_draw_box (w->y + 1, w->x + column, count + 2, menu->max_entry_len + 5, FALSE);
+
+ for (i = 0; i < count; i++)
+ menubar_paint_idx (menubar, i, i == menu->current ? MENU_SELECTED_COLOR : MENU_ENTRY_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_set_color (const WMenuBar * menubar, gboolean current, gboolean hotkey)
+{
+ if (!widget_get_state (CONST_WIDGET (menubar), WST_FOCUSED))
+ tty_setcolor (MENU_INACTIVE_COLOR);
+ else if (current)
+ tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
+ else
+ tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_draw (const WMenuBar * menubar)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ GList *i;
+
+ /* First draw the complete menubar */
+ tty_setcolor (widget_get_state (WIDGET (menubar), WST_FOCUSED) ? MENU_ENTRY_COLOR :
+ MENU_INACTIVE_COLOR);
+ tty_draw_hline (w->y, w->x, ' ', w->cols);
+
+ /* Now each one of the entries */
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+ gboolean is_selected = (menubar->current == (gsize) g_list_position (menubar->menu, i));
+
+ menubar_set_color (menubar, is_selected, FALSE);
+ widget_gotoyx (menubar, 0, menu->start_x);
+
+ tty_print_char (' ');
+ tty_print_string (menu->text.start);
+
+ if (menu->text.hotkey != NULL)
+ {
+ menubar_set_color (menubar, is_selected, TRUE);
+ tty_print_string (menu->text.hotkey);
+ menubar_set_color (menubar, is_selected, FALSE);
+ }
+
+ if (menu->text.end != NULL)
+ tty_print_string (menu->text.end);
+
+ tty_print_char (' ');
+ }
+
+ if (menubar->is_dropped)
+ menubar_draw_drop (menubar);
+ else
+ widget_gotoyx (menubar, 0,
+ MENU (g_list_nth_data (menubar->menu, menubar->current))->start_x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_remove (WMenuBar * menubar)
+{
+ Widget *g;
+
+ if (!menubar->is_dropped)
+ return;
+
+ /* HACK: before refresh the dialog, change the current widget to keep the order
+ of overlapped widgets. This is useful in multi-window editor.
+ In general, menubar should be a special object, not an ordinary widget
+ in the current dialog. */
+ g = WIDGET (WIDGET (menubar)->owner);
+ GROUP (g)->current = widget_find (g, widget_find_by_id (g, menubar->previous_widget));
+
+ menubar->is_dropped = FALSE;
+ do_refresh ();
+ menubar->is_dropped = TRUE;
+
+ /* restore current widget */
+ GROUP (g)->current = widget_find (g, WIDGET (menubar));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_left (WMenuBar * menubar)
+{
+ menubar_remove (menubar);
+ if (menubar->current == 0)
+ menubar->current = g_list_length (menubar->menu) - 1;
+ else
+ menubar->current--;
+ menubar_draw (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_right (WMenuBar * menubar)
+{
+ menubar_remove (menubar);
+ menubar->current = (menubar->current + 1) % g_list_length (menubar->menu);
+ menubar_draw (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_finish (WMenuBar * menubar)
+{
+ Widget *w = WIDGET (menubar);
+
+ menubar->is_dropped = FALSE;
+ w->rect.lines = 1;
+ widget_want_hotkey (w, FALSE);
+ widget_set_options (w, WOP_SELECTABLE, FALSE);
+
+ if (!mc_global.keybar_visible)
+ widget_hide (w);
+ else
+ {
+ /* Move the menubar to the bottom so that widgets displayed on top of
+ * an "invisible" menubar get the first chance to respond to mouse events. */
+ widget_set_bottom (w);
+ }
+
+ /* background must be bottom */
+ if (DIALOG (w->owner)->bg != NULL)
+ widget_set_bottom (WIDGET (DIALOG (w->owner)->bg));
+
+ group_select_widget_by_id (w->owner, menubar->previous_widget);
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_drop (WMenuBar * menubar, unsigned int selected)
+{
+ menubar->is_dropped = TRUE;
+ menubar->current = selected;
+ menubar_draw (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_execute (WMenuBar * menubar)
+{
+ const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ const menu_entry_t *entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current));
+
+ if ((entry != NULL) && (entry->command != CK_IgnoreKey))
+ {
+ Widget *w = WIDGET (menubar);
+
+ mc_global.widget.is_right = (menubar->current != 0);
+ menubar_finish (menubar);
+ send_message (w->owner, w, MSG_ACTION, entry->command, NULL);
+ do_refresh ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_down (WMenuBar * menubar)
+{
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ const unsigned int len = g_list_length (menu->entries);
+ menu_entry_t *entry;
+
+ menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR);
+
+ do
+ {
+ menu->current = (menu->current + 1) % len;
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current));
+ }
+ while ((entry == NULL) || (entry->command == CK_IgnoreKey));
+
+ menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_up (WMenuBar * menubar)
+{
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ const unsigned int len = g_list_length (menu->entries);
+ menu_entry_t *entry;
+
+ menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR);
+
+ do
+ {
+ if (menu->current == 0)
+ menu->current = len - 1;
+ else
+ menu->current--;
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current));
+ }
+ while ((entry == NULL) || (entry->command == CK_IgnoreKey));
+
+ menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_first (WMenuBar * menubar)
+{
+ if (menubar->is_dropped)
+ {
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+
+ if (menu->current == 0)
+ return;
+
+ menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR);
+
+ menu->current = 0;
+
+ while (TRUE)
+ {
+ menu_entry_t *entry;
+
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current));
+
+ if ((entry == NULL) || (entry->command == CK_IgnoreKey))
+ menu->current++;
+ else
+ break;
+ }
+
+ menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR);
+ }
+ else
+ {
+ menubar->current = 0;
+ menubar_draw (menubar);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_last (WMenuBar * menubar)
+{
+ if (menubar->is_dropped)
+ {
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ const unsigned int len = g_list_length (menu->entries);
+ menu_entry_t *entry;
+
+ if (menu->current == len - 1)
+ return;
+
+ menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR);
+
+ menu->current = len;
+
+ do
+ {
+ menu->current--;
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current));
+ }
+ while ((entry == NULL) || (entry->command == CK_IgnoreKey));
+
+ menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR);
+ }
+ else
+ {
+ menubar->current = g_list_length (menubar->menu) - 1;
+ menubar_draw (menubar);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_try_drop_menu (WMenuBar * menubar, int hotkey)
+{
+ GList *i;
+
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+
+ if (menu->text.hotkey != NULL && hotkey == g_ascii_tolower (menu->text.hotkey[0]))
+ {
+ menubar_drop (menubar, g_list_position (menubar->menu, i));
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_try_exec_menu (WMenuBar * menubar, int hotkey)
+{
+ menu_t *menu;
+ GList *i;
+
+ menu = g_list_nth_data (menubar->menu, menubar->current);
+
+ for (i = menu->entries; i != NULL; i = g_list_next (i))
+ {
+ const menu_entry_t *entry = MENUENTRY (i->data);
+
+ if (entry != NULL && entry->text.hotkey != NULL
+ && hotkey == g_ascii_tolower (entry->text.hotkey[0]))
+ {
+ menu->current = g_list_position (menu->entries, i);
+ menubar_execute (menubar);
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_execute_cmd (WMenuBar * menubar, long command)
+{
+ cb_ret_t ret = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, NULL };
+
+ if (menubar->is_dropped)
+ event_data.node =
+ MENU (g_list_nth_data (menubar->menu, menubar->current))->help_node;
+ else
+ event_data.node = "[Menu Bar]";
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ menubar_draw (menubar);
+ }
+ break;
+
+ case CK_Left:
+ menubar_left (menubar);
+ break;
+ case CK_Right:
+ menubar_right (menubar);
+ break;
+ case CK_Up:
+ if (menubar->is_dropped)
+ menubar_up (menubar);
+ break;
+ case CK_Down:
+ if (menubar->is_dropped)
+ menubar_down (menubar);
+ else
+ menubar_drop (menubar, menubar->current);
+ break;
+ case CK_Home:
+ menubar_first (menubar);
+ break;
+ case CK_End:
+ menubar_last (menubar);
+ break;
+
+ case CK_Enter:
+ if (menubar->is_dropped)
+ menubar_execute (menubar);
+ else
+ menubar_drop (menubar, menubar->current);
+ break;
+ case CK_Quit:
+ menubar_finish (menubar);
+ break;
+
+ default:
+ ret = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+menubar_handle_key (WMenuBar * menubar, int key)
+{
+ long cmd;
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ cmd = widget_lookup_key (WIDGET (menubar), key);
+
+ if (cmd != CK_IgnoreKey)
+ ret = menubar_execute_cmd (menubar, cmd);
+
+ if (ret != MSG_HANDLED)
+ {
+ if (menubar->is_dropped)
+ ret = menubar_try_exec_menu (menubar, key);
+ else
+ ret = menubar_try_drop_menu (menubar, key);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+menubar_refresh (WMenuBar * menubar)
+{
+ Widget *w = WIDGET (menubar);
+
+ if (!widget_get_state (w, WST_FOCUSED))
+ return FALSE;
+
+ /* Trick to get all the mouse events */
+ w->rect.lines = LINES;
+
+ /* Trick to get all of the hotkeys */
+ widget_want_hotkey (w, TRUE);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+menubar_free_menu (WMenuBar * menubar)
+{
+ g_clear_list (&menubar->menu, (GDestroyNotify) menu_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WMenuBar *menubar = MENUBAR (w);
+
+ switch (msg)
+ {
+ /* We do not want the focus unless we have been activated */
+ case MSG_FOCUS:
+ if (menubar_refresh (menubar))
+ {
+ menubar_draw (menubar);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_UNFOCUS:
+ return widget_get_state (w, WST_FOCUSED) ? MSG_NOT_HANDLED : MSG_HANDLED;
+
+ /* We don't want the buttonbar to activate while using the menubar */
+ case MSG_HOTKEY:
+ case MSG_KEY:
+ if (widget_get_state (w, WST_FOCUSED))
+ {
+ menubar_handle_key (menubar, parm);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_CURSOR:
+ /* Put the cursor in a suitable place */
+ return MSG_NOT_HANDLED;
+
+ case MSG_DRAW:
+ if (widget_get_state (w, WST_VISIBLE) || menubar_refresh (menubar))
+ menubar_draw (menubar);
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ /* try show menu after screen resize */
+ widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
+ menubar_refresh (menubar);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ menubar_free_menu (menubar);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned int
+menubar_get_menu_by_x_coord (const WMenuBar * menubar, int x)
+{
+ unsigned int i;
+ GList *menu;
+
+ for (i = 0, menu = menubar->menu;
+ menu != NULL && x >= MENU (menu->data)->start_x; i++, menu = g_list_next (menu))
+ ;
+
+ /* Don't set the invalid value -1 */
+ if (i != 0)
+ i--;
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+menubar_mouse_on_menu (const WMenuBar * menubar, int y, int x)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ menu_t *menu;
+ int left_x, right_x, bottom_y;
+
+ if (!menubar->is_dropped)
+ return FALSE;
+
+ menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ left_x = menu->start_x;
+ right_x = left_x + menu->max_entry_len + 3;
+ if (right_x > w->cols)
+ {
+ left_x = w->cols - (menu->max_entry_len + 3);
+ right_x = w->cols;
+ }
+
+ bottom_y = g_list_length (menu->entries) + 2; /* skip bar and top frame */
+
+ return (x >= left_x && x < right_x && y > 1 && y < bottom_y);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_change_selected_item (WMenuBar * menubar, int y)
+{
+ menu_t *menu;
+ menu_entry_t *entry;
+
+ y -= 2; /* skip bar and top frame */
+ menu = MENU (g_list_nth_data (menubar->menu, menubar->current));
+ entry = MENUENTRY (g_list_nth_data (menu->entries, y));
+
+ if (entry != NULL && entry->command != CK_IgnoreKey)
+ {
+ menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR);
+ menu->current = y;
+ menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ static gboolean was_drag = FALSE;
+
+ WMenuBar *menubar = MENUBAR (w);
+ gboolean mouse_on_drop;
+
+ mouse_on_drop = menubar_mouse_on_menu (menubar, event->y, event->x);
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ was_drag = FALSE;
+
+ if (event->y == 0)
+ {
+ /* events on menubar */
+ unsigned int selected;
+
+ selected = menubar_get_menu_by_x_coord (menubar, event->x);
+ menubar_activate (menubar, TRUE, selected);
+ menubar_remove (menubar); /* if already shown */
+ menubar_drop (menubar, selected);
+ }
+ else if (mouse_on_drop)
+ menubar_change_selected_item (menubar, event->y);
+ else
+ {
+ /* mouse click outside menubar or dropdown -- close menu */
+ menubar_finish (menubar);
+
+ /*
+ * @FIXME.
+ *
+ * Unless we clear the 'capture' flag, we'll receive MSG_MOUSE_DRAG
+ * events belonging to this click (in case the user drags the mouse,
+ * of course).
+ *
+ * For the time being, we mark this with FIXME as this flag should
+ * preferably be regarded as "implementation detail" and not be
+ * touched by us. We should think of some other way of communicating
+ * this to the system.
+ */
+ w->mouse.capture = FALSE;
+ }
+ break;
+
+ case MSG_MOUSE_UP:
+ if (was_drag && mouse_on_drop)
+ menubar_execute (menubar);
+ was_drag = FALSE;
+ break;
+
+ case MSG_MOUSE_CLICK:
+ was_drag = FALSE;
+
+ if ((event->buttons & GPM_B_MIDDLE) != 0 && event->y > 0 && menubar->is_dropped)
+ {
+ /* middle click -- everywhere */
+ menubar_execute (menubar);
+ }
+ else if (mouse_on_drop)
+ menubar_execute (menubar);
+ else if (event->y > 0)
+ /* releasing the mouse button outside the menu -- close menu */
+ menubar_finish (menubar);
+ break;
+
+ case MSG_MOUSE_DRAG:
+ if (event->y == 0)
+ {
+ menubar_remove (menubar);
+ menubar_drop (menubar, menubar_get_menu_by_x_coord (menubar, event->x));
+ }
+ else if (mouse_on_drop)
+ menubar_change_selected_item (menubar, event->y);
+
+ was_drag = TRUE;
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ case MSG_MOUSE_SCROLL_DOWN:
+ was_drag = FALSE;
+
+ if (widget_get_state (w, WST_FOCUSED))
+ {
+ if (event->y == 0)
+ {
+ /* menubar: left/right */
+ if (msg == MSG_MOUSE_SCROLL_UP)
+ menubar_left (menubar);
+ else
+ menubar_right (menubar);
+ }
+ else if (mouse_on_drop)
+ {
+ /* drop-down menu: up/down */
+ if (msg == MSG_MOUSE_SCROLL_UP)
+ menubar_up (menubar);
+ else
+ menubar_down (menubar);
+ }
+ }
+ break;
+
+ default:
+ was_drag = FALSE;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+menu_entry_t *
+menu_entry_new (const char *name, long command)
+{
+ menu_entry_t *entry;
+
+ entry = g_new (menu_entry_t, 1);
+ entry->first_letter = ' ';
+ entry->text = hotkey_new (name);
+ entry->command = command;
+ entry->shortcut = NULL;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menu_entry_free (menu_entry_t * entry)
+{
+ if (entry != NULL)
+ {
+ hotkey_free (entry->text);
+ g_free (entry->shortcut);
+ g_free (entry);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+menu_t *
+menu_new (const char *name, GList * entries, const char *help_node)
+{
+ menu_t *menu;
+
+ menu = g_new (menu_t, 1);
+ menu->start_x = 0;
+ menu->text = hotkey_new (name);
+ menu->entries = entries;
+ menu->max_entry_len = 1;
+ menu->max_hotkey_len = 0;
+ menu->current = 0;
+ menu->help_node = g_strdup (help_node);
+
+ return menu;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menu_set_name (menu_t * menu, const char *name)
+{
+ hotkey_free (menu->text);
+ menu->text = hotkey_new (name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menu_free (menu_t * menu)
+{
+ hotkey_free (menu->text);
+ g_list_free_full (menu->entries, (GDestroyNotify) menu_entry_free);
+ g_free (menu->help_node);
+ g_free (menu);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WMenuBar *
+menubar_new (GList * menu)
+{
+ WRect r = { 0, 0, 1, COLS };
+ WMenuBar *menubar;
+ Widget *w;
+
+ menubar = g_new0 (WMenuBar, 1);
+ w = WIDGET (menubar);
+ widget_init (w, &r, menubar_callback, menubar_mouse_callback);
+ w->pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_TOP;
+ w->options |= WOP_TOP_SELECT;
+ w->keymap = menu_map;
+ menubar_set_menu (menubar, menu);
+
+ return menubar;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menubar_set_menu (WMenuBar * menubar, GList * menu)
+{
+ /* delete previous menu */
+ menubar_free_menu (menubar);
+ /* add new menu */
+ menubar->is_dropped = FALSE;
+ menubar->menu = menu;
+ menubar->current = 0;
+ menubar_arrange (menubar);
+ widget_set_state (WIDGET (menubar), WST_FOCUSED, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menubar_add_menu (WMenuBar * menubar, menu_t * menu)
+{
+ if (menu != NULL)
+ {
+ menu_arrange (menu, DIALOG (WIDGET (menubar)->owner)->get_shortcut);
+ menubar->menu = g_list_append (menubar->menu, menu);
+ }
+
+ menubar_arrange (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Properly space menubar items. Should be called when menubar is created
+ * and also when widget width is changed (i.e. upon xterm resize).
+ */
+
+void
+menubar_arrange (WMenuBar * menubar)
+{
+ int start_x = 1;
+ GList *i;
+ int gap;
+
+ if (menubar->menu == NULL)
+ return;
+
+ gap = WIDGET (menubar)->rect.cols - 2;
+
+ /* First, calculate gap between items... */
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+
+ /* preserve length here, to be used below */
+ menu->start_x = hotkey_width (menu->text) + 2;
+ gap -= menu->start_x;
+ }
+
+ if (g_list_next (menubar->menu) == NULL)
+ gap = 1;
+ else
+ gap /= (g_list_length (menubar->menu) - 1);
+
+ if (gap <= 0)
+ {
+ /* We are out of luck - window is too narrow... */
+ gap = 1;
+ }
+ else if (gap >= 3)
+ gap = 3;
+
+ /* ...and now fix start positions of menubar items */
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+ int len = menu->start_x;
+
+ menu->start_x = start_x;
+ start_x += len + gap;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find MenuBar widget in the dialog */
+
+WMenuBar *
+menubar_find (const WDialog * h)
+{
+ return MENUBAR (widget_find_by_type (CONST_WIDGET (h), menubar_callback));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Activate menu bar.
+ *
+ * @param menubar menu bar object
+ * @param dropped whether dropdown menus should be drooped or not
+ * @which number of active dropdown menu
+ */
+void
+menubar_activate (WMenuBar * menubar, gboolean dropped, int which)
+{
+ Widget *w = WIDGET (menubar);
+
+ widget_show (w);
+
+ if (!widget_get_state (w, WST_FOCUSED))
+ {
+ widget_set_options (w, WOP_SELECTABLE, TRUE);
+
+ menubar->is_dropped = dropped;
+ if (which >= 0)
+ menubar->current = (guint) which;
+
+ menubar->previous_widget = group_get_current_widget_id (w->owner);
+
+ /* Bring it to the top so it receives all mouse events before any other widget.
+ * See also comment in menubar_finish(). */
+ widget_select (w);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/menu.h b/lib/widget/menu.h
new file mode 100644
index 0000000..ce2cebe
--- /dev/null
+++ b/lib/widget/menu.h
@@ -0,0 +1,63 @@
+/*
+ Header file for pulldown menu engine for Midnignt Commander
+ */
+
+/** \file menu.h
+ * \brief Header: pulldown menu code
+ */
+
+#ifndef MC__WIDGET_MENU_H
+#define MC__WIDGET_MENU_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define MENUBAR(x) ((WMenuBar *)(x))
+
+#define menu_separator_new() NULL
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct menu_entry_t;
+typedef struct menu_entry_t menu_entry_t;
+
+struct menu_t;
+typedef struct menu_t menu_t;
+
+/* The button bar menu */
+typedef struct WMenuBar
+{
+ Widget widget;
+
+ gboolean is_dropped; /* If the menubar has dropped */
+ GList *menu; /* The actual menus */
+ guint current; /* Current menu on the top bar */
+ unsigned long previous_widget; /* Selected widget ID before activating menu */
+} WMenuBar;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const global_keymap_t *menu_map;
+
+/*** declarations of public functions ************************************************************/
+
+menu_entry_t *menu_entry_new (const char *name, long command);
+void menu_entry_free (menu_entry_t * me);
+
+menu_t *menu_new (const char *name, GList * entries, const char *help_node);
+void menu_set_name (menu_t * menu, const char *name);
+void menu_free (menu_t * menu);
+
+WMenuBar *menubar_new (GList * menu);
+void menubar_set_menu (WMenuBar * menubar, GList * menu);
+void menubar_add_menu (WMenuBar * menubar, menu_t * menu);
+void menubar_arrange (WMenuBar * menubar);
+
+WMenuBar *menubar_find (const WDialog * h);
+
+void menubar_activate (WMenuBar * menubar, gboolean dropped, int which);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_MENU_H */
diff --git a/lib/widget/mouse.c b/lib/widget/mouse.c
new file mode 100644
index 0000000..15ad5f5
--- /dev/null
+++ b/lib/widget/mouse.c
@@ -0,0 +1,227 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 2016-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Human beings.
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file mouse.c
+ * \brief Header: High-level mouse API
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "lib/widget/mouse.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Constructs a mouse event structure.
+ *
+ * It receives a Gpm_Event event and translates it into a higher level protocol.
+ *
+ * Tip: for details on the C mouse API, see MC's lib/tty/mouse.h,
+ * or GPM's excellent 'info' manual:
+ *
+ * http://www.fifi.org/cgi-bin/info2www?(gpm)Event+Types
+ */
+static void
+init_mouse_event (mouse_event_t * event, mouse_msg_t msg, const Gpm_Event * global_gpm,
+ const Widget * w)
+{
+ event->msg = msg;
+ event->x = global_gpm->x - w->rect.x - 1; /* '-1' because Gpm_Event is 1-based. */
+ event->y = global_gpm->y - w->rect.y - 1;
+ event->count = global_gpm->type & (GPM_SINGLE | GPM_DOUBLE | GPM_TRIPLE);
+ event->buttons = global_gpm->buttons;
+ event->result.abort = FALSE;
+ event->result.repeat = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Translate GPM event to high-level event,
+ *
+ * @param w Widget object
+ * @param event GPM event
+ *
+ * @return high level mouse event
+ */
+static mouse_event_t
+mouse_translate_event (Widget * w, Gpm_Event * event)
+{
+ gboolean in_widget;
+ mouse_msg_t msg = MSG_MOUSE_NONE;
+ mouse_event_t local;
+
+ /*
+ * Very special widgets may want to control area outside their bounds.
+ * For such widgets you will have to turn on the 'forced_capture' flag.
+ * You'll also need, in your mouse handler, to inform the system of
+ * events you want to pass on by setting 'event->result.abort' to TRUE.
+ */
+ in_widget = w->mouse.forced_capture || mouse_global_in_widget (event, w);
+
+ if ((event->type & GPM_DOWN) != 0)
+ {
+ if (in_widget)
+ {
+ if ((event->buttons & GPM_B_UP) != 0)
+ msg = MSG_MOUSE_SCROLL_UP;
+ else if ((event->buttons & GPM_B_DOWN) != 0)
+ msg = MSG_MOUSE_SCROLL_DOWN;
+ else
+ {
+ /* Handle normal buttons: anything but the mouse wheel's.
+ *
+ * (Note that turning on capturing for the mouse wheel
+ * buttons doesn't make sense as they don't generate a
+ * mouse_up event, which means we'd never get uncaptured.)
+ */
+ w->mouse.capture = TRUE;
+ msg = MSG_MOUSE_DOWN;
+
+ w->mouse.last_buttons_down = event->buttons;
+ }
+ }
+ }
+ else if ((event->type & GPM_UP) != 0)
+ {
+ /* We trigger the mouse_up event even when !in_widget. That's
+ * because, for example, a paint application should stop drawing
+ * lines when the button is released even outside the canvas. */
+ if (w->mouse.capture)
+ {
+ w->mouse.capture = FALSE;
+ msg = MSG_MOUSE_UP;
+
+ /*
+ * When using xterm, event->buttons reports the buttons' state
+ * after the event occurred (meaning that event->buttons is zero,
+ * because the mouse button is now released). When using GPM,
+ * however, that field reports the button(s) that was released.
+ *
+ * The following makes xterm behave effectively like GPM:
+ */
+ if (event->buttons == 0)
+ event->buttons = w->mouse.last_buttons_down;
+ }
+ }
+ else if ((event->type & GPM_DRAG) != 0)
+ {
+ if (w->mouse.capture)
+ msg = MSG_MOUSE_DRAG;
+ }
+ else if ((event->type & GPM_MOVE) != 0)
+ {
+ if (in_widget)
+ msg = MSG_MOUSE_MOVE;
+ }
+
+ init_mouse_event (&local, msg, event, w);
+
+ return local;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Call widget mouse handler to process high-level mouse event.
+ *
+ * Besides sending to the widget the event itself, this function may also
+ * send one or more pseudo events. Currently, MSG_MOUSE_CLICK is the only
+ * pseudo event in existence but in the future (e.g., with the introduction
+ * of a drag-drop API) there may be more.
+ *
+ * @param w Widget object
+ * @param event high level mouse event
+ *
+ * @return result of mouse event handling
+ */
+static int
+mouse_process_event (Widget * w, mouse_event_t * event)
+{
+ int ret = MOU_UNHANDLED;
+
+ if (event->msg != MSG_MOUSE_NONE)
+ {
+ w->mouse_callback (w, event->msg, event);
+
+ /* If a widget aborts a MSG_MOUSE_DOWN, we uncapture it so it
+ * doesn't steal events from other widgets. */
+ if (event->msg == MSG_MOUSE_DOWN && event->result.abort)
+ w->mouse.capture = FALSE;
+
+ /* Upon releasing the mouse button: if the mouse hasn't been dragged
+ * since the MSG_MOUSE_DOWN, we also trigger a click. */
+ if (event->msg == MSG_MOUSE_UP && w->mouse.last_msg == MSG_MOUSE_DOWN)
+ w->mouse_callback (w, MSG_MOUSE_CLICK, event);
+
+ /* Record the current event type for the benefit of the next event. */
+ w->mouse.last_msg = event->msg;
+
+ if (!event->result.abort)
+ ret = event->result.repeat ? MOU_REPEAT : MOU_NORMAL;
+ }
+
+ return ret;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Translate GPM event to high-level event and process it
+ *
+ * @param w Widget object
+ * @param event GPM event
+ *
+ * @return result of mouse event handling
+ */
+int
+mouse_handle_event (Widget * w, Gpm_Event * event)
+{
+ mouse_event_t me;
+
+ me = mouse_translate_event (w, event);
+
+ return mouse_process_event (w, &me);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/mouse.h b/lib/widget/mouse.h
new file mode 100644
index 0000000..44e7b23
--- /dev/null
+++ b/lib/widget/mouse.h
@@ -0,0 +1,65 @@
+/** \file mouse.h
+ * \brief Header: Hight-level mouse API.
+ *
+ * This is a thin layer over the low-level mouse protocol in lib/tty/mouse.h.
+ * The latter is oblivious to the regions on the screen and is therefore a
+ * bit hard to use in widgets. This layer translates the low level Gpm_Event
+ * into something that's easy to work with in widgets.
+ */
+
+#ifndef MC__WIDGET_MOUSE_H
+#define MC__WIDGET_MOUSE_H
+
+#include "lib/tty/mouse.h" /* Gpm_Event */
+
+/*** enums ***************************************************************************************/
+
+/* Mouse messages */
+typedef enum
+{
+ /*
+ * Notes:
+ * (1) "anywhere" means "inside or outside the widget".
+ * (2) the mouse wheel is not considered "mouse button".
+ */
+ MSG_MOUSE_NONE = 0,
+ MSG_MOUSE_DOWN = 1, /* When mouse button is pressed down inside the widget. */
+ MSG_MOUSE_UP, /* When mouse button, previously pressed inside the widget, is released anywhere. */
+ MSG_MOUSE_CLICK, /* When mouse button, previously pressed inside the widget, is released inside the widget. */
+ MSG_MOUSE_DRAG, /* When a drag, initiated by button press inside the widget, occurs anywhere. */
+ MSG_MOUSE_MOVE, /* (Not currently implemented in MC.) */
+ MSG_MOUSE_SCROLL_UP, /* When mouse wheel is rotated away from the user. */
+ MSG_MOUSE_SCROLL_DOWN /* When mouse wheel is rotated towards the user. */
+} mouse_msg_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Mouse event structure. */
+typedef struct
+{
+ mouse_msg_t msg;
+
+ int x, y; /* Local to the widget. */
+ int buttons; /* Bitwise-or of: GPM_B_LEFT, GPM_B_MIDDLE, GPM_B_RIGHT */
+ int count; /* One of: GPM_SINGLE, GPM_DOUBLE, GPM_TRIPLE */
+
+ /* A mechanism for the callback to report back: */
+ struct
+ {
+ gboolean abort;
+ gboolean repeat;
+ } result;
+} mouse_event_t;
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Translate GPM event to high-level event and process it */
+int mouse_handle_event (Widget * w, Gpm_Event * event);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_MOUSE_H */
diff --git a/lib/widget/quick.c b/lib/widget/quick.c
new file mode 100644
index 0000000..35f5d68
--- /dev/null
+++ b/lib/widget/quick.c
@@ -0,0 +1,626 @@
+/*
+ Widget based utility functions.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Miguel de Icaza, 1994, 1995, 1996
+ Radek Doulik, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file quick.c
+ * \brief Source: quick dialog engine
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h> /* fprintf() */
+
+#include "lib/global.h"
+#include "lib/strutil.h" /* str_term_width1() */
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef ENABLE_NLS
+#define I18N(x) (x = x != NULL && *x != '\0' ? _(x) : x)
+#else
+#define I18N(x) (x = x)
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ Widget *widget;
+ quick_widget_t *quick_widget;
+} quick_widget_item_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static WInput *
+quick_create_input (int y, int x, const quick_widget_t * qw)
+{
+ WInput *in;
+
+ in = input_new (y, x, input_colors, 8, qw->u.input.text, qw->u.input.histname,
+ qw->u.input.completion_flags);
+
+ in->is_password = qw->u.input.is_passwd;
+ in->strip_password = qw->u.input.strip_passwd;
+
+ return in;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+quick_create_labeled_input (GArray * widgets, int *y, int x, quick_widget_t * quick_widget,
+ int *width)
+{
+ quick_widget_item_t in, label;
+
+ label.quick_widget = g_new0 (quick_widget_t, 1);
+ label.quick_widget->widget_type = quick_label;
+ label.quick_widget->options = quick_widget->options;
+ label.quick_widget->state = quick_widget->state;
+ /* FIXME: this should be turned in depend of label_location */
+ label.quick_widget->pos_flags = quick_widget->pos_flags;
+
+ switch (quick_widget->u.input.label_location)
+ {
+ case input_label_above:
+ label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
+ *y += label.widget->rect.lines - 1;
+ g_array_append_val (widgets, label);
+
+ in.widget = WIDGET (quick_create_input (++(*y), x, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
+ break;
+
+ case input_label_left:
+ label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
+ g_array_append_val (widgets, label);
+
+ in.widget = WIDGET (quick_create_input (*y, x + label.widget->rect.cols + 1, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ *width = label.widget->rect.cols + in.widget->rect.cols + 1;
+ break;
+
+ case input_label_right:
+ in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ label.widget =
+ WIDGET (label_new
+ (*y, x + in.widget->rect.cols + 1, I18N (quick_widget->u.input.label_text)));
+ g_array_append_val (widgets, label);
+
+ *width = label.widget->rect.cols + in.widget->rect.cols + 1;
+ break;
+
+ case input_label_below:
+ in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ label.widget = WIDGET (label_new (++(*y), x, I18N (quick_widget->u.input.label_text)));
+ *y += label.widget->rect.lines - 1;
+ g_array_append_val (widgets, label);
+
+ *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
+ break;
+
+ default:
+ return;
+ }
+
+ INPUT (in.widget)->label = LABEL (label.widget);
+ /* cross references */
+ label.quick_widget->u.label.input = in.quick_widget;
+ in.quick_widget->u.input.label = label.quick_widget;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+quick_dialog_skip (quick_dialog_t * quick_dlg, int nskip)
+{
+ int len;
+ int blen = 0;
+ int x, y; /* current positions */
+ int y1 = 0; /* bottom of 1st column in case of two columns */
+ int y2 = -1; /* start of two columns */
+ int width1 = 0; /* width of single column */
+ int width2 = 0; /* width of each of two columns */
+ gboolean have_groupbox = FALSE;
+ gboolean two_columns = FALSE;
+ gboolean put_buttons = FALSE;
+
+ /* x position of 1st column is 3 */
+ const int x1 = 3;
+ /* x position of 2nd column is 4 and it will be fixed later, after creation of all widgets */
+ int x2 = 4;
+
+ GArray *widgets;
+ size_t i;
+ quick_widget_t *quick_widget;
+ WGroupbox *g = NULL;
+ WDialog *dd;
+ GList *input_labels = NULL; /* Widgets not directly requested by the user. */
+ int return_val;
+
+ len = str_term_width1 (I18N (quick_dlg->title)) + 6;
+ quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
+
+ y = 1;
+ x = x1;
+
+ /* create widgets */
+ widgets = g_array_sized_new (FALSE, FALSE, sizeof (quick_widget_item_t), 8);
+
+ for (quick_widget = quick_dlg->widgets; quick_widget->widget_type != quick_end; quick_widget++)
+ {
+ quick_widget_item_t item = { NULL, quick_widget };
+ int width = 0;
+
+ switch (quick_widget->widget_type)
+ {
+ case quick_checkbox:
+ item.widget =
+ WIDGET (check_new
+ (++y, x, *quick_widget->u.checkbox.state,
+ I18N (quick_widget->u.checkbox.text)));
+ g_array_append_val (widgets, item);
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_button:
+ /* single button */
+ item.widget = WIDGET (button_new (++y, x, quick_widget->u.button.action,
+ quick_widget->u.button.action == B_ENTER ?
+ DEFPUSH_BUTTON : NORMAL_BUTTON,
+ I18N (quick_widget->u.button.text),
+ quick_widget->u.button.callback));
+ g_array_append_val (widgets, item);
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_input:
+ *quick_widget->u.input.result = NULL;
+ y++;
+ if (quick_widget->u.input.label_location != input_label_none)
+ {
+ quick_create_labeled_input (widgets, &y, x, quick_widget, &width);
+ input_labels = g_list_prepend (input_labels, quick_widget->u.input.label);
+ }
+ else
+ {
+ item.widget = WIDGET (quick_create_input (y, x, quick_widget));
+ g_array_append_val (widgets, item);
+ width = item.widget->rect.cols;
+ }
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_label:
+ item.widget = WIDGET (label_new (++y, x, I18N (quick_widget->u.label.text)));
+ g_array_append_val (widgets, item);
+ y += item.widget->rect.lines - 1;
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_radio:
+ {
+ WRadio *r;
+ char **items = NULL;
+
+ /* create the copy of radio_items to avoid mwmory leak */
+ items = g_new (char *, quick_widget->u.radio.count + 1);
+ for (i = 0; i < (size_t) quick_widget->u.radio.count; i++)
+ items[i] = g_strdup (_(quick_widget->u.radio.items[i]));
+ items[i] = NULL;
+
+ r = radio_new (++y, x, quick_widget->u.radio.count, (const char **) items);
+ r->pos = r->sel = *quick_widget->u.radio.value;
+ g_strfreev (items);
+ item.widget = WIDGET (r);
+ g_array_append_val (widgets, item);
+ y += item.widget->rect.lines - 1;
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ }
+ break;
+
+ case quick_start_groupbox:
+ I18N (quick_widget->u.groupbox.title);
+ len = str_term_width1 (quick_widget->u.groupbox.title);
+ g = groupbox_new (++y, x, 1, len + 4, quick_widget->u.groupbox.title);
+ item.widget = WIDGET (g);
+ g_array_append_val (widgets, item);
+ have_groupbox = TRUE;
+ break;
+
+ case quick_stop_groupbox:
+ if (g != NULL)
+ {
+ Widget *w = WIDGET (g);
+
+ y++;
+ w->rect.lines = y + 1 - w->rect.y;
+ g = NULL;
+
+ g_array_append_val (widgets, item);
+ }
+ break;
+
+ case quick_separator:
+ y++;
+ if (quick_widget->u.separator.line)
+ {
+ item.widget = WIDGET (hline_new (y, x, 1));
+ g_array_append_val (widgets, item);
+ }
+ break;
+
+ case quick_start_columns:
+ y2 = y;
+ g_array_append_val (widgets, item);
+ two_columns = TRUE;
+ break;
+
+ case quick_next_column:
+ x = x2;
+ y1 = y;
+ y = y2;
+ break;
+
+ case quick_stop_columns:
+ x = x1;
+ y = MAX (y1, y);
+ g_array_append_val (widgets, item);
+ two_columns = FALSE;
+ break;
+
+ case quick_buttons:
+ /* start put several buttons in bottom line */
+ if (quick_widget->u.separator.space)
+ {
+ y++;
+
+ if (quick_widget->u.separator.line)
+ item.widget = WIDGET (hline_new (y, 1, -1));
+ }
+
+ g_array_append_val (widgets, item);
+
+ /* several buttons in bottom line */
+ y++;
+ quick_widget++;
+ for (; quick_widget->widget_type == quick_button; quick_widget++)
+ {
+ item.widget = WIDGET (button_new (y, x++, quick_widget->u.button.action,
+ quick_widget->u.button.action == B_ENTER ?
+ DEFPUSH_BUTTON : NORMAL_BUTTON,
+ I18N (quick_widget->u.button.text),
+ quick_widget->u.button.callback));
+ item.quick_widget = quick_widget;
+ g_array_append_val (widgets, item);
+ blen += item.widget->rect.cols + 1;
+ }
+
+ /* stop dialog build here */
+ blen--;
+ quick_widget->widget_type = quick_end;
+ quick_widget--;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* adjust dialog width */
+ quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, blen + 6);
+ if (have_groupbox)
+ {
+ if (width1 != 0)
+ width1 += 2;
+ if (width2 != 0)
+ width2 += 2;
+ }
+ if (width2 == 0)
+ len = width1 + 6;
+ else
+ {
+ len = width2 * 2 + 7;
+ if (width1 != 0)
+ len = MAX (len, width1 + 6);
+ }
+
+ quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
+ width1 = quick_dlg->rect.cols - 6;
+ width2 = (quick_dlg->rect.cols - 7) / 2;
+
+ if (quick_dlg->rect.x == -1 || quick_dlg->rect.y == -1)
+ dd = dlg_create (TRUE, 0, 0, y + 3, quick_dlg->rect.cols, WPOS_CENTER | WPOS_TRYUP, FALSE,
+ dialog_colors, quick_dlg->callback, quick_dlg->mouse_callback,
+ quick_dlg->help, quick_dlg->title);
+ else
+ dd = dlg_create (TRUE, quick_dlg->rect.y, quick_dlg->rect.x, y + 3, quick_dlg->rect.cols,
+ WPOS_KEEP_DEFAULT, FALSE, dialog_colors, quick_dlg->callback,
+ quick_dlg->mouse_callback, quick_dlg->help, quick_dlg->title);
+
+ /* add widgets into the dialog */
+ x2 = x1 + width2 + 1;
+ g = NULL;
+ two_columns = FALSE;
+ x = (WIDGET (dd)->rect.cols - blen) / 2;
+
+ for (i = 0; i < widgets->len; i++)
+ {
+ quick_widget_item_t *item;
+ int column_width;
+ WRect *r;
+
+ item = &g_array_index (widgets, quick_widget_item_t, i);
+ r = &item->widget->rect;
+ column_width = two_columns ? width2 : width1;
+
+ /* adjust widget width and x position */
+ switch (item->quick_widget->widget_type)
+ {
+ case quick_label:
+ {
+ quick_widget_t *input = item->quick_widget->u.label.input;
+
+ if (input != NULL && input->u.input.label_location == input_label_right)
+ {
+ /* location of this label will be adjusted later */
+ break;
+ }
+ }
+ MC_FALLTHROUGH;
+ case quick_checkbox:
+ case quick_radio:
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ break;
+
+ case quick_button:
+ if (!put_buttons)
+ {
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ }
+ else
+ {
+ r->x = x;
+ x += r->cols + 1;
+ }
+ break;
+
+ case quick_input:
+ {
+ Widget *label = WIDGET (INPUT (item->widget)->label);
+ int width = column_width;
+
+ if (g != NULL)
+ width -= 4;
+
+ switch (item->quick_widget->u.input.label_location)
+ {
+ case input_label_left:
+ /* label was adjusted before; adjust input line */
+ r->x = label->rect.x + label->rect.cols + 1 - WIDGET (label->owner)->rect.x;
+ r->cols = width - label->rect.cols - 1;
+ break;
+
+ case input_label_right:
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ r->cols = width - label->rect.cols - 1;
+ label->rect.x = r->x + r->cols + 1;
+ break;
+
+ default:
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ r->cols = width;
+ break;
+ }
+
+ /* forced update internal variables of input line */
+ r->lines = 1;
+ widget_set_size_rect (item->widget, r);
+ }
+ break;
+
+ case quick_start_groupbox:
+ g = GROUPBOX (item->widget);
+ if (r->x != x1)
+ r->x = x2;
+ r->cols = column_width;
+ break;
+
+ case quick_stop_groupbox:
+ g = NULL;
+ break;
+
+ case quick_separator:
+ if (item->widget != NULL)
+ {
+ if (g != NULL)
+ {
+ Widget *wg = WIDGET (g);
+
+ HLINE (item->widget)->auto_adjust_cols = FALSE;
+ r->x = wg->rect.x + 1 - WIDGET (wg->owner)->rect.x;
+ r->cols = wg->rect.cols;
+ }
+ else if (two_columns)
+ {
+ HLINE (item->widget)->auto_adjust_cols = FALSE;
+ if (r->x != x1)
+ r->x = x2;
+ r->x--;
+ r->cols = column_width + 2;
+ }
+ else
+ HLINE (item->widget)->auto_adjust_cols = TRUE;
+ }
+ break;
+
+ case quick_start_columns:
+ two_columns = TRUE;
+ break;
+
+ case quick_stop_columns:
+ two_columns = FALSE;
+ break;
+
+ case quick_buttons:
+ /* several buttons in bottom line */
+ put_buttons = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (item->widget != NULL)
+ {
+ unsigned long id;
+
+ /* add widget into dialog */
+ item->widget->options |= item->quick_widget->options; /* FIXME: cannot reset flags, setup only */
+ item->widget->state |= item->quick_widget->state; /* FIXME: cannot reset flags, setup only */
+ id = group_add_widget_autopos (GROUP (dd), item->widget, item->quick_widget->pos_flags,
+ NULL);
+ if (item->quick_widget->id != NULL)
+ *item->quick_widget->id = id;
+ }
+ }
+
+ /* skip frame widget */
+ if (dd->bg != NULL)
+ nskip++;
+
+ while (nskip-- != 0)
+ group_set_current_widget_next (GROUP (dd));
+
+ return_val = dlg_run (dd);
+
+ /* Get the data if we found something interesting */
+ if (return_val != B_CANCEL)
+ for (i = 0; i < widgets->len; i++)
+ {
+ quick_widget_item_t *item;
+
+ item = &g_array_index (widgets, quick_widget_item_t, i);
+
+ switch (item->quick_widget->widget_type)
+ {
+ case quick_checkbox:
+ *item->quick_widget->u.checkbox.state = CHECK (item->widget)->state;
+ break;
+
+ case quick_input:
+ if ((item->quick_widget->u.input.completion_flags & INPUT_COMPLETE_CD) != 0)
+ *item->quick_widget->u.input.result =
+ tilde_expand (input_get_ctext (INPUT (item->widget)));
+ else
+ *item->quick_widget->u.input.result = input_get_text (INPUT (item->widget));
+ break;
+
+ case quick_radio:
+ *item->quick_widget->u.radio.value = RADIO (item->widget)->sel;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ widget_destroy (WIDGET (dd));
+
+ g_list_free_full (input_labels, g_free); /* destroy input labels created before */
+ g_array_free (widgets, TRUE);
+
+ return return_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/quick.h b/lib/widget/quick.h
new file mode 100644
index 0000000..8a722c1
--- /dev/null
+++ b/lib/widget/quick.h
@@ -0,0 +1,354 @@
+/** \file quick.h
+ * \brief Header: quick dialog engine
+ */
+
+#ifndef MC__QUICK_H
+#define MC__QUICK_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define QUICK_CHECKBOX(txt, st, id_) \
+{ \
+ .widget_type = quick_checkbox, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .checkbox = { \
+ .text = txt, \
+ .state = st \
+ } \
+ } \
+}
+
+#define QUICK_BUTTON(txt, act, cb, id_) \
+{ \
+ .widget_type = quick_button, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .button = { \
+ .text = txt, \
+ .action = act, \
+ .callback = cb \
+ } \
+ } \
+}
+
+#define QUICK_INPUT(txt, hname, res, id_, is_passwd_, strip_passwd_, completion_flags_) \
+{ \
+ .widget_type = quick_input, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .input = { \
+ .label_text = NULL, \
+ .label_location = input_label_none, \
+ .label = NULL, \
+ .text = txt, \
+ .completion_flags = completion_flags_, \
+ .is_passwd = is_passwd_, \
+ .strip_passwd = strip_passwd_, \
+ .histname = hname, \
+ .result = res \
+ } \
+ } \
+}
+
+#define QUICK_LABELED_INPUT(label_, label_loc, txt, hname, res, id_, is_passwd_, strip_passwd_, completion_flags_) \
+{ \
+ .widget_type = quick_input, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .input = { \
+ .label_text = label_, \
+ .label_location = label_loc, \
+ .label = NULL, \
+ .text = txt, \
+ .completion_flags = completion_flags_, \
+ .is_passwd = is_passwd_, \
+ .strip_passwd = strip_passwd_, \
+ .histname = hname, \
+ .result = res \
+ } \
+ } \
+}
+
+#define QUICK_LABEL(txt, id_) \
+{ \
+ .widget_type = quick_label, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .label = { \
+ .text = txt, \
+ .input = NULL \
+ } \
+ } \
+}
+
+#define QUICK_RADIO(cnt, items_, val, id_) \
+{ \
+ .widget_type = quick_radio, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .radio = { \
+ .count = cnt, \
+ .items = items_, \
+ .value = val \
+ } \
+ } \
+}
+
+#define QUICK_START_GROUPBOX(t) \
+{ \
+ .widget_type = quick_start_groupbox, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .groupbox = { \
+ .title = t \
+ } \
+ } \
+}
+
+#define QUICK_STOP_GROUPBOX \
+{ \
+ .widget_type = quick_stop_groupbox, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_SEPARATOR(line_) \
+{ \
+ .widget_type = quick_separator, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .separator = { \
+ .space = TRUE, \
+ .line = line_ \
+ } \
+ } \
+}
+
+#define QUICK_START_COLUMNS \
+{ \
+ .widget_type = quick_start_columns, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_NEXT_COLUMN \
+{ \
+ .widget_type = quick_next_column, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_STOP_COLUMNS \
+{ \
+ .widget_type = quick_stop_columns, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_START_BUTTONS(space_, line_) \
+{ \
+ .widget_type = quick_buttons, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .separator = { \
+ .space = space_, \
+ .line = line_ \
+ } \
+ } \
+}
+
+#define QUICK_BUTTONS_OK_CANCEL \
+ QUICK_START_BUTTONS (TRUE, TRUE), \
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL), \
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL)
+
+#define QUICK_END \
+{ \
+ .widget_type = quick_end, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+/*** enums ***************************************************************************************/
+
+/* Quick Widgets */
+typedef enum
+{
+ quick_end = 0,
+ quick_checkbox = 1,
+ quick_button = 2,
+ quick_input = 3,
+ quick_label = 4,
+ quick_radio = 5,
+ quick_start_groupbox = 6,
+ quick_stop_groupbox = 7,
+ quick_separator = 8,
+ quick_start_columns = 9,
+ quick_next_column = 10,
+ quick_stop_columns = 11,
+ quick_buttons = 12
+} quick_t;
+
+typedef enum
+{
+ input_label_none = 0,
+ input_label_above = 1,
+ input_label_left = 2,
+ input_label_right = 3,
+ input_label_below = 4
+} quick_input_label_location_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* The widget is placed on relative_?/divisions_? of the parent widget */
+typedef struct quick_widget_t quick_widget_t;
+
+struct quick_widget_t
+{
+ quick_t widget_type;
+
+ widget_options_t options;
+ widget_state_t state;
+ widget_pos_flags_t pos_flags;
+ unsigned long *id;
+
+ /* widget parameters */
+ union
+ {
+ struct
+ {
+ const char *text;
+ gboolean *state; /* in/out */
+ } checkbox;
+
+ struct
+ {
+ const char *text;
+ int action;
+ bcback_fn callback;
+ } button;
+
+ struct
+ {
+ const char *label_text;
+ quick_input_label_location_t label_location;
+ quick_widget_t *label;
+ const char *text;
+ input_complete_t completion_flags;
+ gboolean is_passwd; /* TRUE -- is password */
+ gboolean strip_passwd;
+ const char *histname;
+ char **result;
+ } input;
+
+ struct
+ {
+ const char *text;
+ quick_widget_t *input;
+ } label;
+
+ struct
+ {
+ int count;
+ const char **items;
+ int *value; /* in/out */
+ } radio;
+
+ struct
+ {
+ const char *title;
+ } groupbox;
+
+ struct
+ {
+ gboolean space;
+ gboolean line;
+ } separator;
+ } u;
+};
+
+typedef struct
+{
+ WRect rect; /* if rect.x == -1 or rect.y == -1, then dialog is ceneterd;
+ * rect.lines is unused and ignored */
+ const char *title;
+ const char *help;
+ quick_widget_t *widgets;
+ widget_cb_fn callback;
+ widget_mouse_cb_fn mouse_callback;
+} quick_dialog_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int quick_dialog_skip (quick_dialog_t * quick_dlg, int nskip);
+
+/*** inline functions ****************************************************************************/
+
+static inline int
+quick_dialog (quick_dialog_t * quick_dlg)
+{
+ return quick_dialog_skip (quick_dlg, 1);
+}
+
+#endif /* MC__QUICK_H */
diff --git a/lib/widget/radio.c b/lib/widget/radio.c
new file mode 100644
index 0000000..8fb52d8
--- /dev/null
+++ b/lib/widget/radio.c
@@ -0,0 +1,251 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file radio.c
+ * \brief Source: WRadui widget (radiobuttons)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+const global_keymap_t *radio_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+radio_execute_cmd (WRadio * r, long command)
+{
+ cb_ret_t ret = MSG_HANDLED;
+ Widget *w = WIDGET (r);
+
+ switch (command)
+ {
+ case CK_Up:
+ case CK_Top:
+ if (r->pos == 0)
+ return MSG_NOT_HANDLED;
+
+ if (command == CK_Top)
+ r->pos = 0;
+ else
+ r->pos--;
+ widget_draw (w);
+ return MSG_HANDLED;
+
+ case CK_Down:
+ case CK_Bottom:
+ if (r->pos == r->count - 1)
+ return MSG_NOT_HANDLED;
+
+ if (command == CK_Bottom)
+ r->pos = r->count - 1;
+ else
+ r->pos++;
+ widget_draw (w);
+ return MSG_HANDLED;
+
+ case CK_Select:
+ r->sel = r->pos;
+ widget_set_state (w, WST_FOCUSED, TRUE); /* Also draws the widget */
+ send_message (w->owner, w, MSG_NOTIFY, 0, NULL);
+ return MSG_HANDLED;
+
+ default:
+ ret = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return MSG_HANDLED if we want a redraw */
+static cb_ret_t
+radio_key (WRadio * r, int key)
+{
+ long command;
+
+ command = widget_lookup_key (WIDGET (r), key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+ return radio_execute_cmd (r, command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+radio_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WRadio *r = RADIO (w);
+ int i;
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ for (i = 0; i < r->count; i++)
+ {
+ if (r->texts[i].hotkey != NULL)
+ {
+ int c;
+
+ c = g_ascii_tolower ((gchar) r->texts[i].hotkey[0]);
+ if (c != parm)
+ continue;
+ r->pos = i;
+
+ /* Take action */
+ send_message (w, sender, MSG_ACTION, CK_Select, data);
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ return radio_key (r, parm);
+
+ case MSG_ACTION:
+ return radio_execute_cmd (r, parm);
+
+ case MSG_CURSOR:
+ widget_gotoyx (r, r->pos, 1);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ {
+ gboolean focused;
+
+ focused = widget_get_state (w, WST_FOCUSED);
+
+ for (i = 0; i < r->count; i++)
+ {
+ widget_selectcolor (w, i == r->pos && focused, FALSE);
+ widget_gotoyx (w, i, 0);
+ tty_draw_hline (w->rect.y + i, w->rect.x, ' ', w->rect.cols);
+ tty_print_string ((r->sel == i) ? "(*) " : "( ) ");
+ hotkey_draw (w, r->texts[i], i == r->pos && focused);
+ }
+
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ for (i = 0; i < r->count; i++)
+ hotkey_free (r->texts[i]);
+ g_free (r->texts);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+radio_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ RADIO (w)->pos = event->y;
+ widget_select (w);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ RADIO (w)->pos = event->y;
+ send_message (w, NULL, MSG_ACTION, CK_Select, NULL);
+ send_message (w->owner, w, MSG_POST_KEY, ' ', NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WRadio *
+radio_new (int y, int x, int count, const char **texts)
+{
+ WRect r0 = { y, x, count, 1 };
+ WRadio *r;
+ Widget *w;
+ int i, wmax = 0;
+
+ r = g_new (WRadio, 1);
+ w = WIDGET (r);
+
+ /* Compute the longest string */
+ r->texts = g_new (hotkey_t, count);
+
+ for (i = 0; i < count; i++)
+ {
+ int width;
+
+ r->texts[i] = hotkey_new (texts[i]);
+ width = hotkey_width (r->texts[i]);
+ wmax = MAX (width, wmax);
+ }
+
+ /* 4 is width of "(*) " */
+ r0.cols = 4 + wmax;
+ widget_init (w, &r0, radio_callback, radio_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR | WOP_WANT_HOTKEY;
+ w->keymap = radio_map;
+
+ r->pos = 0;
+ r->sel = 0;
+ r->count = count;
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/radio.h b/lib/widget/radio.h
new file mode 100644
index 0000000..5b52382
--- /dev/null
+++ b/lib/widget/radio.h
@@ -0,0 +1,38 @@
+
+/** \file radio.h
+ * \brief Header: WRadio widget
+ */
+
+#ifndef MC__WIDGET_RADIO_H
+#define MC__WIDGET_RADIO_H
+
+#include "lib/keybind.h" /* global_keymap_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define RADIO(x) ((WRadio *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WRadio
+{
+ Widget widget;
+ int pos;
+ int sel;
+ int count; /* number of members */
+ hotkey_t *texts; /* texts of labels */
+} WRadio;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const global_keymap_t *radio_map;
+
+/*** declarations of public functions ************************************************************/
+
+WRadio *radio_new (int y, int x, int count, const char **text);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_RADIO_H */
diff --git a/lib/widget/rect.c b/lib/widget/rect.c
new file mode 100644
index 0000000..34ae8b0
--- /dev/null
+++ b/lib/widget/rect.c
@@ -0,0 +1,253 @@
+/* Rectangular class for Midnight Commander widgets
+
+ Copyright (C) 2020-2023
+ The Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file widget-common.c
+ * \brief Source: shared stuff of widgets
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "rect.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create new WRect object.
+ *
+ * @param y y-coordinate of left-up corner
+ * @param x x-coordinate of left-up corner
+ * @param lines height
+ * @param cols width
+ *
+ * @return newly allocated WRect object.
+ */
+
+WRect *
+rect_new (int y, int x, int lines, int cols)
+{
+ WRect *r;
+
+ r = g_try_new (WRect, 1);
+
+ if (r != NULL)
+ rect_init (r, y, x, lines, cols);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize WRect object.
+ *
+ * @param r WRect object
+ * @param y y-coordinate of left-up corner
+ * @param x x-coordinate of left-up corner
+ * @param lines height
+ * @param cols width
+ */
+
+void
+rect_init (WRect * r, int y, int x, int lines, int cols)
+{
+ r->y = y;
+ r->x = x;
+ r->lines = lines;
+ r->cols = cols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change position of rectangle area.
+ *
+ * @param r WRect object
+ * @param dy y-shift of left-up corner
+ * @param dx x-shift of left-up corner
+ */
+
+void
+rect_move (WRect * r, int dy, int dx)
+{
+ r->y += dy;
+ r->x += dx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change size of rectangle area keeping it's position.
+ *
+ * @param r WRect object
+ * @param dl change size value of height
+ * @param dc change size value of width
+ */
+
+void
+rect_resize (WRect * r, int dl, int dc)
+{
+ r->lines += dl;
+ r->cols += dc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change size of rectangle area keeping it's center.
+ *
+ * @param r WRect object
+ * @param dl change size value of y-coordinate and height
+ * Positive value means move up and increase height.
+ * Negative value means move down and decrease height.
+ * @param dc change size value of x-coordinate and width
+ * Positive value means move left and increase width.
+ * Negative value means move right and decrease width.
+ */
+
+void
+rect_grow (WRect * r, int dl, int dc)
+{
+ r->y -= dl;
+ r->x -= dc;
+ r->lines += dl * 2;
+ r->cols += dc * 2;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculates the intersection of two rectangle areas.
+ * The resulting rectangle is the largest rectangle which contains intersection of rectangle areas.
+ *
+ * @param r first WRect object
+ * @param r1 second WRect object
+ *
+ * The resulting rectangle is stored in r.
+ */
+
+void
+rect_intersect (WRect * r, const WRect * r1)
+{
+ int y, x;
+ int y1, x1;
+
+ /* right-down corners */
+ y = r->y + r->lines;
+ x = r->x + r->cols;
+ y1 = r1->y + r1->lines;
+ x1 = r1->x + r1->cols;
+
+ /* right-down corner of intersection */
+ y = MIN (y, y1);
+ x = MIN (x, x1);
+
+ /* left-up corner of intersection */
+ r->y = MAX (r->y, r1->y);
+ r->x = MAX (r->x, r1->x);
+
+ /* intersection sizes */
+ r->lines = y - r->y;
+ r->cols = x - r->x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculates the union of two rectangle areas.
+ * The resulting rectangle is the largest rectangle which contains both rectangle areas.
+ *
+ * @param r first WRect object
+ * @param r1 second WRect object
+ *
+ * The resulting rectangle is stored in r.
+ */
+
+void
+rect_union (WRect * r, const WRect * r1)
+{
+ int x, y;
+ int x1, y1;
+
+ /* right-down corners */
+ y = r->y + r->lines;
+ x = r->x + r->cols;
+ y1 = r1->y + r1->lines;
+ x1 = r1->x + r1->cols;
+
+ /* right-down corner of union */
+ y = MAX (y, y1);
+ x = MAX (x, x1);
+
+ /* left-up corner of union */
+ r->y = MIN (r->y, r1->y);
+ r->x = MIN (r->x, r1->x);
+
+ /* union sizes */
+ r->lines = y - r->y;
+ r->cols = x - r->x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether two rectangle areas are overlapped or not.
+ *
+ * @param r1 WRect object
+ * @param r2 WRect object
+ *
+ * @return TRUE if rectangle areas are overlapped, FALSE otherwise.
+ */
+
+gboolean
+rects_are_overlapped (const WRect * r1, const WRect * r2)
+{
+ return !((r2->x >= r1->x + r1->cols) || (r1->x >= r2->x + r2->cols)
+ || (r2->y >= r1->y + r1->lines) || (r1->y >= r2->y + r2->lines));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether two rectangle areas are equal or not.
+ *
+ * @param r1 WRect object
+ * @param r2 WRect object
+ *
+ * @return TRUE if rectangle areas are equal, FALSE otherwise.
+ */
+
+gboolean
+rects_are_equal (const WRect * r1, const WRect * r2)
+{
+ return (r1->y == r2->y && r1->x == r2->x && r1->lines == r2->lines && r1->cols == r2->cols);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/rect.h b/lib/widget/rect.h
new file mode 100644
index 0000000..ca85968
--- /dev/null
+++ b/lib/widget/rect.h
@@ -0,0 +1,45 @@
+
+/** \file rect.h
+ * \brief Header: rectangular class
+ */
+
+#ifndef MC__WIDGET_RECT_H
+#define MC__WIDGET_RECT_H
+
+/*** typedefs (not structures) and defined constants *********************************************/
+
+#define RECT(x) ((WRect *)(x))
+#define CONST_RECT(x) ((const WRect *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures) ****************************************/
+
+struct WRect;
+typedef struct WRect WRect;
+
+struct WRect
+{
+ int y;
+ int x;
+ int lines;
+ int cols;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WRect *rect_new (int y, int x, int lines, int cols);
+void rect_init (WRect * r, int y, int x, int lines, int cols);
+void rect_move (WRect * r, int dy, int dx);
+void rect_resize (WRect * r, int dl, int dc);
+void rect_grow (WRect * r, int dl, int dc);
+void rect_intersect (WRect * r, const WRect * r1);
+void rect_union (WRect * r, const WRect * r1);
+gboolean rects_are_overlapped (const WRect * r1, const WRect * r2);
+gboolean rects_are_equal (const WRect * r1, const WRect * r2);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_RECT_H */
diff --git a/lib/widget/widget-common.c b/lib/widget/widget-common.c
new file mode 100644
index 0000000..821b7b3
--- /dev/null
+++ b/lib/widget/widget-common.c
@@ -0,0 +1,905 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file widget-common.c
+ * \brief Source: shared stuff of widgets
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* maximum value of used widget ID */
+static unsigned long widget_id = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Calc widget ID,
+ * Widget ID is uniq for each widget created during MC session (like PID in OS).
+ *
+ * @return widget ID.
+ */
+static unsigned long
+widget_set_id (void)
+{
+ unsigned long id;
+
+ id = widget_id++;
+ /* TODO IF NEEDED: if id is already used, find next free id. */
+
+ return id;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+widget_default_resize (Widget * w, const WRect * r)
+{
+ if (r == NULL)
+ return MSG_NOT_HANDLED;
+
+ w->rect = *r;
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+widget_do_focus (Widget * w, gboolean enable)
+{
+ if (w != NULL && widget_get_state (WIDGET (w->owner), WST_VISIBLE | WST_FOCUSED))
+ widget_set_state (w, WST_FOCUSED, enable);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Focus specified widget in it's owner.
+ *
+ * @param w widget to be focused.
+ */
+
+static void
+widget_focus (Widget * w)
+{
+ WGroup *g = w->owner;
+
+ if (g == NULL)
+ return;
+
+ if (WIDGET (g->current->data) != w)
+ {
+ widget_do_focus (WIDGET (g->current->data), FALSE);
+ /* Test if focus lost was allowed and focus has really been loose */
+ if (g->current == NULL || !widget_get_state (WIDGET (g->current->data), WST_FOCUSED))
+ {
+ widget_do_focus (w, TRUE);
+ g->current = widget_find (WIDGET (g), w);
+ }
+ }
+ else if (!widget_get_state (w, WST_FOCUSED))
+ widget_do_focus (w, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Put widget on top or bottom of Z-order.
+ */
+static void
+widget_reorder (GList * l, gboolean set_top)
+{
+ WGroup *g = WIDGET (l->data)->owner;
+
+ g->widgets = g_list_remove_link (g->widgets, l);
+ if (set_top)
+ g->widgets = g_list_concat (g->widgets, l);
+ else
+ g->widgets = g_list_concat (l, g->widgets);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+hotkey_cmp (const char *s1, const char *s2)
+{
+ gboolean n1, n2;
+
+ n1 = s1 != NULL;
+ n2 = s2 != NULL;
+
+ if (n1 != n2)
+ return FALSE;
+
+ if (n1 && n2 && strcmp (s1, s2) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+widget_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ /* do nothing */
+ (void) w;
+ (void) msg;
+ (void) event;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const int *
+widget_default_get_colors (const Widget * w)
+{
+ const Widget *owner = CONST_WIDGET (w->owner);
+
+ return (owner == NULL ? NULL : widget_get_colors (owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct hotkey_t
+hotkey_new (const char *text)
+{
+ hotkey_t result;
+ const char *cp, *p;
+
+ if (text == NULL)
+ text = "";
+
+ /* search for '&', that is not on the of text */
+ cp = strchr (text, '&');
+ if (cp != NULL && cp[1] != '\0')
+ {
+ result.start = g_strndup (text, cp - text);
+
+ /* skip '&' */
+ cp++;
+ p = str_cget_next_char (cp);
+ result.hotkey = g_strndup (cp, p - cp);
+
+ cp = p;
+ result.end = g_strdup (cp);
+ }
+ else
+ {
+ result.start = g_strdup (text);
+ result.hotkey = NULL;
+ result.end = NULL;
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hotkey_free (const hotkey_t hotkey)
+{
+ g_free (hotkey.start);
+ g_free (hotkey.hotkey);
+ g_free (hotkey.end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+hotkey_width (const hotkey_t hotkey)
+{
+ int result;
+
+ result = str_term_width1 (hotkey.start);
+ result += (hotkey.hotkey != NULL) ? str_term_width1 (hotkey.hotkey) : 0;
+ result += (hotkey.end != NULL) ? str_term_width1 (hotkey.end) : 0;
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+hotkey_equal (const hotkey_t hotkey1, const hotkey_t hotkey2)
+{
+ /* *INDENT-OFF* */
+ return (strcmp (hotkey1.start, hotkey2.start) == 0) &&
+ hotkey_cmp (hotkey1.hotkey, hotkey2.hotkey) &&
+ hotkey_cmp (hotkey1.end, hotkey2.end);
+ /* *INDENT-ON* */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hotkey_draw (const Widget * w, const hotkey_t hotkey, gboolean focused)
+{
+ if (hotkey.start[0] != '\0')
+ {
+ widget_selectcolor (w, focused, FALSE);
+ tty_print_string (hotkey.start);
+ }
+
+ if (hotkey.hotkey != NULL)
+ {
+ widget_selectcolor (w, focused, TRUE);
+ tty_print_string (hotkey.hotkey);
+ }
+
+ if (hotkey.end != NULL)
+ {
+ widget_selectcolor (w, focused, FALSE);
+ tty_print_string (hotkey.end);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+hotkey_get_text (const hotkey_t hotkey)
+{
+ GString *text;
+
+ text = g_string_new (hotkey.start);
+
+ if (hotkey.hotkey != NULL)
+ {
+ g_string_append_c (text, '&');
+ g_string_append (text, hotkey.hotkey);
+ }
+
+ if (hotkey.end != NULL)
+ g_string_append (text, hotkey.end);
+
+ return g_string_free (text, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_init (Widget * w, const WRect * r, widget_cb_fn callback, widget_mouse_cb_fn mouse_callback)
+{
+ w->id = widget_set_id ();
+ w->rect = *r;
+ w->pos_flags = WPOS_KEEP_DEFAULT;
+ w->callback = callback;
+
+ w->keymap = NULL;
+ w->ext_keymap = NULL;
+ w->ext_mode = FALSE;
+
+ w->mouse_callback = mouse_callback != NULL ? mouse_callback : widget_default_mouse_callback;
+ w->owner = NULL;
+ w->mouse_handler = mouse_handle_event;
+ w->mouse.forced_capture = FALSE;
+ w->mouse.capture = FALSE;
+ w->mouse.last_msg = MSG_MOUSE_NONE;
+ w->mouse.last_buttons_down = 0;
+
+ w->options = WOP_DEFAULT;
+ w->state = WST_CONSTRUCT | WST_VISIBLE;
+
+ w->make_global = widget_default_make_global;
+ w->make_local = widget_default_make_local;
+
+ w->find = widget_default_find;
+ w->find_by_type = widget_default_find_by_type;
+ w->find_by_id = widget_default_find_by_id;
+
+ w->set_state = widget_default_set_state;
+ w->destroy = widget_default_destroy;
+ w->get_colors = widget_default_get_colors;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Default callback for widgets */
+cb_ret_t
+widget_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ (void) sender;
+ (void) parm;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ case MSG_FOCUS:
+ case MSG_UNFOCUS:
+ case MSG_ENABLE:
+ case MSG_DISABLE:
+ case MSG_DRAW:
+ case MSG_DESTROY:
+ case MSG_CURSOR:
+ case MSG_IDLE:
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ return widget_default_resize (w, CONST_RECT (data));
+
+ default:
+ return MSG_NOT_HANDLED;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Apply new options to widget.
+ *
+ * @param w widget
+ * @param options widget option flags to modify. Several flags per call can be modified.
+ * @param enable TRUE if specified options should be added, FALSE if options should be removed
+ */
+void
+widget_set_options (Widget * w, widget_options_t options, gboolean enable)
+{
+ if (enable)
+ w->options |= options;
+ else
+ w->options &= ~options;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_adjust_position (widget_pos_flags_t pos_flags, WRect * r)
+{
+ if ((pos_flags & WPOS_FULLSCREEN) != 0)
+ {
+ r->y = 0;
+ r->x = 0;
+ r->lines = LINES;
+ r->cols = COLS;
+ }
+ else
+ {
+ if ((pos_flags & WPOS_CENTER_HORZ) != 0)
+ r->x = (COLS - r->cols) / 2;
+
+ if ((pos_flags & WPOS_CENTER_VERT) != 0)
+ r->y = (LINES - r->lines) / 2;
+
+ if ((pos_flags & WPOS_TRYUP) != 0)
+ {
+ if (r->y > 3)
+ r->y -= 2;
+ else if (r->y == 3)
+ r->y = 2;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change widget position and size.
+ *
+ * @param w widget
+ * @param y y coordinate of top-left corner
+ * @param x x coordinate of top-left corner
+ * @param lines width
+ * @param cols height
+ */
+
+void
+widget_set_size (Widget * w, int y, int x, int lines, int cols)
+{
+ WRect r = { y, x, lines, cols };
+
+ send_message (w, NULL, MSG_RESIZE, 0, &r);
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change widget position and size.
+ *
+ * @param w widget
+ * @param r WRect object that holds position and size
+ */
+
+void
+widget_set_size_rect (Widget * w, WRect * r)
+{
+ send_message (w, NULL, MSG_RESIZE, 0, r);
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_selectcolor (const Widget * w, gboolean focused, gboolean hotkey)
+{
+ int color;
+ const int *colors;
+
+ colors = widget_get_colors (w);
+
+ if (widget_get_state (w, WST_DISABLED))
+ color = DISABLED_COLOR;
+ else if (hotkey)
+ color = colors[focused ? DLG_COLOR_HOT_FOCUS : DLG_COLOR_HOT_NORMAL];
+ else
+ color = colors[focused ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL];
+
+ tty_setcolor (color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_erase (Widget * w)
+{
+ if (w != NULL)
+ tty_fill_region (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, ' ');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_set_visibility (Widget * w, gboolean make_visible)
+{
+ if (widget_get_state (w, WST_VISIBLE) != make_visible)
+ widget_set_state (w, WST_VISIBLE, make_visible);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether widget is active or not.
+ * Widget is active if it's current in the its owner and each owner in the chain is current too.
+ *
+ * @param w the widget
+ *
+ * @return TRUE if the widget is active, FALSE otherwise
+ */
+
+gboolean
+widget_is_active (const void *w)
+{
+ const WGroup *owner;
+
+ /* Is group top? */
+ if (w == top_dlg->data)
+ return TRUE;
+
+ owner = CONST_WIDGET (w)->owner;
+
+ /* Is widget in any group? */
+ if (owner == NULL)
+ return FALSE;
+
+ if (w != owner->current->data)
+ return FALSE;
+
+ return widget_is_active (owner);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+widget_draw (Widget * w)
+{
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ if (w != NULL && widget_get_state (w, WST_VISIBLE))
+ {
+ WGroup *g = w->owner;
+
+ if (g != NULL && widget_get_state (WIDGET (g), WST_ACTIVE))
+ ret = w->callback (w, NULL, MSG_DRAW, 0, NULL);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Replace widget in the dialog.
+ *
+ * @param old_w old widget that need to be replaced
+ * @param new_w new widget that will replace @old_w
+ */
+
+void
+widget_replace (Widget * old_w, Widget * new_w)
+{
+ WGroup *g = old_w->owner;
+ gboolean should_focus = FALSE;
+ GList *holder;
+
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ /* locate widget position in the list */
+ if (old_w == g->current->data)
+ holder = g->current;
+ else
+ holder = g_list_find (g->widgets, old_w);
+
+ /* if old widget is focused, we should focus the new one... */
+ if (widget_get_state (old_w, WST_FOCUSED))
+ should_focus = TRUE;
+ /* ...but if new widget isn't selectable, we cannot focus it */
+ if (!widget_get_options (new_w, WOP_SELECTABLE))
+ should_focus = FALSE;
+
+ /* if new widget isn't selectable, select other widget before replace */
+ if (!should_focus)
+ {
+ GList *l;
+
+ for (l = group_get_widget_next_of (holder);
+ !widget_is_focusable (WIDGET (l->data)) && l != holder;
+ l = group_get_widget_next_of (l))
+ ;
+
+ widget_select (WIDGET (l->data));
+ }
+
+ /* replace widget */
+ new_w->owner = g;
+ new_w->id = old_w->id;
+ holder->data = new_w;
+
+ send_message (old_w, NULL, MSG_DESTROY, 0, NULL);
+ send_message (new_w, NULL, MSG_INIT, 0, NULL);
+
+ if (should_focus)
+ widget_select (new_w);
+ else
+ widget_draw (new_w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+widget_is_focusable (const Widget * w)
+{
+ return (widget_get_options (w, WOP_SELECTABLE) && widget_get_state (w, WST_VISIBLE) &&
+ !widget_get_state (w, WST_DISABLED));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Select specified widget in it's owner.
+ *
+ * Note: this function (and widget_focus(), which it calls) is a no-op
+ * if the widget is already selected.
+ *
+ * @param w widget to be selected
+ */
+
+void
+widget_select (Widget * w)
+{
+ WGroup *g;
+
+ if (!widget_get_options (w, WOP_SELECTABLE))
+ return;
+
+ g = GROUP (w->owner);
+ if (g != NULL)
+ {
+ if (widget_get_options (w, WOP_TOP_SELECT))
+ {
+ GList *l;
+
+ l = widget_find (WIDGET (g), w);
+ widget_reorder (l, TRUE);
+ }
+
+ widget_focus (w);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set widget at bottom of widget list.
+ */
+
+void
+widget_set_bottom (Widget * w)
+{
+ widget_reorder (widget_find (WIDGET (w->owner), w), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Look up key event of widget and translate it to command ID.
+ * @param w widget
+ * @param key key event
+ *
+ * @return command ID binded with @key.
+ */
+
+long
+widget_lookup_key (Widget * w, int key)
+{
+ if (w->ext_mode)
+ {
+ w->ext_mode = FALSE;
+ return keybind_lookup_keymap_command (w->ext_keymap, key);
+ }
+
+ return keybind_lookup_keymap_command (w->keymap, key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default widget callback to convert widget coordinates from local (relative to owner) to global
+ * (relative to screen).
+ *
+ * @param w widget
+ * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
+ */
+
+void
+widget_default_make_global (Widget * w, const WRect * delta)
+{
+ if (delta != NULL)
+ rect_move (&w->rect, delta->y, delta->x);
+ else if (w->owner != NULL)
+ rect_move (&w->rect, WIDGET (w->owner)->rect.y, WIDGET (w->owner)->rect.x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default widget callback to convert widget coordinates from global (relative to screen) to local
+ * (relative to owner).
+ *
+ * @param w widget
+ * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
+ */
+
+void
+widget_default_make_local (Widget * w, const WRect * delta)
+{
+ if (delta != NULL)
+ rect_move (&w->rect, -delta->y, -delta->x);
+ else if (w->owner != NULL)
+ rect_move (&w->rect, -WIDGET (w->owner)->rect.y, -WIDGET (w->owner)->rect.x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Default callback function to find widget.
+ *
+ * @param w widget
+ * @param what widget to find
+ *
+ * @return holder of @what if widget is @what, NULL otherwise
+ */
+
+GList *
+widget_default_find (const Widget * w, const Widget * what)
+{
+ return (w != what
+ || w->owner == NULL) ? NULL : g_list_find (CONST_GROUP (w->owner)->widgets, what);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default callback function to find widget by widget type using widget callback.
+ *
+ * @param w widget
+ * @param cb widget callback
+ *
+ * @return @w if widget callback is @cb, NULL otherwise
+ */
+
+Widget *
+widget_default_find_by_type (const Widget * w, widget_cb_fn cb)
+{
+ return (w->callback == cb ? WIDGET (w) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Default callback function to find widget by widget ID.
+ *
+ * @param w widget
+ * @param id widget ID
+ *
+ * @return @w if widget id is equal to @id, NULL otherwise
+ */
+
+Widget *
+widget_default_find_by_id (const Widget * w, unsigned long id)
+{
+ return (w->id == id ? WIDGET (w) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default callback function to modify state of widget.
+ *
+ * @param w widget
+ * @param state widget state flag to modify
+ * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
+ * Only one flag per call can be modified.
+ * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
+ */
+
+cb_ret_t
+widget_default_set_state (Widget * w, widget_state_t state, gboolean enable)
+{
+ gboolean ret = MSG_HANDLED;
+ Widget *owner = WIDGET (GROUP (w->owner));
+
+ if (enable)
+ w->state |= state;
+ else
+ w->state &= ~state;
+
+ if (enable)
+ {
+ /* exclusive bits */
+ switch (state)
+ {
+ case WST_CONSTRUCT:
+ w->state &= ~(WST_ACTIVE | WST_SUSPENDED | WST_CLOSED);
+ break;
+ case WST_ACTIVE:
+ w->state &= ~(WST_CONSTRUCT | WST_SUSPENDED | WST_CLOSED);
+ break;
+ case WST_SUSPENDED:
+ w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_CLOSED);
+ break;
+ case WST_CLOSED:
+ w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_SUSPENDED);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (owner == NULL)
+ return MSG_NOT_HANDLED;
+
+ switch (state)
+ {
+ case WST_VISIBLE:
+ if (widget_get_state (owner, WST_ACTIVE))
+ {
+ /* redraw owner to show/hide widget */
+ widget_draw (owner);
+
+ if (!enable)
+ {
+ /* try select another widget if current one got hidden */
+ if (w == GROUP (owner)->current->data)
+ group_select_next_widget (GROUP (owner));
+
+ widget_update_cursor (owner); /* FIXME: unneeded? */
+ }
+ }
+ break;
+
+ case WST_DISABLED:
+ ret = send_message (w, NULL, enable ? MSG_DISABLE : MSG_ENABLE, 0, NULL);
+ if (ret == MSG_HANDLED && widget_get_state (owner, WST_ACTIVE))
+ ret = widget_draw (w);
+ break;
+
+ case WST_FOCUSED:
+ {
+ widget_msg_t msg;
+
+ msg = enable ? MSG_FOCUS : MSG_UNFOCUS;
+ ret = send_message (w, NULL, msg, 0, NULL);
+ if (ret == MSG_HANDLED && widget_get_state (owner, WST_ACTIVE))
+ {
+ widget_draw (w);
+ /* Notify owner that focus was moved from one widget to another */
+ send_message (owner, w, MSG_CHANGED_FOCUS, 0, NULL);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Default callback function to destroy widget.
+ *
+ * @param w widget
+ */
+
+void
+widget_default_destroy (Widget * w)
+{
+ send_message (w, NULL, MSG_DESTROY, 0, NULL);
+ g_free (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* get mouse pointer location within widget */
+
+Gpm_Event
+mouse_get_local (const Gpm_Event * global, const Widget * w)
+{
+ Gpm_Event local;
+
+ memset (&local, 0, sizeof (local));
+
+ local.buttons = global->buttons;
+ local.x = global->x - w->rect.x;
+ local.y = global->y - w->rect.y;
+ local.type = global->type;
+
+ return local;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mouse_global_in_widget (const Gpm_Event * event, const Widget * w)
+{
+ const WRect *r = &w->rect;
+
+ return (event->x > r->x) && (event->y > r->y) && (event->x <= r->x + r->cols)
+ && (event->y <= r->y + r->lines);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/widget-common.h b/lib/widget/widget-common.h
new file mode 100644
index 0000000..19acec1
--- /dev/null
+++ b/lib/widget/widget-common.h
@@ -0,0 +1,462 @@
+
+/** \file widget-common.h
+ * \brief Header: shared stuff of widgets
+ */
+
+#ifndef MC__WIDGET_COMMON_H
+#define MC__WIDGET_COMMON_H
+
+#include "lib/keybind.h" /* global_keymap_t */
+#include "lib/tty/mouse.h"
+#include "lib/widget/mouse.h" /* mouse_msg_t, mouse_event_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define WIDGET(x) ((Widget *)(x))
+#define CONST_WIDGET(x) ((const Widget *)(x))
+
+#define widget_gotoyx(w, _y, _x) tty_gotoyx (CONST_WIDGET(w)->rect.y + (_y), CONST_WIDGET(w)->rect.x + (_x))
+/* Sets/clear the specified flag in the options field */
+#define widget_want_cursor(w,i) widget_set_options(w, WOP_WANT_CURSOR, i)
+#define widget_want_hotkey(w,i) widget_set_options(w, WOP_WANT_HOTKEY, i)
+#define widget_want_tab(w,i) widget_set_options(w, WOP_WANT_TAB, i)
+#define widget_idle(w,i) widget_set_state(w, WST_IDLE, i)
+#define widget_disable(w,i) widget_set_state(w, WST_DISABLED, i)
+
+/*** enums ***************************************************************************************/
+
+/* Widget messages */
+typedef enum
+{
+ MSG_INIT = 0, /* Initialize widget */
+ MSG_FOCUS, /* Draw widget in focused state or widget has got focus */
+ MSG_UNFOCUS, /* Draw widget in unfocused state or widget has been unfocused */
+ MSG_CHANGED_FOCUS, /* Notification to owner about focus state change */
+ MSG_ENABLE, /* Change state to enabled */
+ MSG_DISABLE, /* Change state to disabled */
+ MSG_DRAW, /* Draw widget on screen */
+ MSG_KEY, /* Sent to widgets on key press */
+ MSG_HOTKEY, /* Sent to widget to catch preprocess key */
+ MSG_HOTKEY_HANDLED, /* A widget has got the hotkey */
+ MSG_UNHANDLED_KEY, /* Key that no widget handled */
+ MSG_POST_KEY, /* The key has been handled */
+ MSG_ACTION, /* Send to widget to handle command */
+ MSG_NOTIFY, /* Typically sent to dialog to inform it of state-change
+ * of listboxes, check- and radiobuttons. */
+ MSG_CURSOR, /* Sent to widget to position the cursor */
+ MSG_IDLE, /* The idle state is active */
+ MSG_RESIZE, /* Screen size has changed */
+ MSG_VALIDATE, /* Dialog is to be closed */
+ MSG_END, /* Shut down dialog */
+ MSG_DESTROY /* Sent to widget at destruction time */
+} widget_msg_t;
+
+/* Widgets are expected to answer to the following messages:
+ MSG_FOCUS: MSG_HANDLED if the accept the focus, MSG_NOT_HANDLED if they do not.
+ MSG_UNFOCUS: MSG_HANDLED if they accept to release the focus, MSG_NOT_HANDLED if they don't.
+ MSG_KEY: MSG_HANDLED if they actually used the key, MSG_NOT_HANDLED if not.
+ MSG_HOTKEY: MSG_HANDLED if they actually used the key, MSG_NOT_HANDLED if not.
+ */
+
+typedef enum
+{
+ MSG_NOT_HANDLED = 0,
+ MSG_HANDLED = 1
+} cb_ret_t;
+
+/* Widget options */
+typedef enum
+{
+ WOP_DEFAULT = (0 << 0),
+ WOP_WANT_HOTKEY = (1 << 0),
+ WOP_WANT_CURSOR = (1 << 1),
+ WOP_WANT_TAB = (1 << 2), /* Should the tab key be sent to the dialog? */
+ WOP_IS_INPUT = (1 << 3),
+ WOP_SELECTABLE = (1 << 4),
+ WOP_TOP_SELECT = (1 << 5)
+} widget_options_t;
+
+/* Widget state */
+typedef enum
+{
+ WST_DEFAULT = (0 << 0),
+ WST_VISIBLE = (1 << 0), /* Widget is visible */
+ WST_DISABLED = (1 << 1), /* Widget cannot be selected */
+ WST_IDLE = (1 << 2),
+ WST_MODAL = (1 << 3), /* Widget (dialog) is modal */
+ WST_FOCUSED = (1 << 4),
+
+ WST_CONSTRUCT = (1 << 15), /* Widget has been constructed but not run yet */
+ WST_ACTIVE = (1 << 16), /* Dialog is visible and active */
+ WST_SUSPENDED = (1 << 17), /* Dialog is suspended */
+ WST_CLOSED = (1 << 18) /* Dialog is closed */
+} widget_state_t;
+
+/* Flags for widget repositioning on dialog resize */
+typedef enum
+{
+ WPOS_FULLSCREEN = (1 << 0), /* widget occupies the whole screen */
+ WPOS_CENTER_HORZ = (1 << 1), /* center widget in horizontal */
+ WPOS_CENTER_VERT = (1 << 2), /* center widget in vertical */
+ WPOS_CENTER = WPOS_CENTER_HORZ | WPOS_CENTER_VERT, /* center widget */
+ WPOS_TRYUP = (1 << 3), /* try to move two lines up the widget */
+ WPOS_KEEP_LEFT = (1 << 4), /* keep widget distance to left border of dialog */
+ WPOS_KEEP_RIGHT = (1 << 5), /* keep widget distance to right border of dialog */
+ WPOS_KEEP_TOP = (1 << 6), /* keep widget distance to top border of dialog */
+ WPOS_KEEP_BOTTOM = (1 << 7), /* keep widget distance to bottom border of dialog */
+ WPOS_KEEP_HORZ = WPOS_KEEP_LEFT | WPOS_KEEP_RIGHT,
+ WPOS_KEEP_VERT = WPOS_KEEP_TOP | WPOS_KEEP_BOTTOM,
+ WPOS_KEEP_ALL = WPOS_KEEP_HORZ | WPOS_KEEP_VERT,
+ WPOS_KEEP_DEFAULT = WPOS_KEEP_LEFT | WPOS_KEEP_TOP
+} widget_pos_flags_t;
+/* NOTES:
+ * If WPOS_FULLSCREEN is set then all other position flags are ignored.
+ * If WPOS_CENTER_HORZ flag is used, other horizontal flags (WPOS_KEEP_LEFT, WPOS_KEEP_RIGHT,
+ * and WPOS_KEEP_HORZ) are ignored.
+ * If WPOS_CENTER_VERT flag is used, other horizontal flags (WPOS_KEEP_TOP, WPOS_KEEP_BOTTOM,
+ * and WPOS_KEEP_VERT) are ignored.
+ */
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Widget callback */
+typedef cb_ret_t (*widget_cb_fn) (Widget * widget, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+/* Widget mouse callback */
+typedef void (*widget_mouse_cb_fn) (Widget * w, mouse_msg_t msg, mouse_event_t * event);
+/* translate mouse event and process it */
+typedef int (*widget_mouse_handle_fn) (Widget * w, Gpm_Event * event);
+
+/* Every Widget must have this as its first element */
+struct Widget
+{
+ WRect rect; /* position and size */
+ /* ATTENTION! For groups, don't change @rect members directly to avoid
+ incorrect reposion and resize of group members. */
+ widget_pos_flags_t pos_flags; /* repositioning flags */
+ widget_options_t options;
+ widget_state_t state;
+ unsigned long id; /* uniq widget ID */
+ widget_cb_fn callback;
+ widget_mouse_cb_fn mouse_callback;
+ WGroup *owner;
+
+ /* Key-related fields */
+ const global_keymap_t *keymap; /* main keymap */
+ const global_keymap_t *ext_keymap; /* extended keymap */
+ gboolean ext_mode; /* use keymap or ext_keymap */
+
+ /* Mouse-related fields. */
+ widget_mouse_handle_fn mouse_handler;
+ struct
+ {
+ /* Public members: */
+ gboolean forced_capture; /* Overrides the 'capture' member. Set explicitly by the programmer. */
+
+ /* Implementation details: */
+ gboolean capture; /* Whether the widget "owns" the mouse. */
+ mouse_msg_t last_msg; /* The previous event type processed. */
+ int last_buttons_down;
+ } mouse;
+
+ void (*make_global) (Widget * w, const WRect * delta);
+ void (*make_local) (Widget * w, const WRect * delta);
+
+ GList *(*find) (const Widget * w, const Widget * what);
+ Widget *(*find_by_type) (const Widget * w, widget_cb_fn cb);
+ Widget *(*find_by_id) (const Widget * w, unsigned long id);
+
+ /* *INDENT-OFF* */
+ cb_ret_t (*set_state) (Widget * w, widget_state_t state, gboolean enable);
+ /* *INDENT-ON* */
+ void (*destroy) (Widget * w);
+
+ const int *(*get_colors) (const Widget * w);
+};
+
+/* structure for label (caption) with hotkey, if original text does not contain
+ * hotkey, only start is valid and is equal to original text
+ * hotkey is defined as char*, but mc support only singlebyte hotkey
+ */
+typedef struct hotkey_t
+{
+ char *start; /* never NULL */
+ char *hotkey; /* can be NULL */
+ char *end; /* can be NULL */
+} hotkey_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* create hotkey from text */
+hotkey_t hotkey_new (const char *text);
+/* release hotkey, free all mebers of hotkey_t */
+void hotkey_free (const hotkey_t hotkey);
+/* return width on terminal of hotkey */
+int hotkey_width (const hotkey_t hotkey);
+/* compare two hotkeys */
+gboolean hotkey_equal (const hotkey_t hotkey1, const hotkey_t hotkey2);
+/* draw hotkey of widget */
+void hotkey_draw (const Widget * w, const hotkey_t hotkey, gboolean focused);
+/* get text of hotkey */
+char *hotkey_get_text (const hotkey_t hotkey);
+
+/* widget initialization */
+void widget_init (Widget * w, const WRect * r, widget_cb_fn callback,
+ widget_mouse_cb_fn mouse_callback);
+/* Default callback for widgets */
+cb_ret_t widget_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+void widget_set_options (Widget * w, widget_options_t options, gboolean enable);
+void widget_adjust_position (widget_pos_flags_t pos_flags, WRect * r);
+void widget_set_size (Widget * w, int y, int x, int lines, int cols);
+void widget_set_size_rect (Widget * w, WRect * r);
+/* select color for widget in dependence of state */
+void widget_selectcolor (const Widget * w, gboolean focused, gboolean hotkey);
+cb_ret_t widget_draw (Widget * w);
+void widget_erase (Widget * w);
+void widget_set_visibility (Widget * w, gboolean make_visible);
+gboolean widget_is_active (const void *w);
+void widget_replace (Widget * old, Widget * new);
+gboolean widget_is_focusable (const Widget * w);
+void widget_select (Widget * w);
+void widget_set_bottom (Widget * w);
+
+long widget_lookup_key (Widget * w, int key);
+
+void widget_default_make_global (Widget * w, const WRect * delta);
+void widget_default_make_local (Widget * w, const WRect * delta);
+
+GList *widget_default_find (const Widget * w, const Widget * what);
+Widget *widget_default_find_by_type (const Widget * w, widget_cb_fn cb);
+Widget *widget_default_find_by_id (const Widget * w, unsigned long id);
+
+cb_ret_t widget_default_set_state (Widget * w, widget_state_t state, gboolean enable);
+
+void widget_default_destroy (Widget * w);
+
+/* get mouse pointer location within widget */
+Gpm_Event mouse_get_local (const Gpm_Event * global, const Widget * w);
+gboolean mouse_global_in_widget (const Gpm_Event * event, const Widget * w);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline cb_ret_t
+send_message (void *w, void *sender, widget_msg_t msg, int parm, void *data)
+{
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+#if 1
+ if (w != NULL) /* This must be always true, but... */
+#endif
+ ret = WIDGET (w)->callback (WIDGET (w), WIDGET (sender), msg, parm, data);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether one or several option flags are set or not.
+ * @param w widget
+ * @param options widget option flags
+ *
+ * @return TRUE if all requested option flags are set, FALSE otherwise.
+ */
+
+static inline gboolean
+widget_get_options (const Widget * w, widget_options_t options)
+{
+ return ((w->options & options) == options);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Check whether one or several state flags are set or not.
+ * @param w widget
+ * @param state widget state flags
+ *
+ * @return TRUE if all requested state flags are set, FALSE otherwise.
+ */
+
+static inline gboolean
+widget_get_state (const Widget * w, widget_state_t state)
+{
+ return ((w->state & state) == state);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert widget coordinates from local (relative to owner) to global (relative to screen).
+ *
+ * @param w widget
+ */
+
+static inline void
+widget_make_global (Widget * w)
+{
+ w->make_global (w, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert widget coordinates from global (relative to screen) to local (relative to owner).
+ *
+ * @param w widget
+ */
+
+static inline void
+widget_make_local (Widget * w)
+{
+ w->make_local (w, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Find widget.
+ *
+ * @param w widget
+ * @param what widget to find
+ *
+ * @return result of @w->find()
+ */
+
+static inline GList *
+widget_find (const Widget * w, const Widget * what)
+{
+ return w->find (w, what);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Find widget by widget type using widget callback.
+ *
+ * @param w widget
+ * @param cb widget callback
+ *
+ * @return result of @w->find_by_type()
+ */
+
+static inline Widget *
+widget_find_by_type (const Widget * w, widget_cb_fn cb)
+{
+ return w->find_by_type (w, cb);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find widget by widget ID.
+ *
+ * @param w widget
+ * @param id widget ID
+ *
+ * @return result of @w->find_by_id()
+ */
+
+static inline Widget *
+widget_find_by_id (const Widget * w, unsigned long id)
+{
+ return w->find_by_id (w, id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Modify state of widget.
+ *
+ * @param w widget
+ * @param state widget state flag to modify
+ * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
+ * Only one flag per call can be modified.
+ * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
+ */
+
+static inline cb_ret_t
+widget_set_state (Widget * w, widget_state_t state, gboolean enable)
+{
+ return w->set_state (w, state, enable);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy widget.
+ *
+ * @param w widget
+ */
+
+static inline void
+widget_destroy (Widget * w)
+{
+ w->destroy (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get color colors of widget.
+ *
+ * @param w widget
+ * @return color colors
+ */
+static inline const int *
+widget_get_colors (const Widget * w)
+{
+ return w->get_colors (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Update cursor position in the specified widget.
+ *
+ * @param w widget
+ *
+ * @return TRUE if cursor was updated successfully, FALSE otherwise
+ */
+
+static inline gboolean
+widget_update_cursor (Widget * w)
+{
+ return (send_message (w, NULL, MSG_CURSOR, 0, NULL) == MSG_HANDLED);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+widget_show (Widget * w)
+{
+ widget_set_visibility (w, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+widget_hide (Widget * w)
+{
+ widget_set_visibility (w, FALSE);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether two widgets are overlapped or not.
+ * @param a 1st widget
+ * @param b 2nd widget
+ *
+ * @return TRUE if widgets are overlapped, FALSE otherwise.
+ */
+
+static inline gboolean
+widget_overlapped (const Widget * a, const Widget * b)
+{
+ return rects_are_overlapped (&a->rect, &b->rect);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__WIDGET_COMMON_H */
diff --git a/lib/widget/wtools.c b/lib/widget/wtools.c
new file mode 100644
index 0000000..a4af4b5
--- /dev/null
+++ b/lib/widget/wtools.c
@@ -0,0 +1,729 @@
+/*
+ Widget based utility functions.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Authors:
+ Miguel de Icaza, 1994, 1995, 1996
+ Radek Doulik, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file wtools.c
+ * \brief Source: widget based utility functions
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* tty_getch() */
+#include "lib/strutil.h"
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static WDialog *last_query_dlg;
+
+static int sel_pos = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** default query callback, used to reposition query */
+
+static cb_ret_t
+query_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ if ((w->pos_flags & WPOS_CENTER) == 0)
+ {
+ WDialog *prev_dlg = NULL;
+ int ypos, xpos;
+ WRect r;
+
+ /* get dialog under h */
+ if (top_dlg != NULL)
+ {
+ if (top_dlg->data != (void *) h)
+ prev_dlg = DIALOG (top_dlg->data);
+ else
+ {
+ GList *p;
+
+ /* Top dialog is current if it is visible.
+ Get previous dialog in stack */
+ p = g_list_next (top_dlg);
+ if (p != NULL)
+ prev_dlg = DIALOG (p->data);
+ }
+ }
+
+ /* if previous dialog is not fullscreen'd -- overlap it */
+ if (prev_dlg == NULL || (WIDGET (prev_dlg)->pos_flags & WPOS_FULLSCREEN) != 0)
+ ypos = LINES / 3 - (w->rect.lines - 3) / 2;
+ else
+ ypos = WIDGET (prev_dlg)->rect.y + 2;
+
+ /* if dialog is too high, place it centered */
+ if (ypos + w->rect.lines < LINES / 2)
+ w->pos_flags |= WPOS_CENTER;
+
+ xpos = COLS / 2 - w->rect.cols / 2;
+
+ /* set position */
+ rect_init (&r, ypos, xpos, w->rect.lines, w->rect.cols);
+
+ return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ }
+ MC_FALLTHROUGH;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Create message dialog */
+
+static WDialog *
+do_create_message (int flags, const char *title, const char *text)
+{
+ char *p;
+ WDialog *d;
+
+ /* Add empty lines before and after the message */
+ p = g_strconcat ("\n", text, "\n", (char *) NULL);
+ query_dialog (title, p, flags, 0);
+ d = last_query_dlg;
+
+ /* do resize before initing and running */
+ send_message (d, NULL, MSG_RESIZE, 0, NULL);
+
+ dlg_init (d);
+ g_free (p);
+
+ return d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show message dialog. Dismiss it when any key is pressed.
+ * Not safe to call from background.
+ */
+
+static void
+fg_message (int flags, const char *title, const char *text)
+{
+ WDialog *d;
+
+ d = do_create_message (flags, title, text);
+ tty_getch ();
+ dlg_run_done (d);
+ widget_destroy (WIDGET (d));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show message box from background */
+
+#ifdef ENABLE_BACKGROUND
+static void
+bg_message (int dummy, int *flags, char *title, const char *text)
+{
+ (void) dummy;
+ title = g_strconcat (_("Background process:"), " ", title, (char *) NULL);
+ fg_message (*flags, title, text);
+ g_free (title);
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Show dialog, not background safe.
+ *
+ * If the arguments "header" and "text" should be translated,
+ * that MUST be done by the caller of fg_input_dialog_help().
+ *
+ * The argument "history_name" holds the name of a section
+ * in the history file. Data entered in the input field of
+ * the dialog box will be stored there.
+ *
+ */
+static char *
+fg_input_dialog_help (const char *header, const char *text, const char *help,
+ const char *history_name, const char *def_text, gboolean strip_password,
+ input_complete_t completion_flags)
+{
+ char *p_text;
+ char histname[64] = "inp|";
+ gboolean is_passwd = FALSE;
+ char *my_str;
+ int ret;
+
+ /* label text */
+ p_text = g_strstrip (g_strdup (text));
+
+ /* input history */
+ if (history_name != NULL && *history_name != '\0')
+ g_strlcpy (histname + 3, history_name, sizeof (histname) - 3);
+
+ /* The special value of def_text is used to identify password boxes
+ and hide characters with "*". Don't save passwords in history! */
+ if (def_text == INPUT_PASSWORD)
+ {
+ is_passwd = TRUE;
+ histname[3] = '\0';
+ def_text = "";
+ }
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (p_text, input_label_above, def_text, histname, &my_str,
+ NULL, is_passwd, strip_password, completion_flags),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, COLS / 2 };
+
+ quick_dialog_t qdlg = {
+ r, header, help,
+ quick_widgets, NULL, NULL
+ };
+
+ ret = quick_dialog (&qdlg);
+ }
+
+ g_free (p_text);
+
+ return (ret != B_CANCEL) ? my_str : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static int
+wtools_parent_call (void *routine, gpointer ctx, int argc, ...)
+{
+ ev_background_parent_call_t event_data;
+
+ event_data.routine = routine;
+ event_data.ctx = ctx;
+ event_data.argc = argc;
+ va_start (event_data.ap, argc);
+ mc_event_raise (MCEVENT_GROUP_CORE, "background_parent_call", (gpointer) & event_data);
+ va_end (event_data.ap);
+ return event_data.ret.i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+wtools_parent_call_string (void *routine, int argc, ...)
+{
+ ev_background_parent_call_t event_data;
+
+ event_data.routine = routine;
+ event_data.argc = argc;
+ va_start (event_data.ap, argc);
+ mc_event_raise (MCEVENT_GROUP_CORE, "background_parent_call_string", (gpointer) & event_data);
+ va_end (event_data.ap);
+ return event_data.ret.s;
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Used to ask questions to the user */
+int
+query_dialog (const char *header, const char *text, int flags, int count, ...)
+{
+ va_list ap;
+ WDialog *query_dlg;
+ WGroup *g;
+ WButton *button;
+ int win_len = 0;
+ int i;
+ int result = -1;
+ int cols, lines;
+ const int *query_colors = (flags & D_ERROR) != 0 ? alarm_colors : dialog_colors;
+ widget_pos_flags_t pos_flags =
+ (flags & D_CENTER) != 0 ? (WPOS_CENTER | WPOS_TRYUP) : WPOS_KEEP_DEFAULT;
+
+ if (header == MSG_ERROR)
+ header = _("Error");
+
+ if (count > 0)
+ {
+ va_start (ap, count);
+ for (i = 0; i < count; i++)
+ {
+ char *cp = va_arg (ap, char *);
+
+ win_len += str_term_width1 (cp) + 6;
+ if (strchr (cp, '&') != NULL)
+ win_len--;
+ }
+ va_end (ap);
+ }
+
+ /* count coordinates */
+ str_msg_term_size (text, &lines, &cols);
+ cols = 6 + MAX (win_len, MAX (str_term_width1 (header), cols));
+ lines += 4 + (count > 0 ? 2 : 0);
+
+ /* prepare dialog */
+ query_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, pos_flags, FALSE, query_colors, query_default_callback,
+ NULL, "[QueryBox]", header);
+ g = GROUP (query_dlg);
+
+ if (count > 0)
+ {
+ WButton *defbutton = NULL;
+
+ group_add_widget_autopos (g, label_new (2, 3, text), WPOS_KEEP_TOP | WPOS_CENTER_HORZ,
+ NULL);
+ group_add_widget (g, hline_new (lines - 4, -1, -1));
+
+ cols = (cols - win_len - 2) / 2 + 2;
+ va_start (ap, count);
+ for (i = 0; i < count; i++)
+ {
+ int xpos;
+ char *cur_name;
+
+ cur_name = va_arg (ap, char *);
+ xpos = str_term_width1 (cur_name) + 6;
+ if (strchr (cur_name, '&') != NULL)
+ xpos--;
+
+ button = button_new (lines - 3, cols, B_USER + i, NORMAL_BUTTON, cur_name, NULL);
+ group_add_widget (g, button);
+ cols += xpos;
+ if (i == sel_pos)
+ defbutton = button;
+ }
+ va_end (ap);
+
+ /* do resize before running and selecting any widget */
+ send_message (query_dlg, NULL, MSG_RESIZE, 0, NULL);
+
+ if (defbutton != NULL)
+ widget_select (WIDGET (defbutton));
+
+ /* run dialog and make result */
+ switch (dlg_run (query_dlg))
+ {
+ case B_CANCEL:
+ break;
+ default:
+ result = query_dlg->ret_value - B_USER;
+ }
+
+ /* free used memory */
+ widget_destroy (WIDGET (query_dlg));
+ }
+ else
+ {
+ group_add_widget_autopos (g, label_new (2, 3, text), WPOS_KEEP_TOP | WPOS_CENTER_HORZ,
+ NULL);
+ group_add_widget (g, button_new (0, 0, 0, HIDDEN_BUTTON, "-", NULL));
+ last_query_dlg = query_dlg;
+ }
+ sel_pos = 0;
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+query_set_sel (int new_sel)
+{
+ sel_pos = new_sel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create message dialog. The caller must call dlg_run_done() and
+ * widget_destroy() to dismiss it. Not safe to call from background.
+ */
+
+WDialog *
+create_message (int flags, const char *title, const char *text, ...)
+{
+ va_list args;
+ WDialog *d;
+ char *p;
+
+ va_start (args, text);
+ p = g_strdup_vprintf (text, args);
+ va_end (args);
+
+ d = do_create_message (flags, title, p);
+ g_free (p);
+
+ return d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show message box, background safe */
+
+void
+message (int flags, const char *title, const char *text, ...)
+{
+ char *p;
+ va_list ap;
+
+ va_start (ap, text);
+ p = g_strdup_vprintf (text, ap);
+ va_end (ap);
+
+ if (title == MSG_ERROR)
+ title = _("Error");
+
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ {
+ union
+ {
+ void *p;
+ void (*f) (int, int *, char *, const char *);
+ } func;
+ func.f = bg_message;
+
+ wtools_parent_call (func.p, NULL, 3, sizeof (flags), &flags, strlen (title), title,
+ strlen (p), p);
+ }
+ else
+#endif /* ENABLE_BACKGROUND */
+ fg_message (flags, title, p);
+
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show error message box */
+
+gboolean
+mc_error_message (GError ** mcerror, int *code)
+{
+ if (mcerror == NULL || *mcerror == NULL)
+ return FALSE;
+
+ if ((*mcerror)->code == 0)
+ message (D_ERROR, MSG_ERROR, "%s", (*mcerror)->message);
+ else
+ message (D_ERROR, MSG_ERROR, _("%s (%d)"), (*mcerror)->message, (*mcerror)->code);
+
+ if (code != NULL)
+ *code = (*mcerror)->code;
+
+ g_error_free (*mcerror);
+ *mcerror = NULL;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show input dialog, background safe.
+ *
+ * If the arguments "header" and "text" should be translated,
+ * that MUST be done by the caller of these wrappers.
+ */
+
+char *
+input_dialog_help (const char *header, const char *text, const char *help,
+ const char *history_name, const char *def_text, gboolean strip_password,
+ input_complete_t completion_flags)
+{
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ {
+ union
+ {
+ void *p;
+ char *(*f) (const char *, const char *, const char *, const char *, const char *,
+ gboolean, input_complete_t);
+ } func;
+ func.f = fg_input_dialog_help;
+ return wtools_parent_call_string (func.p, 7,
+ strlen (header), header, strlen (text),
+ text, strlen (help), help,
+ strlen (history_name), history_name,
+ strlen (def_text), def_text,
+ sizeof (gboolean), strip_password,
+ sizeof (input_complete_t), completion_flags);
+ }
+ else
+#endif /* ENABLE_BACKGROUND */
+ return fg_input_dialog_help (header, text, help, history_name, def_text, strip_password,
+ completion_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show input dialog with default help, background safe */
+
+char *
+input_dialog (const char *header, const char *text, const char *history_name, const char *def_text,
+ input_complete_t completion_flags)
+{
+ return input_dialog_help (header, text, "[Input Line Keys]", history_name, def_text, FALSE,
+ completion_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+input_expand_dialog (const char *header, const char *text,
+ const char *history_name, const char *def_text,
+ input_complete_t completion_flags)
+{
+ char *result;
+
+ result = input_dialog (header, text, history_name, def_text, completion_flags);
+ if (result)
+ {
+ char *expanded;
+
+ expanded = tilde_expand (result);
+ g_free (result);
+ return expanded;
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create status message window object and initialize it
+ *
+ * @param title window title
+ * @param delay initial delay to raise window in seconds
+ * @param init_cb callback to initialize user-defined part of status message
+ * @param update_cb callback to update of status message
+ * @param deinit_cb callback to deinitialize user-defined part of status message
+ *
+ * @return newly allocate status message window
+ */
+
+status_msg_t *
+status_msg_create (const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb)
+{
+ status_msg_t *sm;
+
+ sm = g_try_new (status_msg_t, 1);
+ status_msg_init (sm, title, delay, init_cb, update_cb, deinit_cb);
+
+ return sm;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy status message window object
+ *
+ * @param sm status message window object
+ */
+
+void
+status_msg_destroy (status_msg_t * sm)
+{
+ status_msg_deinit (sm);
+ g_free (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize already created status message window object
+ *
+ * @param sm status message window object
+ * @param title window title
+ * @param delay initial delay to raise window in seconds
+ * @param init_cb callback to initialize user-defined part of status message
+ * @param update_cb callback to update of status message
+ * @param deinit_cb callback to deinitialize user-defined part of status message
+ */
+
+void
+status_msg_init (status_msg_t * sm, const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb)
+{
+ gint64 start;
+
+ /* repaint screen to remove previous finished dialog */
+ mc_refresh ();
+
+ start = g_get_monotonic_time ();
+
+ sm->dlg = dlg_create (TRUE, 0, 0, 7, MIN (MAX (40, COLS / 2), COLS), WPOS_CENTER, FALSE,
+ dialog_colors, NULL, NULL, NULL, title);
+ sm->start = start;
+ sm->delay = (gint64) (delay * G_USEC_PER_SEC);
+ sm->block = FALSE;
+
+ sm->init = init_cb;
+ sm->update = update_cb;
+ sm->deinit = deinit_cb;
+
+ if (sm->init != NULL)
+ sm->init (sm);
+
+ if (mc_time_elapsed (&start, sm->delay))
+ {
+ /* We will manage the dialog without any help, that's why we have to call dlg_init */
+ dlg_init (sm->dlg);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinitialize status message window object
+ *
+ * @param sm status message window object
+ */
+
+void
+status_msg_deinit (status_msg_t * sm)
+{
+ if (sm == NULL)
+ return;
+
+ if (sm->deinit != NULL)
+ sm->deinit (sm);
+
+ /* close and destroy dialog */
+ dlg_run_done (sm->dlg);
+ widget_destroy (WIDGET (sm->dlg));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Update status message window
+ *
+ * @param sm status message window object
+ *
+ * @return value of pressed key
+ */
+
+int
+status_msg_common_update (status_msg_t * sm)
+{
+ int c;
+ Gpm_Event event;
+
+ if (sm == NULL)
+ return B_ENTER;
+
+ /* This should not happen, but... */
+ if (sm->dlg == NULL)
+ return B_ENTER;
+
+ if (widget_get_state (WIDGET (sm->dlg), WST_CONSTRUCT))
+ {
+ /* dialog is not shown yet */
+
+ /* do not change sm->start */
+ gint64 start = sm->start;
+
+ if (mc_time_elapsed (&start, sm->delay))
+ dlg_init (sm->dlg);
+
+ return B_ENTER;
+ }
+
+ event.x = -1; /* Don't show the GPM cursor */
+ c = tty_get_event (&event, FALSE, sm->block);
+ if (c == EV_NONE)
+ return B_ENTER;
+
+ /* Reinitialize by non-B_CANCEL value to avoid old values
+ after events other than selecting a button */
+ sm->dlg->ret_value = B_ENTER;
+ dlg_process_event (sm->dlg, c, &event);
+
+ return sm->dlg->ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback to initialize already created simple status message window object
+ *
+ * @param sm status message window object
+ */
+
+void
+simple_status_msg_init_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ Widget *wd = WIDGET (sm->dlg);
+ WGroup *wg = GROUP (sm->dlg);
+ WRect r;
+
+ const char *b_name = N_("&Abort");
+ int b_width;
+ int wd_width, y;
+ Widget *b;
+
+#ifdef ENABLE_NLS
+ b_name = _(b_name);
+#endif
+
+ b_width = str_term_width1 (b_name) + 4;
+ wd_width = MAX (wd->rect.cols, b_width + 6);
+
+ y = 2;
+ ssm->label = label_new (y++, 3, NULL);
+ group_add_widget_autopos (wg, ssm->label, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+ group_add_widget (wg, hline_new (y++, -1, -1));
+ b = WIDGET (button_new (y++, 3, B_CANCEL, NORMAL_BUTTON, b_name, NULL));
+ group_add_widget_autopos (wg, b, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+
+ r = wd->rect;
+ r.lines = y + 2;
+ r.cols = wd_width;
+ widget_set_size_rect (wd, &r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/wtools.h b/lib/widget/wtools.h
new file mode 100644
index 0000000..73c56ca
--- /dev/null
+++ b/lib/widget/wtools.h
@@ -0,0 +1,100 @@
+/** \file wtools.h
+ * \brief Header: widget based utility functions
+ */
+
+#ifndef MC__WTOOLS_H
+#define MC__WTOOLS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* Pass this as def_text to request a password */
+#define INPUT_PASSWORD ((char *) -1)
+
+/* Use this as header for message() - it expands to "Error" */
+#define MSG_ERROR ((char *) -1)
+
+typedef struct status_msg_t status_msg_t;
+#define STATUS_MSG(x) ((status_msg_t *)(x))
+
+typedef struct simple_status_msg_t simple_status_msg_t;
+#define SIMPLE_STATUS_MSG(x) ((simple_status_msg_t *)(x))
+
+typedef void (*status_msg_cb) (status_msg_t * sm);
+typedef int (*status_msg_update_cb) (status_msg_t * sm);
+
+/*** enums ***************************************************************************************/
+
+/* flags for message() and query_dialog() */
+enum
+{
+ D_NORMAL = 0,
+ D_ERROR = (1 << 0),
+ D_CENTER = (1 << 1)
+} /* dialog options */ ;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Base class for status message of long-time operations.
+ Useful to show progress of long-time operations and interrupt it. */
+
+struct status_msg_t
+{
+ WDialog *dlg; /* pointer to status message dialog */
+ gint64 start; /* start time in microseconds */
+ gint64 delay; /* delay before raise the 'dlg' in microseconds */
+ gboolean block; /* how to get event using tty_get_event() */
+
+ status_msg_cb init; /* callback to init derived classes */
+ status_msg_update_cb update; /* callback to update dlg */
+ status_msg_cb deinit; /* callback to deinit derived classes */
+};
+
+/* Simple status message with label and 'Abort' button */
+struct simple_status_msg_t
+{
+ status_msg_t status_msg; /* base class */
+
+ WLabel *label;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* The input dialogs */
+char *input_dialog (const char *header, const char *text,
+ const char *history_name, const char *def_text,
+ input_complete_t completion_flags);
+char *input_dialog_help (const char *header, const char *text, const char *help,
+ const char *history_name, const char *def_text, gboolean strip_password,
+ input_complete_t completion_flags);
+char *input_expand_dialog (const char *header, const char *text, const char *history_name,
+ const char *def_text, input_complete_t completion_flags);
+
+int query_dialog (const char *header, const char *text, int flags, int count, ...);
+void query_set_sel (int new_sel);
+
+/* Create message box but don't dismiss it yet, not background safe */
+/* *INDENT-OFF* */
+WDialog *create_message (int flags, const char *title, const char *text, ...)
+ G_GNUC_PRINTF (3, 4);
+
+/* Show message box, background safe */
+void message (int flags, const char *title, const char *text, ...) G_GNUC_PRINTF (3, 4);
+/* *INDENT-ON* */
+
+gboolean mc_error_message (GError ** mcerror, int *code);
+
+status_msg_t *status_msg_create (const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb);
+void status_msg_destroy (status_msg_t * sm);
+void status_msg_init (status_msg_t * sm, const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb);
+void status_msg_deinit (status_msg_t * sm);
+int status_msg_common_update (status_msg_t * sm);
+
+void simple_status_msg_init_cb (status_msg_t * sm);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WTOOLS_H */