diff options
Diffstat (limited to '')
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, ¤t_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, ¤t_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, ¤t_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, ¤t_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 (¤t_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 */ |