diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:12 +0000 |
commit | 8ccb487c21368a7fdc8c7c72315325bf0aa06147 (patch) | |
tree | b2056fae01d325924508a41731edfbd4c3cddd23 /src/filemanager | |
parent | Initial commit. (diff) | |
download | mc-upstream/3%4.8.29.tar.xz mc-upstream/3%4.8.29.zip |
Adding upstream version 3:4.8.29.upstream/3%4.8.29upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/filemanager')
46 files changed, 33960 insertions, 0 deletions
diff --git a/src/filemanager/Makefile.am b/src/filemanager/Makefile.am new file mode 100644 index 0000000..bf0c183 --- /dev/null +++ b/src/filemanager/Makefile.am @@ -0,0 +1,40 @@ + +noinst_LTLIBRARIES = libmcfilemanager.la + +libmcfilemanager_la_SOURCES = \ + achown.c \ + boxes.c boxes.h \ + cd.c cd.h \ + chmod.c \ + chown.c \ + cmd.c cmd.h \ + command.c command.h \ + dir.c dir.h \ + ext.c ext.h \ + file.c file.h \ + filegui.c filegui.h \ + filemanager.h filemanager.c \ + filenot.c filenot.h \ + fileopctx.c fileopctx.h \ + find.c \ + hotlist.c hotlist.h \ + info.c info.h \ + ioblksize.h \ + layout.c layout.h \ + mountlist.c mountlist.h \ + panelize.c panelize.h \ + panel.c panel.h \ + tree.c tree.h \ + treestore.c treestore.h + +# Unmaintained, unsupported, etc +# listmode.c listmode.h + +AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(PCRE_CPPFLAGS) + +if ENABLE_EXT2FS_ATTR +libmcfilemanager_la_SOURCES += \ + chattr.c + +AM_CPPFLAGS += @EXT2FS_CFLAGS@ @E2P_CFLAGS@ +endif diff --git a/src/filemanager/Makefile.in b/src/filemanager/Makefile.in new file mode 100644 index 0000000..cb3d720 --- /dev/null +++ b/src/filemanager/Makefile.in @@ -0,0 +1,839 @@ +# 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_EXT2FS_ATTR_TRUE@am__append_1 = \ +@ENABLE_EXT2FS_ATTR_TRUE@ chattr.c + +@ENABLE_EXT2FS_ATTR_TRUE@am__append_2 = @EXT2FS_CFLAGS@ @E2P_CFLAGS@ +subdir = src/filemanager +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/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) +libmcfilemanager_la_LIBADD = +am__libmcfilemanager_la_SOURCES_DIST = achown.c boxes.c boxes.h cd.c \ + cd.h chmod.c chown.c cmd.c cmd.h command.c command.h dir.c \ + dir.h ext.c ext.h file.c file.h filegui.c filegui.h \ + filemanager.h filemanager.c filenot.c filenot.h fileopctx.c \ + fileopctx.h find.c hotlist.c hotlist.h info.c info.h \ + ioblksize.h layout.c layout.h mountlist.c mountlist.h \ + panelize.c panelize.h panel.c panel.h tree.c tree.h \ + treestore.c treestore.h chattr.c +@ENABLE_EXT2FS_ATTR_TRUE@am__objects_1 = chattr.lo +am_libmcfilemanager_la_OBJECTS = achown.lo boxes.lo cd.lo chmod.lo \ + chown.lo cmd.lo command.lo dir.lo ext.lo file.lo filegui.lo \ + filemanager.lo filenot.lo fileopctx.lo find.lo hotlist.lo \ + info.lo layout.lo mountlist.lo panelize.lo panel.lo tree.lo \ + treestore.lo $(am__objects_1) +libmcfilemanager_la_OBJECTS = $(am_libmcfilemanager_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)/achown.Plo ./$(DEPDIR)/boxes.Plo \ + ./$(DEPDIR)/cd.Plo ./$(DEPDIR)/chattr.Plo \ + ./$(DEPDIR)/chmod.Plo ./$(DEPDIR)/chown.Plo \ + ./$(DEPDIR)/cmd.Plo ./$(DEPDIR)/command.Plo \ + ./$(DEPDIR)/dir.Plo ./$(DEPDIR)/ext.Plo ./$(DEPDIR)/file.Plo \ + ./$(DEPDIR)/filegui.Plo ./$(DEPDIR)/filemanager.Plo \ + ./$(DEPDIR)/filenot.Plo ./$(DEPDIR)/fileopctx.Plo \ + ./$(DEPDIR)/find.Plo ./$(DEPDIR)/hotlist.Plo \ + ./$(DEPDIR)/info.Plo ./$(DEPDIR)/layout.Plo \ + ./$(DEPDIR)/mountlist.Plo ./$(DEPDIR)/panel.Plo \ + ./$(DEPDIR)/panelize.Plo ./$(DEPDIR)/tree.Plo \ + ./$(DEPDIR)/treestore.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 = $(libmcfilemanager_la_SOURCES) +DIST_SOURCES = $(am__libmcfilemanager_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_CPPFLAGS = @PCRE_CPPFLAGS@ +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 = libmcfilemanager.la +libmcfilemanager_la_SOURCES = achown.c boxes.c boxes.h cd.c cd.h \ + chmod.c chown.c cmd.c cmd.h command.c command.h dir.c dir.h \ + ext.c ext.h file.c file.h filegui.c filegui.h filemanager.h \ + filemanager.c filenot.c filenot.h fileopctx.c fileopctx.h \ + find.c hotlist.c hotlist.h info.c info.h ioblksize.h layout.c \ + layout.h mountlist.c mountlist.h panelize.c panelize.h panel.c \ + panel.h tree.c tree.h treestore.c treestore.h $(am__append_1) + +# Unmaintained, unsupported, etc +# listmode.c listmode.h +AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(PCRE_CPPFLAGS) \ + $(am__append_2) +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 src/filemanager/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/filemanager/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}; \ + } + +libmcfilemanager.la: $(libmcfilemanager_la_OBJECTS) $(libmcfilemanager_la_DEPENDENCIES) $(EXTRA_libmcfilemanager_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libmcfilemanager_la_OBJECTS) $(libmcfilemanager_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/achown.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxes.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chattr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chmod.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chown.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filegui.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filemanager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filenot.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileopctx.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/find.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hotlist.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/layout.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mountlist.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/panel.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/panelize.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tree.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/treestore.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)/achown.Plo + -rm -f ./$(DEPDIR)/boxes.Plo + -rm -f ./$(DEPDIR)/cd.Plo + -rm -f ./$(DEPDIR)/chattr.Plo + -rm -f ./$(DEPDIR)/chmod.Plo + -rm -f ./$(DEPDIR)/chown.Plo + -rm -f ./$(DEPDIR)/cmd.Plo + -rm -f ./$(DEPDIR)/command.Plo + -rm -f ./$(DEPDIR)/dir.Plo + -rm -f ./$(DEPDIR)/ext.Plo + -rm -f ./$(DEPDIR)/file.Plo + -rm -f ./$(DEPDIR)/filegui.Plo + -rm -f ./$(DEPDIR)/filemanager.Plo + -rm -f ./$(DEPDIR)/filenot.Plo + -rm -f ./$(DEPDIR)/fileopctx.Plo + -rm -f ./$(DEPDIR)/find.Plo + -rm -f ./$(DEPDIR)/hotlist.Plo + -rm -f ./$(DEPDIR)/info.Plo + -rm -f ./$(DEPDIR)/layout.Plo + -rm -f ./$(DEPDIR)/mountlist.Plo + -rm -f ./$(DEPDIR)/panel.Plo + -rm -f ./$(DEPDIR)/panelize.Plo + -rm -f ./$(DEPDIR)/tree.Plo + -rm -f ./$(DEPDIR)/treestore.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)/achown.Plo + -rm -f ./$(DEPDIR)/boxes.Plo + -rm -f ./$(DEPDIR)/cd.Plo + -rm -f ./$(DEPDIR)/chattr.Plo + -rm -f ./$(DEPDIR)/chmod.Plo + -rm -f ./$(DEPDIR)/chown.Plo + -rm -f ./$(DEPDIR)/cmd.Plo + -rm -f ./$(DEPDIR)/command.Plo + -rm -f ./$(DEPDIR)/dir.Plo + -rm -f ./$(DEPDIR)/ext.Plo + -rm -f ./$(DEPDIR)/file.Plo + -rm -f ./$(DEPDIR)/filegui.Plo + -rm -f ./$(DEPDIR)/filemanager.Plo + -rm -f ./$(DEPDIR)/filenot.Plo + -rm -f ./$(DEPDIR)/fileopctx.Plo + -rm -f ./$(DEPDIR)/find.Plo + -rm -f ./$(DEPDIR)/hotlist.Plo + -rm -f ./$(DEPDIR)/info.Plo + -rm -f ./$(DEPDIR)/layout.Plo + -rm -f ./$(DEPDIR)/mountlist.Plo + -rm -f ./$(DEPDIR)/panel.Plo + -rm -f ./$(DEPDIR)/panelize.Plo + -rm -f ./$(DEPDIR)/tree.Plo + -rm -f ./$(DEPDIR)/treestore.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/src/filemanager/achown.c b/src/filemanager/achown.c new file mode 100644 index 0000000..655abb1 --- /dev/null +++ b/src/filemanager/achown.c @@ -0,0 +1,1102 @@ +/* + Chown-advanced command -- for the Midnight Commander + + Copyright (C) 1994-2022 + 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 achown.c + * \brief Source: Contains functions for advanced chowning + */ + +#include <config.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.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/vfs/vfs.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/widget.h" + +#include "cmd.h" /* advanced_chown_cmd() */ + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define BX 5 +#define BY 5 + +#define BUTTONS 9 +#define BUTTONS_PERM 5 + +#define B_SETALL B_USER +#define B_SKIP (B_USER + 1) + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static struct +{ + unsigned long id; + int ret_cmd; + button_flags_t flags; + int x; + int len; + const char *text; +} advanced_chown_but[BUTTONS] = +{ + /* *INDENT-OFF* */ + { 0, B_ENTER, NARROW_BUTTON, 3, 0, " " }, + { 0, B_ENTER, NARROW_BUTTON, 11, 0, " " }, + { 0, B_ENTER, NARROW_BUTTON, 19, 0, " " }, + { 0, B_ENTER, NARROW_BUTTON, 29, 0, "" }, + { 0, B_ENTER, NARROW_BUTTON, 47, 0, "" }, + + { 0, B_SETALL, NORMAL_BUTTON, 0, 0, N_("Set &all") }, + { 0, B_SKIP, NORMAL_BUTTON, 0, 0, N_("S&kip") }, + { 0, B_ENTER, DEFPUSH_BUTTON, 0, 0, N_("&Set") }, + { 0, B_CANCEL, NORMAL_BUTTON, 0, 0, N_("&Cancel") } + /* *INDENT-ON* */ +}; + +static int current_file; +static gboolean ignore_all; + +static WButton *b_att[3]; /* permission */ +static WButton *b_user, *b_group; /* owner */ +static WLabel *l_filename; +static WLabel *l_mode; + +static int flag_pos; +static int x_toggle; +static char ch_flags[11]; +static const char ch_perm[] = "rwx"; +static mode_t ch_cmode; +static struct stat sf_stat; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +advanced_chown_init (void) +{ + static gboolean i18n = FALSE; + int i; + + if (i18n) + return; + + i18n = TRUE; + + for (i = BUTTONS_PERM; i < BUTTONS; i++) + { +#ifdef ENABLE_NLS + advanced_chown_but[i].text = _(advanced_chown_but[i].text); +#endif /* ENABLE_NLS */ + + advanced_chown_but[i].len = str_term_width1 (advanced_chown_but[i].text) + 3; + if (advanced_chown_but[i].flags == DEFPUSH_BUTTON) + advanced_chown_but[i].len += 2; /* "<>" */ + } + +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +inc_flag_pos (void) +{ + if (flag_pos == 10) + { + flag_pos = 0; + return MSG_NOT_HANDLED; + } + + flag_pos++; + + return flag_pos % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +dec_flag_pos (void) +{ + if (flag_pos == 0) + { + flag_pos = 10; + return MSG_NOT_HANDLED; + } + + flag_pos--; + + return (flag_pos + 1) % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +set_perm_by_flags (char *s, int f_p) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (ch_flags[f_p + i] == '+') + s[i] = ch_perm[i]; + else if (ch_flags[f_p + i] == '-') + s[i] = '-'; + else + s[i] = (ch_cmode & (1 << (8 - f_p - i))) != 0 ? ch_perm[i] : '-'; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static mode_t +get_perm (char *s, int base) +{ + mode_t m = 0; + + m |= (s[0] == '-') ? 0 : + ((s[0] == '+') ? (mode_t) (1 << (base + 2)) : (1 << (base + 2)) & ch_cmode); + + m |= (s[1] == '-') ? 0 : + ((s[1] == '+') ? (mode_t) (1 << (base + 1)) : (1 << (base + 1)) & ch_cmode); + + m |= (s[2] == '-') ? 0 : ((s[2] == '+') ? (mode_t) (1 << base) : (1 << base) & ch_cmode); + + return m; +} + +/* --------------------------------------------------------------------------------------------- */ + +static mode_t +get_mode (void) +{ + mode_t m; + + m = ch_cmode ^ (ch_cmode & 0777); + m |= get_perm (ch_flags, 6); + m |= get_perm (ch_flags + 3, 3); + m |= get_perm (ch_flags + 6, 0); + + return m; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +update_permissions (void) +{ + set_perm_by_flags (b_att[0]->text.start, 0); + set_perm_by_flags (b_att[1]->text.start, 3); + set_perm_by_flags (b_att[2]->text.start, 6); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +update_ownership (void) +{ + button_set_text (b_user, get_owner (sf_stat.st_uid)); + button_set_text (b_group, get_group (sf_stat.st_gid)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +print_flags (const WDialog * h) +{ + int i; + + tty_setcolor (COLOR_NORMAL); + + for (i = 0; i < 3; i++) + { + widget_gotoyx (h, BY + 1, advanced_chown_but[0].x + 6 + i); + tty_print_char (ch_flags[i]); + } + + for (i = 0; i < 3; i++) + { + widget_gotoyx (h, BY + 1, advanced_chown_but[1].x + 6 + i); + tty_print_char (ch_flags[i + 3]); + } + + for (i = 0; i < 3; i++) + { + widget_gotoyx (h, BY + 1, advanced_chown_but[2].x + 6 + i); + tty_print_char (ch_flags[i + 6]); + } + + update_permissions (); + + for (i = 0; i < 15; i++) + { + widget_gotoyx (h, BY + 1, advanced_chown_but[3].x + 6 + i); + tty_print_char (ch_flags[9]); + } + for (i = 0; i < 15; i++) + { + widget_gotoyx (h, BY + 1, advanced_chown_but[4].x + 6 + i); + tty_print_char (ch_flags[10]); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +advanced_chown_refresh (const WDialog * h) +{ + tty_setcolor (COLOR_NORMAL); + + widget_gotoyx (h, BY - 1, advanced_chown_but[0].x + 5); + tty_print_string (_("owner")); + widget_gotoyx (h, BY - 1, advanced_chown_but[1].x + 5); + tty_print_string (_("group")); + widget_gotoyx (h, BY - 1, advanced_chown_but[2].x + 5); + tty_print_string (_("other")); + + widget_gotoyx (h, BY - 1, advanced_chown_but[3].x + 5); + tty_print_string (_("owner")); + widget_gotoyx (h, BY - 1, advanced_chown_but[4].x + 5); + tty_print_string (_("group")); + + widget_gotoyx (h, BY + 1, 3); + tty_print_string (_("Flag")); + print_flags (h); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +advanced_chown_info_update (void) +{ + /* mode */ + label_set_textv (l_mode, _("Permissions (octal): %o"), get_mode ()); + + /* permissions */ + update_permissions (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +update_mode (WGroup * g) +{ + print_flags (DIALOG (g)); + advanced_chown_info_update (); + widget_set_state (WIDGET (g->current->data), WST_FOCUSED, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +perm_button_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WButton *b = BUTTON (w); + WGroup *g = w->owner; + int i = 0; + int f_pos; + + /* one of permission buttons */ + if (b == b_att[0]) + f_pos = 0; + else if (b == b_att[1]) + f_pos = 1; + else /* if (w == b_att [1] */ + f_pos = 2; + + switch (msg) + { + case MSG_FOCUS: + if (b->hotpos == -1) + b->hotpos = 0; + + flag_pos = f_pos * 3 + b->hotpos; + return MSG_HANDLED; + + case MSG_KEY: + switch (parm) + { + case '*': + parm = '='; + MC_FALLTHROUGH; + + case '-': + case '=': + case '+': + flag_pos = f_pos * 3 + b->hotpos; + ch_flags[flag_pos] = parm; + update_mode (g); + send_message (w, NULL, MSG_KEY, KEY_RIGHT, NULL); + if (b->hotpos == 2) + group_select_next_widget (g); + break; + + case XCTRL ('f'): + case KEY_RIGHT: + { + cb_ret_t ret; + + ret = inc_flag_pos (); + b->hotpos = flag_pos % 3; + return ret; + } + + case XCTRL ('b'): + case KEY_LEFT: + { + cb_ret_t ret; + + ret = dec_flag_pos (); + b->hotpos = flag_pos % 3; + return ret; + } + + case 'x': + i++; + MC_FALLTHROUGH; + + case 'w': + i++; + MC_FALLTHROUGH; + + case 'r': + b->hotpos = i; + MC_FALLTHROUGH; + + case ' ': + i = b->hotpos; + + flag_pos = f_pos * 3 + i; + if (b->text.start[flag_pos % 3] == '-') + ch_flags[flag_pos] = '+'; + else + ch_flags[flag_pos] = '-'; + update_mode (w->owner); + break; + + case '4': + i++; + MC_FALLTHROUGH; + + case '2': + i++; + MC_FALLTHROUGH; + + case '1': + b->hotpos = i; + flag_pos = f_pos * 3 + i; + ch_flags[flag_pos] = '='; + update_mode (g); + break; + + default: + break; + } + /* continue key handling in the dialog level */ + return MSG_NOT_HANDLED; + + default: + return button_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +perm_button_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) +{ + switch (msg) + { + case MSG_MOUSE_DOWN: + /* place cursor on flag that is being modified */ + BUTTON (w)->hotpos = CLAMP (event->x - 1, 0, 2); + MC_FALLTHROUGH; + + default: + button_mouse_default_callback (w, msg, event); + break; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WButton * +perm_button_new (int y, int x, int action, button_flags_t flags, const char *text, + bcback_fn callback) +{ + WButton *b; + Widget *w; + + /* create base button using native API */ + b = button_new (y, x, action, flags, text, callback); + w = WIDGET (b); + + /* we don't want HOTKEY */ + widget_want_hotkey (w, FALSE); + + w->callback = perm_button_callback; + w->mouse_callback = perm_button_mouse_callback; + + return b; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chl_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_KEY: + switch (parm) + { + case KEY_LEFT: + case KEY_RIGHT: + { + WDialog *h = DIALOG (w); + + h->ret_value = parm; + dlg_stop (h); + } + break; + default: + break; + } + MC_FALLTHROUGH; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +user_group_button_cb (WButton * button, int action) +{ + Widget *w = WIDGET (button); + int f_pos; + gboolean chl_end; + + (void) action; + + if (button == b_user) + f_pos = BUTTONS_PERM - 2; + else if (button == b_group) + f_pos = BUTTONS_PERM - 1; + else + return 0; /* do nothing */ + + do + { + WGroup *g = w->owner; + WDialog *h = DIALOG (g); + Widget *wh = WIDGET (h); + + gboolean is_owner = (f_pos == BUTTONS_PERM - 2); + const char *title; + int lxx, b_pos; + WDialog *chl_dlg; + WListbox *chl_list; + int result; + int fe; + struct passwd *chl_pass; + struct group *chl_grp; + + chl_end = FALSE; + + if (is_owner) + { + title = _("owner"); + lxx = WIDGET (b_user)->rect.x + 1; + } + else + { + title = _("group"); + lxx = WIDGET (b_group)->rect.x + 1; + } + + chl_dlg = + dlg_create (TRUE, wh->rect.y - 1, lxx, wh->rect.lines + 2, 17, WPOS_KEEP_DEFAULT, TRUE, + dialog_colors, chl_callback, NULL, "[Advanced Chown]", title); + + /* get new listboxes */ + chl_list = + listbox_new (1, 1, WIDGET (chl_dlg)->rect.lines - 2, WIDGET (chl_dlg)->rect.cols - 2, + FALSE, NULL); + listbox_add_item (chl_list, LISTBOX_APPEND_AT_END, 0, "<Unknown>", NULL, FALSE); + if (is_owner) + { + /* get and put user names in the listbox */ + setpwent (); + while ((chl_pass = getpwent ()) != NULL) + listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_pass->pw_name, NULL, + FALSE); + endpwent (); + fe = listbox_search_text (chl_list, get_owner (sf_stat.st_uid)); + } + else + { + /* get and put group names in the listbox */ + setgrent (); + while ((chl_grp = getgrent ()) != NULL) + listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_grp->gr_name, NULL, + FALSE); + endgrent (); + fe = listbox_search_text (chl_list, get_group (sf_stat.st_gid)); + } + + listbox_select_entry (chl_list, fe); + + b_pos = chl_list->pos; + group_add_widget (GROUP (chl_dlg), chl_list); + + result = dlg_run (chl_dlg); + + if (result != B_CANCEL) + { + if (b_pos != chl_list->pos) + { + gboolean ok = FALSE; + char *text; + + listbox_get_current (chl_list, &text, NULL); + if (is_owner) + { + chl_pass = getpwnam (text); + if (chl_pass != NULL) + { + sf_stat.st_uid = chl_pass->pw_uid; + ok = TRUE; + } + } + else + { + chl_grp = getgrnam (text); + if (chl_grp != NULL) + { + sf_stat.st_gid = chl_grp->gr_gid; + ok = TRUE; + } + } + + if (!ok) + group_select_current_widget (g); + else + { + ch_flags[f_pos + 6] = '+'; + update_ownership (); + group_select_current_widget (g); + print_flags (h); + } + } + + if (result == KEY_LEFT) + { + if (!is_owner) + chl_end = TRUE; + group_select_prev_widget (g); + f_pos--; + } + else if (result == KEY_RIGHT) + { + if (is_owner) + chl_end = TRUE; + group_select_next_widget (g); + f_pos++; + } + } + + /* Here we used to redraw the window */ + widget_destroy (WIDGET (chl_dlg)); + } + while (chl_end); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +advanced_chown_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_DRAW: + frame_callback (w, NULL, MSG_DRAW, 0, NULL); + advanced_chown_refresh (DIALOG (w->owner)); + advanced_chown_info_update (); + return MSG_HANDLED; + + default: + return frame_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +advanced_chown_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WGroup *g = GROUP (w); + int i = 0; + + switch (msg) + { + case MSG_KEY: + switch (parm) + { + case ALT ('x'): + i++; + MC_FALLTHROUGH; + + case ALT ('w'): + i++; + MC_FALLTHROUGH; + + case ALT ('r'): + parm = i + 3; + for (i = 0; i < 3; i++) + ch_flags[i * 3 + parm - 3] = (x_toggle & (1 << parm)) ? '-' : '+'; + x_toggle ^= (1 << parm); + update_mode (g); + widget_draw (w); + break; + + case XCTRL ('x'): + i++; + MC_FALLTHROUGH; + + case XCTRL ('w'): + i++; + MC_FALLTHROUGH; + + case XCTRL ('r'): + parm = i; + for (i = 0; i < 3; i++) + ch_flags[i * 3 + parm] = (x_toggle & (1 << parm)) ? '-' : '+'; + x_toggle ^= (1 << parm); + update_mode (g); + widget_draw (w); + break; + + default: + break; + } + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WDialog * +advanced_chown_dlg_create (WPanel * panel) +{ + gboolean single_set; + WDialog *ch_dlg; + WGroup *ch_grp; + int lines = 12; + int cols = 74; + int i; + int y; + + memset (ch_flags, '=', 11); + flag_pos = 0; + x_toggle = 070; + + single_set = (panel->marked < 2); + if (!single_set) + lines += 2; + + ch_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, + advanced_chown_callback, NULL, "[Advanced Chown]", _("Chown advanced command")); + ch_grp = GROUP (ch_dlg); + + /* draw background */ + ch_dlg->bg->callback = advanced_chown_bg_callback; + + l_filename = label_new (2, 3, ""); + group_add_widget (ch_grp, l_filename); + + group_add_widget (ch_grp, hline_new (3, -1, -1)); + +#define XTRACT(i,y,cb) y, BX+advanced_chown_but[i].x, \ + advanced_chown_but[i].ret_cmd, advanced_chown_but[i].flags, \ + (advanced_chown_but[i].text), cb + b_att[0] = perm_button_new (XTRACT (0, BY, NULL)); + advanced_chown_but[0].id = group_add_widget (ch_grp, b_att[0]); + b_att[1] = perm_button_new (XTRACT (1, BY, NULL)); + advanced_chown_but[1].id = group_add_widget (ch_grp, b_att[1]); + b_att[2] = perm_button_new (XTRACT (2, BY, NULL)); + advanced_chown_but[2].id = group_add_widget (ch_grp, b_att[2]); + b_user = button_new (XTRACT (3, BY, user_group_button_cb)); + advanced_chown_but[3].id = group_add_widget (ch_grp, b_user); + b_group = button_new (XTRACT (4, BY, user_group_button_cb)); + advanced_chown_but[4].id = group_add_widget (ch_grp, b_group); + + l_mode = label_new (BY + 2, 3, ""); + group_add_widget (ch_grp, l_mode); + + y = BY + 3; + if (!single_set) + { + i = BUTTONS_PERM; + group_add_widget (ch_grp, hline_new (y++, -1, -1)); + advanced_chown_but[i].id = group_add_widget (ch_grp, + button_new (y, + WIDGET (ch_dlg)->rect.cols / 2 - + advanced_chown_but[i].len, + advanced_chown_but[i].ret_cmd, + advanced_chown_but[i].flags, + advanced_chown_but[i].text, NULL)); + i++; + advanced_chown_but[i].id = group_add_widget (ch_grp, + button_new (y, + WIDGET (ch_dlg)->rect.cols / 2 + 1, + advanced_chown_but[i].ret_cmd, + advanced_chown_but[i].flags, + advanced_chown_but[i].text, NULL)); + y++; + } + + i = BUTTONS_PERM + 2; + group_add_widget (ch_grp, hline_new (y++, -1, -1)); + advanced_chown_but[i].id = group_add_widget (ch_grp, + button_new (y, + WIDGET (ch_dlg)->rect.cols / 2 - + advanced_chown_but[i].len, + advanced_chown_but[i].ret_cmd, + advanced_chown_but[i].flags, + advanced_chown_but[i].text, NULL)); + i++; + advanced_chown_but[i].id = group_add_widget (ch_grp, + button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, + advanced_chown_but[i].ret_cmd, + advanced_chown_but[i].flags, + advanced_chown_but[i].text, NULL)); + + widget_select (WIDGET (b_att[0])); + + return ch_dlg; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +advanced_chown_done (gboolean need_update) +{ + if (need_update) + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static const GString * +next_file (const WPanel * panel) +{ + while (!panel->dir.list[current_file].f.marked) + current_file++; + + return panel->dir.list[current_file].fname; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +try_advanced_chown (const vfs_path_t * p, mode_t m, uid_t u, gid_t g) +{ + int chmod_result; + const char *fname; + + fname = x_basename (vfs_path_as_str (p)); + + while ((chmod_result = mc_chmod (p, m)) == -1 && !ignore_all) + { + int my_errno = errno; + int result; + char *msg; + + msg = g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), fname, unix_error_string (my_errno)); + result = + query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"), + _("&Cancel")); + g_free (msg); + + switch (result) + { + case 0: + /* call mc_chown() only, if mc_chmod() didn't fail */ + return TRUE; + + case 1: + ignore_all = TRUE; + /* call mc_chown() only, if mc_chmod() didn't fail */ + return TRUE; + + case 2: + /* retry chmod of this file */ + break; + + case 3: + default: + /* stop remain files processing */ + return FALSE; + } + } + + /* call mc_chown() only, if mc_chmod didn't fail */ + while (chmod_result != -1 && mc_chown (p, u, g) == -1 && !ignore_all) + { + int my_errno = errno; + int result; + char *msg; + + msg = g_strdup_printf (_("Cannot chown \"%s\"\n%s"), fname, unix_error_string (my_errno)); + result = + query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"), + _("&Cancel")); + g_free (msg); + + switch (result) + { + case 0: + /* try next file */ + return TRUE; + + case 1: + ignore_all = TRUE; + /* try next file */ + return TRUE; + + case 2: + /* retry chown of this file */ + break; + + case 3: + default: + /* stop remain files processing */ + return FALSE; + } + } + + return TRUE; + +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +do_advanced_chown (WPanel * panel, const vfs_path_t * p, mode_t m, uid_t u, gid_t g) +{ + gboolean ret; + + ret = try_advanced_chown (p, m, u, g); + + do_file_mark (panel, current_file, 0); + + return ret; +} + + /* --------------------------------------------------------------------------------------------- */ + +static void +apply_advanced_chowns (WPanel * panel, vfs_path_t * vpath, struct stat *sf) +{ + gid_t a_gid = sf->st_gid; + uid_t a_uid = sf->st_uid; + gboolean ok; + + if (!do_advanced_chown (panel, vpath, get_mode (), + (ch_flags[9] == '+') ? a_uid : (uid_t) (-1), + (ch_flags[10] == '+') ? a_gid : (gid_t) (-1))) + return; + + do + { + const GString *fname; + + fname = next_file (panel); + vpath = vfs_path_from_str (fname->str); + ok = (mc_stat (vpath, sf) == 0); + + if (!ok) + { + /* if current file was deleted outside mc -- try next file */ + /* decrease panel->marked */ + do_file_mark (panel, current_file, 0); + + /* try next file */ + ok = TRUE; + } + else + { + ch_cmode = sf->st_mode; + + ok = do_advanced_chown (panel, vpath, get_mode (), + (ch_flags[9] == '+') ? a_uid : (uid_t) (-1), + (ch_flags[10] == '+') ? a_gid : (gid_t) (-1)); + } + + vfs_path_free (vpath, TRUE); + } + while (ok && panel->marked != 0); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +advanced_chown_cmd (WPanel * panel) +{ + gboolean need_update; + gboolean end_chown; + + /* Number of files at startup */ + int files_on_begin; + + files_on_begin = MAX (1, panel->marked); + + advanced_chown_init (); + + current_file = 0; + ignore_all = FALSE; + + do + { /* do while any files remaining */ + vfs_path_t *vpath; + WDialog *ch_dlg; + const GString *fname; + int result; + int file_idx; + + do_refresh (); + + need_update = FALSE; + end_chown = FALSE; + + if (panel->marked != 0) + fname = next_file (panel); /* next marked file */ + else + fname = selection (panel)->fname; /* single file */ + + vpath = vfs_path_from_str (fname->str); + + if (mc_stat (vpath, &sf_stat) != 0) + { + vfs_path_free (vpath, TRUE); + break; + } + + ch_cmode = sf_stat.st_mode; + + ch_dlg = advanced_chown_dlg_create (panel); + + file_idx = files_on_begin == 1 ? 1 : (files_on_begin - panel->marked + 1); + label_set_textv (l_filename, "%s (%d/%d)", + str_fit_to_term (fname->str, WIDGET (ch_dlg)->rect.cols - 20, J_LEFT_FIT), + file_idx, files_on_begin); + update_ownership (); + + result = dlg_run (ch_dlg); + + switch (result) + { + case B_CANCEL: + end_chown = TRUE; + break; + + case B_ENTER: + if (panel->marked <= 1) + { + /* single or last file */ + if (mc_chmod (vpath, get_mode ()) == -1) + message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"), + fname->str, unix_error_string (errno)); + /* call mc_chown only, if mc_chmod didn't fail */ + else if (mc_chown + (vpath, (ch_flags[9] == '+') ? sf_stat.st_uid : (uid_t) (-1), + (ch_flags[10] == '+') ? sf_stat.st_gid : (gid_t) (-1)) == -1) + message (D_ERROR, MSG_ERROR, _("Cannot chown \"%s\"\n%s"), fname->str, + unix_error_string (errno)); + + end_chown = TRUE; + } + else if (!try_advanced_chown + (vpath, get_mode (), (ch_flags[9] == '+') ? sf_stat.st_uid : (uid_t) (-1), + (ch_flags[10] == '+') ? sf_stat.st_gid : (gid_t) (-1))) + { + /* stop multiple files processing */ + result = B_CANCEL; + end_chown = TRUE; + } + + need_update = TRUE; + break; + + case B_SETALL: + apply_advanced_chowns (panel, vpath, &sf_stat); + need_update = TRUE; + end_chown = TRUE; + break; + + case B_SKIP: + default: + break; + } + + if (panel->marked != 0 && result != B_CANCEL) + { + do_file_mark (panel, current_file, 0); + need_update = TRUE; + } + + vfs_path_free (vpath, TRUE); + + widget_destroy (WIDGET (ch_dlg)); + } + while (panel->marked != 0 && !end_chown); + + advanced_chown_done (need_update); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/boxes.c b/src/filemanager/boxes.c new file mode 100644 index 0000000..062661e --- /dev/null +++ b/src/filemanager/boxes.c @@ -0,0 +1,1342 @@ +/* + Some misc dialog boxes for the program. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Miguel de Icaza, 1994, 1995 + Jakub Jelinek, 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 boxes.c + * \brief Source: Some misc dialog boxes for the program + */ + +#include <config.h> + +#include <ctype.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" +#include "lib/tty/color.h" /* tty_use_colors() */ +#include "lib/tty/key.h" /* XCTRL and ALT macros */ +#include "lib/skin.h" /* INPUT_COLOR */ +#include "lib/mcconfig.h" /* Load/save user formats */ +#include "lib/strutil.h" + +#include "lib/vfs/vfs.h" +#ifdef ENABLE_VFS_FTP +#include "src/vfs/ftpfs/ftpfs.h" +#endif /* ENABLE_VFS_FTP */ + +#include "lib/util.h" /* Q_() */ +#include "lib/widget.h" + +#include "src/setup.h" +#include "src/history.h" /* MC_HISTORY_ESC_TIMEOUT */ +#include "src/execute.h" /* pause_after_run */ +#ifdef ENABLE_BACKGROUND +#include "src/background.h" /* task_list */ +#endif + +#ifdef HAVE_CHARSET +#include "lib/charsets.h" +#include "src/selcodepage.h" +#endif + +#include "command.h" /* For cmdline */ +#include "dir.h" +#include "tree.h" +#include "layout.h" /* for get_nth_panel_name proto */ +#include "filemanager.h" + +#include "boxes.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#ifdef ENABLE_BACKGROUND +#define B_STOP (B_USER+1) +#define B_RESUME (B_USER+2) +#define B_KILL (B_USER+3) +#endif /* ENABLE_BACKGROUND */ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static unsigned long configure_old_esc_mode_id, configure_time_out_id; + +/* Index in list_formats[] for "brief" */ +static const int panel_list_brief_idx = 1; +/* Index in list_formats[] for "user defined" */ +static const int panel_list_user_idx = 3; + +static char **status_format; +static unsigned long panel_list_formats_id, panel_user_format_id, panel_brief_cols_id; +static unsigned long user_mini_status_id, mini_user_format_id; + +#ifdef HAVE_CHARSET +static int new_display_codepage; +#endif /* HAVE_CHARSET */ + +#if defined(ENABLE_VFS) && defined(ENABLE_VFS_FTP) +static unsigned long ftpfs_always_use_proxy_id, ftpfs_proxy_host_id; +#endif /* ENABLE_VFS && ENABLE_VFS_FTP */ + +static GPtrArray *skin_names; +static gchar *current_skin_name; + +#ifdef ENABLE_BACKGROUND +static WListbox *bg_list = NULL; +#endif /* ENABLE_BACKGROUND */ + +static unsigned long shadows_id; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +configure_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_NOTIFY: + /* message from "Single press" checkbutton */ + if (sender != NULL && sender->id == configure_old_esc_mode_id) + { + const gboolean not_single = !CHECK (sender)->state; + Widget *ww; + + /* input line */ + ww = widget_find_by_id (w, configure_time_out_id); + widget_disable (ww, not_single); + + return MSG_HANDLED; + } + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +skin_apply (const gchar * skin_override) +{ + GError *mcerror = NULL; + + mc_skin_deinit (); + mc_skin_init (skin_override, &mcerror); + mc_fhl_free (&mc_filehighlight); + mc_filehighlight = mc_fhl_new (TRUE); + dlg_set_default_colors (); + input_set_default_colors (); + if (mc_global.mc_run_mode == MC_RUN_FULL) + command_set_default_colors (); + panel_deinit (); + panel_init (); + repaint_screen (); + + mc_error_message (&mcerror, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +static const gchar * +skin_name_to_label (const gchar * name) +{ + if (strcmp (name, "default") == 0) + return _("< Default >"); + return name; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +skin_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_RESIZE: + { + WDialog *d = DIALOG (w); + const WRect *wd = &WIDGET (d->data)->rect; + WRect r = w->rect; + + r.y = wd->y + (wd->lines - r.lines) / 2; + r.x = wd->x + wd->cols / 2; + + return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r); + } + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +sel_skin_button (WButton * button, int action) +{ + int result; + WListbox *skin_list; + WDialog *skin_dlg; + const gchar *skin_name; + unsigned int i; + unsigned int pos = 1; + + (void) action; + + skin_dlg = + dlg_create (TRUE, 0, 0, 13, 24, WPOS_KEEP_DEFAULT, TRUE, dialog_colors, skin_dlg_callback, + NULL, "[Appearance]", _("Skins")); + /* use Appearance dialog for positioning */ + skin_dlg->data = WIDGET (button)->owner; + + /* set dialog location before all */ + send_message (skin_dlg, NULL, MSG_RESIZE, 0, NULL); + + skin_list = listbox_new (1, 1, 11, 22, FALSE, NULL); + skin_name = "default"; + listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name), + (void *) skin_name, FALSE); + + if (strcmp (skin_name, current_skin_name) == 0) + listbox_select_entry (skin_list, 0); + + for (i = 0; i < skin_names->len; i++) + { + skin_name = g_ptr_array_index (skin_names, i); + if (strcmp (skin_name, "default") != 0) + { + listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name), + (void *) skin_name, FALSE); + if (strcmp (skin_name, current_skin_name) == 0) + listbox_select_entry (skin_list, pos); + pos++; + } + } + + /* make list stick to all sides of dialog, effectively make it be resized with dialog */ + group_add_widget_autopos (GROUP (skin_dlg), skin_list, WPOS_KEEP_ALL, NULL); + + result = dlg_run (skin_dlg); + if (result == B_ENTER) + { + gchar *skin_label; + + listbox_get_current (skin_list, &skin_label, (void **) &skin_name); + g_free (current_skin_name); + current_skin_name = g_strdup (skin_name); + skin_apply (skin_name); + + button_set_text (button, str_fit_to_term (skin_label, 20, J_LEFT_FIT)); + } + widget_destroy (WIDGET (skin_dlg)); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +appearance_box_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_INIT: +#ifdef ENABLE_SHADOWS + if (!tty_use_colors ()) +#endif + { + Widget *shadow; + + shadow = widget_find_by_id (w, shadows_id); + CHECK (shadow)->state = FALSE; + widget_disable (shadow, TRUE); + } + return MSG_HANDLED; + + case MSG_NOTIFY: + if (sender != NULL && sender->id == shadows_id) + { + mc_global.tty.shadows = CHECK (sender)->state; + repaint_screen (); + return MSG_HANDLED; + } + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +panel_listing_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_NOTIFY: + if (sender != NULL && sender->id == panel_list_formats_id) + { + WCheck *ch; + WInput *in1, *in2, *in3; + + in1 = INPUT (widget_find_by_id (w, panel_user_format_id)); + in2 = INPUT (widget_find_by_id (w, panel_brief_cols_id)); + ch = CHECK (widget_find_by_id (w, user_mini_status_id)); + in3 = INPUT (widget_find_by_id (w, mini_user_format_id)); + + if (!ch->state) + input_assign_text (in3, status_format[RADIO (sender)->sel]); + input_update (in1, FALSE); + input_update (in2, FALSE); + input_update (in3, FALSE); + widget_disable (WIDGET (in1), RADIO (sender)->sel != panel_list_user_idx); + widget_disable (WIDGET (in2), RADIO (sender)->sel != panel_list_brief_idx); + return MSG_HANDLED; + } + + if (sender != NULL && sender->id == user_mini_status_id) + { + WInput *in; + + in = INPUT (widget_find_by_id (w, mini_user_format_id)); + + if (CHECK (sender)->state) + { + widget_disable (WIDGET (in), FALSE); + input_assign_text (in, status_format[3]); + } + else + { + WRadio *r; + + r = RADIO (widget_find_by_id (w, panel_list_formats_id)); + widget_disable (WIDGET (in), TRUE); + input_assign_text (in, status_format[r->sel]); + } + /* input_update (in, FALSE); */ + return MSG_HANDLED; + } + + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef HAVE_CHARSET +static int +sel_charset_button (WButton * button, int action) +{ + int new_dcp; + + (void) action; + + new_dcp = select_charset (-1, -1, new_display_codepage, TRUE); + + if (new_dcp != SELECT_CHARSET_CANCEL) + { + const char *cpname; + + new_display_codepage = new_dcp; + cpname = (new_display_codepage == SELECT_CHARSET_OTHER_8BIT) ? + _("Other 8 bit") : + ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name; + if (cpname != NULL) + mc_global.utf8_display = str_isutf8 (cpname); + else + cpname = _("7-bit ASCII"); /* FIXME */ + + button_set_text (button, cpname); + widget_draw (WIDGET (WIDGET (button)->owner)); + } + + return 0; +} +#endif /* HAVE_CHARSET */ + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_RESIZE: + { + WRect r = w->rect; + Widget *bar; + + r.lines = LINES - 9; + r.cols = COLS - 20; + dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r); + + bar = WIDGET (find_buttonbar (h)); + bar->rect.x = 0; + bar->rect.y = LINES - 1; + return MSG_HANDLED; + } + + case MSG_ACTION: + return send_message (find_tree (h), NULL, MSG_ACTION, parm, NULL); + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +#if defined(ENABLE_VFS) && defined (ENABLE_VFS_FTP) +static cb_ret_t +confvfs_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_NOTIFY: + /* message from "Always use ftp proxy" checkbutton */ + if (sender != NULL && sender->id == ftpfs_always_use_proxy_id) + { + const gboolean not_use = !CHECK (sender)->state; + Widget *wi; + + /* input */ + wi = widget_find_by_id (w, ftpfs_proxy_host_id); + widget_disable (wi, not_use); + return MSG_HANDLED; + } + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} +#endif /* ENABLE_VFS && ENABLE_VFS_FTP */ + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_BACKGROUND +static void +jobs_fill_listbox (WListbox * list) +{ + static const char *state_str[2] = { "", "" }; + TaskList *tl; + + if (state_str[0][0] == '\0') + { + state_str[0] = _("Running"); + state_str[1] = _("Stopped"); + } + + for (tl = task_list; tl != NULL; tl = tl->next) + { + char *s; + + s = g_strconcat (state_str[tl->state], " ", tl->info, (char *) NULL); + listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, s, (void *) tl, FALSE); + g_free (s); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +task_cb (WButton * button, int action) +{ + TaskList *tl; + int sig = 0; + + (void) button; + + if (bg_list->list == NULL) + return 0; + + /* Get this instance information */ + listbox_get_current (bg_list, NULL, (void **) &tl); + +#ifdef SIGTSTP + if (action == B_STOP) + { + sig = SIGSTOP; + tl->state = Task_Stopped; + } + else if (action == B_RESUME) + { + sig = SIGCONT; + tl->state = Task_Running; + } + else +#endif + if (action == B_KILL) + sig = SIGKILL; + + if (sig == SIGKILL) + unregister_task_running (tl->pid, tl->fd); + + kill (tl->pid, sig); + listbox_remove_list (bg_list); + jobs_fill_listbox (bg_list); + + /* This can be optimized to just redraw this widget :-) */ + widget_draw (WIDGET (WIDGET (button)->owner)); + + return 0; +} +#endif /* ENABLE_BACKGROUND */ + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +configure_box (void) +{ + const char *pause_options[] = { + N_("&Never"), + N_("On dum&b terminals"), + N_("Alwa&ys") + }; + + int pause_options_num; + + pause_options_num = G_N_ELEMENTS (pause_options); + + { + char time_out[BUF_TINY] = ""; + char *time_out_new; + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_START_COLUMNS, + QUICK_START_GROUPBOX (N_("File operations")), + QUICK_CHECKBOX (N_("&Verbose operation"), &verbose, NULL), + QUICK_CHECKBOX (N_("Compute tota&ls"), &file_op_compute_totals, NULL), + QUICK_CHECKBOX (N_("Classic pro&gressbar"), &classic_progressbar, NULL), + QUICK_CHECKBOX (N_("Mkdi&r autoname"), &auto_fill_mkdir_name, NULL), + QUICK_CHECKBOX (N_("&Preallocate space"), &mc_global.vfs.preallocate_space, + NULL), + QUICK_STOP_GROUPBOX, + QUICK_START_GROUPBOX (N_("Esc key mode")), + QUICK_CHECKBOX (N_("S&ingle press"), &old_esc_mode, &configure_old_esc_mode_id), + QUICK_LABELED_INPUT (N_("Timeout:"), input_label_left, + (const char *) time_out, MC_HISTORY_ESC_TIMEOUT, + &time_out_new, &configure_time_out_id, FALSE, FALSE, + INPUT_COMPLETE_NONE), + QUICK_STOP_GROUPBOX, + QUICK_START_GROUPBOX (N_("Pause after run")), + QUICK_RADIO (pause_options_num, pause_options, &pause_after_run, NULL), + QUICK_STOP_GROUPBOX, + QUICK_NEXT_COLUMN, + QUICK_START_GROUPBOX (N_("Other options")), + QUICK_CHECKBOX (N_("Use internal edi&t"), &use_internal_edit, NULL), + QUICK_CHECKBOX (N_("Use internal vie&w"), &use_internal_view, NULL), + QUICK_CHECKBOX (N_("A&sk new file name"), + &editor_ask_filename_before_edit, NULL), + QUICK_CHECKBOX (N_("Auto m&enus"), &auto_menu, NULL), + QUICK_CHECKBOX (N_("&Drop down menus"), &drop_menus, NULL), + QUICK_CHECKBOX (N_("S&hell patterns"), &easy_patterns, NULL), + QUICK_CHECKBOX (N_("Co&mplete: show all"), + &mc_global.widget.show_all_if_ambiguous, NULL), + QUICK_CHECKBOX (N_("Rotating d&ash"), &nice_rotating_dash, NULL), + QUICK_CHECKBOX (N_("Cd follows lin&ks"), &mc_global.vfs.cd_symlinks, NULL), + QUICK_CHECKBOX (N_("Sa&fe delete"), &safe_delete, NULL), + QUICK_CHECKBOX (N_("Safe overwrite"), &safe_overwrite, NULL), /* w/o hotkey */ + QUICK_CHECKBOX (N_("A&uto save setup"), &auto_save_setup, NULL), + QUICK_SEPARATOR (FALSE), + QUICK_SEPARATOR (FALSE), + QUICK_STOP_GROUPBOX, + QUICK_STOP_COLUMNS, + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 60 }; + + quick_dialog_t qdlg = { + r, N_("Configure options"), "[Configuration]", + quick_widgets, configure_callback, NULL + }; + + g_snprintf (time_out, sizeof (time_out), "%d", old_esc_mode_timeout); + +#ifndef USE_INTERNAL_EDIT + quick_widgets[17].state = WST_DISABLED; +#endif + + if (!old_esc_mode) + quick_widgets[10].state = quick_widgets[11].state = WST_DISABLED; + +#ifndef HAVE_POSIX_FALLOCATE + mc_global.vfs.preallocate_space = FALSE; + quick_widgets[7].state = WST_DISABLED; +#endif + + if (quick_dialog (&qdlg) == B_ENTER) + { + if (time_out_new[0] == '\0') + old_esc_mode_timeout = 0; + else + old_esc_mode_timeout = atoi (time_out_new); + } + + g_free (time_out_new); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +appearance_box (void) +{ + gboolean shadows = mc_global.tty.shadows; + + current_skin_name = g_strdup (mc_skin__default.name); + skin_names = mc_skin_list (); + + { + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_START_COLUMNS, + QUICK_LABEL (N_("Skin:"), NULL), + QUICK_NEXT_COLUMN, + QUICK_BUTTON (str_fit_to_term (skin_name_to_label (current_skin_name), 20, J_LEFT_FIT), + B_USER, sel_skin_button, NULL), + QUICK_STOP_COLUMNS, + QUICK_SEPARATOR (TRUE), + QUICK_CHECKBOX (N_("&Shadows"), &mc_global.tty.shadows, &shadows_id), + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 54 }; + + quick_dialog_t qdlg = { + r, N_("Appearance"), "[Appearance]", + quick_widgets, appearance_box_callback, NULL + }; + + if (quick_dialog (&qdlg) == B_ENTER) + mc_config_set_string (mc_global.main_config, CONFIG_APP_SECTION, "skin", + current_skin_name); + else + { + skin_apply (NULL); + mc_global.tty.shadows = shadows; + } + } + + g_free (current_skin_name); + g_ptr_array_foreach (skin_names, (GFunc) g_free, NULL); + g_ptr_array_free (skin_names, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_options_box (void) +{ + gboolean simple_swap; + + simple_swap = mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION, + "simple_swap", FALSE); + { + const char *qsearch_options[] = { + N_("Case &insensitive"), + N_("Cas&e sensitive"), + N_("Use panel sort mo&de") + }; + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_START_COLUMNS, + QUICK_START_GROUPBOX (N_("Main options")), + QUICK_CHECKBOX (N_("Show mi&ni-status"), &panels_options.show_mini_info, NULL), + QUICK_CHECKBOX (N_("Use SI si&ze units"), &panels_options.kilobyte_si, NULL), + QUICK_CHECKBOX (N_("Mi&x all files"), &panels_options.mix_all_files, NULL), + QUICK_CHECKBOX (N_("Show &backup files"), &panels_options.show_backups, NULL), + QUICK_CHECKBOX (N_("Show &hidden files"), &panels_options.show_dot_files, NULL), + QUICK_CHECKBOX (N_("&Fast dir reload"), &panels_options.fast_reload, NULL), + QUICK_CHECKBOX (N_("Ma&rk moves down"), &panels_options.mark_moves_down, NULL), + QUICK_CHECKBOX (N_("Re&verse files only"), &panels_options.reverse_files_only, + NULL), + QUICK_CHECKBOX (N_("Simple s&wap"), &simple_swap, NULL), + QUICK_CHECKBOX (N_("A&uto save panels setup"), &panels_options.auto_save_setup, + NULL), + QUICK_SEPARATOR (FALSE), + QUICK_SEPARATOR (FALSE), + QUICK_SEPARATOR (FALSE), + QUICK_STOP_GROUPBOX, + QUICK_NEXT_COLUMN, + QUICK_START_GROUPBOX (N_("Navigation")), + QUICK_CHECKBOX (N_("L&ynx-like motion"), &panels_options.navigate_with_arrows, + NULL), + QUICK_CHECKBOX (N_("Pa&ge scrolling"), &panels_options.scroll_pages, NULL), + QUICK_CHECKBOX (N_("Center &scrolling"), &panels_options.scroll_center, NULL), + QUICK_CHECKBOX (N_("&Mouse page scrolling"), &panels_options.mouse_move_pages, + NULL), + QUICK_STOP_GROUPBOX, + QUICK_START_GROUPBOX (N_("File highlight")), + QUICK_CHECKBOX (N_("File &types"), &panels_options.filetype_mode, NULL), + QUICK_CHECKBOX (N_("&Permissions"), &panels_options.permission_mode, NULL), + QUICK_STOP_GROUPBOX, + QUICK_START_GROUPBOX (N_("Quick search")), + QUICK_RADIO (QSEARCH_NUM, qsearch_options, (int *) &panels_options.qsearch_mode, + NULL), + QUICK_STOP_GROUPBOX, + QUICK_STOP_COLUMNS, + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 60 }; + + quick_dialog_t qdlg = { + r, N_("Panel options"), "[Panel options]", + quick_widgets, NULL, NULL + }; + + if (quick_dialog (&qdlg) != B_ENTER) + return; + } + + mc_config_set_bool (mc_global.main_config, CONFIG_PANELS_SECTION, "simple_swap", simple_swap); + + if (!panels_options.fast_reload_msg_shown && panels_options.fast_reload) + { + message (D_NORMAL, _("Information"), + _("Using the fast reload option may not reflect the exact\n" + "directory contents. In this case you'll need to do a\n" + "manual reload of the directory. See the man page for\n" "the details.")); + panels_options.fast_reload_msg_shown = TRUE; + } + + update_panels (UP_RELOAD, UP_KEEPSEL); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* return list type */ +int +panel_listing_box (WPanel * panel, int num, char **userp, char **minip, gboolean * use_msformat, + int *brief_cols) +{ + int result = -1; + const char *p = NULL; + + if (panel == NULL) + { + p = get_nth_panel_name (num); + panel = panel_empty_new (p); + } + + { + gboolean user_mini_status; + char panel_brief_cols_in[BUF_TINY]; + char *panel_brief_cols_out = NULL; + char *panel_user_format = NULL; + char *mini_user_format = NULL; + + /* Controls whether the array strings have been translated */ + const char *list_formats[LIST_FORMATS] = { + N_("&Full file list"), + N_("&Brief file list:"), + N_("&Long file list"), + N_("&User defined:") + }; + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_START_COLUMNS, + QUICK_RADIO (LIST_FORMATS, list_formats, &result, &panel_list_formats_id), + QUICK_NEXT_COLUMN, + QUICK_SEPARATOR (FALSE), + QUICK_LABELED_INPUT (_ ("columns"), input_label_right, panel_brief_cols_in, + "panel-brief-cols-input", &panel_brief_cols_out, + &panel_brief_cols_id, FALSE, FALSE, INPUT_COMPLETE_NONE), + QUICK_STOP_COLUMNS, + QUICK_INPUT (panel->user_format, "user-fmt-input", &panel_user_format, + &panel_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE), + QUICK_SEPARATOR (TRUE), + QUICK_CHECKBOX (N_("User &mini status"), &user_mini_status, &user_mini_status_id), + QUICK_INPUT (panel->user_status_format[panel->list_format], "mini_input", + &mini_user_format, &mini_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE), + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 48 }; + + quick_dialog_t qdlg = { + r, N_("Listing format"), "[Listing Format...]", + quick_widgets, panel_listing_callback, NULL + }; + + user_mini_status = panel->user_mini_status; + result = panel->list_format; + status_format = panel->user_status_format; + + g_snprintf (panel_brief_cols_in, sizeof (panel_brief_cols_in), "%d", panel->brief_cols); + + if ((int) panel->list_format != panel_list_brief_idx) + quick_widgets[4].state = WST_DISABLED; + + if ((int) panel->list_format != panel_list_user_idx) + quick_widgets[6].state = WST_DISABLED; + + if (!user_mini_status) + quick_widgets[9].state = WST_DISABLED; + + if (quick_dialog (&qdlg) == B_CANCEL) + result = -1; + else + { + int cols; + char *error = NULL; + + *userp = panel_user_format; + *minip = mini_user_format; + *use_msformat = user_mini_status; + + cols = strtol (panel_brief_cols_out, &error, 10); + if (*error == '\0') + *brief_cols = cols; + else + *brief_cols = panel->brief_cols; + + g_free (panel_brief_cols_out); + } + } + + if (p != NULL) + { + int i; + + g_free (panel->user_format); + for (i = 0; i < LIST_FORMATS; i++) + g_free (panel->user_status_format[i]); + g_free (panel); + } + + return result; +} + +/* --------------------------------------------------------------------------------------------- */ + +const panel_field_t * +sort_box (dir_sort_options_t * op, const panel_field_t * sort_field) +{ + char **sort_orders_names; + gsize i; + gsize sort_names_num = 0; + int sort_idx = 0; + const panel_field_t *result = NULL; + + sort_orders_names = panel_get_sortable_fields (&sort_names_num); + + for (i = 0; i < sort_names_num; i++) + if (strcmp (sort_orders_names[i], _(sort_field->title_hotkey)) == 0) + { + sort_idx = i; + break; + } + + { + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_START_COLUMNS, + QUICK_RADIO (sort_names_num, (const char **) sort_orders_names, &sort_idx, NULL), + QUICK_NEXT_COLUMN, + QUICK_CHECKBOX (N_("Executable &first"), &op->exec_first, NULL), + QUICK_CHECKBOX (N_("Cas&e sensitive"), &op->case_sensitive, NULL), + QUICK_CHECKBOX (N_("&Reverse"), &op->reverse, NULL), + QUICK_STOP_COLUMNS, + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 40 }; + + quick_dialog_t qdlg = { + r, N_("Sort order"), "[Sort Order...]", + quick_widgets, NULL, NULL + }; + + if (quick_dialog (&qdlg) != B_CANCEL) + result = panel_get_field_by_title_hotkey (sort_orders_names[sort_idx]); + + if (result == NULL) + result = sort_field; + } + + g_strfreev (sort_orders_names); + + return result; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +confirm_box (void) +{ + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + /* TRANSLATORS: no need to translate 'Confirmation', it's just a context prefix */ + QUICK_CHECKBOX (Q_("Confirmation|&Delete"), &confirm_delete, NULL), + QUICK_CHECKBOX (Q_("Confirmation|O&verwrite"), &confirm_overwrite, NULL), + QUICK_CHECKBOX (Q_("Confirmation|&Execute"), &confirm_execute, NULL), + QUICK_CHECKBOX (Q_("Confirmation|E&xit"), &confirm_exit, NULL), + QUICK_CHECKBOX (Q_("Confirmation|Di&rectory hotlist delete"), + &confirm_directory_hotlist_delete, NULL), + QUICK_CHECKBOX (Q_("Confirmation|&History cleanup"), + &mc_global.widget.confirm_history_cleanup, NULL), + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 46 }; + + quick_dialog_t qdlg = { + r, N_("Confirmation"), "[Confirmation]", + quick_widgets, NULL, NULL + }; + + (void) quick_dialog (&qdlg); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifndef HAVE_CHARSET +void +display_bits_box (void) +{ + gboolean new_meta; + int current_mode; + + const char *display_bits_str[] = { + N_("&UTF-8 output"), + N_("&Full 8 bits output"), + N_("&ISO 8859-1"), + N_("7 &bits") + }; + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_RADIO (4, display_bits_str, ¤t_mode, NULL), + QUICK_SEPARATOR (TRUE), + QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL), + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 46 }; + + quick_dialog_t qdlg = { + r, _("Display bits"), "[Display bits]", + quick_widgets, NULL, NULL + }; + + if (mc_global.full_eight_bits) + current_mode = 0; + else if (mc_global.eight_bit_clean) + current_mode = 1; + else + current_mode = 2; + + new_meta = !use_8th_bit_as_meta; + + if (quick_dialog (&qdlg) != B_CANCEL) + { + mc_global.eight_bit_clean = current_mode < 3; + mc_global.full_eight_bits = current_mode < 2; +#ifndef HAVE_SLANG + tty_display_8bit (mc_global.eight_bit_clean); +#else + tty_display_8bit (mc_global.full_eight_bits); +#endif + use_8th_bit_as_meta = !new_meta; + } +} + +/* --------------------------------------------------------------------------------------------- */ +#else /* HAVE_CHARSET */ + +void +display_bits_box (void) +{ + const char *cpname; + + new_display_codepage = mc_global.display_codepage; + + cpname = (new_display_codepage < 0) ? _("Other 8 bit") + : ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name; + + { + gboolean new_meta; + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_START_COLUMNS, + QUICK_LABEL (N_("Input / display codepage:"), NULL), + QUICK_NEXT_COLUMN, + QUICK_BUTTON (cpname, B_USER, sel_charset_button, NULL), + QUICK_STOP_COLUMNS, + QUICK_SEPARATOR (TRUE), + QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL), + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 46 }; + + quick_dialog_t qdlg = { + r, N_("Display bits"), "[Display bits]", + quick_widgets, NULL, NULL + }; + + new_meta = !use_8th_bit_as_meta; + application_keypad_mode (); + + if (quick_dialog (&qdlg) == B_ENTER) + { + char *errmsg; + + mc_global.display_codepage = new_display_codepage; + + errmsg = init_translation_table (mc_global.source_codepage, mc_global.display_codepage); + if (errmsg != NULL) + { + message (D_ERROR, MSG_ERROR, "%s", errmsg); + g_free (errmsg); + } + +#ifdef HAVE_SLANG + tty_display_8bit (mc_global.display_codepage != 0 && mc_global.display_codepage != 1); +#else + tty_display_8bit (mc_global.display_codepage != 0); +#endif + use_8th_bit_as_meta = !new_meta; + + repaint_screen (); + } + } +} +#endif /* HAVE_CHARSET */ + +/* --------------------------------------------------------------------------------------------- */ +/** Show tree in a box, not on a panel */ + +char * +tree_box (const char *current_dir) +{ + WTree *mytree; + WDialog *dlg; + WGroup *g; + Widget *wd; + char *val = NULL; + WButtonBar *bar; + + (void) current_dir; + + /* Create the components */ + dlg = dlg_create (TRUE, 0, 0, LINES - 9, COLS - 20, WPOS_CENTER, FALSE, dialog_colors, + tree_callback, NULL, "[Directory Tree]", _("Directory tree")); + g = GROUP (dlg); + wd = WIDGET (dlg); + + mytree = tree_new (2, 2, wd->rect.lines - 6, wd->rect.cols - 5, FALSE); + group_add_widget_autopos (g, mytree, WPOS_KEEP_ALL, NULL); + group_add_widget_autopos (g, hline_new (wd->rect.lines - 4, 1, -1), WPOS_KEEP_BOTTOM, NULL); + bar = buttonbar_new (); + group_add_widget (g, bar); + /* restore ButtonBar coordinates after add_widget() */ + WIDGET (bar)->rect.x = 0; + WIDGET (bar)->rect.y = LINES - 1; + + if (dlg_run (dlg) == B_ENTER) + { + const vfs_path_t *selected_name; + + selected_name = tree_selected_name (mytree); + val = g_strdup (vfs_path_as_str (selected_name)); + } + + widget_destroy (wd); + return val; +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS +void +configure_vfs_box (void) +{ + char buffer2[BUF_TINY]; +#ifdef ENABLE_VFS_FTP + char buffer3[BUF_TINY]; + + g_snprintf (buffer3, sizeof (buffer3), "%i", ftpfs_directory_timeout); +#endif + + g_snprintf (buffer2, sizeof (buffer2), "%i", vfs_timeout); + + { + char *ret_timeout; +#ifdef ENABLE_VFS_FTP + char *ret_passwd; + char *ret_ftp_proxy; + char *ret_directory_timeout; +#endif /* ENABLE_VFS_FTP */ + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_LABELED_INPUT (N_("Timeout for freeing VFSs (sec):"), input_label_left, + buffer2, "input-timo-vfs", &ret_timeout, NULL, FALSE, FALSE, + INPUT_COMPLETE_NONE), +#ifdef ENABLE_VFS_FTP + QUICK_SEPARATOR (TRUE), + QUICK_LABELED_INPUT (N_("FTP anonymous password:"), input_label_left, + ftpfs_anonymous_passwd, "input-passwd", &ret_passwd, NULL, + FALSE, FALSE, INPUT_COMPLETE_NONE), + QUICK_LABELED_INPUT (N_("FTP directory cache timeout (sec):"), input_label_left, + buffer3, "input-timeout", &ret_directory_timeout, NULL, + FALSE, FALSE, INPUT_COMPLETE_NONE), + QUICK_CHECKBOX (N_("&Always use ftp proxy:"), &ftpfs_always_use_proxy, + &ftpfs_always_use_proxy_id), + QUICK_INPUT (ftpfs_proxy_host, "input-ftp-proxy", &ret_ftp_proxy, + &ftpfs_proxy_host_id, FALSE, FALSE, INPUT_COMPLETE_HOSTNAMES), + QUICK_CHECKBOX (N_("&Use ~/.netrc"), &ftpfs_use_netrc, NULL), + QUICK_CHECKBOX (N_("Use &passive mode"), &ftpfs_use_passive_connections, NULL), + QUICK_CHECKBOX (N_("Use passive mode over pro&xy"), + &ftpfs_use_passive_connections_over_proxy, NULL), +#endif /* ENABLE_VFS_FTP */ + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 56 }; + + quick_dialog_t qdlg = { + r, N_("Virtual File System Setting"), "[Virtual FS]", + quick_widgets, +#ifdef ENABLE_VFS_FTP + confvfs_callback, +#else + NULL, +#endif + NULL, + }; + +#ifdef ENABLE_VFS_FTP + if (!ftpfs_always_use_proxy) + quick_widgets[5].state = WST_DISABLED; +#endif + + if (quick_dialog (&qdlg) != B_CANCEL) + { + /* cppcheck-suppress uninitvar */ + if (ret_timeout[0] == '\0') + vfs_timeout = 0; + else + vfs_timeout = atoi (ret_timeout); + g_free (ret_timeout); + + if (vfs_timeout < 0 || vfs_timeout > 10000) + vfs_timeout = 10; +#ifdef ENABLE_VFS_FTP + g_free (ftpfs_anonymous_passwd); + /* cppcheck-suppress uninitvar */ + ftpfs_anonymous_passwd = ret_passwd; + g_free (ftpfs_proxy_host); + /* cppcheck-suppress uninitvar */ + ftpfs_proxy_host = ret_ftp_proxy; + /* cppcheck-suppress uninitvar */ + if (ret_directory_timeout[0] == '\0') + ftpfs_directory_timeout = 0; + else + ftpfs_directory_timeout = atoi (ret_directory_timeout); + g_free (ret_directory_timeout); +#endif + } + } +} + +#endif /* ENABLE_VFS */ + +/* --------------------------------------------------------------------------------------------- */ + +char * +cd_box (const WPanel * panel) +{ + const Widget *w = CONST_WIDGET (panel); + char *my_str; + + quick_widget_t quick_widgets[] = { + QUICK_LABELED_INPUT (N_("cd"), input_label_left, "", "input", &my_str, NULL, FALSE, TRUE, + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD), + QUICK_END + }; + + WRect r = { w->rect.y + w->rect.lines - 6, w->rect.x, 0, w->rect.cols }; + + quick_dialog_t qdlg = { + r, N_("Quick cd"), "[Quick cd]", + quick_widgets, NULL, NULL + }; + + return (quick_dialog (&qdlg) != B_CANCEL) ? my_str : NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +symlink_box (const vfs_path_t * existing_vpath, const vfs_path_t * new_vpath, + char **ret_existing, char **ret_new) +{ + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_LABELED_INPUT (N_("Existing filename (filename symlink will point to):"), + input_label_above, vfs_path_as_str (existing_vpath), "input-2", + ret_existing, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES), + QUICK_SEPARATOR (FALSE), + QUICK_LABELED_INPUT (N_("Symbolic link filename:"), input_label_above, + vfs_path_as_str (new_vpath), "input-1", + ret_new, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES), + QUICK_BUTTONS_OK_CANCEL, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 64 }; + + quick_dialog_t qdlg = { + r, N_("Symbolic link"), "[File Menu]", + quick_widgets, NULL, NULL + }; + + if (quick_dialog (&qdlg) == B_CANCEL) + { + *ret_new = NULL; + *ret_existing = NULL; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_BACKGROUND +void +jobs_box (void) +{ + struct + { + const char *name; + int flags; + int value; + int len; + bcback_fn callback; + } + job_but[] = + { + /* *INDENT-OFF* */ + { N_("&Stop"), NORMAL_BUTTON, B_STOP, 0, task_cb }, + { N_("&Resume"), NORMAL_BUTTON, B_RESUME, 0, task_cb }, + { N_("&Kill"), NORMAL_BUTTON, B_KILL, 0, task_cb }, + { N_("&OK"), DEFPUSH_BUTTON, B_CANCEL, 0, NULL } + /* *INDENT-ON* */ + }; + + size_t i; + const size_t n_but = G_N_ELEMENTS (job_but); + + WDialog *jobs_dlg; + WGroup *g; + int cols = 60; + int lines = 15; + int x = 0; + + for (i = 0; i < n_but; i++) + { +#ifdef ENABLE_NLS + job_but[i].name = _(job_but[i].name); +#endif /* ENABLE_NLS */ + + job_but[i].len = str_term_width1 (job_but[i].name) + 3; + if (job_but[i].flags == DEFPUSH_BUTTON) + job_but[i].len += 2; + x += job_but[i].len; + } + + x += (int) n_but - 1; + cols = MAX (cols, x + 6); + + jobs_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, NULL, NULL, + "[Background jobs]", _("Background jobs")); + g = GROUP (jobs_dlg); + + bg_list = listbox_new (2, 2, lines - 6, cols - 6, FALSE, NULL); + jobs_fill_listbox (bg_list); + group_add_widget (g, bg_list); + + group_add_widget (g, hline_new (lines - 4, -1, -1)); + + x = (cols - x) / 2; + for (i = 0; i < n_but; i++) + { + group_add_widget (g, button_new (lines - 3, x, job_but[i].value, job_but[i].flags, + job_but[i].name, job_but[i].callback)); + x += job_but[i].len + 1; + } + + (void) dlg_run (jobs_dlg); + widget_destroy (WIDGET (jobs_dlg)); +} +#endif /* ENABLE_BACKGROUND */ + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/boxes.h b/src/filemanager/boxes.h new file mode 100644 index 0000000..6cb115e --- /dev/null +++ b/src/filemanager/boxes.h @@ -0,0 +1,37 @@ +/** \file boxes.h + * \brief Header: Some misc dialog boxes for the program + */ + +#ifndef MC__BOXES_H +#define MC__BOXES_H + +#include "dir.h" +#include "panel.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 configure_box (void); +void appearance_box (void); +void panel_options_box (void); +int panel_listing_box (WPanel * p, int num, char **user, char **mini, gboolean * use_msformat, + int *brief_cols); +const panel_field_t *sort_box (dir_sort_options_t * op, const panel_field_t * sort_field); +void confirm_box (void); +void display_bits_box (void); +void configure_vfs_box (void); +void jobs_box (void); +char *cd_box (const WPanel * panel); +void symlink_box (const vfs_path_t * existing_vpath, const vfs_path_t * new_vpath, + char **ret_existing, char **ret_new); +char *tree_box (const char *current_dir); + +/*** inline functions ****************************************************************************/ +#endif /* MC__BOXES_H */ diff --git a/src/filemanager/cd.c b/src/filemanager/cd.c new file mode 100644 index 0000000..55191d5 --- /dev/null +++ b/src/filemanager/cd.c @@ -0,0 +1,299 @@ +/* + cd_to() function. + + Copyright (C) 1995-2022 + Free Software Foundation, Inc. + + Written by: + Slava Zanko <slavazanko@gmail.com>, 2013 + Andrew Borodin <aborodin@vmail.ru>, 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/>. + */ + +/** \file cd.c + * \brief Source: cd_to() function + */ + +#include <config.h> + +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include "lib/global.h" +#include "lib/vfs/vfs.h" +#include "lib/strescape.h" /* strutils_shell_unescape() */ +#include "lib/util.h" /* whitespace() */ + +#include "filemanager.h" /* current_panel, panel.h, layout.h */ +#include "tree.h" /* sync_tree() */ + +#include "cd.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** + * Expand the argument to "cd" and change directory. First try tilde + * expansion, then variable substitution. If the CDPATH variable is set + * (e.g. CDPATH=".:~:/usr"), try all the paths contained there. + * We do not support such rare substitutions as ${var:-value} etc. + * No quoting is implemented here, so ${VAR} and $VAR will be always + * substituted. Wildcards are not supported either. + * Advanced users should be encouraged to use "\cd" instead of "cd" if + * they want the behavior they are used to in the shell. + * + * @param _path string to examine + * @return newly allocated string + */ + +static GString * +examine_cd (const char *_path) +{ + /* *INDENT-OFF* */ + typedef enum + { + copy_sym, + subst_var + } state_t; + /* *INDENT-ON* */ + + state_t state = copy_sym; + GString *q; + char *path_tilde, *path; + char *p; + + /* Tilde expansion */ + path = strutils_shell_unescape (_path); + path_tilde = tilde_expand (path); + g_free (path); + + q = g_string_sized_new (32); + + /* Variable expansion */ + for (p = path_tilde; *p != '\0';) + { + switch (state) + { + case copy_sym: + if (p[0] == '\\' && p[1] == '$') + { + g_string_append_c (q, '$'); + p += 2; + } + else if (p[0] != '$' || p[1] == '[' || p[1] == '(') + { + g_string_append_c (q, *p); + p++; + } + else + state = subst_var; + break; + + case subst_var: + { + char *s = NULL; + char c; + const char *t = NULL; + + /* skip dollar */ + p++; + + if (p[0] == '{') + { + p++; + s = strchr (p, '}'); + } + if (s == NULL) + s = strchr (p, PATH_SEP); + if (s == NULL) + s = strchr (p, '\0'); + c = *s; + *s = '\0'; + t = getenv (p); + *s = c; + if (t == NULL) + { + g_string_append_c (q, '$'); + if (p[-1] != '$') + g_string_append_c (q, '{'); + } + else + { + g_string_append (q, t); + p = s; + if (*s == '}') + p++; + } + + state = copy_sym; + break; + } + + default: + break; + } + } + + g_free (path_tilde); + + return q; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* CDPATH handling */ +static gboolean +handle_cdpath (const char *path) +{ + gboolean result = FALSE; + + /* CDPATH handling */ + if (!IS_PATH_SEP (*path)) + { + char *cdpath, *p; + char c; + + cdpath = g_strdup (getenv ("CDPATH")); + p = cdpath; + c = (p == NULL) ? '\0' : ':'; + + while (!result && c == ':') + { + char *s; + + s = strchr (p, ':'); + if (s == NULL) + s = strchr (p, '\0'); + c = *s; + *s = '\0'; + if (*p != '\0') + { + vfs_path_t *r_vpath; + + r_vpath = vfs_path_build_filename (p, path, (char *) NULL); + result = panel_cd (current_panel, r_vpath, cd_parse_command); + vfs_path_free (r_vpath, TRUE); + } + *s = c; + p = s + 1; + } + g_free (cdpath); + } + + return result; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** Execute the cd command to specified path + * + * @param path path to cd + */ + +void +cd_to (const char *path) +{ + char *p; + + /* Remove leading whitespaces. */ + /* Any final whitespace should be removed here (to see why, try "cd fred "). */ + /* NOTE: I think we should not remove the extra space, + that way, we can cd into hidden directories */ + /* FIXME: what about interpreting quoted strings like the shell. + so one could type "cd <tab> M-a <enter>" and it would work. */ + p = g_strstrip (g_strdup (path)); + + if (get_current_type () == view_tree) + { + vfs_path_t *new_vpath = NULL; + + if (p[0] == '\0') + { + new_vpath = vfs_path_from_str (mc_config_get_home_dir ()); + sync_tree (new_vpath); + } + else if (DIR_IS_DOTDOT (p)) + { + if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 || + strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1) + { + vfs_path_t *tmp_vpath = current_panel->cwd_vpath; + + current_panel->cwd_vpath = + vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1); + vfs_path_free (tmp_vpath, TRUE); + } + sync_tree (current_panel->cwd_vpath); + } + else + { + if (IS_PATH_SEP (*p)) + new_vpath = vfs_path_from_str (p); + else + new_vpath = vfs_path_append_new (current_panel->cwd_vpath, p, (char *) NULL); + + sync_tree (new_vpath); + } + + vfs_path_free (new_vpath, TRUE); + } + else + { + GString *s_path; + vfs_path_t *q_vpath; + gboolean ok; + + s_path = examine_cd (p); + + if (s_path->len == 0) + q_vpath = vfs_path_from_str (mc_config_get_home_dir ()); + else + q_vpath = vfs_path_from_str_flags (s_path->str, VPF_NO_CANON); + + ok = panel_cd (current_panel, q_vpath, cd_parse_command); + if (!ok) + ok = handle_cdpath (s_path->str); + if (!ok) + { + char *d; + + d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD); + message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), d, + unix_error_string (errno)); + g_free (d); + } + + vfs_path_free (q_vpath, TRUE); + g_string_free (s_path, TRUE); + } + + g_free (p); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/cd.h b/src/filemanager/cd.h new file mode 100644 index 0000000..a63278a --- /dev/null +++ b/src/filemanager/cd.h @@ -0,0 +1,22 @@ +/** \file cd.h + * \brief Header: cd function + */ + +#ifndef MC__CD_H +#define MC__CD_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 cd_to (const char *path); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__CD_H */ diff --git a/src/filemanager/chattr.c b/src/filemanager/chattr.c new file mode 100644 index 0000000..b8967ea --- /dev/null +++ b/src/filemanager/chattr.c @@ -0,0 +1,1363 @@ +/* + Chattr command -- for the Midnight Commander + + Copyright (C) 2020-2022 + 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 chattr.c + * \brief Source: chattr command + */ + +/* TODO: change attributes recursively (ticket #3109) */ + +#include <config.h> + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <e2p/e2p.h> +#include <ext2fs/ext2_fs.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" /* tty_print*() */ +#include "lib/tty/color.h" /* tty_setcolor() */ +#include "lib/skin.h" /* COLOR_NORMAL, DISABLED_COLOR */ +#include "lib/vfs/vfs.h" +#include "lib/widget.h" + +#include "src/keymap.h" /* chattr_map */ + +#include "cmd.h" /* chattr_cmd(), chattr_get_as_str() */ + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define B_MARKED B_USER +#define B_SETALL (B_USER + 1) +#define B_SETMRK (B_USER + 2) +#define B_CLRMRK (B_USER + 3) + +#define BUTTONS 6 + +#define CHATTRBOXES(x) ((WChattrBoxes *)(x)) + +/*** file scope type declarations ****************************************************************/ + +typedef struct WFileAttrText WFileAttrText; + +struct WFileAttrText +{ + Widget widget; /* base class */ + + char *filename; + int filename_width; /* cached width of file name */ + char attrs[32 + 1]; /* 32 bits in attributes (unsigned long) */ +}; + +typedef struct WChattrBoxes WChattrBoxes; + +struct WChattrBoxes +{ + WGroup base; /* base class */ + + int pos; /* The current checkbox selected */ + int top; /* The first flag displayed */ +}; + +/*** file scope variables ************************************************************************/ + +/* see /usr/include/ext2fs/ext2_fs.h + * + * EXT2_SECRM_FL 0x00000001 -- Secure deletion + * EXT2_UNRM_FL 0x00000002 -- Undelete + * EXT2_COMPR_FL 0x00000004 -- Compress file + * EXT2_SYNC_FL 0x00000008 -- Synchronous updates + * EXT2_IMMUTABLE_FL 0x00000010 -- Immutable file + * EXT2_APPEND_FL 0x00000020 -- writes to file may only append + * EXT2_NODUMP_FL 0x00000040 -- do not dump file + * EXT2_NOATIME_FL 0x00000080 -- do not update atime + * * Reserved for compression usage... + * EXT2_DIRTY_FL 0x00000100 + * EXT2_COMPRBLK_FL 0x00000200 -- One or more compressed clusters + * EXT2_NOCOMPR_FL 0x00000400 -- Access raw compressed data + * * nb: was previously EXT2_ECOMPR_FL + * EXT4_ENCRYPT_FL 0x00000800 -- encrypted inode + * * End compression flags --- maybe not all used + * EXT2_BTREE_FL 0x00001000 -- btree format dir + * EXT2_INDEX_FL 0x00001000 -- hash-indexed directory + * EXT2_IMAGIC_FL 0x00002000 + * EXT3_JOURNAL_DATA_FL 0x00004000 -- file data should be journaled + * EXT2_NOTAIL_FL 0x00008000 -- file tail should not be merged + * EXT2_DIRSYNC_FL 0x00010000 -- Synchronous directory modifications + * EXT2_TOPDIR_FL 0x00020000 -- Top of directory hierarchies + * EXT4_HUGE_FILE_FL 0x00040000 -- Set to each huge file + * EXT4_EXTENTS_FL 0x00080000 -- Inode uses extents + * EXT4_VERITY_FL 0x00100000 -- Verity protected inode + * EXT4_EA_INODE_FL 0x00200000 -- Inode used for large EA + * EXT4_EOFBLOCKS_FL 0x00400000 was here, unused + * FS_NOCOW_FL 0x00800000 -- Do not cow file + * EXT4_SNAPFILE_FL 0x01000000 -- Inode is a snapshot + * FS_DAX_FL 0x02000000 -- Inode is DAX + * EXT4_SNAPFILE_DELETED_FL 0x04000000 -- Snapshot is being deleted + * EXT4_SNAPFILE_SHRUNK_FL 0x08000000 -- Snapshot shrink has completed + * EXT4_INLINE_DATA_FL 0x10000000 -- Inode has inline data + * EXT4_PROJINHERIT_FL 0x20000000 -- Create with parents projid + * EXT4_CASEFOLD_FL 0x40000000 -- Casefolded file + * 0x80000000 -- unused yet + */ + +static struct +{ + unsigned long flags; + char attr; + const char *text; + gboolean selected; + gboolean state; /* state of checkboxes */ +} check_attr[] = +{ + /* *INDENT-OFF* */ + { EXT2_SECRM_FL, 's', N_("Secure deletion"), FALSE, FALSE }, + { EXT2_UNRM_FL, 'u', N_("Undelete"), FALSE, FALSE }, + { EXT2_SYNC_FL, 'S', N_("Synchronous updates"), FALSE, FALSE }, + { EXT2_DIRSYNC_FL, 'D', N_("Synchronous directory updates"), FALSE, FALSE }, + { EXT2_IMMUTABLE_FL, 'i', N_("Immutable"), FALSE, FALSE }, + { EXT2_APPEND_FL, 'a', N_("Append only"), FALSE, FALSE }, + { EXT2_NODUMP_FL, 'd', N_("No dump"), FALSE, FALSE }, + { EXT2_NOATIME_FL, 'A', N_("No update atime"), FALSE, FALSE }, + { EXT2_COMPR_FL, 'c', N_("Compress"), FALSE, FALSE }, +#ifdef EXT2_COMPRBLK_FL + /* removed in v1.43-WIP-2015-05-18 + ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */ + { EXT2_COMPRBLK_FL, 'B', N_("Compressed clusters"), FALSE, FALSE }, +#endif +#ifdef EXT2_DIRTY_FL + /* removed in v1.43-WIP-2015-05-18 + ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */ + { EXT2_DIRTY_FL, 'Z', N_("Compressed dirty file"), FALSE, FALSE }, +#endif +#ifdef EXT2_NOCOMPR_FL + /* removed in v1.43-WIP-2015-05-18 + ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */ + { EXT2_NOCOMPR_FL, 'X', N_("Compression raw access"), FALSE, FALSE }, +#endif +#ifdef EXT4_ENCRYPT_FL + { EXT4_ENCRYPT_FL, 'E', N_("Encrypted inode"), FALSE, FALSE }, +#endif + { EXT3_JOURNAL_DATA_FL, 'j', N_("Journaled data"), FALSE, FALSE }, + { EXT2_INDEX_FL, 'I', N_("Indexed directory"), FALSE, FALSE }, + { EXT2_NOTAIL_FL, 't', N_("No tail merging"), FALSE, FALSE }, + { EXT2_TOPDIR_FL, 'T', N_("Top of directory hierarchies"), FALSE, FALSE }, + { EXT4_EXTENTS_FL, 'e', N_("Inode uses extents"), FALSE, FALSE }, +#ifdef EXT4_HUGE_FILE_FL + /* removed in v1.43.9 + ext2fsprogs 4825daeb0228e556444d199274b08c499ac3706c 2018-02-06 */ + { EXT4_HUGE_FILE_FL, 'h', N_("Huge_file"), FALSE, FALSE }, +#endif + { FS_NOCOW_FL, 'C', N_("No COW"), FALSE, FALSE }, +#ifdef FS_DAX_FL + /* added in v1.45.7 + ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */ + { FS_DAX_FL, 'x', N_("Direct access for files"), FALSE, FALSE }, +#endif +#ifdef EXT4_CASEFOLD_FL + /* added in v1.45.0 + ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */ + { EXT4_CASEFOLD_FL, 'F', N_("Casefolded file"), FALSE, FALSE }, +#endif +#ifdef EXT4_INLINE_DATA_FL + { EXT4_INLINE_DATA_FL, 'N', N_("Inode has inline data"), FALSE, FALSE }, +#endif +#ifdef EXT4_PROJINHERIT_FL + /* added in v1.43-WIP-2016-05-12 + ext2fsprogs e1cec4464bdaf93ea609de43c5cdeb6a1f553483 2016-03-07 + 97d7e2fdb2ebec70c3124c1a6370d28ec02efad0 2016-05-09 */ + { EXT4_PROJINHERIT_FL, 'P', N_("Project hierarchy"), FALSE, FALSE }, +#endif +#ifdef EXT4_VERITY_FL + /* added in v1.44.4 + ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14 + v1.44.5 + ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */ + { EXT4_VERITY_FL, 'V', N_("Verity protected inode"), FALSE, FALSE } +#endif + /* *INDENT-ON* */ +}; + +/* number of attributes */ +static const size_t check_attr_num = G_N_ELEMENTS (check_attr); + +/* modifable attribute numbers */ +static int check_attr_mod[32]; +static int check_attr_mod_num = 0; /* 0..31 */ + +/* maximum width of attribute text */ +static int check_attr_width = 0; + +static struct +{ + int ret_cmd; + button_flags_t flags; + int width; + const char *text; + Widget *button; +} chattr_but[BUTTONS] = +{ + /* *INDENT-OFF* */ + /* 0 */ { B_SETALL, NORMAL_BUTTON, 0, N_("Set &all"), NULL }, + /* 1 */ { B_MARKED, NORMAL_BUTTON, 0, N_("&Marked all"), NULL }, + /* 2 */ { B_SETMRK, NORMAL_BUTTON, 0, N_("S&et marked"), NULL }, + /* 3 */ { B_CLRMRK, NORMAL_BUTTON, 0, N_("C&lear marked"), NULL }, + /* 4 */ { B_ENTER, DEFPUSH_BUTTON, 0, N_("&Set"), NULL }, + /* 5 */ { B_CANCEL, NORMAL_BUTTON, 0, N_("&Cancel"), NULL } + /* *INDENT-ON* */ +}; + +static gboolean flags_changed; +static int current_file; +static gboolean ignore_all; + +static unsigned long and_mask, or_mask, flags; + +static WFileAttrText *file_attr; + +/* x-coord of widget in the dialog */ +static const int wx = 3; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static inline gboolean +chattr_is_modifiable (size_t i) +{ + return ((check_attr[i].flags & EXT2_FL_USER_MODIFIABLE) != 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattr_fill_str (unsigned long attr, char *str) +{ + size_t i; + + for (i = 0; i < check_attr_num; i++) + str[i] = (attr & check_attr[i].flags) != 0 ? check_attr[i].attr : '-'; + + str[check_attr_num] = '\0'; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +fileattrtext_fill (WFileAttrText * fat, unsigned long attr) +{ + chattr_fill_str (attr, fat->attrs); + widget_draw (WIDGET (fat)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +fileattrtext_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WFileAttrText *fat = (WFileAttrText *) w; + + switch (msg) + { + case MSG_DRAW: + { + int color; + size_t i; + + color = COLOR_NORMAL; + tty_setcolor (color); + + if (w->rect.cols > fat->filename_width) + { + widget_gotoyx (w, 0, (w->rect.cols - fat->filename_width) / 2); + tty_print_string (fat->filename); + } + else + { + widget_gotoyx (w, 0, 0); + tty_print_string (str_trunc (fat->filename, w->rect.cols)); + } + + /* hope that w->cols is greater than check_attr_num */ + widget_gotoyx (w, 1, (w->rect.cols - check_attr_num) / 2); + for (i = 0; i < check_attr_num; i++) + { + /* Do not set new color for each symbol. Try to use previous color. */ + if (chattr_is_modifiable (i)) + { + if (color == DISABLED_COLOR) + { + color = COLOR_NORMAL; + tty_setcolor (color); + } + } + else + { + if (color != DISABLED_COLOR) + { + color = DISABLED_COLOR; + tty_setcolor (color); + } + } + + tty_print_char (fat->attrs[i]); + } + return MSG_HANDLED; + } + + case MSG_RESIZE: + { + const WRect *wo = &CONST_WIDGET (w->owner)->rect; + + widget_default_callback (w, sender, msg, parm, data); + /* intially file name may be wider than screen */ + if (fat->filename_width > wo->cols - wx * 2) + { + w->rect.x = wo->x + wx; + w->rect.cols = wo->cols - wx * 2; + } + return MSG_HANDLED; + } + + case MSG_DESTROY: + g_free (fat->filename); + return MSG_HANDLED; + + default: + return widget_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WFileAttrText * +fileattrtext_new (int y, int x, const char *filename, unsigned long attr) +{ + WRect r = { y, x, 2, 1 }; + WFileAttrText *fat; + int width; + + width = str_term_width1 (filename); + r.cols = MAX (width, (int) check_attr_num); + + fat = g_new (WFileAttrText, 1); + widget_init (WIDGET (fat), &r, fileattrtext_callback, NULL); + + fat->filename = g_strdup (filename); + fat->filename_width = width; + fileattrtext_fill (fat, attr); + + return fat; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattr_draw_select (const Widget * w, gboolean selected) +{ + widget_gotoyx (w, 0, -1); + tty_print_char (selected ? '*' : ' '); + widget_gotoyx (w, 0, 1); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattr_toggle_select (const WChattrBoxes * cb, int Id) +{ + Widget *w; + + /* find checkbox */ + w = WIDGET (g_list_nth_data (CONST_GROUP (cb)->widgets, Id - cb->top)); + + check_attr[Id].selected = !check_attr[Id].selected; + + tty_setcolor (COLOR_NORMAL); + chattr_draw_select (w, check_attr[Id].selected); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +chattrboxes_draw_scrollbar (const WChattrBoxes * cb) +{ + const Widget *w = CONST_WIDGET (cb); + int max_line; + int line; + int i; + + /* Are we at the top? */ + widget_gotoyx (w, 0, w->rect.cols); + if (cb->top == 0) + tty_print_one_vline (TRUE); + else + tty_print_char ('^'); + + max_line = w->rect.lines - 1; + + /* Are we at the bottom? */ + widget_gotoyx (w, max_line, w->rect.cols); + if (cb->top + w->rect.lines == check_attr_mod_num || w->rect.lines >= check_attr_mod_num) + tty_print_one_vline (TRUE); + else + tty_print_char ('v'); + + /* Now draw the nice relative pointer */ + line = 1 + (cb->pos * (w->rect.lines - 2)) / check_attr_mod_num; + + for (i = 1; i < max_line; i++) + { + widget_gotoyx (w, i, w->rect.cols); + if (i != line) + tty_print_one_vline (TRUE); + else + tty_print_char ('*'); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattrboxes_draw (WChattrBoxes * cb) +{ + Widget *w = WIDGET (cb); + int i; + GList *l; + const int *colors; + + colors = widget_get_colors (w); + tty_setcolor (colors[DLG_COLOR_NORMAL]); + tty_fill_region (w->rect.y, w->rect.x - 1, w->rect.lines, w->rect.cols + 1, ' '); + + /* redraw checkboxes */ + group_default_callback (w, NULL, MSG_DRAW, 0, NULL); + + /* draw scrollbar */ + tty_setcolor (colors[DLG_COLOR_NORMAL]); + if (!mc_global.tty.slow_terminal && check_attr_mod_num > w->rect.lines) + chattrboxes_draw_scrollbar (cb); + + /* mark selected checkboxes */ + for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l)) + chattr_draw_select (WIDGET (l->data), check_attr[i].selected); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattrboxes_rename (WChattrBoxes * cb) +{ + Widget *w = WIDGET (cb); + gboolean active; + int i; + GList *l; + char btext[BUF_SMALL]; /* FIXME: is 128 bytes enough? */ + + active = widget_get_state (w, WST_ACTIVE); + + /* lock the group to avoid redraw of checkboxes individually */ + if (active) + widget_set_state (w, WST_SUSPENDED, TRUE); + + for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l)) + { + WCheck *c = CHECK (l->data); + int m; + + m = check_attr_mod[i]; + g_snprintf (btext, sizeof (btext), "(%c) %s", check_attr[m].attr, check_attr[m].text); + check_set_text (c, btext); + c->state = check_attr[m].state; + } + + /* unlock */ + if (active) + widget_set_state (w, WST_ACTIVE, TRUE); + + widget_draw (w); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +checkboxes_save_state (const WChattrBoxes * cb) +{ + int i; + GList *l; + + for (i = cb->top, l = CONST_GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l)) + { + int m; + + m = check_attr_mod[i]; + check_attr[m].state = CHECK (l->data)->state; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_down (WChattrBoxes * cb) +{ + if (cb->pos == cb->top + WIDGET (cb)->rect.lines - 1) + { + /* We are on the last checkbox. + Keep this position. */ + + if (cb->pos == check_attr_mod_num - 1) + /* get out of widget */ + return MSG_NOT_HANDLED; + + /* emulate scroll of checkboxes */ + checkboxes_save_state (cb); + cb->pos++; + cb->top++; + chattrboxes_rename (cb); + } + else /* cb->pos > cb-top */ + { + GList *l; + + /* select next checkbox */ + cb->pos++; + l = g_list_next (GROUP (cb)->current); + widget_select (WIDGET (l->data)); + } + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_page_down (WChattrBoxes * cb) +{ + WGroup *g = GROUP (cb); + GList *l; + + if (cb->pos == check_attr_mod_num - 1) + { + /* We are on the last checkbox. + Keep this position. + Do nothing. */ + l = g_list_last (g->widgets); + } + else + { + int i = WIDGET (cb)->rect.lines; + + checkboxes_save_state (cb); + + if (cb->top > check_attr_mod_num - 2 * i) + i = check_attr_mod_num - i - cb->top; + if (cb->top + i < 0) + i = -cb->top; + if (i == 0) + { + cb->pos = check_attr_mod_num - 1; + cb->top += i; + l = g_list_last (g->widgets); + } + else + { + cb->pos += i; + cb->top += i; + l = g_list_nth (g->widgets, cb->pos - cb->top); + } + + chattrboxes_rename (cb); + } + + widget_select (WIDGET (l->data)); + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_end (WChattrBoxes * cb) +{ + GList *l; + + checkboxes_save_state (cb); + cb->pos = check_attr_mod_num - 1; + cb->top = cb->pos - WIDGET (cb)->rect.lines + 1; + l = g_list_last (GROUP (cb)->widgets); + chattrboxes_rename (cb); + widget_select (WIDGET (l->data)); + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_up (WChattrBoxes * cb) +{ + if (cb->pos == cb->top) + { + /* We are on the first checkbox. + Keep this position. */ + + if (cb->top == 0) + /* get out of widget */ + return MSG_NOT_HANDLED; + + /* emulate scroll of checkboxes */ + checkboxes_save_state (cb); + cb->pos--; + cb->top--; + chattrboxes_rename (cb); + } + else /* cb->pos > cb-top */ + { + GList *l; + + /* select previous checkbox */ + cb->pos--; + l = g_list_previous (GROUP (cb)->current); + widget_select (WIDGET (l->data)); + } + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_page_up (WChattrBoxes * cb) +{ + WGroup *g = GROUP (cb); + GList *l; + + if (cb->pos == 0 && cb->top == 0) + { + /* We are on the first checkbox. + Keep this position. + Do nothing. */ + l = g_list_first (g->widgets); + } + else + { + int i = WIDGET (cb)->rect.lines; + + checkboxes_save_state (cb); + + if (cb->top < i) + i = cb->top; + if (i == 0) + { + cb->pos = 0; + cb->top -= i; + l = g_list_first (g->widgets); + } + else + { + cb->pos -= i; + cb->top -= i; + l = g_list_nth (g->widgets, cb->pos - cb->top); + } + + chattrboxes_rename (cb); + } + + widget_select (WIDGET (l->data)); + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_home (WChattrBoxes * cb) +{ + GList *l; + + checkboxes_save_state (cb); + cb->pos = 0; + cb->top = 0; + l = g_list_first (GROUP (cb)->widgets); + chattrboxes_rename (cb); + widget_select (WIDGET (l->data)); + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_execute_cmd (WChattrBoxes * cb, long command) +{ + switch (command) + { + case CK_Down: + return chattrboxes_down (cb); + + case CK_PageDown: + return chattrboxes_page_down (cb); + + case CK_Bottom: + return chattrboxes_end (cb); + + case CK_Up: + return chattrboxes_up (cb); + + case CK_PageUp: + return chattrboxes_page_up (cb); + + case CK_Top: + return chattrboxes_home (cb); + + case CK_Mark: + case CK_MarkAndDown: + { + chattr_toggle_select (cb, cb->pos); /* FIXME */ + if (command == CK_MarkAndDown) + chattrboxes_down (cb); + + return MSG_HANDLED; + } + + default: + return MSG_NOT_HANDLED; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_key (WChattrBoxes * cb, int key) +{ + long command; + + command = widget_lookup_key (WIDGET (cb), key); + if (command == CK_IgnoreKey) + return MSG_NOT_HANDLED; + return chattrboxes_execute_cmd (cb, command); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chattrboxes_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WChattrBoxes *cb = CHATTRBOXES (w); + WGroup *g = GROUP (w); + + switch (msg) + { + case MSG_DRAW: + chattrboxes_draw (cb); + return MSG_HANDLED; + + case MSG_NOTIFY: + { + /* handle checkboxes */ + int i; + + i = g_list_index (g->widgets, sender); + if (i >= 0) + { + int m; + + i += cb->top; + m = check_attr_mod[i]; + flags ^= check_attr[m].flags; + fileattrtext_fill (file_attr, flags); + chattr_toggle_select (cb, i); + flags_changed = TRUE; + return MSG_HANDLED; + } + } + return MSG_NOT_HANDLED; + + case MSG_CHANGED_FOCUS: + /* sender is one of chattr checkboxes */ + if (widget_get_state (sender, WST_FOCUSED)) + { + int i; + + i = g_list_index (g->widgets, sender); + cb->pos = cb->top + i; + } + return MSG_HANDLED; + + case MSG_KEY: + { + cb_ret_t ret; + + ret = chattrboxes_key (cb, parm); + if (ret != MSG_HANDLED) + ret = group_default_callback (w, NULL, MSG_KEY, parm, NULL); + + return ret; + } + + case MSG_ACTION: + return chattrboxes_execute_cmd (cb, parm); + + case MSG_DESTROY: + /* save all states */ + checkboxes_save_state (cb); + MC_FALLTHROUGH; + + default: + return group_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +chattrboxes_handle_mouse_event (Widget * w, Gpm_Event * event) +{ + int mou; + + mou = mouse_handle_event (w, event); + if (mou == MOU_UNHANDLED) + mou = group_handle_mouse_event (w, event); + + return mou; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattrboxes_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) +{ + WChattrBoxes *cb = CHATTRBOXES (w); + + (void) event; + + switch (msg) + { + case MSG_MOUSE_SCROLL_UP: + chattrboxes_up (cb); + break; + + case MSG_MOUSE_SCROLL_DOWN: + chattrboxes_down (cb); + break; + + default: + /* return MOU_UNHANDLED */ + event->result.abort = TRUE; + break; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WChattrBoxes * +chattrboxes_new (const WRect * r) +{ + WChattrBoxes *cb; + Widget *w; + WGroup *cbg; + int i; + + cb = g_new0 (WChattrBoxes, 1); + w = WIDGET (cb); + cbg = GROUP (cb); + group_init (cbg, r, chattrboxes_callback, chattrboxes_mouse_callback); + w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR; + w->mouse_handler = chattrboxes_handle_mouse_event; + w->keymap = chattr_map; + + /* create checkboxes */ + for (i = 0; i < r->lines; i++) + { + int m; + WCheck *check; + + m = check_attr_mod[i]; + + check = check_new (i, 0, check_attr[m].state, NULL); + group_add_widget (cbg, check); + } + + chattrboxes_rename (cb); + + /* select first checkbox */ + cbg->current = cbg->widgets; + + return cb; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattr_init (void) +{ + static gboolean i18n = FALSE; + size_t i; + + for (i = 0; i < check_attr_num; i++) + check_attr[i].selected = FALSE; + + if (i18n) + return; + + i18n = TRUE; + + for (i = 0; i < check_attr_num; i++) + if (chattr_is_modifiable (i)) + { + int width; + +#ifdef ENABLE_NLS + check_attr[i].text = _(check_attr[i].text); +#endif + + check_attr_mod[check_attr_mod_num++] = i; + + width = 4 + str_term_width1 (check_attr[i].text); /* "(Q) text " */ + check_attr_width = MAX (check_attr_width, width); + } + + check_attr_width += 1 + 3 + 1; /* mark, [x] and space */ + + for (i = 0; i < BUTTONS; i++) + { +#ifdef ENABLE_NLS + chattr_but[i].text = _(chattr_but[i].text); +#endif + + chattr_but[i].width = str_term_width1 (chattr_but[i].text) + 3; /* [], spaces and w/o & */ + if (chattr_but[i].flags == DEFPUSH_BUTTON) + chattr_but[i].width += 2; /* <> */ + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WDialog * +chattr_dlg_create (WPanel * panel, const char *fname, unsigned long attr) +{ + Widget *mw = WIDGET (WIDGET (panel)->owner); + gboolean single_set; + WDialog *ch_dlg; + int lines, cols; + int checkboxes_lines = check_attr_mod_num; + size_t i; + int y; + Widget *dw; + WGroup *dg; + WChattrBoxes *cb; + const int cb_scrollbar_width = 1; + WRect r; + + /* prepate to set up checkbox states */ + for (i = 0; i < check_attr_num; i++) + check_attr[i].state = chattr_is_modifiable (i) && (attr & check_attr[i].flags) != 0; + + cols = check_attr_width + cb_scrollbar_width; + + single_set = (panel->marked < 2); + + lines = 5 + checkboxes_lines + 4; + if (!single_set) + lines += 3; + + if (lines >= mw->rect.lines - 2) + { + int dl; + + dl = lines - (mw->rect.lines - 2); + lines -= dl; + checkboxes_lines -= dl; + } + + ch_dlg = + dlg_create (TRUE, 0, 0, lines, cols + wx * 2, WPOS_CENTER, FALSE, dialog_colors, + dlg_default_callback, NULL, "[Chattr]", _("Chattr command")); + dg = GROUP (ch_dlg); + dw = WIDGET (ch_dlg); + + y = 2; + file_attr = fileattrtext_new (y, wx, fname, attr); + group_add_widget_autopos (dg, file_attr, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL); + y += WIDGET (file_attr)->rect.lines; + group_add_widget (dg, hline_new (y++, -1, -1)); + + if (cols < WIDGET (file_attr)->rect.cols) + { + r = dw->rect; + cols = WIDGET (file_attr)->rect.cols; + cols = MIN (cols, mw->rect.cols - wx * 2); + r.cols = cols + wx * 2; + r.lines = lines; + widget_set_size_rect (dw, &r); + } + + checkboxes_lines = MIN (check_attr_mod_num, checkboxes_lines); + rect_init (&r, y++, wx, checkboxes_lines > 0 ? checkboxes_lines : 1, cols); + cb = chattrboxes_new (&r); + group_add_widget_autopos (dg, cb, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL); + + y += checkboxes_lines - 1; + cols = 0; + + for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++) + { + if (i == 0 || i == BUTTONS - 2) + group_add_widget (dg, hline_new (y++, -1, -1)); + + chattr_but[i].button = WIDGET (button_new (y, dw->rect.cols / 2 + 1 - chattr_but[i].width, + chattr_but[i].ret_cmd, chattr_but[i].flags, + chattr_but[i].text, NULL)); + group_add_widget (dg, chattr_but[i].button); + + i++; + chattr_but[i].button = + WIDGET (button_new (y++, dw->rect.cols / 2 + 2, chattr_but[i].ret_cmd, + chattr_but[i].flags, chattr_but[i].text, NULL)); + group_add_widget (dg, chattr_but[i].button); + + /* two buttons in a row */ + cols = + MAX (cols, chattr_but[i - 1].button->rect.cols + 1 + chattr_but[i].button->rect.cols); + } + + /* adjust dialog size and button positions */ + cols += 6; + if (cols > dw->rect.cols) + { + r = dw->rect; + r.lines = lines; + r.cols = cols; + widget_set_size_rect (dw, &r); + + /* dialog center */ + cols = dw->rect.x + dw->rect.cols / 2 + 1; + + for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++) + { + Widget *b; + + b = chattr_but[i++].button; + r = b->rect; + r.x = cols - r.cols; + widget_set_size_rect (b, &r); + + b = chattr_but[i].button; + r = b->rect; + r.x = cols + 1; + widget_set_size_rect (b, &r); + } + } + + widget_select (WIDGET (cb)); + + return ch_dlg; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattr_done (gboolean need_update) +{ + if (need_update) + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static const GString * +next_file (const WPanel * panel) +{ + while (!panel->dir.list[current_file].f.marked) + current_file++; + + return panel->dir.list[current_file].fname; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +try_chattr (const char *p, unsigned long m) +{ + while (fsetflags (p, m) == -1 && !ignore_all) + { + int my_errno = errno; + int result; + char *msg; + + msg = + g_strdup_printf (_("Cannot chattr \"%s\"\n%s"), x_basename (p), + unix_error_string (my_errno)); + result = + query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"), + _("&Cancel")); + g_free (msg); + + switch (result) + { + case 0: + /* try next file */ + return TRUE; + + case 1: + ignore_all = TRUE; + /* try next file */ + return TRUE; + + case 2: + /* retry this file */ + break; + + case 3: + default: + /* stop remain files processing */ + return FALSE; + } + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +do_chattr (WPanel * panel, const vfs_path_t * p, unsigned long m) +{ + gboolean ret; + + m &= and_mask; + m |= or_mask; + + ret = try_chattr (vfs_path_as_str (p), m); + + do_file_mark (panel, current_file, 0); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chattr_apply_mask (WPanel * panel, vfs_path_t * vpath, unsigned long m) +{ + gboolean ok; + + if (!do_chattr (panel, vpath, m)) + return; + + do + { + const GString *fname; + + fname = next_file (panel); + ok = (fgetflags (fname->str, &m) == 0); + + if (!ok) + { + /* if current file was deleted outside mc -- try next file */ + /* decrease panel->marked */ + do_file_mark (panel, current_file, 0); + + /* try next file */ + ok = TRUE; + } + else + { + vpath = vfs_path_from_str (fname->str); + flags = m; + ok = do_chattr (panel, vpath, m); + vfs_path_free (vpath, TRUE); + } + } + while (ok && panel->marked != 0); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +chattr_cmd (WPanel * panel) +{ + gboolean need_update = FALSE; + gboolean end_chattr = FALSE; + + chattr_init (); + + current_file = 0; + ignore_all = FALSE; + + do + { /* do while any files remaining */ + vfs_path_t *vpath; + WDialog *ch_dlg; + const GString *fname; + const char *fname2; + size_t i; + int result; + + if (!vfs_current_is_local ()) + { + message (D_ERROR, MSG_ERROR, "%s", + _("Cannot change attributes on non-local filesystems")); + break; + } + + do_refresh (); + + need_update = FALSE; + end_chattr = FALSE; + + if (panel->marked != 0) + fname = next_file (panel); /* next marked file */ + else + fname = selection (panel)->fname; /* single file */ + + vpath = vfs_path_from_str (fname->str); + fname2 = vfs_path_as_str (vpath); + + if (fgetflags (fname2, &flags) != 0) + { + message (D_ERROR, MSG_ERROR, _("Cannot get flags of \"%s\"\n%s"), fname->str, + unix_error_string (errno)); + vfs_path_free (vpath, TRUE); + break; + } + + flags_changed = FALSE; + + ch_dlg = chattr_dlg_create (panel, fname->str, flags); + result = dlg_run (ch_dlg); + widget_destroy (WIDGET (ch_dlg)); + + switch (result) + { + case B_CANCEL: + end_chattr = TRUE; + break; + + case B_ENTER: + if (flags_changed) + { + if (panel->marked <= 1) + { + /* single or last file */ + if (fsetflags (fname2, flags) == -1 && !ignore_all) + message (D_ERROR, MSG_ERROR, _("Cannot chattr \"%s\"\n%s"), fname->str, + unix_error_string (errno)); + end_chattr = TRUE; + } + else if (!try_chattr (fname2, flags)) + { + /* stop multiple files processing */ + result = B_CANCEL; + end_chattr = TRUE; + } + } + + need_update = TRUE; + break; + + case B_SETALL: + case B_MARKED: + or_mask = 0; + and_mask = ~0; + + for (i = 0; i < check_attr_num; i++) + if (chattr_is_modifiable (i) && (check_attr[i].selected || result == B_SETALL)) + { + if (check_attr[i].state) + or_mask |= check_attr[i].flags; + else + and_mask &= ~check_attr[i].flags; + } + + chattr_apply_mask (panel, vpath, flags); + need_update = TRUE; + end_chattr = TRUE; + break; + + case B_SETMRK: + or_mask = 0; + and_mask = ~0; + + for (i = 0; i < check_attr_num; i++) + if (chattr_is_modifiable (i) && check_attr[i].selected) + or_mask |= check_attr[i].flags; + + chattr_apply_mask (panel, vpath, flags); + need_update = TRUE; + end_chattr = TRUE; + break; + + case B_CLRMRK: + or_mask = 0; + and_mask = ~0; + + for (i = 0; i < check_attr_num; i++) + if (chattr_is_modifiable (i) && check_attr[i].selected) + and_mask &= ~check_attr[i].flags; + + chattr_apply_mask (panel, vpath, flags); + need_update = TRUE; + end_chattr = TRUE; + break; + + default: + break; + } + + if (panel->marked != 0 && result != B_CANCEL) + { + do_file_mark (panel, current_file, 0); + need_update = TRUE; + } + + vfs_path_free (vpath, TRUE); + + } + while (panel->marked != 0 && !end_chattr); + + chattr_done (need_update); +} + +/* --------------------------------------------------------------------------------------------- */ + +const char * +chattr_get_as_str (unsigned long attr) +{ + static char str[32 + 1]; /* 32 bits in attributes (unsigned long) */ + + chattr_fill_str (attr, str); + + return str; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/chmod.c b/src/filemanager/chmod.c new file mode 100644 index 0000000..9e24eb1 --- /dev/null +++ b/src/filemanager/chmod.c @@ -0,0 +1,660 @@ +/* + Chmod command -- for the Midnight Commander + + Copyright (C) 1994-2022 + 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 chmod.c + * \brief Source: chmod command + */ + +#include <config.h> + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" +#include "lib/skin.h" +#include "lib/vfs/vfs.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/widget.h" + +#include "cmd.h" /* chmod_cmd() */ + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define PX 3 +#define PY 2 + +#define B_MARKED B_USER +#define B_SETALL (B_USER + 1) +#define B_SETMRK (B_USER + 2) +#define B_CLRMRK (B_USER + 3) + +#define BUTTONS 6 +#define BUTTONS_PERM 12 +#define LABELS 4 + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static struct +{ + mode_t mode; + const char *text; + gboolean selected; + WCheck *check; +} check_perm[BUTTONS_PERM] = +{ + /* *INDENT-OFF* */ + { S_ISUID, N_("set &user ID on execution"), FALSE, NULL }, + { S_ISGID, N_("set &group ID on execution"), FALSE, NULL }, + { S_ISVTX, N_("stick&y bit"), FALSE, NULL }, + { S_IRUSR, N_("&read by owner"), FALSE, NULL }, + { S_IWUSR, N_("&write by owner"), FALSE, NULL }, + { S_IXUSR, N_("e&xecute/search by owner"), FALSE, NULL }, + { S_IRGRP, N_("rea&d by group"), FALSE, NULL }, + { S_IWGRP, N_("write by grou&p"), FALSE, NULL }, + { S_IXGRP, N_("execu&te/search by group"), FALSE, NULL }, + { S_IROTH, N_("read &by others"), FALSE, NULL }, + { S_IWOTH, N_("wr&ite by others"), FALSE, NULL }, + { S_IXOTH, N_("execute/searc&h by others"), FALSE, NULL } + /* *INDENT-ON* */ +}; + +static int check_perm_len = 0; + +static const char *file_info_labels[LABELS] = { + N_("Name:"), + N_("Permissions (octal):"), + N_("Owner name:"), + N_("Group name:") +}; + +static int file_info_labels_len = 0; + +static struct +{ + int ret_cmd; + button_flags_t flags; + int y; /* vertical position relatively to dialog bottom boundary */ + int len; + const char *text; +} chmod_but[BUTTONS] = +{ + /* *INDENT-OFF* */ + { B_SETALL, NORMAL_BUTTON, 6, 0, N_("Set &all") }, + { B_MARKED, NORMAL_BUTTON, 6, 0, N_("&Marked all") }, + { B_SETMRK, NORMAL_BUTTON, 5, 0, N_("S&et marked") }, + { B_CLRMRK, NORMAL_BUTTON, 5, 0, N_("C&lear marked") }, + { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") }, + { B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") } + /* *INDENT-ON* */ +}; + +static gboolean mode_change; +static int current_file; +static gboolean ignore_all; + +static mode_t and_mask, or_mask, ch_mode; + +static WLabel *statl; +static WGroupbox *file_gb; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +chmod_init (void) +{ + static gboolean i18n = FALSE; + int i, len; + + for (i = 0; i < BUTTONS_PERM; i++) + check_perm[i].selected = FALSE; + + if (i18n) + return; + + i18n = TRUE; + +#ifdef ENABLE_NLS + for (i = 0; i < BUTTONS_PERM; i++) + check_perm[i].text = _(check_perm[i].text); + + for (i = 0; i < LABELS; i++) + file_info_labels[i] = _(file_info_labels[i]); + + for (i = 0; i < BUTTONS; i++) + chmod_but[i].text = _(chmod_but[i].text); +#endif /* ENABLE_NLS */ + + for (i = 0; i < BUTTONS_PERM; i++) + { + len = str_term_width1 (check_perm[i].text); + check_perm_len = MAX (check_perm_len, len); + } + + check_perm_len += 1 + 3 + 1; /* mark, [x] and space */ + + for (i = 0; i < LABELS; i++) + { + len = str_term_width1 (file_info_labels[i]) + 2; /* spaces around */ + file_info_labels_len = MAX (file_info_labels_len, len); + } + + for (i = 0; i < BUTTONS; i++) + { + chmod_but[i].len = str_term_width1 (chmod_but[i].text) + 3; /* [], spaces and w/o & */ + if (chmod_but[i].flags == DEFPUSH_BUTTON) + chmod_but[i].len += 2; /* <> */ + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chmod_draw_select (const WDialog * h, int Id) +{ + widget_gotoyx (h, PY + Id + 1, PX + 1); + tty_print_char (check_perm[Id].selected ? '*' : ' '); + widget_gotoyx (h, PY + Id + 1, PX + 3); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chmod_toggle_select (const WDialog * h, int Id) +{ + check_perm[Id].selected = !check_perm[Id].selected; + tty_setcolor (COLOR_NORMAL); + chmod_draw_select (h, Id); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chmod_refresh (const WDialog * h) +{ + int i; + int y, x; + + tty_setcolor (COLOR_NORMAL); + + for (i = 0; i < BUTTONS_PERM; i++) + chmod_draw_select (h, i); + + y = WIDGET (file_gb)->rect.y + 1; + x = WIDGET (file_gb)->rect.x + 2; + + tty_gotoyx (y, x); + tty_print_string (file_info_labels[0]); + tty_gotoyx (y + 2, x); + tty_print_string (file_info_labels[1]); + tty_gotoyx (y + 4, x); + tty_print_string (file_info_labels[2]); + tty_gotoyx (y + 6, x); + tty_print_string (file_info_labels[3]); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chmod_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_DRAW: + frame_callback (w, NULL, MSG_DRAW, 0, NULL); + chmod_refresh (CONST_DIALOG (w->owner)); + return MSG_HANDLED; + + default: + return frame_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chmod_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WGroup *g = GROUP (w); + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_NOTIFY: + { + /* handle checkboxes */ + int i; + + /* whether notification was sent by checkbox? */ + for (i = 0; i < BUTTONS_PERM; i++) + if (sender == WIDGET (check_perm[i].check)) + break; + + if (i < BUTTONS_PERM) + { + ch_mode ^= check_perm[i].mode; + label_set_textv (statl, "%o", (unsigned int) ch_mode); + chmod_toggle_select (h, i); + mode_change = TRUE; + return MSG_HANDLED; + } + } + + return MSG_NOT_HANDLED; + + case MSG_KEY: + if (parm == 'T' || parm == 't' || parm == KEY_IC) + { + int i; + unsigned long id; + + id = group_get_current_widget_id (g); + for (i = 0; i < BUTTONS_PERM; i++) + if (id == WIDGET (check_perm[i].check)->id) + break; + + if (i < BUTTONS_PERM) + { + chmod_toggle_select (h, i); + if (parm == KEY_IC) + group_select_next_widget (g); + return MSG_HANDLED; + } + } + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WDialog * +chmod_dlg_create (WPanel * panel, const char *fname, const struct stat *sf_stat) +{ + gboolean single_set; + WDialog *ch_dlg; + WGroup *g; + int lines, cols; + int i, y; + int perm_gb_len; + int file_gb_len; + const char *c_fname, *c_fown, *c_fgrp; + char buffer[BUF_TINY]; + + mode_change = FALSE; + + single_set = (panel->marked < 2); + perm_gb_len = check_perm_len + 2; + file_gb_len = file_info_labels_len + 2; + cols = str_term_width1 (fname) + 2 + 1; + file_gb_len = MAX (file_gb_len, cols); + + lines = single_set ? 20 : 23; + cols = perm_gb_len + file_gb_len + 1 + 6; + + if (cols > COLS) + { + /* shrink the right groupbox */ + cols = COLS; + file_gb_len = cols - (perm_gb_len + 1 + 6); + } + + ch_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, + chmod_callback, NULL, "[Chmod]", _("Chmod command")); + g = GROUP (ch_dlg); + + /* draw background */ + ch_dlg->bg->callback = chmod_bg_callback; + + group_add_widget (g, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _("Permission"))); + + for (i = 0; i < BUTTONS_PERM; i++) + { + check_perm[i].check = check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0, + check_perm[i].text); + group_add_widget (g, check_perm[i].check); + } + + file_gb = groupbox_new (PY, PX + perm_gb_len + 1, BUTTONS_PERM + 2, file_gb_len, _("File")); + group_add_widget (g, file_gb); + + /* Set the labels */ + y = PY + 2; + cols = PX + perm_gb_len + 3; + c_fname = str_trunc (fname, file_gb_len - 3); + group_add_widget (g, label_new (y, cols, c_fname)); + g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode); + statl = label_new (y + 2, cols, buffer); + group_add_widget (g, statl); + c_fown = str_trunc (get_owner (sf_stat->st_uid), file_gb_len - 3); + group_add_widget (g, label_new (y + 4, cols, c_fown)); + c_fgrp = str_trunc (get_group (sf_stat->st_gid), file_gb_len - 3); + group_add_widget (g, label_new (y + 6, cols, c_fgrp)); + + if (!single_set) + { + i = 0; + + group_add_widget (g, hline_new (lines - chmod_but[i].y - 1, -1, -1)); + + for (; i < BUTTONS - 2; i++) + { + y = lines - chmod_but[i].y; + group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len, + chmod_but[i].ret_cmd, chmod_but[i].flags, + chmod_but[i].text, NULL)); + i++; + group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, + chmod_but[i].ret_cmd, chmod_but[i].flags, + chmod_but[i].text, NULL)); + } + } + + i = BUTTONS - 2; + y = lines - chmod_but[i].y; + group_add_widget (g, hline_new (y - 1, -1, -1)); + group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len, + chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text, + NULL)); + i++; + group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chmod_but[i].ret_cmd, + chmod_but[i].flags, chmod_but[i].text, NULL)); + + /* select first checkbox */ + widget_select (WIDGET (check_perm[0].check)); + + return ch_dlg; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chmod_done (gboolean need_update) +{ + if (need_update) + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static const GString * +next_file (const WPanel * panel) +{ + while (!panel->dir.list[current_file].f.marked) + current_file++; + + return panel->dir.list[current_file].fname; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +try_chmod (const vfs_path_t * p, mode_t m) +{ + while (mc_chmod (p, m) == -1 && !ignore_all) + { + int my_errno = errno; + int result; + char *msg; + + msg = + g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), x_basename (vfs_path_as_str (p)), + unix_error_string (my_errno)); + result = + query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"), + _("&Cancel")); + g_free (msg); + + switch (result) + { + case 0: + /* try next file */ + return TRUE; + + case 1: + ignore_all = TRUE; + /* try next file */ + return TRUE; + + case 2: + /* retry this file */ + break; + + case 3: + default: + /* stop remain files processing */ + return FALSE; + } + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +do_chmod (WPanel * panel, const vfs_path_t * p, struct stat *sf) +{ + gboolean ret; + + sf->st_mode &= and_mask; + sf->st_mode |= or_mask; + + ret = try_chmod (p, sf->st_mode); + + do_file_mark (panel, current_file, 0); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +apply_mask (WPanel * panel, vfs_path_t * vpath, struct stat *sf) +{ + gboolean ok; + + if (!do_chmod (panel, vpath, sf)) + return; + + do + { + const GString *fname; + + fname = next_file (panel); + vpath = vfs_path_from_str (fname->str); + ok = (mc_stat (vpath, sf) == 0); + + if (!ok) + { + /* if current file was deleted outside mc -- try next file */ + /* decrease panel->marked */ + do_file_mark (panel, current_file, 0); + + /* try next file */ + ok = TRUE; + } + else + { + ch_mode = sf->st_mode; + + ok = do_chmod (panel, vpath, sf); + } + + vfs_path_free (vpath, TRUE); + } + while (ok && panel->marked != 0); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +chmod_cmd (WPanel * panel) +{ + gboolean need_update; + gboolean end_chmod; + + chmod_init (); + + current_file = 0; + ignore_all = FALSE; + + do + { /* do while any files remaining */ + vfs_path_t *vpath; + WDialog *ch_dlg; + struct stat sf_stat; + const GString *fname; + int i, result; + + do_refresh (); + + need_update = FALSE; + end_chmod = FALSE; + + if (panel->marked != 0) + fname = next_file (panel); /* next marked file */ + else + fname = selection (panel)->fname; /* single file */ + + vpath = vfs_path_from_str (fname->str); + + if (mc_stat (vpath, &sf_stat) != 0) + { + vfs_path_free (vpath, TRUE); + break; + } + + ch_mode = sf_stat.st_mode; + + ch_dlg = chmod_dlg_create (panel, fname->str, &sf_stat); + result = dlg_run (ch_dlg); + + switch (result) + { + case B_CANCEL: + end_chmod = TRUE; + break; + + case B_ENTER: + if (mode_change) + { + if (panel->marked <= 1) + { + /* single or last file */ + if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all) + message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"), fname->str, + unix_error_string (errno)); + end_chmod = TRUE; + } + else if (!try_chmod (vpath, ch_mode)) + { + /* stop multiple files processing */ + result = B_CANCEL; + end_chmod = TRUE; + } + } + + need_update = TRUE; + break; + + case B_SETALL: + case B_MARKED: + and_mask = or_mask = 0; + and_mask = ~and_mask; + + for (i = 0; i < BUTTONS_PERM; i++) + if (check_perm[i].selected || result == B_SETALL) + { + if (check_perm[i].check->state) + or_mask |= check_perm[i].mode; + else + and_mask &= ~check_perm[i].mode; + } + + apply_mask (panel, vpath, &sf_stat); + need_update = TRUE; + end_chmod = TRUE; + break; + + case B_SETMRK: + and_mask = or_mask = 0; + and_mask = ~and_mask; + + for (i = 0; i < BUTTONS_PERM; i++) + if (check_perm[i].selected) + or_mask |= check_perm[i].mode; + + apply_mask (panel, vpath, &sf_stat); + need_update = TRUE; + end_chmod = TRUE; + break; + + case B_CLRMRK: + and_mask = or_mask = 0; + and_mask = ~and_mask; + + for (i = 0; i < BUTTONS_PERM; i++) + if (check_perm[i].selected) + and_mask &= ~check_perm[i].mode; + + apply_mask (panel, vpath, &sf_stat); + need_update = TRUE; + end_chmod = TRUE; + break; + + default: + break; + } + + if (panel->marked != 0 && result != B_CANCEL) + { + do_file_mark (panel, current_file, 0); + need_update = TRUE; + } + + vfs_path_free (vpath, TRUE); + + widget_destroy (WIDGET (ch_dlg)); + } + while (panel->marked != 0 && !end_chmod); + + chmod_done (need_update); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/chown.c b/src/filemanager/chown.c new file mode 100644 index 0000000..9f6c206 --- /dev/null +++ b/src/filemanager/chown.c @@ -0,0 +1,549 @@ +/* + Chown command -- for the Midnight Commander + + Copyright (C) 1994-2022 + 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 chown.c + * \brief Source: chown command + */ + +#include <config.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" +#include "lib/skin.h" +#include "lib/vfs/vfs.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/widget.h" + +#include "src/setup.h" /* panels_options */ + +#include "cmd.h" /* chown_cmd() */ + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define GH 12 +#define GW 21 + +#define BUTTONS 5 + +#define B_SETALL B_USER +#define B_SETUSR (B_USER + 1) +#define B_SETGRP (B_USER + 2) + +#define LABELS 5 + +#define chown_label(n,txt) label_set_text (chown_label [n].l, txt) + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + + +static struct +{ + int ret_cmd; + button_flags_t flags; + int y; + int len; + const char *text; +} chown_but[BUTTONS] = +{ + /* *INDENT-OFF* */ + { B_SETALL, NORMAL_BUTTON, 5, 0, N_("Set &all") }, + { B_SETGRP, NORMAL_BUTTON, 5, 0, N_("Set &groups") }, + { B_SETUSR, NORMAL_BUTTON, 5, 0, N_("Set &users") }, + { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") }, + { B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") } + /* *INDENT-ON* */ +}; + +/* summary length of three buttons */ +static int blen = 0; + +static struct +{ + int y; + WLabel *l; +} chown_label[LABELS] = +{ + /* *INDENT-OFF* */ + { 4, NULL }, + { 6, NULL }, + { 8, NULL }, + { 10, NULL }, + { 12, NULL } + /* *INDENT-ON* */ +}; + +static int current_file; +static gboolean ignore_all; + +static WListbox *l_user, *l_group; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +chown_init (void) +{ + static gboolean i18n = FALSE; + int i; + + if (i18n) + return; + + i18n = TRUE; + +#ifdef ENABLE_NLS + for (i = 0; i < BUTTONS; i++) + chown_but[i].text = _(chown_but[i].text); +#endif /* ENABLE_NLS */ + + for (i = 0; i < BUTTONS; i++) + { + chown_but[i].len = str_term_width1 (chown_but[i].text) + 3; /* [], spaces and w/o & */ + if (chown_but[i].flags == DEFPUSH_BUTTON) + chown_but[i].len += 2; /* <> */ + + if (i < BUTTONS - 2) + blen += chown_but[i].len; + } + + blen += 2; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chown_refresh (const Widget * h) +{ + int y = 3; + int x = 7 + GW * 2; + + tty_setcolor (COLOR_NORMAL); + + widget_gotoyx (h, y + 0, x); + tty_print_string (_("Name")); + widget_gotoyx (h, y + 2, x); + tty_print_string (_("Owner name")); + widget_gotoyx (h, y + 4, x); + tty_print_string (_("Group name")); + widget_gotoyx (h, y + 6, x); + tty_print_string (_("Size")); + widget_gotoyx (h, y + 8, x); + tty_print_string (_("Permission")); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +chown_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_DRAW: + frame_callback (w, NULL, MSG_DRAW, 0, NULL); + chown_refresh (WIDGET (w->owner)); + return MSG_HANDLED; + + default: + return frame_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WDialog * +chown_dlg_create (WPanel * panel) +{ + int single_set; + WDialog *ch_dlg; + WGroup *g; + int lines, cols; + int i, y; + struct passwd *l_pass; + struct group *l_grp; + + single_set = (panel->marked < 2) ? 3 : 0; + lines = GH + 4 + (single_set != 0 ? 2 : 4); + cols = GW * 3 + 2 + 6; + + ch_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, NULL, NULL, + "[Chown]", _("Chown command")); + g = GROUP (ch_dlg); + + /* draw background */ + ch_dlg->bg->callback = chown_bg_callback; + + group_add_widget (g, groupbox_new (2, 3, GH, GW, _("User name"))); + l_user = listbox_new (3, 4, GH - 2, GW - 2, FALSE, NULL); + group_add_widget (g, l_user); + /* add field for unknown names (numbers) */ + listbox_add_item (l_user, LISTBOX_APPEND_AT_END, 0, _("<Unknown user>"), NULL, FALSE); + /* get and put user names in the listbox */ + setpwent (); + while ((l_pass = getpwent ()) != NULL) + listbox_add_item (l_user, LISTBOX_APPEND_SORTED, 0, l_pass->pw_name, NULL, FALSE); + endpwent (); + + group_add_widget (g, groupbox_new (2, 4 + GW, GH, GW, _("Group name"))); + l_group = listbox_new (3, 5 + GW, GH - 2, GW - 2, FALSE, NULL); + group_add_widget (g, l_group); + /* add field for unknown names (numbers) */ + listbox_add_item (l_group, LISTBOX_APPEND_AT_END, 0, _("<Unknown group>"), NULL, FALSE); + /* get and put group names in the listbox */ + setgrent (); + while ((l_grp = getgrent ()) != NULL) + listbox_add_item (l_group, LISTBOX_APPEND_SORTED, 0, l_grp->gr_name, NULL, FALSE); + endgrent (); + + group_add_widget (g, groupbox_new (2, 5 + GW * 2, GH, GW, _("File"))); + /* add widgets for the file information */ + for (i = 0; i < LABELS; i++) + { + chown_label[i].l = label_new (chown_label[i].y, 7 + GW * 2, ""); + group_add_widget (g, chown_label[i].l); + } + + if (single_set == 0) + { + int x; + + group_add_widget (g, hline_new (lines - chown_but[0].y - 1, -1, -1)); + + y = lines - chown_but[0].y; + x = (cols - blen) / 2; + + for (i = 0; i < BUTTONS - 2; i++) + { + group_add_widget (g, button_new (y, x, chown_but[i].ret_cmd, chown_but[i].flags, + chown_but[i].text, NULL)); + x += chown_but[i].len + 1; + } + } + + i = BUTTONS - 2; + y = lines - chown_but[i].y; + group_add_widget (g, hline_new (y - 1, -1, -1)); + group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chown_but[i].len, + chown_but[i].ret_cmd, chown_but[i].flags, chown_but[i].text, + NULL)); + i++; + group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chown_but[i].ret_cmd, + chown_but[i].flags, chown_but[i].text, NULL)); + + /* select first listbox */ + widget_select (WIDGET (l_user)); + + return ch_dlg; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chown_done (gboolean need_update) +{ + if (need_update) + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static const GString * +next_file (const WPanel * panel) +{ + while (!panel->dir.list[current_file].f.marked) + current_file++; + + return panel->dir.list[current_file].fname; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +try_chown (const vfs_path_t * p, uid_t u, gid_t g) +{ + while (mc_chown (p, u, g) == -1 && !ignore_all) + { + int my_errno = errno; + int result; + char *msg; + + msg = + g_strdup_printf (_("Cannot chown \"%s\"\n%s"), x_basename (vfs_path_as_str (p)), + unix_error_string (my_errno)); + result = + query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"), + _("&Cancel")); + g_free (msg); + + switch (result) + { + case 0: + /* try next file */ + return TRUE; + + case 1: + ignore_all = TRUE; + /* try next file */ + return TRUE; + + case 2: + /* retry this file */ + break; + + case 3: + default: + /* stop remain files processing */ + return FALSE; + } + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +do_chown (WPanel * panel, const vfs_path_t * p, uid_t u, gid_t g) +{ + gboolean ret; + + ret = try_chown (p, u, g); + + do_file_mark (panel, current_file, 0); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +apply_chowns (WPanel * panel, vfs_path_t * vpath, uid_t u, gid_t g) +{ + gboolean ok; + + if (!do_chown (panel, vpath, u, g)) + return; + + do + { + const GString *fname; + struct stat sf; + + fname = next_file (panel); + vpath = vfs_path_from_str (fname->str); + ok = (mc_stat (vpath, &sf) == 0); + + if (!ok) + { + /* if current file was deleted outside mc -- try next file */ + /* decrease panel->marked */ + do_file_mark (panel, current_file, 0); + + /* try next file */ + ok = TRUE; + } + else + ok = do_chown (panel, vpath, u, g); + + vfs_path_free (vpath, TRUE); + } + while (ok && panel->marked != 0); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +chown_cmd (WPanel * panel) +{ + gboolean need_update; + gboolean end_chown; + + chown_init (); + + current_file = 0; + ignore_all = FALSE; + + do + { /* do while any files remaining */ + vfs_path_t *vpath; + WDialog *ch_dlg; + struct stat sf_stat; + const GString *fname; + int result; + char buffer[BUF_TINY]; + uid_t new_user = (uid_t) (-1); + gid_t new_group = (gid_t) (-1); + + do_refresh (); + + need_update = FALSE; + end_chown = FALSE; + + if (panel->marked != 0) + fname = next_file (panel); /* next marked file */ + else + fname = selection (panel)->fname; /* single file */ + + vpath = vfs_path_from_str (fname->str); + + if (mc_stat (vpath, &sf_stat) != 0) + { + vfs_path_free (vpath, TRUE); + break; + } + + ch_dlg = chown_dlg_create (panel); + + /* select in listboxes */ + listbox_select_entry (l_user, listbox_search_text (l_user, get_owner (sf_stat.st_uid))); + listbox_select_entry (l_group, listbox_search_text (l_group, get_group (sf_stat.st_gid))); + + chown_label (0, str_trunc (fname->str, GW - 4)); + chown_label (1, str_trunc (get_owner (sf_stat.st_uid), GW - 4)); + chown_label (2, str_trunc (get_group (sf_stat.st_gid), GW - 4)); + size_trunc_len (buffer, GW - 4, sf_stat.st_size, 0, panels_options.kilobyte_si); + chown_label (3, buffer); + chown_label (4, string_perm (sf_stat.st_mode)); + + result = dlg_run (ch_dlg); + + switch (result) + { + case B_CANCEL: + end_chown = TRUE; + break; + + case B_ENTER: + case B_SETALL: + { + struct group *grp; + struct passwd *user; + char *text; + + listbox_get_current (l_group, &text, NULL); + grp = getgrnam (text); + if (grp != NULL) + new_group = grp->gr_gid; + listbox_get_current (l_user, &text, NULL); + user = getpwnam (text); + if (user != NULL) + new_user = user->pw_uid; + if (result == B_ENTER) + { + if (panel->marked <= 1) + { + /* single or last file */ + if (mc_chown (vpath, new_user, new_group) == -1) + message (D_ERROR, MSG_ERROR, _("Cannot chown \"%s\"\n%s"), + fname->str, unix_error_string (errno)); + end_chown = TRUE; + } + else if (!try_chown (vpath, new_user, new_group)) + { + /* stop multiple files processing */ + result = B_CANCEL; + end_chown = TRUE; + } + } + else + { + apply_chowns (panel, vpath, new_user, new_group); + end_chown = TRUE; + } + + need_update = TRUE; + break; + } + + case B_SETUSR: + { + struct passwd *user; + char *text; + + listbox_get_current (l_user, &text, NULL); + user = getpwnam (text); + if (user != NULL) + { + new_user = user->pw_uid; + apply_chowns (panel, vpath, new_user, new_group); + need_update = TRUE; + end_chown = TRUE; + } + break; + } + + case B_SETGRP: + { + struct group *grp; + char *text; + + listbox_get_current (l_group, &text, NULL); + grp = getgrnam (text); + if (grp != NULL) + { + new_group = grp->gr_gid; + apply_chowns (panel, vpath, new_user, new_group); + need_update = TRUE; + end_chown = TRUE; + } + break; + } + + default: + break; + } + + if (panel->marked != 0 && result != B_CANCEL) + { + do_file_mark (panel, current_file, 0); + need_update = TRUE; + } + + vfs_path_free (vpath, TRUE); + + widget_destroy (WIDGET (ch_dlg)); + } + while (panel->marked != 0 && !end_chown); + + chown_done (need_update); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/cmd.c b/src/filemanager/cmd.c new file mode 100644 index 0000000..730f16c --- /dev/null +++ b/src/filemanager/cmd.c @@ -0,0 +1,1458 @@ +/* + Routines invoked by a function key + They normally operate on the current panel. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + 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 cmd.c + * \brief Source: routines invoked by a function key + * + * They normally operate on the current panel. + */ + +#include <config.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_MMAP +#include <sys/mman.h> +#endif +#ifdef ENABLE_VFS_NET +#include <netdb.h> +#endif +#include <unistd.h> +#include <stdlib.h> +#include <pwd.h> +#include <grp.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" /* LINES, tty_touch_screen() */ +#include "lib/tty/key.h" /* ALT() macro */ +#include "lib/mcconfig.h" +#include "lib/filehighlight.h" /* MC_FHL_INI_FILE */ +#include "lib/vfs/vfs.h" +#include "lib/fileloc.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/widget.h" +#include "lib/keybind.h" /* CK_Down, CK_History */ +#include "lib/event.h" /* mc_event_raise() */ + +#include "src/setup.h" +#include "src/execute.h" /* toggle_panels() */ +#include "src/history.h" +#include "src/usermenu.h" /* MC_GLOBAL_MENU */ +#include "src/util.h" /* check_for_default() */ + +#include "src/viewer/mcviewer.h" + +#ifdef USE_INTERNAL_EDIT +#include "src/editor/edit.h" +#endif + +#ifdef USE_DIFF_VIEW +#include "src/diffviewer/ydiff.h" +#endif + +#include "fileopctx.h" +#include "filenot.h" +#include "hotlist.h" /* hotlist_show() */ +#include "tree.h" /* tree_chdir() */ +#include "filemanager.h" /* change_panel() */ +#include "command.h" /* cmdline */ +#include "layout.h" /* get_current_type() */ +#include "ext.h" /* regex_command() */ +#include "boxes.h" /* cd_box() */ +#include "dir.h" +#include "cd.h" /* cd_to() */ + +#include "cmd.h" /* Our definitions */ + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#ifdef HAVE_MMAP +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif +#endif /* HAVE_MMAP */ + +/*** file scope type declarations ****************************************************************/ + +enum CompareMode +{ + compare_quick = 0, + compare_size_only, + compare_thourough +}; + +/*** file scope variables ************************************************************************/ + +#ifdef ENABLE_VFS_NET +static const char *machine_str = N_("Enter machine name (F1 for details):"); +#endif /* ENABLE_VFS_NET */ + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Run viewer (internal or external) on the currently selected file. + * If @plain_view is TRUE, force internal viewer and raw mode (used for F13). + */ +static void +do_view_cmd (WPanel * panel, gboolean plain_view) +{ + /* Directories are viewed by changing to them */ + if (S_ISDIR (selection (panel)->st.st_mode) || link_isdir (selection (panel))) + { + vfs_path_t *fname_vpath; + + if (confirm_view_dir && (panel->marked != 0 || panel->dirs_marked != 0) && + query_dialog (_("Confirmation"), _("Files tagged, want to cd?"), D_NORMAL, 2, + _("&Yes"), _("&No")) != 0) + return; + + fname_vpath = vfs_path_from_str (selection (panel)->fname->str); + if (!panel_cd (panel, fname_vpath, cd_exact)) + message (D_ERROR, MSG_ERROR, _("Cannot change directory")); + vfs_path_free (fname_vpath, TRUE); + } + else + { + int file_idx; + vfs_path_t *filename_vpath; + + file_idx = panel->selected; + filename_vpath = vfs_path_from_str (panel->dir.list[file_idx].fname->str); + view_file (filename_vpath, plain_view, use_internal_view); + vfs_path_free (filename_vpath, TRUE); + } + + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +do_edit (const vfs_path_t * what_vpath) +{ + edit_file_at_line (what_vpath, use_internal_edit, 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +compare_files (const vfs_path_t * vpath1, const vfs_path_t * vpath2, off_t size) +{ + int file1; + int result = -1; /* Different by default */ + + if (size == 0) + return 0; + + file1 = open (vfs_path_as_str (vpath1), O_RDONLY); + if (file1 >= 0) + { + int file2; + + file2 = open (vfs_path_as_str (vpath2), O_RDONLY); + if (file2 >= 0) + { +#ifdef HAVE_MMAP + char *data1; + + /* Ugly if jungle */ + data1 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file1, 0); + if (data1 != (char *) -1) + { + char *data2; + + data2 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file2, 0); + if (data2 != (char *) -1) + { + rotate_dash (TRUE); + result = memcmp (data1, data2, size); + munmap (data2, size); + } + munmap (data1, size); + } +#else + /* Don't have mmap() :( Even more ugly :) */ + char buf1[BUFSIZ], buf2[BUFSIZ]; + int n1, n2; + + rotate_dash (TRUE); + do + { + while ((n1 = read (file1, buf1, sizeof (buf1))) == -1 && errno == EINTR) + ; + while ((n2 = read (file2, buf2, sizeof (buf2))) == -1 && errno == EINTR) + ; + } + while (n1 == n2 && n1 == sizeof (buf1) && memcmp (buf1, buf2, sizeof (buf1)) == 0); + result = (n1 != n2) || memcmp (buf1, buf2, n1); +#endif /* !HAVE_MMAP */ + close (file2); + } + close (file1); + } + rotate_dash (FALSE); + + return result; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +compare_dir (WPanel * panel, const WPanel * other, enum CompareMode mode) +{ + int i, j; + + /* No marks by default */ + panel->marked = 0; + panel->total = 0; + panel->dirs_marked = 0; + + /* Handle all files in the panel */ + for (i = 0; i < panel->dir.len; i++) + { + file_entry_t *source = &panel->dir.list[i]; + const char *source_fname; + + /* Default: unmarked */ + file_mark (panel, i, 0); + + /* Skip directories */ + if (S_ISDIR (source->st.st_mode)) + continue; + + source_fname = source->fname->str; + if (panel->is_panelized) + source_fname = x_basename (source_fname); + + /* Search the corresponding entry from the other panel */ + for (j = 0; j < other->dir.len; j++) + { + const char *other_fname; + + other_fname = other->dir.list[j].fname->str; + if (other->is_panelized) + other_fname = x_basename (other_fname); + + if (strcmp (source_fname, other_fname) == 0) + break; + } + + if (j >= other->dir.len) + /* Not found -> mark */ + do_file_mark (panel, i, 1); + else + { + /* Found */ + file_entry_t *target = &other->dir.list[j]; + + if (mode != compare_size_only) + /* Older version is not marked */ + if (source->st.st_mtime < target->st.st_mtime) + continue; + + /* Newer version with different size is marked */ + if (source->st.st_size != target->st.st_size) + { + do_file_mark (panel, i, 1); + continue; + } + + if (mode == compare_size_only) + continue; + + if (mode == compare_quick) + { + /* Thorough compare off, compare only time stamps */ + /* Mark newer version, don't mark version with the same date */ + if (source->st.st_mtime > target->st.st_mtime) + do_file_mark (panel, i, 1); + + continue; + } + + /* Thorough compare on, do byte-by-byte comparison */ + { + vfs_path_t *src_name, *dst_name; + + src_name = + vfs_path_append_new (panel->cwd_vpath, source->fname->str, (char *) NULL); + dst_name = + vfs_path_append_new (other->cwd_vpath, target->fname->str, (char *) NULL); + if (compare_files (src_name, dst_name, source->st.st_size)) + do_file_mark (panel, i, 1); + vfs_path_free (src_name, TRUE); + vfs_path_free (dst_name, TRUE); + } + } + } /* for (i ...) */ +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +do_link (link_type_t link_type, const char *fname) +{ + char *dest = NULL, *src = NULL; + vfs_path_t *dest_vpath = NULL; + + if (link_type == LINK_HARDLINK) + { + vfs_path_t *fname_vpath; + + src = g_strdup_printf (_("Link %s to:"), str_trunc (fname, 46)); + dest = + input_expand_dialog (_("Link"), src, MC_HISTORY_FM_LINK, "", INPUT_COMPLETE_FILENAMES); + if (dest == NULL || *dest == '\0') + goto cleanup; + + save_cwds_stat (); + + fname_vpath = vfs_path_from_str (fname); + dest_vpath = vfs_path_from_str (dest); + if (mc_link (fname_vpath, dest_vpath) == -1) + message (D_ERROR, MSG_ERROR, _("link: %s"), unix_error_string (errno)); + vfs_path_free (fname_vpath, TRUE); + } + else + { + vfs_path_t *s, *d; + + /* suggest the full path for symlink, and either the full or + relative path to the file it points to */ + s = vfs_path_append_new (current_panel->cwd_vpath, fname, (char *) NULL); + + if (get_other_type () == view_listing) + d = vfs_path_append_new (other_panel->cwd_vpath, fname, (char *) NULL); + else + d = vfs_path_from_str (fname); + + if (link_type == LINK_SYMLINK_RELATIVE) + { + char *s_str; + + s_str = diff_two_paths (other_panel->cwd_vpath, s); + vfs_path_free (s, TRUE); + s = vfs_path_from_str_flags (s_str, VPF_NO_CANON); + g_free (s_str); + } + + symlink_box (s, d, &dest, &src); + vfs_path_free (d, TRUE); + vfs_path_free (s, TRUE); + + if (dest == NULL || *dest == '\0' || src == NULL || *src == '\0') + goto cleanup; + + save_cwds_stat (); + + dest_vpath = vfs_path_from_str_flags (dest, VPF_NO_CANON); + + s = vfs_path_from_str (src); + if (mc_symlink (dest_vpath, s) == -1) + message (D_ERROR, MSG_ERROR, _("symlink: %s"), unix_error_string (errno)); + vfs_path_free (s, TRUE); + } + + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + repaint_screen (); + + cleanup: + vfs_path_free (dest_vpath, TRUE); + g_free (src); + g_free (dest); +} + +/* --------------------------------------------------------------------------------------------- */ + +#if defined(ENABLE_VFS_UNDELFS) || defined(ENABLE_VFS_NET) +static void +nice_cd (const char *text, const char *xtext, const char *help, + const char *history_name, const char *prefix, int to_home, gboolean strip_password) +{ + char *machine; + char *cd_path; + + machine = + input_dialog_help (text, xtext, help, history_name, INPUT_LAST_TEXT, strip_password, + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_HOSTNAMES | + INPUT_COMPLETE_USERNAMES); + if (machine == NULL) + return; + + to_home = 0; /* FIXME: how to solve going to home nicely? /~/ is + ugly as hell and leads to problems in vfs layer */ + + if (strncmp (prefix, machine, strlen (prefix)) == 0) + cd_path = g_strconcat (machine, to_home ? "/~/" : (char *) NULL, (char *) NULL); + else + cd_path = g_strconcat (prefix, machine, to_home ? "/~/" : (char *) NULL, (char *) NULL); + + g_free (machine); + + if (!IS_PATH_SEP (*cd_path)) + { + char *tmp = cd_path; + + cd_path = g_strconcat (PATH_SEP_STR, tmp, (char *) NULL); + g_free (tmp); + } + + { + panel_view_mode_t save_type; + vfs_path_t *cd_vpath; + + save_type = get_panel_type (MENU_PANEL_IDX); + + if (save_type != view_listing) + create_panel (MENU_PANEL_IDX, view_listing); + + cd_vpath = vfs_path_from_str_flags (cd_path, VPF_NO_CANON); + if (!panel_do_cd (MENU_PANEL, cd_vpath, cd_parse_command)) + { + message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\""), cd_path); + + if (save_type != view_listing) + create_panel (MENU_PANEL_IDX, save_type); + } + vfs_path_free (cd_vpath, TRUE); + } + g_free (cd_path); + + /* In case of passive panel, restore current VFS directory that was changed in panel_do_cd() */ + if (MENU_PANEL != current_panel) + (void) mc_chdir (current_panel->cwd_vpath); +} +#endif /* ENABLE_VFS_UNDELFS || ENABLE_VFS_NET */ + +/* --------------------------------------------------------------------------------------------- */ + +static void +configure_panel_listing (WPanel * p, int list_format, int brief_cols, gboolean use_msformat, + char **user, char **status) +{ + p->user_mini_status = use_msformat; + p->list_format = list_format; + + if (list_format == list_brief) + p->brief_cols = brief_cols; + + if (list_format == list_user || use_msformat) + { + g_free (p->user_format); + p->user_format = *user; + *user = NULL; + + g_free (p->user_status_format[list_format]); + p->user_status_format[list_format] = *status; + *status = NULL; + + set_panel_formats (p); + } + + set_panel_formats (p); + do_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +switch_to_listing (int panel_index) +{ + if (get_panel_type (panel_index) != view_listing) + create_panel (panel_index, view_listing); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +gboolean +view_file_at_line (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal, + long start_line, off_t search_start, off_t search_end) +{ + gboolean ret = TRUE; + + if (plain_view) + { + mcview_mode_flags_t changed_flags; + + mcview_clear_mode_flags (&changed_flags); + mcview_altered_flags.hex = FALSE; + mcview_altered_flags.magic = FALSE; + mcview_altered_flags.nroff = FALSE; + if (mcview_global_flags.hex) + changed_flags.hex = TRUE; + if (mcview_global_flags.magic) + changed_flags.magic = TRUE; + if (mcview_global_flags.nroff) + changed_flags.nroff = TRUE; + mcview_global_flags.hex = FALSE; + mcview_global_flags.magic = FALSE; + mcview_global_flags.nroff = FALSE; + + ret = mcview_viewer (NULL, filename_vpath, start_line, search_start, search_end); + + if (changed_flags.hex && !mcview_altered_flags.hex) + mcview_global_flags.hex = TRUE; + if (changed_flags.magic && !mcview_altered_flags.magic) + mcview_global_flags.magic = TRUE; + if (changed_flags.nroff && !mcview_altered_flags.nroff) + mcview_global_flags.nroff = TRUE; + + dialog_switch_process_pending (); + } + else if (internal) + { + char view_entry[BUF_TINY]; + + if (start_line > 0) + g_snprintf (view_entry, sizeof (view_entry), "View:%ld", start_line); + else + strcpy (view_entry, "View"); + + ret = (regex_command (filename_vpath, view_entry) == 0); + if (ret) + { + ret = mcview_viewer (NULL, filename_vpath, start_line, search_start, search_end); + dialog_switch_process_pending (); + } + } + else + { + static const char *viewer = NULL; + + if (viewer == NULL) + { + viewer = getenv ("VIEWER"); + if (viewer == NULL) + viewer = getenv ("PAGER"); + if (viewer == NULL) + viewer = "view"; + } + + execute_external_editor_or_viewer (viewer, filename_vpath, start_line); + } + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ +/** view_file (filename, plain_view, internal) + * + * Inputs: + * filename_vpath: The file name to view + * plain_view: If set does not do any fancy pre-processing (no filtering) and + * always invokes the internal viewer. + * internal: If set uses the internal viewer, otherwise an external viewer. + */ + +gboolean +view_file (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal) +{ + return view_file_at_line (filename_vpath, plain_view, internal, 0, 0, 0); +} + + +/* --------------------------------------------------------------------------------------------- */ +/** Run user's preferred viewer on the currently selected file */ + +void +view_cmd (WPanel * panel) +{ + do_view_cmd (panel, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Ask for file and run user's preferred viewer on it */ + +void +view_file_cmd (const WPanel * panel) +{ + char *filename; + vfs_path_t *vpath; + + filename = + input_expand_dialog (_("View file"), _("Filename:"), + MC_HISTORY_FM_VIEW_FILE, selection (panel)->fname->str, + INPUT_COMPLETE_FILENAMES); + if (filename == NULL) + return; + + vpath = vfs_path_from_str (filename); + g_free (filename); + view_file (vpath, FALSE, use_internal_view); + vfs_path_free (vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Run plain internal viewer on the currently selected file */ +void +view_raw_cmd (WPanel * panel) +{ + do_view_cmd (panel, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +view_filtered_cmd (const WPanel * panel) +{ + char *command; + const char *initial_command; + + if (input_is_empty (cmdline)) + initial_command = selection (panel)->fname->str; + else + initial_command = input_get_ctext (cmdline); + + command = + input_dialog (_("Filtered view"), + _("Filter command and arguments:"), + MC_HISTORY_FM_FILTERED_VIEW, initial_command, + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS); + + if (command != NULL) + { + mcview_viewer (command, NULL, 0, 0, 0); + g_free (command); + dialog_switch_process_pending (); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +edit_file_at_line (const vfs_path_t * what_vpath, gboolean internal, long start_line) +{ + +#ifdef USE_INTERNAL_EDIT + if (internal) + edit_file (what_vpath, start_line); + else +#endif /* USE_INTERNAL_EDIT */ + { + static const char *editor = NULL; + + (void) internal; + + if (editor == NULL) + { + editor = getenv ("EDITOR"); + if (editor == NULL) + editor = get_default_editor (); + } + + execute_external_editor_or_viewer (editor, what_vpath, start_line); + } + + if (mc_global.mc_run_mode == MC_RUN_FULL) + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + +#ifdef USE_INTERNAL_EDIT + if (use_internal_edit) + dialog_switch_process_pending (); + else +#endif /* USE_INTERNAL_EDIT */ + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +edit_cmd (const WPanel * panel) +{ + vfs_path_t *fname; + + fname = vfs_path_from_str (selection (panel)->fname->str); + if (regex_command (fname, "Edit") == 0) + do_edit (fname); + vfs_path_free (fname, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef USE_INTERNAL_EDIT +void +edit_cmd_force_internal (const WPanel * panel) +{ + vfs_path_t *fname; + + fname = vfs_path_from_str (selection (panel)->fname->str); + if (regex_command (fname, "Edit") == 0) + edit_file_at_line (fname, TRUE, 1); + vfs_path_free (fname, TRUE); +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +void +edit_cmd_new (void) +{ + vfs_path_t *fname_vpath = NULL; + + if (editor_ask_filename_before_edit) + { + char *fname; + + fname = input_expand_dialog (_("Edit file"), _("Enter file name:"), + MC_HISTORY_EDIT_LOAD, "", INPUT_COMPLETE_FILENAMES); + if (fname == NULL) + return; + + if (*fname != '\0') + fname_vpath = vfs_path_from_str (fname); + + g_free (fname); + } + +#ifdef HAVE_CHARSET + mc_global.source_codepage = default_source_codepage; +#endif + do_edit (fname_vpath); + + vfs_path_free (fname_vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +mkdir_cmd (WPanel * panel) +{ + char *dir; + const char *name = ""; + + /* If 'on' then automatically fills name with current selected item name */ + if (auto_fill_mkdir_name && !DIR_IS_DOTDOT (selection (panel)->fname->str)) + name = selection (panel)->fname->str; + + dir = + input_expand_dialog (_("Create a new Directory"), + _("Enter directory name:"), MC_HISTORY_FM_MKDIR, name, + INPUT_COMPLETE_FILENAMES); + + if (dir != NULL && *dir != '\0') + { + vfs_path_t *absdir; + + if (IS_PATH_SEP (dir[0]) || dir[0] == '~') + absdir = vfs_path_from_str (dir); + else + { + /* possible escaped '~' */ + /* allow create directory with name '~' */ + char *tmpdir = dir; + + if (dir[0] == '\\' && dir[1] == '~') + tmpdir = dir + 1; + + absdir = vfs_path_append_new (panel->cwd_vpath, tmpdir, (char *) NULL); + } + + save_cwds_stat (); + + if (my_mkdir (absdir, 0777) != 0) + message (D_ERROR, MSG_ERROR, "%s", unix_error_string (errno)); + else + { + update_panels (UP_OPTIMIZE, dir); + repaint_screen (); + select_item (panel); + } + + vfs_path_free (absdir, TRUE); + } + g_free (dir); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +reread_cmd (void) +{ + panel_update_flags_t flag = UP_ONLY_CURRENT; + + if (get_current_type () == view_listing && get_other_type () == view_listing && + vfs_path_equal (current_panel->cwd_vpath, other_panel->cwd_vpath)) + flag = UP_OPTIMIZE; + + update_panels (UP_RELOAD | flag, UP_KEEPSEL); + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +ext_cmd (void) +{ + vfs_path_t *extdir_vpath; + int dir = 0; + + if (geteuid () == 0) + dir = query_dialog (_("Extension file edit"), + _("Which extension file you want to edit?"), D_NORMAL, 2, + _("&User"), _("&System Wide")); + + extdir_vpath = vfs_path_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL); + + if (dir == 0) + { + vfs_path_t *buffer_vpath; + + buffer_vpath = mc_config_get_full_vpath (MC_EXT_FILE); + check_for_default (extdir_vpath, buffer_vpath); + do_edit (buffer_vpath); + vfs_path_free (buffer_vpath, TRUE); + } + else if (dir == 1) + { + if (!exist_file (vfs_path_get_last_path_str (extdir_vpath))) + { + vfs_path_free (extdir_vpath, TRUE); + extdir_vpath = + vfs_path_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL); + } + do_edit (extdir_vpath); + } + + vfs_path_free (extdir_vpath, TRUE); + flush_extension_file (); +} + +/* --------------------------------------------------------------------------------------------- */ +/** edit file menu for mc */ + +void +edit_mc_menu_cmd (void) +{ + vfs_path_t *buffer_vpath; + vfs_path_t *menufile_vpath; + int dir = 0; + + query_set_sel (1); + dir = query_dialog (_("Menu edit"), + _("Which menu file do you want to edit?"), + D_NORMAL, geteuid ()? 2 : 3, _("&Local"), _("&User"), _("&System Wide")); + + menufile_vpath = + vfs_path_build_filename (mc_global.sysconfig_dir, MC_GLOBAL_MENU, (char *) NULL); + + if (!exist_file (vfs_path_get_last_path_str (menufile_vpath))) + { + vfs_path_free (menufile_vpath, TRUE); + menufile_vpath = + vfs_path_build_filename (mc_global.share_data_dir, MC_GLOBAL_MENU, (char *) NULL); + } + + switch (dir) + { + case 0: + buffer_vpath = vfs_path_from_str (MC_LOCAL_MENU); + check_for_default (menufile_vpath, buffer_vpath); + chmod (vfs_path_get_last_path_str (buffer_vpath), 0600); + break; + + case 1: + buffer_vpath = mc_config_get_full_vpath (MC_USERMENU_FILE); + check_for_default (menufile_vpath, buffer_vpath); + break; + + case 2: + buffer_vpath = + vfs_path_build_filename (mc_global.sysconfig_dir, MC_GLOBAL_MENU, (char *) NULL); + if (!exist_file (vfs_path_get_last_path_str (buffer_vpath))) + { + vfs_path_free (buffer_vpath, TRUE); + buffer_vpath = + vfs_path_build_filename (mc_global.share_data_dir, MC_GLOBAL_MENU, (char *) NULL); + } + break; + + default: + vfs_path_free (menufile_vpath, TRUE); + return; + } + + do_edit (buffer_vpath); + + vfs_path_free (buffer_vpath, TRUE); + vfs_path_free (menufile_vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +edit_fhl_cmd (void) +{ + vfs_path_t *fhlfile_vpath = NULL; + int dir = 0; + + if (geteuid () == 0) + dir = query_dialog (_("Highlighting groups file edit"), + _("Which highlighting file you want to edit?"), D_NORMAL, 2, + _("&User"), _("&System Wide")); + + fhlfile_vpath = + vfs_path_build_filename (mc_global.sysconfig_dir, MC_FHL_INI_FILE, (char *) NULL); + + if (dir == 0) + { + vfs_path_t *buffer_vpath; + + buffer_vpath = mc_config_get_full_vpath (MC_FHL_INI_FILE); + check_for_default (fhlfile_vpath, buffer_vpath); + do_edit (buffer_vpath); + vfs_path_free (buffer_vpath, TRUE); + } + else if (dir == 1) + { + if (!exist_file (vfs_path_get_last_path_str (fhlfile_vpath))) + { + vfs_path_free (fhlfile_vpath, TRUE); + fhlfile_vpath = + vfs_path_build_filename (mc_global.sysconfig_dir, MC_FHL_INI_FILE, (char *) NULL); + } + do_edit (fhlfile_vpath); + } + + vfs_path_free (fhlfile_vpath, TRUE); + /* refresh highlighting rules */ + mc_fhl_free (&mc_filehighlight); + mc_filehighlight = mc_fhl_new (TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +hotlist_cmd (WPanel * panel) +{ + char *target; + + target = hotlist_show (LIST_HOTLIST, panel); + if (target == NULL) + return; + + if (get_current_type () == view_tree) + { + vfs_path_t *vpath; + + vpath = vfs_path_from_str (target); + tree_chdir (the_tree, vpath); + vfs_path_free (vpath, TRUE); + } + else + { + vfs_path_t *deprecated_vpath; + const char *deprecated_path; + + deprecated_vpath = vfs_path_from_str_flags (target, VPF_USE_DEPRECATED_PARSER); + deprecated_path = vfs_path_as_str (deprecated_vpath); + cd_to (deprecated_path); + vfs_path_free (deprecated_vpath, TRUE); + } + + g_free (target); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS +void +vfs_list (WPanel * panel) +{ + char *target; + vfs_path_t *target_vpath; + + target = hotlist_show (LIST_VFSLIST, panel); + if (target == NULL) + return; + + target_vpath = vfs_path_from_str (target); + if (!panel_cd (current_panel, target_vpath, cd_exact)) + message (D_ERROR, MSG_ERROR, _("Cannot change directory")); + vfs_path_free (target_vpath, TRUE); + g_free (target); +} +#endif /* ENABLE_VFS */ + +/* --------------------------------------------------------------------------------------------- */ + +void +compare_dirs_cmd (void) +{ + int choice; + enum CompareMode thorough_flag; + + choice = + query_dialog (_("Compare directories"), + _("Select compare method:"), D_NORMAL, 4, + _("&Quick"), _("&Size only"), _("&Thorough"), _("&Cancel")); + + if (choice < 0 || choice > 2) + return; + + thorough_flag = choice; + + if (get_current_type () == view_listing && get_other_type () == view_listing) + { + compare_dir (current_panel, other_panel, thorough_flag); + compare_dir (other_panel, current_panel, thorough_flag); + } + else + message (D_ERROR, MSG_ERROR, + _("Both panels should be in the listing mode\nto use this command")); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef USE_DIFF_VIEW +void +diff_view_cmd (void) +{ + /* both panels must be in the list mode */ + if (get_current_type () == view_listing && get_other_type () == view_listing) + { + if (get_current_index () == 0) + dview_diff_cmd (current_panel, other_panel); + else + dview_diff_cmd (other_panel, current_panel); + + if (mc_global.mc_run_mode == MC_RUN_FULL) + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + + dialog_switch_process_pending (); + } +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +void +swap_cmd (void) +{ + swap_panels (); + tty_touch_screen (); + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +link_cmd (link_type_t link_type) +{ + const char *filename = selection (current_panel)->fname->str; + + if (filename != NULL) + do_link (link_type, filename); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +edit_symlink_cmd (void) +{ + const file_entry_t *fe; + const char *p; + + fe = selection (current_panel); + p = fe->fname->str; + + if (!S_ISLNK (fe->st.st_mode)) + message (D_ERROR, MSG_ERROR, _("'%s' is not a symbolic link"), p); + else + { + char buffer[MC_MAXPATHLEN]; + int i; + + i = readlink (p, buffer, sizeof (buffer) - 1); + if (i > 0) + { + char *q, *dest; + + buffer[i] = '\0'; + + q = g_strdup_printf (_("Symlink '%s\' points to:"), str_trunc (p, 32)); + dest = + input_expand_dialog (_("Edit symlink"), q, MC_HISTORY_FM_EDIT_LINK, buffer, + INPUT_COMPLETE_FILENAMES); + g_free (q); + + if (dest != NULL && *dest != '\0' && strcmp (buffer, dest) != 0) + { + vfs_path_t *p_vpath; + + p_vpath = vfs_path_from_str (p); + + save_cwds_stat (); + + if (mc_unlink (p_vpath) == -1) + message (D_ERROR, MSG_ERROR, _("edit symlink, unable to remove %s: %s"), p, + unix_error_string (errno)); + else + { + vfs_path_t *dest_vpath; + + dest_vpath = vfs_path_from_str_flags (dest, VPF_NO_CANON); + if (mc_symlink (dest_vpath, p_vpath) == -1) + message (D_ERROR, MSG_ERROR, _("edit symlink: %s"), + unix_error_string (errno)); + vfs_path_free (dest_vpath, TRUE); + } + + vfs_path_free (p_vpath, TRUE); + + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + repaint_screen (); + } + + g_free (dest); + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +help_cmd (void) +{ + ev_help_t event_data = { NULL, NULL }; + + if (current_panel->quick_search.active) + event_data.node = "[Quick search]"; + else + event_data.node = "[main]"; + + mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +user_file_menu_cmd (void) +{ + (void) user_menu_cmd (NULL, NULL, -1); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS_FTP +void +ftplink_cmd (void) +{ + nice_cd (_("FTP to machine"), _(machine_str), + "[FTP File System]", ":ftplink_cmd: FTP to machine ", "ftp://", 1, TRUE); +} +#endif /* ENABLE_VFS_FTP */ + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS_SFTP +void +sftplink_cmd (void) +{ + nice_cd (_("SFTP to machine"), _(machine_str), + "[SFTP (SSH File Transfer Protocol) filesystem]", + ":sftplink_cmd: SFTP to machine ", "sftp://", 1, TRUE); +} +#endif /* ENABLE_VFS_SFTP */ + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS_FISH +void +fishlink_cmd (void) +{ + nice_cd (_("Shell link to machine"), _(machine_str), + "[FIle transfer over SHell filesystem]", ":fishlink_cmd: Shell link to machine ", + "sh://", 1, TRUE); +} +#endif /* ENABLE_VFS_FISH */ + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS_UNDELFS +void +undelete_cmd (void) +{ + nice_cd (_("Undelete files on an ext2 file system"), + _("Enter device (without /dev/) to undelete\nfiles on: (F1 for details)"), + "[Undelete File System]", ":undelete_cmd: Undel on ext2 fs ", "undel://", 0, FALSE); +} +#endif /* ENABLE_VFS_UNDELFS */ + +/* --------------------------------------------------------------------------------------------- */ + +void +quick_cd_cmd (WPanel * panel) +{ + char *p; + + p = cd_box (panel); + if (p != NULL && *p != '\0') + cd_to (p); + g_free (p); +} + +/* --------------------------------------------------------------------------------------------- */ +/*! + \brief calculate dirs sizes + + calculate dirs sizes and resort panel: + dirs_selected = show size for selected dirs, + otherwise = show size for dir under cursor: + dir under cursor ".." = show size for all dirs, + otherwise = show size for dir under cursor + */ + +void +smart_dirsize_cmd (WPanel * panel) +{ + const file_entry_t *entry = &panel->dir.list[panel->selected]; + + if ((S_ISDIR (entry->st.st_mode) && DIR_IS_DOTDOT (entry->fname->str)) || panel->dirs_marked) + dirsizes_cmd (panel); + else + single_dirsize_cmd (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +single_dirsize_cmd (WPanel * panel) +{ + file_entry_t *entry = &panel->dir.list[panel->selected]; + + if (S_ISDIR (entry->st.st_mode) && !DIR_IS_DOTDOT (entry->fname->str)) + { + size_t dir_count = 0; + size_t count = 0; + uintmax_t total = 0; + dirsize_status_msg_t dsm; + vfs_path_t *p; + + p = vfs_path_from_str (entry->fname->str); + + memset (&dsm, 0, sizeof (dsm)); + status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb, + dirsize_status_update_cb, dirsize_status_deinit_cb); + + if (compute_dir_size (p, &dsm, &dir_count, &count, &total, FALSE) == FILE_CONT) + { + entry->st.st_size = (off_t) total; + entry->f.dir_size_computed = 1; + } + + vfs_path_free (p, TRUE); + + status_msg_deinit (STATUS_MSG (&dsm)); + } + + if (panels_options.mark_moves_down) + send_message (panel, NULL, MSG_ACTION, CK_Down, NULL); + + recalculate_panel_summary (panel); + + if (panel->sort_field->sort_routine == (GCompareFunc) sort_size) + panel_re_sort (panel); + + panel->dirty = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +dirsizes_cmd (WPanel * panel) +{ + int i; + dirsize_status_msg_t dsm; + + memset (&dsm, 0, sizeof (dsm)); + status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb, + dirsize_status_update_cb, dirsize_status_deinit_cb); + + for (i = 0; i < panel->dir.len; i++) + if (S_ISDIR (panel->dir.list[i].st.st_mode) + && ((panel->dirs_marked != 0 && panel->dir.list[i].f.marked) + || panel->dirs_marked == 0) && !DIR_IS_DOTDOT (panel->dir.list[i].fname->str)) + { + vfs_path_t *p; + size_t dir_count = 0; + size_t count = 0; + uintmax_t total = 0; + gboolean ok; + + p = vfs_path_from_str (panel->dir.list[i].fname->str); + ok = compute_dir_size (p, &dsm, &dir_count, &count, &total, FALSE) != FILE_CONT; + vfs_path_free (p, TRUE); + if (ok) + break; + + panel->dir.list[i].st.st_size = (off_t) total; + panel->dir.list[i].f.dir_size_computed = 1; + } + + status_msg_deinit (STATUS_MSG (&dsm)); + + recalculate_panel_summary (panel); + + if (panel->sort_field->sort_routine == (GCompareFunc) sort_size) + panel_re_sort (panel); + + panel->dirty = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +save_setup_cmd (void) +{ + vfs_path_t *vpath; + const char *path; + + vpath = vfs_path_from_str_flags (mc_config_get_path (), VPF_STRIP_HOME); + path = vfs_path_as_str (vpath); + + if (save_setup (TRUE, TRUE)) + message (D_NORMAL, _("Setup"), _("Setup saved to %s"), path); + else + message (D_ERROR, _("Setup"), _("Unable to save setup to %s"), path); + + vfs_path_free (vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +info_cmd_no_menu (void) +{ + if (get_panel_type (0) == view_info) + create_panel (0, view_listing); + else if (get_panel_type (1) == view_info) + create_panel (1, view_listing); + else + create_panel (current_panel == left_panel ? 1 : 0, view_info); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +quick_cmd_no_menu (void) +{ + if (get_panel_type (0) == view_quick) + create_panel (0, view_listing); + else if (get_panel_type (1) == view_quick) + create_panel (1, view_listing); + else + create_panel (current_panel == left_panel ? 1 : 0, view_quick); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +listing_cmd (void) +{ + WPanel *p; + + switch_to_listing (MENU_PANEL_IDX); + + p = PANEL (get_panel_widget (MENU_PANEL_IDX)); + + p->is_panelized = FALSE; + panel_set_filter (p, NULL); /* including panel reload */ +} + +/* --------------------------------------------------------------------------------------------- */ + +void +setup_listing_format_cmd (void) +{ + int list_format; + gboolean use_msformat; + int brief_cols; + char *user, *status; + WPanel *p = NULL; + + if (SELECTED_IS_PANEL) + p = MENU_PANEL_IDX == 0 ? left_panel : right_panel; + + list_format = panel_listing_box (p, MENU_PANEL_IDX, &user, &status, &use_msformat, &brief_cols); + if (list_format != -1) + { + switch_to_listing (MENU_PANEL_IDX); + p = MENU_PANEL_IDX == 0 ? left_panel : right_panel; + configure_panel_listing (p, list_format, brief_cols, use_msformat, &user, &status); + g_free (user); + g_free (status); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_tree_cmd (void) +{ + create_panel (MENU_PANEL_IDX, view_tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +info_cmd (void) +{ + create_panel (MENU_PANEL_IDX, view_info); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +quick_view_cmd (void) +{ + if (PANEL (get_panel_widget (MENU_PANEL_IDX)) == current_panel) + (void) change_panel (); + create_panel (MENU_PANEL_IDX, view_quick); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef HAVE_CHARSET +void +encoding_cmd (void) +{ + if (SELECTED_IS_PANEL) + panel_change_encoding (MENU_PANEL); +} +#endif + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/cmd.h b/src/filemanager/cmd.h new file mode 100644 index 0000000..26bfdb7 --- /dev/null +++ b/src/filemanager/cmd.h @@ -0,0 +1,172 @@ +/** \file cmd.h + * \brief Header: routines invoked by a function key + * + * They normally operate on the current panel. + */ + +#ifndef MC__CMD_H +#define MC__CMD_H + +#include "lib/global.h" + +#include "file.h" /* panel_operate() */ +#include "panel.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +typedef enum +{ + LINK_HARDLINK = 0, + LINK_SYMLINK_ABSOLUTE, + LINK_SYMLINK_RELATIVE +} link_type_t; + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +#ifdef ENABLE_VFS_FTP +void ftplink_cmd (void); +#endif +#ifdef ENABLE_VFS_SFTP +void sftplink_cmd (void); +#endif +#ifdef ENABLE_VFS_FISH +void fishlink_cmd (void); +#endif +void undelete_cmd (void); +void help_cmd (void); +void smart_dirsize_cmd (WPanel * panel); +void single_dirsize_cmd (WPanel * panel); +void dirsizes_cmd (WPanel * panel); +gboolean view_file_at_line (const vfs_path_t * filename_vpath, gboolean plain_view, + gboolean internal, long start_line, off_t search_start, + off_t search_end); +gboolean view_file (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal); +void view_cmd (WPanel * panel); +void view_file_cmd (const WPanel * panel); +void view_raw_cmd (WPanel * panel); +void view_filtered_cmd (const WPanel * panel); +void edit_file_at_line (const vfs_path_t * what_vpath, gboolean internal, long start_line); +void edit_cmd (const WPanel * panel); +void edit_cmd_new (void); +#ifdef USE_INTERNAL_EDIT +void edit_cmd_force_internal (const WPanel * panel); +#endif +void mkdir_cmd (WPanel * panel); +void reread_cmd (void); +void vfs_list (WPanel * panel); +void ext_cmd (void); +void edit_mc_menu_cmd (void); +void edit_fhl_cmd (void); +void hotlist_cmd (WPanel * panel); +void compare_dirs_cmd (void); +#ifdef USE_DIFF_VIEW +void diff_view_cmd (void); +#endif +void panel_tree_cmd (void); +void link_cmd (link_type_t link_type); +void edit_symlink_cmd (void); +void swap_cmd (void); +void quick_cd_cmd (WPanel * panel); +void save_setup_cmd (void); +void user_file_menu_cmd (void); +void info_cmd (void); +void listing_cmd (void); +void setup_listing_format_cmd (void); +void quick_cmd_no_menu (void); +void info_cmd_no_menu (void); +void quick_view_cmd (void); +#ifdef HAVE_CHARSET +void encoding_cmd (void); +#endif +/* achown.c */ +void advanced_chown_cmd (WPanel * panel); +/* chmod.c */ +void chmod_cmd (WPanel * panel); +/* chown.c */ +void chown_cmd (WPanel * panel); +#ifdef ENABLE_EXT2FS_ATTR +/* chattr.c */ +void chattr_cmd (WPanel * panel); +const char *chattr_get_as_str (unsigned long attr); +#endif +/* find.c */ +void find_cmd (WPanel * panel); + +/* --------------------------------------------------------------------------------------------- */ +/*** inline functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** + * Copy, default to the other panel. + */ + +static inline void +copy_cmd (WPanel * panel) +{ + panel_operate (panel, OP_COPY, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Copy, default to the same panel, ignore marks. + */ + +static inline void +copy_cmd_local (WPanel * panel) +{ + panel_operate (panel, OP_COPY, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Move/rename, default to the other panel. + */ + +static inline void +rename_cmd (WPanel * panel) +{ + panel_operate (panel, OP_MOVE, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Move/rename, default to the same panel, ignore marks. + */ + +static inline void +rename_cmd_local (WPanel * panel) +{ + panel_operate (panel, OP_MOVE, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Remove. + */ + +static inline void +delete_cmd (WPanel * panel) +{ + panel_operate (panel, OP_DELETE, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Remove, ignore marks. + */ + +static inline void +delete_cmd_local (WPanel * panel) +{ + panel_operate (panel, OP_DELETE, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +#endif /* MC__CMD_H */ diff --git a/src/filemanager/command.c b/src/filemanager/command.c new file mode 100644 index 0000000..b596432 --- /dev/null +++ b/src/filemanager/command.c @@ -0,0 +1,253 @@ +/* + Command line widget. + This widget is derived from the WInput widget, it's used to cope + with all the magic of the command input line, we depend on some + help from the program's callback. + + Copyright (C) 1995-2022 + Free Software Foundation, Inc. + + Written by: + Slava Zanko <slavazanko@gmail.com>, 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 command.c + * \brief Source: command line widget + */ + +#include <config.h> + +#include <stdlib.h> +#include <string.h> + +#include "lib/global.h" +#include "lib/vfs/vfs.h" /* vfs_current_is_local() */ +#include "lib/skin.h" /* DEFAULT_COLOR */ +#include "lib/util.h" /* whitespace() */ +#include "lib/widget.h" + +#include "src/setup.h" /* quit */ +#ifdef ENABLE_SUBSHELL +#include "src/subshell/subshell.h" +#endif +#include "src/execute.h" /* shell_execute() */ +#include "src/usermenu.h" /* expand_format() */ + +#include "filemanager.h" /* quiet_quit_cmd(), layout.h */ +#include "cd.h" /* cd_to() */ + +#include "command.h" + +/*** global variables ****************************************************************************/ + +/* This holds the command line */ +WInput *cmdline; + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/* Color styles command line */ +static input_colors_t command_colors; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** Handle Enter on the command line + * + * @param lc_cmdline string for handling + * @return MSG_HANDLED on sucsess else MSG_NOT_HANDLED + */ + +static cb_ret_t +enter (WInput * lc_cmdline) +{ + const char *cmd; + + if (!command_prompt) + return MSG_HANDLED; + + cmd = input_get_ctext (lc_cmdline); + + /* Any initial whitespace should be removed at this point */ + while (whiteness (*cmd)) + cmd++; + + if (*cmd == '\0') + return MSG_HANDLED; + + if (strncmp (cmd, "cd", 2) == 0 && (cmd[2] == '\0' || whitespace (cmd[2]))) + { + cd_to (cmd + 2); + input_clean (lc_cmdline); + return MSG_HANDLED; + } + else if (strcmp (cmd, "exit") == 0) + { + input_assign_text (lc_cmdline, ""); + if (!quiet_quit_cmd ()) + return MSG_NOT_HANDLED; + } + else + { + GString *command; + size_t i; + + if (!vfs_current_is_local ()) + { + message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems")); + return MSG_NOT_HANDLED; + } +#ifdef ENABLE_SUBSHELL + /* Check this early before we clean command line + * (will be checked again by shell_execute) */ + if (mc_global.tty.use_subshell && subshell_state != INACTIVE) + { + message (D_ERROR, MSG_ERROR, _("The shell is already running a command")); + return MSG_NOT_HANDLED; + } +#endif + command = g_string_sized_new (32); + + for (i = 0; cmd[i] != '\0'; i++) + { + if (cmd[i] != '%') + g_string_append_c (command, cmd[i]); + else + { + char *s; + + s = expand_format (NULL, cmd[++i], TRUE); + g_string_append (command, s); + g_free (s); + } + } + + input_clean (lc_cmdline); + shell_execute (command->str, 0); + g_string_free (command, TRUE); + +#ifdef ENABLE_SUBSHELL + if ((quit & SUBSHELL_EXIT) != 0) + { + if (quiet_quit_cmd ()) + return MSG_HANDLED; + + quit = 0; + /* restart subshell */ + if (mc_global.tty.use_subshell) + init_subshell (); + } + + if (mc_global.tty.use_subshell) + do_load_prompt (); +#endif + } + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Default command line callback + * + * @param w Widget object + * @param msg message for handling + * @param parm extra parameter such as key code + * + * @return MSG_NOT_HANDLED on fail else MSG_HANDLED + */ + +static cb_ret_t +command_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_KEY: + /* Special case: we handle the enter key */ + if (parm == '\n') + return enter (INPUT (w)); + MC_FALLTHROUGH; + + default: + return input_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +WInput * +command_new (int y, int x, int cols) +{ + WInput *cmd; + Widget *w; + + cmd = input_new (y, x, command_colors, cols, "", "cmdline", + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES + | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS | + INPUT_COMPLETE_SHELL_ESC); + w = WIDGET (cmd); + /* Don't set WOP_SELECTABLE up, otherwise panels will be unselected */ + widget_set_options (w, WOP_SELECTABLE, FALSE); + /* Add our hooks */ + w->callback = command_callback; + + return cmd; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Set colors for the command line. + */ + +void +command_set_default_colors (void) +{ + command_colors[WINPUTC_MAIN] = DEFAULT_COLOR; + command_colors[WINPUTC_MARK] = COMMAND_MARK_COLOR; + command_colors[WINPUTC_UNCHANGED] = DEFAULT_COLOR; + command_colors[WINPUTC_HISTORY] = COMMAND_HISTORY_COLOR; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Insert quoted text in input line. The function is meant for the + * command line, so the percent sign is quoted as well. + * + * @param in WInput object + * @param text string for insertion + * @param insert_extra_space add extra space + */ + +void +command_insert (WInput * in, const char *text, gboolean insert_extra_space) +{ + char *quoted_text; + + quoted_text = name_quote (text, TRUE); + input_insert (in, quoted_text, insert_extra_space); + g_free (quoted_text); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/command.h b/src/filemanager/command.h new file mode 100644 index 0000000..9aacca4 --- /dev/null +++ b/src/filemanager/command.h @@ -0,0 +1,27 @@ +/** \file command.h + * \brief Header: command line widget + */ + +#ifndef MC__COMMAND_H +#define MC__COMMAND_H + +#include "lib/widget.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +extern WInput *cmdline; + +/*** declarations of public functions ************************************************************/ + +WInput *command_new (int y, int x, int len); +void command_set_default_colors (void); +void command_insert (WInput * in, const char *text, gboolean insert_extra_space); + +/*** inline functions ****************************************************************************/ +#endif /* MC__COMMAND_H */ diff --git a/src/filemanager/dir.c b/src/filemanager/dir.c new file mode 100644 index 0000000..6494654 --- /dev/null +++ b/src/filemanager/dir.c @@ -0,0 +1,849 @@ +/* + Directory routines + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + 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 src/filemanager/dir.c + * \brief Source: directory routines + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "lib/global.h" +#include "lib/tty/tty.h" +#include "lib/search.h" +#include "lib/vfs/vfs.h" +#include "lib/fs.h" +#include "lib/strutil.h" +#include "lib/util.h" + +#include "src/setup.h" /* panels_options */ + +#include "treestore.h" +#include "file.h" /* file_is_symlink_to_dir() */ +#include "dir.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define MY_ISDIR(x) (\ + (is_exe (x->st.st_mode) && !(S_ISDIR (x->st.st_mode) || link_isdir (x)) && exec_first) \ + ? 1 \ + : ( (S_ISDIR (x->st.st_mode) || link_isdir (x)) ? 2 : 0) ) + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/* Reverse flag */ +static int reverse = 1; + +/* Are the files sorted case sensitively? */ +static gboolean case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT; + +/* Are the exec_bit files top in list */ +static gboolean exec_first = TRUE; + +static dir_list dir_copy = { NULL, 0, 0, NULL }; + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* + sort_orders_t sort_orders [SORT_TYPES_TOTAL] = { + { N_("&Unsorted"), unsorted }, + { N_("&Name"), sort_name }, + { N_("&Extension"), sort_ext }, + { N_("&Modify time"), sort_time }, + { N_("&Access time"), sort_atime }, + { N_("C&Hange time"), sort_ctime }, + { N_("&Size"), sort_size }, + { N_("&Inode"), sort_inode }, + }; + */ + +static inline int +key_collate (const char *t1, const char *t2) +{ + int dotdot = 0; + int ret; + + dotdot = (t1[0] == '.' ? 1 : 0) | ((t2[0] == '.' ? 1 : 0) << 1); + + switch (dotdot) + { + case 0: + case 3: + ret = str_key_collate (t1, t2, case_sensitive) * reverse; + break; + case 1: + ret = -1; /* t1 < t2 */ + break; + case 2: + ret = 1; /* t1 > t2 */ + break; + default: + ret = 0; /* it must not happen */ + } + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline int +compare_by_names (file_entry_t * a, file_entry_t * b) +{ + /* create key if does not exist, key will be freed after sorting */ + if (a->sort_key == NULL) + a->sort_key = str_create_key_for_filename (a->fname->str, case_sensitive); + if (b->sort_key == NULL) + b->sort_key = str_create_key_for_filename (b->fname->str, case_sensitive); + + return key_collate (a->sort_key, b->sort_key); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * clear keys, should be call after sorting is finished. + */ + +static void +clean_sort_keys (dir_list * list, int start, int count) +{ + int i; + + for (i = 0; i < count; i++) + { + file_entry_t *fentry; + + fentry = &list->list[i + start]; + str_release_key (fentry->sort_key, case_sensitive); + fentry->sort_key = NULL; + str_release_key (fentry->second_sort_key, case_sensitive); + fentry->second_sort_key = NULL; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * If you change handle_dirent then check also handle_path. + * @return FALSE = don't add, TRUE = add to the list + */ + +static gboolean +handle_dirent (struct vfs_dirent *dp, const file_filter_t * filter, struct stat *buf1, + gboolean * link_to_dir, gboolean * stale_link) +{ + vfs_path_t *vpath; + gboolean ok = TRUE; + + if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name)) + return FALSE; + if (!panels_options.show_dot_files && (dp->d_name[0] == '.')) + return FALSE; + if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~') + return FALSE; + + vpath = vfs_path_from_str (dp->d_name); + if (mc_lstat (vpath, buf1) == -1) + { + /* + * lstat() fails - such entries should be identified by + * buf1->st_mode being 0. + * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted. + */ + memset (buf1, 0, sizeof (*buf1)); + } + + if (S_ISDIR (buf1->st_mode)) + tree_store_mark_checked (dp->d_name); + + /* A link to a file or a directory? */ + *link_to_dir = file_is_symlink_to_dir (vpath, buf1, stale_link); + + vfs_path_free (vpath, TRUE); + + if (filter != NULL && filter->handler != NULL) + { + gboolean files_only = (filter->flags & SELECT_FILES_ONLY) != 0; + + ok = ((S_ISDIR (buf1->st_mode) || *link_to_dir) && files_only) + || mc_search_run (filter->handler, dp->d_name, 0, strlen (dp->d_name), NULL); + } + + return ok; +} + +/* --------------------------------------------------------------------------------------------- */ +/** get info about ".." */ + +static gboolean +dir_get_dotdot_stat (const vfs_path_t * vpath, struct stat *st) +{ + gboolean ret = FALSE; + + if ((vpath != NULL) && (st != NULL)) + { + const char *path; + + path = vfs_path_get_by_index (vpath, 0)->path; + if (path != NULL && *path != '\0') + { + vfs_path_t *tmp_vpath; + + tmp_vpath = vfs_path_append_new (vpath, "..", (char *) NULL); + ret = mc_stat (tmp_vpath, st) == 0; + vfs_path_free (tmp_vpath, TRUE); + } + } + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +alloc_dir_copy (int size) +{ + if (dir_copy.size < size) + { + if (dir_copy.list != NULL) + dir_list_free_list (&dir_copy); + + dir_copy.list = g_new0 (file_entry_t, size); + dir_copy.size = size; + dir_copy.len = 0; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Increase or decrease directory list size. + * + * @param list directory list + * @param delta value by increase (if positive) or decrease (if negative) list size + * + * @return FALSE on failure, TRUE on success + */ + +gboolean +dir_list_grow (dir_list * list, int delta) +{ + int size; + gboolean clear_flag = FALSE; + + if (list == NULL) + return FALSE; + + if (delta == 0) + return TRUE; + + size = list->size + delta; + if (size <= 0) + { + size = DIR_LIST_MIN_SIZE; + clear_flag = TRUE; + } + + if (size != list->size) + { + file_entry_t *fe; + + fe = g_try_renew (file_entry_t, list->list, size); + if (fe == NULL) + return FALSE; + + list->list = fe; + list->size = size; + } + + list->len = clear_flag ? 0 : MIN (list->len, size); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Append file info to the directory list. + * + * @param list directory list + * @param fname file name + * @param st file stat info + * @param link_to_dir is file link to directory + * @param stale_link is file stale elink + * + * @return FALSE on failure, TRUE on success + */ + +gboolean +dir_list_append (dir_list * list, const char *fname, const struct stat * st, + gboolean link_to_dir, gboolean stale_link) +{ + file_entry_t *fentry; + + /* Need to grow the *list? */ + if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP)) + return FALSE; + + fentry = &list->list[list->len]; + fentry->fname = g_string_new (fname); + fentry->f.marked = 0; + fentry->f.link_to_dir = link_to_dir ? 1 : 0; + fentry->f.stale_link = stale_link ? 1 : 0; + fentry->f.dir_size_computed = 0; + fentry->st = *st; + fentry->sort_key = NULL; + fentry->second_sort_key = NULL; + + list->len++; + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +unsorted (file_entry_t * a, file_entry_t * b) +{ + (void) a; + (void) b; + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_name (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + return compare_by_names (a, b); + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_vers (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + { + int result; + + result = filevercmp (a->fname->str, b->fname->str); + if (result != 0) + return result * reverse; + + return compare_by_names (a, b); + } + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_ext (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + { + int r; + + if (a->second_sort_key == NULL) + a->second_sort_key = str_create_key (extension (a->fname->str), case_sensitive); + if (b->second_sort_key == NULL) + b->second_sort_key = str_create_key (extension (b->fname->str), case_sensitive); + + r = str_key_collate (a->second_sort_key, b->second_sort_key, case_sensitive); + if (r != 0) + return r * reverse; + + return compare_by_names (a, b); + } + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_time (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + { + int result = _GL_CMP (a->st.st_mtime, b->st.st_mtime); + + if (result != 0) + return result * reverse; + + return compare_by_names (a, b); + } + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_ctime (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + { + int result = _GL_CMP (a->st.st_ctime, b->st.st_ctime); + + if (result != 0) + return result * reverse; + + return compare_by_names (a, b); + } + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_atime (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + { + int result = _GL_CMP (a->st.st_atime, b->st.st_atime); + + if (result != 0) + return result * reverse; + + return compare_by_names (a, b); + } + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_inode (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + return (a->st.st_ino - b->st.st_ino) * reverse; + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +sort_size (file_entry_t * a, file_entry_t * b) +{ + int ad = MY_ISDIR (a); + int bd = MY_ISDIR (b); + + if (ad == bd || panels_options.mix_all_files) + { + int result = _GL_CMP (a->st.st_size, b->st.st_size); + + if (result != 0) + return result * reverse; + + return compare_by_names (a, b); + } + + return bd - ad; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op) +{ + if (list->len > 1 && sort != (GCompareFunc) unsorted) + { + file_entry_t *fentry = &list->list[0]; + int dot_dot_found; + + /* If there is an ".." entry the caller must take care to + ensure that it occupies the first list element. */ + dot_dot_found = DIR_IS_DOTDOT (fentry->fname->str) ? 1 : 0; + reverse = sort_op->reverse ? -1 : 1; + case_sensitive = sort_op->case_sensitive ? 1 : 0; + exec_first = sort_op->exec_first; + qsort (&(list->list)[dot_dot_found], list->len - dot_dot_found, sizeof (file_entry_t), + sort); + + clean_sort_keys (list, dot_dot_found, list->len - dot_dot_found); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +dir_list_clean (dir_list * list) +{ + int i; + + for (i = 0; i < list->len; i++) + { + file_entry_t *fentry; + + fentry = &list->list[i]; + g_string_free (fentry->fname, TRUE); + fentry->fname = NULL; + } + + list->len = 0; + /* reduce memory usage */ + dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +dir_list_free_list (dir_list * list) +{ + int i; + + for (i = 0; i < list->len; i++) + { + file_entry_t *fentry; + + fentry = &list->list[i]; + g_string_free (fentry->fname, TRUE); + } + + MC_PTR_FREE (list->list); + list->len = 0; + list->size = 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Used to set up a directory list when there is no access to a directory */ + +gboolean +dir_list_init (dir_list * list) +{ + file_entry_t *fentry; + + /* Need to grow the *list? */ + if (list->size == 0 && !dir_list_grow (list, DIR_LIST_RESIZE_STEP)) + { + list->len = 0; + return FALSE; + } + + fentry = &list->list[0]; + memset (fentry, 0, sizeof (*fentry)); + fentry->fname = g_string_new (".."); + fentry->f.link_to_dir = 0; + fentry->f.stale_link = 0; + fentry->f.dir_size_computed = 0; + fentry->f.marked = 0; + fentry->st.st_mode = 040755; + list->len = 1; + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + handle_path is a simplified handle_dirent. The difference is that + handle_path doesn't pay attention to panels_options.show_dot_files + and panels_options.show_backups. + Moreover handle_path can't be used with a filemask. + If you change handle_path then check also handle_dirent. */ +/* Return values: FALSE = don't add, TRUE = add to the list */ + +gboolean +handle_path (const char *path, struct stat * buf1, gboolean * link_to_dir, gboolean * stale_link) +{ + vfs_path_t *vpath; + + if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path)) + return FALSE; + + vpath = vfs_path_from_str (path); + if (mc_lstat (vpath, buf1) == -1) + { + vfs_path_free (vpath, TRUE); + return FALSE; + } + + if (S_ISDIR (buf1->st_mode)) + tree_store_mark_checked (path); + + /* A link to a file or a directory? */ + *link_to_dir = FALSE; + *stale_link = FALSE; + if (S_ISLNK (buf1->st_mode)) + { + struct stat buf2; + + if (mc_stat (vpath, &buf2) == 0) + *link_to_dir = S_ISDIR (buf2.st_mode) != 0; + else + *stale_link = TRUE; + } + + vfs_path_free (vpath, TRUE); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +gboolean +dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort, + const dir_sort_options_t * sort_op, const file_filter_t * filter) +{ + DIR *dirp; + struct vfs_dirent *dp; + struct stat st; + file_entry_t *fentry; + const char *vpath_str; + gboolean ret = TRUE; + + /* ".." (if any) must be the first entry in the list */ + if (!dir_list_init (list)) + return FALSE; + + fentry = &list->list[0]; + if (dir_get_dotdot_stat (vpath, &st)) + fentry->st = st; + + if (list->callback != NULL) + list->callback (DIR_OPEN, (void *) vpath); + dirp = mc_opendir (vpath); + if (dirp == NULL) + return FALSE; + + tree_store_start_check (vpath); + + vpath_str = vfs_path_as_str (vpath); + /* Do not add a ".." entry to the root directory */ + if (IS_PATH_SEP (vpath_str[0]) && vpath_str[1] == '\0') + dir_list_clean (list); + + while (ret && (dp = mc_readdir (dirp)) != NULL) + { + gboolean link_to_dir, stale_link; + + if (list->callback != NULL) + list->callback (DIR_READ, dp); + + if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link)) + continue; + + if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link)) + ret = FALSE; + } + + if (ret) + dir_list_sort (list, sort, sort_op); + + if (list->callback != NULL) + list->callback (DIR_CLOSE, NULL); + mc_closedir (dirp); + tree_store_end_check (); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +gboolean +if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file) +{ + struct stat b; + + if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0) + return is_exe (b.st_mode); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** If filter is null, then it is a match */ + +gboolean +dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort, + const dir_sort_options_t * sort_op, const file_filter_t * filter) +{ + DIR *dirp; + struct vfs_dirent *dp; + int i; + struct stat st; + int marked_cnt; + GHashTable *marked_files; + const char *tmp_path; + gboolean ret = TRUE; + + if (list->callback != NULL) + list->callback (DIR_OPEN, (void *) vpath); + dirp = mc_opendir (vpath); + if (dirp == NULL) + { + dir_list_clean (list); + dir_list_init (list); + return FALSE; + } + + tree_store_start_check (vpath); + + marked_files = g_hash_table_new (g_str_hash, g_str_equal); + alloc_dir_copy (list->len); + for (marked_cnt = i = 0; i < list->len; i++) + { + file_entry_t *fentry, *dfentry; + + fentry = &list->list[i]; + dfentry = &dir_copy.list[i]; + + dfentry->fname = mc_g_string_dup (fentry->fname); + dfentry->f.marked = fentry->f.marked; + dfentry->f.dir_size_computed = fentry->f.dir_size_computed; + dfentry->f.link_to_dir = fentry->f.link_to_dir; + dfentry->f.stale_link = fentry->f.stale_link; + dfentry->sort_key = NULL; + dfentry->second_sort_key = NULL; + if (fentry->f.marked) + { + g_hash_table_insert (marked_files, dfentry->fname->str, dfentry); + marked_cnt++; + } + } + + /* save len for later dir_list_clean() */ + dir_copy.len = list->len; + + /* Add ".." except to the root directory. The ".." entry + (if any) must be the first in the list. */ + tmp_path = vfs_path_get_by_index (vpath, 0)->path; + if (vfs_path_elements_count (vpath) == 1 && IS_PATH_SEP (tmp_path[0]) && tmp_path[1] == '\0') + { + /* root directory */ + dir_list_clean (list); + } + else + { + dir_list_clean (list); + if (!dir_list_init (list)) + { + dir_list_free_list (&dir_copy); + mc_closedir (dirp); + return FALSE; + } + + if (dir_get_dotdot_stat (vpath, &st)) + { + file_entry_t *fentry; + + fentry = &list->list[0]; + fentry->st = st; + } + } + + while (ret && (dp = mc_readdir (dirp)) != NULL) + { + gboolean link_to_dir, stale_link; + + if (list->callback != NULL) + list->callback (DIR_READ, dp); + + if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link)) + continue; + + if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link)) + ret = FALSE; + else + { + file_entry_t *fentry; + + fentry = &list->list[list->len - 1]; + + /* + * If we have marked files in the copy, scan through the copy + * to find matching file. Decrease number of remaining marks if + * we copied one. + */ + fentry->f.marked = (marked_cnt > 0 + && g_hash_table_lookup (marked_files, dp->d_name) != NULL); + if (fentry->f.marked) + marked_cnt--; + } + } + + if (ret) + dir_list_sort (list, sort, sort_op); + + if (list->callback != NULL) + list->callback (DIR_CLOSE, NULL); + mc_closedir (dirp); + tree_store_end_check (); + + g_hash_table_destroy (marked_files); + dir_list_free_list (&dir_copy); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +file_filter_clear (file_filter_t * filter) +{ + MC_PTR_FREE (filter->value); + mc_search_free (filter->handler); + filter->handler = NULL; + /* keep filter->flags */ +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/dir.h b/src/filemanager/dir.h new file mode 100644 index 0000000..f6a65ee --- /dev/null +++ b/src/filemanager/dir.h @@ -0,0 +1,115 @@ +/** \file dir.h + * \brief Header: directory routines + */ + +#ifndef MC__DIR_H +#define MC__DIR_H + +#include <sys/stat.h> + +#include "lib/global.h" +#include "lib/search.h" +#include "lib/util.h" +#include "lib/vfs/vfs.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +#define DIR_LIST_MIN_SIZE 128 +#define DIR_LIST_RESIZE_STEP 128 + +typedef enum +{ + DIR_OPEN = 0, + DIR_READ, + DIR_CLOSE +} dir_list_cb_state_t; + +/* selection flags */ +typedef enum +{ + SELECT_FILES_ONLY = 1 << 0, + SELECT_MATCH_CASE = 1 << 1, + SELECT_SHELL_PATTERNS = 1 << 2 +} select_flags_t; + +#define FILE_FILTER_DEFAULT_FLAGS (SELECT_FILES_ONLY | SELECT_MATCH_CASE | SELECT_SHELL_PATTERNS) + +/* dir_list callback */ +typedef void (*dir_list_cb_fn) (dir_list_cb_state_t state, void *data); + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/** + * A structure to represent directory content + */ +typedef struct +{ + file_entry_t *list; /**< list of file_entry_t objects */ + int size; /**< number of allocated elements in list (capacity) */ + int len; /**< number of used elements in list */ + dir_list_cb_fn callback; /**< callback to visualize of directory read */ +} dir_list; + +/** + * A structure to represent sort options for directory content + */ +typedef struct dir_sort_options_struct +{ + gboolean reverse; /**< sort is reverse */ + gboolean case_sensitive; /**< sort is case sensitive */ + gboolean exec_first; /**< executables are at top of list */ +} dir_sort_options_t; + +/* filter */ +typedef struct +{ + char *value; + mc_search_t *handler; + select_flags_t flags; +} file_filter_t; + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +gboolean dir_list_grow (dir_list * list, int delta); +gboolean dir_list_append (dir_list * list, const char *fname, const struct stat *st, + gboolean link_to_dir, gboolean stale_link); + +gboolean dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort, + const dir_sort_options_t * sort_op, const file_filter_t * filter); +gboolean dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort, + const dir_sort_options_t * sort_op, const file_filter_t * filter); +void dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op); +gboolean dir_list_init (dir_list * list); +void dir_list_clean (dir_list * list); +void dir_list_free_list (dir_list * list); +gboolean handle_path (const char *path, struct stat *buf1, gboolean * link_to_dir, + gboolean * stale_link); + +/* Sorting functions */ +int unsorted (file_entry_t * a, file_entry_t * b); +int sort_name (file_entry_t * a, file_entry_t * b); +int sort_vers (file_entry_t * a, file_entry_t * b); +int sort_ext (file_entry_t * a, file_entry_t * b); +int sort_time (file_entry_t * a, file_entry_t * b); +int sort_atime (file_entry_t * a, file_entry_t * b); +int sort_ctime (file_entry_t * a, file_entry_t * b); +int sort_size (file_entry_t * a, file_entry_t * b); +int sort_inode (file_entry_t * a, file_entry_t * b); + +gboolean if_link_is_exe (const vfs_path_t * full_name, const file_entry_t * file); + +void file_filter_clear (file_filter_t * filter); + +/*** inline functions ****************************************************************************/ + +static inline gboolean +link_isdir (const file_entry_t * file) +{ + return (gboolean) file->f.link_to_dir; +} + +#endif /* MC__DIR_H */ diff --git a/src/filemanager/ext.c b/src/filemanager/ext.c new file mode 100644 index 0000000..3c54747 --- /dev/null +++ b/src/filemanager/ext.c @@ -0,0 +1,1084 @@ +/* + Extension dependent execution. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Jakub Jelinek, 1995 + Miguel de Icaza, 1994 + 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 ext.c + * \brief Source: extension dependent execution + */ + +#include <config.h> + +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "lib/global.h" +#include "lib/tty/tty.h" +#include "lib/search.h" +#include "lib/fileloc.h" +#include "lib/mcconfig.h" +#include "lib/util.h" +#include "lib/vfs/vfs.h" +#include "lib/widget.h" +#ifdef HAVE_CHARSET +#include "lib/charsets.h" /* get_codepage_index */ +#endif + +#ifdef USE_FILE_CMD +#include "src/setup.h" /* use_file_to_check_type */ +#endif +#include "src/execute.h" +#include "src/history.h" +#include "src/usermenu.h" + +#include "src/consaver/cons.saver.h" +#include "src/viewer/mcviewer.h" + +#ifdef HAVE_CHARSET +#include "src/selcodepage.h" /* do_set_codepage */ +#endif + +#include "filemanager.h" /* current_panel */ +#include "panel.h" /* panel_cd */ + +#include "ext.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#ifdef USE_FILE_CMD +#ifdef FILE_B +#define FILE_CMD "file -z " FILE_B FILE_S FILE_L +#else +#define FILE_CMD "file -z " FILE_S FILE_L +#endif +#endif + +/*** file scope type declarations ****************************************************************/ + +typedef char *(*quote_func_t) (const char *name, gboolean quote_percent); + +/*** file scope variables ************************************************************************/ + +/* This variable points to a copy of the mc.ext file in memory + * With this we avoid loading/parsing the file each time we + * need it + */ +static mc_config_t *ext_ini = NULL; +static gchar **ext_ini_groups = NULL; +static vfs_path_t *localfilecopy_vpath = NULL; +static char buffer[BUF_1K]; + +static char *pbuffer = NULL; +static time_t localmtime = 0; +static quote_func_t quote_func = name_quote; +static gboolean run_view = FALSE; +static gboolean is_cd = FALSE; +static gboolean written_nonspace = FALSE; +static gboolean do_local_copy = FALSE; + +static const char *descr_group = "mc.ext.ini"; +static const char *default_group = "Default"; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +exec_cleanup_script (vfs_path_t * script_vpath) +{ + if (script_vpath != NULL) + { + (void) mc_unlink (script_vpath); + vfs_path_free (script_vpath, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +exec_cleanup_file_name (const vfs_path_t * filename_vpath, gboolean has_changed) +{ + if (localfilecopy_vpath == NULL) + return; + + if (has_changed) + { + struct stat mystat; + + mc_stat (localfilecopy_vpath, &mystat); + has_changed = localmtime != mystat.st_mtime; + } + mc_ungetlocalcopy (filename_vpath, localfilecopy_vpath, has_changed); + vfs_path_free (localfilecopy_vpath, TRUE); + localfilecopy_vpath = NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +exec_get_file_name (const vfs_path_t * filename_vpath) +{ + if (!do_local_copy) + return quote_func (vfs_path_get_last_path_str (filename_vpath), FALSE); + + if (localfilecopy_vpath == NULL) + { + struct stat mystat; + localfilecopy_vpath = mc_getlocalcopy (filename_vpath); + if (localfilecopy_vpath == NULL) + return NULL; + + mc_stat (localfilecopy_vpath, &mystat); + localmtime = mystat.st_mtime; + } + + return quote_func (vfs_path_get_last_path_str (localfilecopy_vpath), FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +exec_expand_format (char symbol, gboolean is_result_quoted) +{ + char *text; + + text = expand_format (NULL, symbol, TRUE); + if (is_result_quoted && text != NULL) + { + char *quoted_text; + + quoted_text = g_strdup_printf ("\"%s\"", text); + g_free (text); + text = quoted_text; + } + return text; +} + +/* --------------------------------------------------------------------------------------------- */ + +static GString * +exec_get_export_variables (const vfs_path_t * filename_vpath) +{ + char *text; + GString *export_vars_string; + size_t i; + + /* *INDENT-OFF* */ + struct + { + const char symbol; + const char *name; + const gboolean is_result_quoted; + } export_variables[] = { + {'p', "MC_EXT_BASENAME", FALSE}, + {'d', "MC_EXT_CURRENTDIR", FALSE}, + {'s', "MC_EXT_SELECTED", TRUE}, + {'t', "MC_EXT_ONLYTAGGED", TRUE}, + {'\0', NULL, FALSE} + }; + /* *INDENT-ON* */ + + text = exec_get_file_name (filename_vpath); + if (text == NULL) + return NULL; + + export_vars_string = g_string_new ("MC_EXT_FILENAME="); + g_string_append_printf (export_vars_string, "%s\nexport MC_EXT_FILENAME\n", text); + g_free (text); + + for (i = 0; export_variables[i].name != NULL; i++) + { + text = + exec_expand_format (export_variables[i].symbol, export_variables[i].is_result_quoted); + if (text != NULL) + { + g_string_append_printf (export_vars_string, + "%s=%s\nexport %s\n", export_variables[i].name, text, + export_variables[i].name); + g_free (text); + } + } + + return export_vars_string; +} + +/* --------------------------------------------------------------------------------------------- */ + +static GString * +exec_make_shell_string (const char *lc_data, const vfs_path_t * filename_vpath) +{ + GString *shell_string; + char lc_prompt[80] = "\0"; + gboolean parameter_found = FALSE; + gboolean expand_prefix_found = FALSE; + + shell_string = g_string_new (""); + + for (; *lc_data != '\0' && *lc_data != '\n'; lc_data++) + { + if (parameter_found) + { + if (*lc_data == '}') + { + char *parameter; + + parameter_found = FALSE; + parameter = + input_dialog (_("Parameter"), lc_prompt, MC_HISTORY_EXT_PARAMETER, "", + INPUT_COMPLETE_NONE); + if (parameter == NULL) + { + /* User canceled */ + g_string_free (shell_string, TRUE); + exec_cleanup_file_name (filename_vpath, FALSE); + return NULL; + } + g_string_append (shell_string, parameter); + written_nonspace = TRUE; + g_free (parameter); + } + else + { + size_t len = strlen (lc_prompt); + + if (len < sizeof (lc_prompt) - 1) + { + lc_prompt[len] = *lc_data; + lc_prompt[len + 1] = '\0'; + } + } + } + else if (expand_prefix_found) + { + expand_prefix_found = FALSE; + if (*lc_data == '{') + parameter_found = TRUE; + else + { + int i; + + i = check_format_view (lc_data); + if (i != 0) + { + lc_data += i - 1; + run_view = TRUE; + } + else + { + i = check_format_cd (lc_data); + if (i > 0) + { + is_cd = TRUE; + quote_func = fake_name_quote; + do_local_copy = FALSE; + pbuffer = buffer; + lc_data += i - 1; + } + else + { + char *v; + + i = check_format_var (lc_data, &v); + if (i > 0) + { + g_string_append (shell_string, v); + g_free (v); + lc_data += i; + } + else + { + char *text; + + if (*lc_data != 'f') + text = expand_format (NULL, *lc_data, !is_cd); + else + { + text = exec_get_file_name (filename_vpath); + if (text == NULL) + { + g_string_free (shell_string, TRUE); + return NULL; + } + } + + if (!is_cd) + g_string_append (shell_string, text); + else + { + strcpy (pbuffer, text); + pbuffer = strchr (pbuffer, 0); + } + + g_free (text); + written_nonspace = TRUE; + } + } + } + } + } + else if (*lc_data == '%') + expand_prefix_found = TRUE; + else + { + if (!whitespace (*lc_data)) + written_nonspace = TRUE; + if (is_cd) + *(pbuffer++) = *lc_data; + else + g_string_append_c (shell_string, *lc_data); + } + } /* for */ + + return shell_string; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +exec_extension_view (void *target, char *cmd, const vfs_path_t * filename_vpath, int start_line) +{ + mcview_mode_flags_t def_flags = { + /* *INDENT-OFF* */ + .wrap = FALSE, + .hex = mcview_global_flags.hex, + .magic = FALSE, + .nroff = mcview_global_flags.nroff + /* *INDENT-ON* */ + }; + + mcview_mode_flags_t changed_flags; + + mcview_clear_mode_flags (&changed_flags); + mcview_altered_flags.hex = FALSE; + mcview_altered_flags.nroff = FALSE; + if (def_flags.hex != mcview_global_flags.hex) + changed_flags.hex = TRUE; + if (def_flags.nroff != mcview_global_flags.nroff) + changed_flags.nroff = TRUE; + + if (target == NULL) + mcview_viewer (cmd, filename_vpath, start_line, 0, 0); + else + mcview_load ((WView *) target, cmd, vfs_path_as_str (filename_vpath), start_line, 0, 0); + + if (changed_flags.hex && !mcview_altered_flags.hex) + mcview_global_flags.hex = def_flags.hex; + if (changed_flags.nroff && !mcview_altered_flags.nroff) + mcview_global_flags.nroff = def_flags.nroff; + + dialog_switch_process_pending (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +exec_extension_cd (WPanel * panel) +{ + char *q; + vfs_path_t *p_vpath; + + *pbuffer = '\0'; + pbuffer = buffer; + /* Search last non-space character. Start search at the end in order + not to short filenames containing spaces. */ + q = pbuffer + strlen (pbuffer) - 1; + while (q >= pbuffer && whitespace (*q)) + q--; + q[1] = 0; + + p_vpath = vfs_path_from_str_flags (pbuffer, VPF_NO_CANON); + panel_cd (panel, p_vpath, cd_parse_command); + vfs_path_free (p_vpath, TRUE); +} + + +/* --------------------------------------------------------------------------------------------- */ + +static vfs_path_t * +exec_extension (WPanel * panel, void *target, const vfs_path_t * filename_vpath, + const char *lc_data, int start_line) +{ + GString *shell_string, *export_variables; + vfs_path_t *script_vpath = NULL; + int cmd_file_fd; + FILE *cmd_file; + char *cmd = NULL; + + pbuffer = NULL; + localmtime = 0; + quote_func = name_quote; + run_view = FALSE; + is_cd = FALSE; + written_nonspace = FALSE; + + /* Avoid making a local copy if we are doing a cd */ + do_local_copy = !vfs_file_is_local (filename_vpath); + + shell_string = exec_make_shell_string (lc_data, filename_vpath); + if (shell_string == NULL) + goto ret; + + if (is_cd) + { + exec_extension_cd (panel); + g_string_free (shell_string, TRUE); + goto ret; + } + + /* + * All commands should be run in /bin/sh regardless of user shell. + * To do that, create temporary shell script and run it. + * Sometimes it's not needed (e.g. for %cd and %view commands), + * but it's easier to create it anyway. + */ + cmd_file_fd = mc_mkstemps (&script_vpath, "mcext", SCRIPT_SUFFIX); + + if (cmd_file_fd == -1) + { + message (D_ERROR, MSG_ERROR, + _("Cannot create temporary command file\n%s"), unix_error_string (errno)); + g_string_free (shell_string, TRUE); + goto ret; + } + + cmd_file = fdopen (cmd_file_fd, "w"); + fputs ("#! /bin/sh\n\n", cmd_file); + + export_variables = exec_get_export_variables (filename_vpath); + if (export_variables != NULL) + { + fputs (export_variables->str, cmd_file); + g_string_free (export_variables, TRUE); + } + + fputs (shell_string->str, cmd_file); + g_string_free (shell_string, TRUE); + + /* + * Make the script remove itself when it finishes. + * Don't do it for the viewer - it may need to rerun the script, + * so we clean up after calling view(). + */ + if (!run_view) + fprintf (cmd_file, "\n/bin/rm -f %s\n", vfs_path_as_str (script_vpath)); + + fclose (cmd_file); + + if ((run_view && !written_nonspace) || is_cd) + { + exec_cleanup_script (script_vpath); + script_vpath = NULL; + } + else + { + /* Set executable flag on the command file ... */ + mc_chmod (script_vpath, S_IRWXU); + /* ... but don't rely on it - run /bin/sh explicitly */ + cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (script_vpath), (char *) NULL); + } + + if (run_view) + { + /* If we've written whitespace only, then just load filename into view */ + if (!written_nonspace) + exec_extension_view (target, NULL, filename_vpath, start_line); + else + exec_extension_view (target, cmd, filename_vpath, start_line); + } + else + { + shell_execute (cmd, EXECUTE_INTERNAL); + if (mc_global.tty.console_flag != '\0') + { + handle_console (CONSOLE_SAVE); + if (output_lines != 0 && mc_global.keybar_visible) + { + unsigned char end_line; + + end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1; + show_console_contents (output_start_y, end_line - output_lines, end_line); + } + } + } + + g_free (cmd); + + exec_cleanup_file_name (filename_vpath, TRUE); + ret: + return script_vpath; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Run cmd_file with args, put result into buf. + * If error, put '\0' into buf[0] + * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors. + * + * NOTES: buf is null-terminated string. + */ + +#ifdef USE_FILE_CMD +static int +get_popen_information (const char *cmd_file, const char *args, char *buf, int buflen) +{ + gboolean read_bytes = FALSE; + char *command; + FILE *f; + + command = g_strconcat (cmd_file, args, " 2>/dev/null", (char *) NULL); + f = popen (command, "r"); + g_free (command); + + if (f != NULL) + { +#ifdef __QNXNTO__ + if (setvbuf (f, NULL, _IOFBF, 0) != 0) + { + (void) pclose (f); + return -1; + } +#endif + read_bytes = (fgets (buf, buflen, f) != NULL); + if (!read_bytes) + buf[0] = '\0'; /* Paranoid termination */ + pclose (f); + } + else + { + buf[0] = '\0'; /* Paranoid termination */ + return -1; + } + + buf[buflen - 1] = '\0'; + + return read_bytes ? 1 : 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Run the "file" command on the local file. + * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors. + */ + +static int +get_file_type_local (const vfs_path_t * filename_vpath, char *buf, int buflen) +{ + char *tmp; + int ret; + + tmp = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE); + ret = get_popen_information (FILE_CMD, tmp, buf, buflen); + g_free (tmp); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Run the "enca" command on the local file. + * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors. + */ + +#ifdef HAVE_CHARSET +static int +get_file_encoding_local (const vfs_path_t * filename_vpath, char *buf, int buflen) +{ + char *tmp, *lang, *args; + int ret; + + tmp = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE); + lang = name_quote (autodetect_codeset, FALSE); + args = g_strconcat (" -L", lang, " -i ", tmp, (char *) NULL); + + ret = get_popen_information ("enca", args, buf, buflen); + + g_free (args); + g_free (lang); + g_free (tmp); + + return ret; +} +#endif /* HAVE_CHARSET */ + +/* --------------------------------------------------------------------------------------------- */ +/** + * Invoke the "file" command on the file and match its output against PTR. + * have_type is a flag that is set if we already have tried to determine + * the type of that file. + * Return TRUE for match, FALSE otherwise. + */ + +static gboolean +regex_check_type (const vfs_path_t * filename_vpath, const char *ptr, gboolean case_insense, + gboolean * have_type, GError ** mcerror) +{ + gboolean found = FALSE; + + /* Following variables are valid if *have_type is TRUE */ + static char content_string[2048]; + static size_t content_shift = 0; + static int got_data = 0; + + mc_return_val_if_error (mcerror, FALSE); + + if (!*have_type) + { + vfs_path_t *localfile_vpath; + +#ifdef HAVE_CHARSET + static char encoding_id[21]; /* CSISO51INISCYRILLIC -- 20 */ + int got_encoding_data; +#endif /* HAVE_CHARSET */ + + /* Don't repeate even unsuccessful checks */ + *have_type = TRUE; + + localfile_vpath = mc_getlocalcopy (filename_vpath); + if (localfile_vpath == NULL) + { + mc_propagate_error (mcerror, 0, _("Cannot fetch a local copy of %s"), + vfs_path_as_str (filename_vpath)); + return FALSE; + } + + +#ifdef HAVE_CHARSET + got_encoding_data = is_autodetect_codeset_enabled + ? get_file_encoding_local (localfile_vpath, encoding_id, sizeof (encoding_id)) : 0; + + if (got_encoding_data > 0) + { + char *pp; + int cp_id; + + pp = strchr (encoding_id, '\n'); + if (pp != NULL) + *pp = '\0'; + + cp_id = get_codepage_index (encoding_id); + if (cp_id == -1) + cp_id = default_source_codepage; + + do_set_codepage (cp_id); + } +#endif /* HAVE_CHARSET */ + + got_data = get_file_type_local (localfile_vpath, content_string, sizeof (content_string)); + + mc_ungetlocalcopy (filename_vpath, localfile_vpath, FALSE); + + if (got_data > 0) + { + char *pp; + + pp = strchr (content_string, '\n'); + if (pp != NULL) + *pp = '\0'; + +#ifndef FILE_B + { + const char *real_name; /* name used with "file" */ + size_t real_len; + + real_name = vfs_path_get_last_path_str (localfile_vpath); + real_len = strlen (real_name); + + if (strncmp (content_string, real_name, real_len) == 0) + { + /* Skip "real_name: " */ + content_shift = real_len; + + /* Solaris' file prints tab(s) after ':' */ + if (content_string[content_shift] == ':') + for (content_shift++; whitespace (content_string[content_shift]); + content_shift++) + ; + } + } +#endif /* FILE_B */ + } + else + { + /* No data */ + content_string[0] = '\0'; + } + vfs_path_free (localfile_vpath, TRUE); + } + + if (got_data == -1) + { + mc_propagate_error (mcerror, 0, "%s", _("Pipe failed")); + return FALSE; + } + + if (content_string[0] != '\0') + { + mc_search_t *search; + + search = mc_search_new (ptr, DEFAULT_CHARSET); + if (search != NULL) + { + search->search_type = MC_SEARCH_T_REGEX; + search->is_case_sensitive = !case_insense; + found = mc_search_run (search, content_string + content_shift, 0, -1, NULL); + mc_search_free (search); + } + else + { + mc_propagate_error (mcerror, 0, "%s", _("Regular expression error")); + } + } + + return found; +} +#endif /* USE_FILE_CMD */ + +/* --------------------------------------------------------------------------------------------- */ + +static void +check_old_extension_file (void) +{ + char *extension_old_file; + + extension_old_file = mc_config_get_full_path (MC_EXT_OLD_FILE); + if (exist_file (extension_old_file)) + message (D_ERROR, _("Warning"), + _("You have an outdated %s file.\nMidnight Commander now uses %s file.\n" + "Please copy your modifications of the old file to the new one."), + extension_old_file, MC_EXT_FILE); + g_free (extension_old_file); +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +load_extension_file (void) +{ + char *extension_file; + gboolean mc_user_ext = TRUE; + gboolean home_error = FALSE; + + extension_file = mc_config_get_full_path (MC_EXT_FILE); + if (!exist_file (extension_file)) + { + g_free (extension_file); + + check_old_extension_file (); + + check_stock_mc_ext: + extension_file = mc_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL); + if (!exist_file (extension_file)) + { + g_free (extension_file); + extension_file = + mc_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL); + if (!exist_file (extension_file)) + MC_PTR_FREE (extension_file); + } + mc_user_ext = FALSE; + } + + if (extension_file != NULL) + { + ext_ini = mc_config_init (extension_file, TRUE); + g_free (extension_file); + } + if (ext_ini == NULL) + return FALSE; + + /* Check version */ + if (!mc_config_has_group (ext_ini, descr_group)) + { + flush_extension_file (); + + if (!mc_user_ext) + { + char *title; + + title = g_strdup_printf (_(" %s%s file error"), mc_global.sysconfig_dir, MC_EXT_FILE); + message (D_ERROR, title, + _("The format of the %s%s file has changed with version 4.0. " + "It seems that the installation has failed. Please fetch a fresh copy " + "from the Midnight Commander package."), + mc_global.sysconfig_dir, MC_EXT_FILE); + g_free (title); + return FALSE; + } + + home_error = TRUE; + goto check_stock_mc_ext; + } + + if (home_error) + { + char *title; + + extension_file = mc_config_get_full_path (MC_EXT_FILE); + title = g_strdup_printf (_("%s file error"), extension_file); + message (D_ERROR, title, + _("The format of the %s file has changed with version 4.0. You may either want " + "to copy it from %s%s or use that file as an example of how to write it."), + extension_file, mc_global.sysconfig_dir, MC_EXT_FILE); + g_free (extension_file); + g_free (title); + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +flush_extension_file (void) +{ + g_strfreev (ext_ini_groups); + ext_ini_groups = NULL; + + mc_config_deinit (ext_ini); + ext_ini = NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * The second argument is action, i.e. Open, View or Edit + * Use target object to open file in. + * + * This function returns: + * + * -1 for a failure or user interrupt + * 0 if no command was run + * 1 if some command was run + * + * If action == "View" then a parameter is checked in the form of "View:%d", + * if the value for %d exists, then the viewer is started up at that line number. + */ + +int +regex_command_for (void *target, const vfs_path_t * filename_vpath, const char *action, + vfs_path_t ** script_vpath) +{ + const char *filename; + size_t filename_len; + gboolean found = FALSE; + gboolean error_flag = FALSE; + int ret = 0; + struct stat mystat; + int view_at_line_number = 0; +#ifdef USE_FILE_CMD + gboolean have_type = FALSE; /* Flag used by regex_check_type() */ +#endif + char **group_iter; + char *include_group = NULL; + const char *current_group; + + if (filename_vpath == NULL) + return 0; + + if (script_vpath != NULL) + *script_vpath = NULL; + + /* Check for the special View:%d parameter */ + if (strncmp (action, "View:", 5) == 0) + { + view_at_line_number = atoi (action + 5); + action = "View"; + } + + if (ext_ini == NULL && !load_extension_file ()) + return 0; + + mc_stat (filename_vpath, &mystat); + + filename = vfs_path_get_last_path_str (filename_vpath); + filename = x_basename (filename); + filename_len = strlen (filename); + + if (ext_ini_groups == NULL) + ext_ini_groups = mc_config_get_groups (ext_ini, NULL); + + /* find matched type, regex or shell pattern */ + for (group_iter = ext_ini_groups; *group_iter != NULL && !found; group_iter++) + { + enum + { + TYPE_UNUSED, + TYPE_NOT_FOUND, + TYPE_FOUND + } type_state = TYPE_UNUSED; + + const gchar *g = *group_iter; + gchar *pattern; + gboolean ignore_case; + + if (strcmp (g, descr_group) == 0 || strncmp (g, "Include/", 8) == 0 + || strcmp (g, default_group) == 0) + continue; + + /* The "Directory" parameter is a special case: if it's present then + "Type", "Regex", and "Shell" parameters are ignored */ + pattern = mc_config_get_string_raw (ext_ini, g, "Directory", NULL); + if (pattern != NULL) + { + found = S_ISDIR (mystat.st_mode) + && mc_search (pattern, DEFAULT_CHARSET, vfs_path_as_str (filename_vpath), + MC_SEARCH_T_REGEX); + g_free (pattern); + + continue; /* stop if found */ + } + +#ifdef USE_FILE_CMD + if (use_file_to_check_type) + { + pattern = mc_config_get_string_raw (ext_ini, g, "Type", NULL); + if (pattern != NULL) + { + GError *mcerror = NULL; + + ignore_case = mc_config_get_bool (ext_ini, g, "TypeIgnoreCase", FALSE); + type_state = + regex_check_type (filename_vpath, pattern, ignore_case, &have_type, &mcerror) + ? TYPE_FOUND : TYPE_NOT_FOUND; + g_free (pattern); + + if (mc_error_message (&mcerror, NULL)) + error_flag = TRUE; /* leave it if file cannot be opened */ + + if (type_state == TYPE_NOT_FOUND) + continue; + } + } +#endif /* USE_FILE_CMD */ + + pattern = mc_config_get_string_raw (ext_ini, g, "Regex", NULL); + if (pattern != NULL) + { + mc_search_t *search; + + ignore_case = mc_config_get_bool (ext_ini, g, "RegexIgnoreCase", FALSE); + search = mc_search_new (pattern, DEFAULT_CHARSET); + g_free (pattern); + + if (search != NULL) + { + search->search_type = MC_SEARCH_T_REGEX; + search->is_case_sensitive = !ignore_case; + found = mc_search_run (search, filename, 0, filename_len, NULL); + mc_search_free (search); + } + + found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND); + } + else + { + pattern = mc_config_get_string_raw (ext_ini, g, "Shell", NULL); + if (pattern != NULL) + { + int (*cmp_func) (const char *s1, const char *s2, size_t n); + size_t pattern_len; + + ignore_case = mc_config_get_bool (ext_ini, g, "ShellIgnoreCase", FALSE); + cmp_func = ignore_case ? strncasecmp : strncmp; + pattern_len = strlen (pattern); + + if (*pattern == '.' && filename_len >= pattern_len) + found = + cmp_func (pattern, filename + filename_len - pattern_len, pattern_len) == 0; + else + found = pattern_len == filename_len + && cmp_func (pattern, filename, filename_len) == 0; + + g_free (pattern); + + found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND); + } + else + found = type_state == TYPE_FOUND; + } + } + + /* group is found, process actions */ + if (found) + { + char *include_value; + + group_iter--; + + /* "Include" parameter has the highest priority over any actions */ + include_value = mc_config_get_string_raw (ext_ini, *group_iter, "Include", NULL); + if (include_value != NULL) + { + /* find "Include/include_value" group */ + include_group = g_strconcat ("Include/", include_value, (char *) NULL); + g_free (include_value); + found = mc_config_has_group (ext_ini, include_group); + } + } + + if (found) + current_group = include_group != NULL ? include_group : *group_iter; + else + { + current_group = default_group; + found = mc_config_has_group (ext_ini, current_group); + } + + if (found && !error_flag) + { + gchar *action_value; + + action_value = mc_config_get_string_raw (ext_ini, current_group, action, NULL); + /* Empty commands just stop searching through, they don't do anything */ + if (action_value != NULL && *action_value != '\0') + { + vfs_path_t *sv; + + sv = exec_extension (current_panel, target, filename_vpath, action_value, + view_at_line_number); + if (script_vpath != NULL) + *script_vpath = sv; + else + exec_cleanup_script (sv); + + ret = 1; + } + + g_free (action_value); + } + + g_free (include_group); + + return (error_flag ? -1 : ret); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/ext.h b/src/filemanager/ext.h new file mode 100644 index 0000000..771ca18 --- /dev/null +++ b/src/filemanager/ext.h @@ -0,0 +1,33 @@ +/** \file ext.h + * \brief Header: extension dependent execution + */ + +#ifndef MC__EXT_H +#define MC__EXT_H +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +int regex_command_for (void *target, const vfs_path_t * filename_vpath, const char *action, + vfs_path_t ** script_vpath); + +/* Call it after the user has edited the mc.ext file, + * to flush the cached mc.ext file + */ +void flush_extension_file (void); + +/*** inline functions ****************************************************************************/ + +static inline int +regex_command (const vfs_path_t * filename_vpath, const char *action) +{ + return regex_command_for (NULL, filename_vpath, action, NULL); +} + +#endif /* MC__EXT_H */ diff --git a/src/filemanager/file.c b/src/filemanager/file.c new file mode 100644 index 0000000..5303d40 --- /dev/null +++ b/src/filemanager/file.c @@ -0,0 +1,3568 @@ +/* + File management. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Janne Kukonlehto, 1994, 1995 + Fred Leeflang, 1994, 1995 + Miguel de Icaza, 1994, 1995, 1996 + Jakub Jelinek, 1995, 1996 + Norbert Warmuth, 1997 + Pavel Machek, 1998 + Andrew Borodin <aborodin@vmail.ru>, 2011-2022 + + The copy code was based in GNU's cp, and was written by: + Torbjorn Granlund, David MacKenzie, and Jim Meyering. + + The move code was based in GNU's mv, and was written by: + Mike Parker and David MacKenzie. + + Janne Kukonlehto added much error recovery to them for being used + in an interactive program. + + 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/>. + */ + +/* + * Please note that all dialogs used here must be safe for background + * operations. + */ + +/** \file src/filemanager/file.c + * \brief Source: file management + */ + +/* {{{ Include files */ + +#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 <unistd.h> + +#include "lib/global.h" +#include "lib/tty/tty.h" +#include "lib/tty/key.h" +#include "lib/search.h" +#include "lib/strescape.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/vfs/vfs.h" +#include "lib/widget.h" + +#include "src/setup.h" +#ifdef ENABLE_BACKGROUND +#include "src/background.h" /* do_background() */ +#endif + +/* Needed for other_panel and WTree */ +#include "dir.h" +#include "filegui.h" +#include "filenot.h" +#include "tree.h" +#include "filemanager.h" /* other_panel */ +#include "layout.h" /* rotate_dash() */ +#include "ioblksize.h" /* io_blksize() */ + +#include "file.h" + +/* }}} */ + +/*** global variables ****************************************************************************/ + +/* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */ +const char *op_names[3] = { + N_("DialogTitle|Copy"), + N_("DialogTitle|Move"), + N_("DialogTitle|Delete") +}; + +/*** file scope macro definitions ****************************************************************/ + +#define FILEOP_UPDATE_INTERVAL 2 +#define FILEOP_STALLING_INTERVAL 4 +#define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC) +#define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC) + +/*** file scope type declarations ****************************************************************/ + +/* This is a hard link cache */ +struct link +{ + const struct vfs_class *vfs; + dev_t dev; + ino_t ino; + short linkcount; + mode_t st_mode; + vfs_path_t *src_vpath; + vfs_path_t *dst_vpath; +}; + +/* Status of the destination file */ +typedef enum +{ + DEST_NONE = 0, /**< Not created */ + DEST_SHORT_QUERY, /**< Created, not fully copied, query to do */ + DEST_SHORT_KEEP, /**< Created, not fully copied, keep it */ + DEST_SHORT_DELETE, /**< Created, not fully copied, delete it */ + DEST_FULL /**< Created, fully copied */ +} dest_status_t; + +/* Status of hard link creation */ +typedef enum +{ + HARDLINK_OK = 0, /**< Hardlink was created successfully */ + HARDLINK_CACHED, /**< Hardlink was added to the cache */ + HARDLINK_NOTLINK, /**< This is not a hard link */ + HARDLINK_UNSUPPORTED, /**< VFS doesn't support hard links */ + HARDLINK_ERROR, /**< Hard link creation error */ + HARDLINK_ABORT /**< Stop file operation after hardlink creation error */ +} hardlink_status_t; + +/* + * This array introduced to avoid translation problems. The former (op_names) + * is assumed to be nouns, suitable in dialog box titles; this one should + * contain whatever is used in prompt itself (i.e. in russian, it's verb). + * (I don't use spaces around the words, because someday they could be + * dropped, when widgets get smarter) + */ + +/* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */ +static const char *op_names1[] = { + N_("FileOperation|Copy"), + N_("FileOperation|Move"), + N_("FileOperation|Delete") +}; + +/* + * These are formats for building a prompt. Parts encoded as follows: + * %o - operation from op_names1 + * %f - file/files or files/directories, as appropriate + * %m - "with source mask" or question mark for delete + * %s - source name (truncated) + * %d - number of marked files + * %n - the '\n' symbol to form two-line prompt for delete or space for other operations + */ +/* xgettext:no-c-format */ +static const char *one_format = N_("%o %f%n\"%s\"%m"); +/* xgettext:no-c-format */ +static const char *many_format = N_("%o %d %f%m"); + +static const char *prompt_parts[] = { + N_("file"), + N_("files"), + N_("directory"), + N_("directories"), + N_("files/directories"), + /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */ + N_(" with source mask:") +}; + +/*** file scope variables ************************************************************************/ + +/* the hard link cache */ +static GSList *linklist = NULL; + +/* the files-to-be-erased list */ +static GQueue *erase_list = NULL; + +/* + * In copy_dir_dir we use two additional single linked lists: The first - + * variable name 'parent_dirs' - holds information about already copied + * directories and is used to detect cyclic symbolic links. + * The second ('dest_dirs' below) holds information about just created + * target directories and is used to detect when an directory is copied + * into itself (we don't want to copy infinitly). + * Both lists don't use the linkcount and name structure members of struct + * link. + */ +static GSList *dest_dirs = NULL; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +dirsize_status_locate_buttons (dirsize_status_msg_t * dsm) +{ + status_msg_t *sm = STATUS_MSG (dsm); + Widget *wd = WIDGET (sm->dlg); + int y, x; + WRect r; + + y = wd->rect.y + 5; + x = wd->rect.x; + + if (!dsm->allow_skip) + { + /* single button: "Abort" */ + x += (wd->rect.cols - dsm->abort_button->rect.cols) / 2; + r = dsm->abort_button->rect; + r.y = y; + r.x = x; + widget_set_size_rect (dsm->abort_button, &r); + } + else + { + /* two buttons: "Abort" and "Skip" */ + int cols; + + cols = dsm->abort_button->rect.cols + dsm->skip_button->rect.cols + 1; + x += (wd->rect.cols - cols) / 2; + r = dsm->abort_button->rect; + r.y = y; + r.x = x; + widget_set_size_rect (dsm->abort_button, &r); + x += dsm->abort_button->rect.cols + 1; + r = dsm->skip_button->rect; + r.y = y; + r.x = x; + widget_set_size_rect (dsm->skip_button, &r); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +build_dest (file_op_context_t * ctx, const char *src, const char *dest, FileProgressStatus * status) +{ + char *s, *q; + const char *fnsource; + + *status = FILE_CONT; + + s = g_strdup (src); + + /* We remove \n from the filename since regex routines would use \n as an anchor */ + /* this is just to be allowed to maniupulate file names with \n on it */ + for (q = s; *q != '\0'; q++) + if (*q == '\n') + *q = ' '; + + fnsource = x_basename (s); + + if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL)) + { + q = NULL; + *status = FILE_SKIP; + } + else + { + q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask); + if (ctx->search_handle->error != MC_SEARCH_E_OK) + { + if (ctx->search_handle->error_str != NULL) + message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str); + + *status = FILE_ABORT; + } + } + + MC_PTR_FREE (s); + + if (*status == FILE_CONT) + { + char *repl_dest; + + repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest); + if (ctx->search_handle->error == MC_SEARCH_E_OK) + s = mc_build_filename (repl_dest, q, (char *) NULL); + else + { + if (ctx->search_handle->error_str != NULL) + message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str); + + *status = FILE_ABORT; + } + + g_free (repl_dest); + } + + g_free (q); + + return s; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +free_link (void *data) +{ + struct link *lp = (struct link *) data; + + vfs_path_free (lp->src_vpath, TRUE); + vfs_path_free (lp->dst_vpath, TRUE); + g_free (lp); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void * +free_erase_list (GQueue * lp) +{ + if (lp != NULL) + g_queue_free_full (lp, free_link); + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void * +free_linklist (GSList * lp) +{ + g_slist_free_full (lp, free_link); + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static const struct link * +is_in_linklist (const GSList * lp, const vfs_path_t * vpath, const struct stat *sb) +{ + const struct vfs_class *class; + ino_t ino = sb->st_ino; + dev_t dev = sb->st_dev; + + class = vfs_path_get_last_path_vfs (vpath); + + for (; lp != NULL; lp = (const GSList *) g_slist_next (lp)) + { + const struct link *lnk = (const struct link *) lp->data; + + if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev) + return lnk; + } + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Check and made hardlink + * + * @return FALSE if the inode wasn't found in the cache and TRUE if it was found + * and a hardlink was successfully made + */ + +static hardlink_status_t +check_hardlinks (const vfs_path_t * src_vpath, const struct stat *src_stat, + const vfs_path_t * dst_vpath, gboolean * skip_all) +{ + struct link *lnk; + ino_t ino = src_stat->st_ino; + dev_t dev = src_stat->st_dev; + + if (src_stat->st_nlink < 2) + return HARDLINK_NOTLINK; + if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0) + return HARDLINK_UNSUPPORTED; + + lnk = (struct link *) is_in_linklist (linklist, src_vpath, src_stat); + if (lnk != NULL) + { + int stat_result; + struct stat link_stat; + + stat_result = mc_stat (lnk->src_vpath, &link_stat); + + if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev) + { + const struct vfs_class *lp_name_class; + const struct vfs_class *my_vfs; + + lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath); + my_vfs = vfs_path_get_last_path_vfs (src_vpath); + + if (lp_name_class == my_vfs) + { + const struct vfs_class *p_class, *dst_name_class; + + dst_name_class = vfs_path_get_last_path_vfs (dst_vpath); + p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath); + + if (dst_name_class == p_class) + { + gboolean ok; + + while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*skip_all) + { + FileProgressStatus status; + + status = + file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"), + vfs_path_as_str (lnk->dst_vpath)); + if (status == FILE_ABORT) + return HARDLINK_ABORT; + if (status == FILE_RETRY) + continue; + if (status == FILE_SKIPALL) + *skip_all = TRUE; + break; + } + + /* if stat() finished unsuccessfully, don't try to create link */ + if (!ok) + return HARDLINK_ERROR; + + while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*skip_all) + { + FileProgressStatus status; + + status = + file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"), + vfs_path_as_str (dst_vpath)); + if (status == FILE_ABORT) + return HARDLINK_ABORT; + if (status == FILE_RETRY) + continue; + if (status == FILE_SKIPALL) + *skip_all = TRUE; + break; + } + + /* Success? */ + return (ok ? HARDLINK_OK : HARDLINK_ERROR); + } + } + } + + if (!*skip_all) + { + FileProgressStatus status; + + /* Message w/o "Retry" action. + * + * FIXME: Can't say what errno is here. Define it and don't display. + * + * file_error() displays a message with text representation of errno + * and the string passed to file_error() should provide the format "%s" + * for that at end (see previous file_error() call for the reference). + * But if format for errno isn't provided, it is safe, because C standard says: + * "If the format is exhausted while arguments remain, the excess arguments + * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999, + * section 7.19.6.1, paragraph 2). + * + */ + errno = 0; + status = + file_error (FALSE, _("Cannot create target hardlink \"%s\""), + vfs_path_as_str (dst_vpath)); + + if (status == FILE_ABORT) + return HARDLINK_ABORT; + + if (status == FILE_SKIPALL) + *skip_all = TRUE; + } + + return HARDLINK_ERROR; + } + + lnk = g_try_new (struct link, 1); + if (lnk != NULL) + { + lnk->vfs = vfs_path_get_last_path_vfs (src_vpath); + lnk->ino = ino; + lnk->dev = dev; + lnk->linkcount = 0; + lnk->st_mode = 0; + lnk->src_vpath = vfs_path_clone (src_vpath); + lnk->dst_vpath = vfs_path_clone (dst_vpath); + + linklist = g_slist_prepend (linklist, lnk); + } + + return HARDLINK_CACHED; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Duplicate the contents of the symbolic link src_vpath in dst_vpath. + * Try to make a stable symlink if the option "stable symlink" was + * set in the file mask dialog. + * If dst_path is an existing symlink it will be deleted silently + * (upper levels take already care of existing files at dst_vpath). + */ + +static FileProgressStatus +make_symlink (file_op_context_t * ctx, const vfs_path_t * src_vpath, const vfs_path_t * dst_vpath) +{ + const char *src_path; + const char *dst_path; + char link_target[MC_MAXPATHLEN]; + int len; + FileProgressStatus return_status; + struct stat dst_stat; + gboolean dst_is_symlink; + vfs_path_t *link_target_vpath = NULL; + + src_path = vfs_path_as_str (src_vpath); + dst_path = vfs_path_as_str (dst_vpath); + + dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode); + + retry_src_readlink: + len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1); + if (len < 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_src_readlink; + } + goto ret; + } + + link_target[len] = '\0'; + + if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath))) + { + message (D_ERROR, MSG_ERROR, + _("Cannot make stable symlinks across " + "non-local filesystems:\n\nOption Stable Symlinks will be disabled")); + ctx->stable_symlinks = FALSE; + } + + if (ctx->stable_symlinks && !g_path_is_absolute (link_target)) + { + const char *r; + + r = strrchr (src_path, PATH_SEP); + if (r != NULL) + { + char *p; + vfs_path_t *q; + + p = g_strndup (src_path, r - src_path + 1); + if (g_path_is_absolute (dst_path)) + q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON); + else + q = vfs_path_build_filename (p, dst_path, (char *) NULL); + + if (vfs_path_tokens_count (q) > 1) + { + char *s; + vfs_path_t *tmp_vpath1, *tmp_vpath2; + + tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1); + s = g_strconcat (p, link_target, (char *) NULL); + g_strlcpy (link_target, s, sizeof (link_target)); + g_free (s); + tmp_vpath2 = vfs_path_from_str (link_target); + s = diff_two_paths (tmp_vpath1, tmp_vpath2); + vfs_path_free (tmp_vpath1, TRUE); + vfs_path_free (tmp_vpath2, TRUE); + if (s != NULL) + { + g_strlcpy (link_target, s, sizeof (link_target)); + g_free (s); + } + } + g_free (p); + vfs_path_free (q, TRUE); + } + } + link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON); + + retry_dst_symlink: + if (mc_symlink (link_target_vpath, dst_vpath) == 0) + { + /* Success */ + return_status = FILE_CONT; + goto ret; + } + /* + * if dst_exists, it is obvious that this had failed. + * We can delete the old symlink and try again... + */ + if (dst_is_symlink && mc_unlink (dst_vpath) == 0 + && mc_symlink (link_target_vpath, dst_vpath) == 0) + { + /* Success */ + return_status = FILE_CONT; + goto ret; + } + + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_dst_symlink; + } + + ret: + vfs_path_free (link_target_vpath, TRUE); + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * do_compute_dir_size: + * + * Computes the number of bytes used by the files in a directory + */ + +static FileProgressStatus +do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm, + size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total, + mc_stat_fn stat_func) +{ + static gint64 timestamp = 0; + /* update with 25 FPS rate */ + static const gint64 delay = G_USEC_PER_SEC / 25; + + status_msg_t *sm = STATUS_MSG (dsm); + int res; + struct stat s; + DIR *dir; + struct vfs_dirent *dirent; + FileProgressStatus ret = FILE_CONT; + + (*dir_count)++; + + dir = mc_opendir (dirname_vpath); + if (dir == NULL) + return ret; + + while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL) + { + vfs_path_t *tmp_vpath; + + if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name)) + continue; + + tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL); + + res = stat_func (tmp_vpath, &s); + if (res == 0) + { + if (S_ISDIR (s.st_mode)) + ret = + do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total, + stat_func); + else + { + ret = FILE_CONT; + + (*ret_marked)++; + *ret_total += (uintmax_t) s.st_size; + } + + if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (×tamp, delay)) + { + dsm->dirname_vpath = tmp_vpath; + dsm->dir_count = *dir_count; + dsm->total_size = *ret_total; + ret = sm->update (sm); + } + } + + vfs_path_free (tmp_vpath, TRUE); + } + + mc_closedir (dir); + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * panel_compute_totals: + * + * compute the number of files and the number of bytes + * used up by the whole selection, recursing directories + * as required. In addition, it checks to see if it will + * overwrite any files by doing the copy. + */ + +static FileProgressStatus +panel_compute_totals (const WPanel * panel, dirsize_status_msg_t * sm, size_t * ret_count, + uintmax_t * ret_total, gboolean follow_symlinks) +{ + int i; + size_t dir_count = 0; + mc_stat_fn stat_func = follow_symlinks ? mc_stat : mc_lstat; + + for (i = 0; i < panel->dir.len; i++) + { + const file_entry_t *fe = &panel->dir.list[i]; + const struct stat *s; + + if (fe->f.marked == 0) + continue; + + s = &fe->st; + + if (S_ISDIR (s->st_mode) || (follow_symlinks && link_isdir (fe) && fe->f.stale_link == 0)) + { + vfs_path_t *p; + FileProgressStatus status; + + p = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL); + status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func); + vfs_path_free (p, TRUE); + + if (status != FILE_CONT) + return status; + } + else + { + (*ret_count)++; + *ret_total += (uintmax_t) s->st_size; + } + } + + return FILE_CONT; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Initialize variables for progress bars */ +static FileProgressStatus +panel_operate_init_totals (const WPanel * panel, const vfs_path_t * source, + const struct stat *source_stat, file_op_context_t * ctx, + gboolean compute_totals, filegui_dialog_type_t dialog_type) +{ + FileProgressStatus status; + +#ifdef ENABLE_BACKGROUND + if (mc_global.we_are_background) + return FILE_CONT; +#endif + + if (verbose && compute_totals) + { + dirsize_status_msg_t dsm; + gboolean stale_link = FALSE; + + memset (&dsm, 0, sizeof (dsm)); + dsm.allow_skip = TRUE; + status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb, + dirsize_status_update_cb, dirsize_status_deinit_cb); + + ctx->progress_count = 0; + ctx->progress_bytes = 0; + + if (source == NULL) + status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes, + ctx->follow_links); + else if (S_ISDIR (source_stat->st_mode) + || (ctx->follow_links + && file_is_symlink_to_dir (source, (struct stat *) source_stat, &stale_link) + && !stale_link)) + { + size_t dir_count = 0; + + status = do_compute_dir_size (source, &dsm, &dir_count, &ctx->progress_count, + &ctx->progress_bytes, ctx->stat_func); + } + else + { + ctx->progress_count++; + ctx->progress_bytes += (uintmax_t) source_stat->st_size; + status = FILE_CONT; + } + + status_msg_deinit (STATUS_MSG (&dsm)); + + ctx->progress_totals_computed = (status == FILE_CONT); + + if (status == FILE_SKIP) + status = FILE_CONT; + } + else + { + status = FILE_CONT; + ctx->progress_count = panel->marked; + ctx->progress_bytes = panel->total; + ctx->progress_totals_computed = verbose && dialog_type == FILEGUI_DIALOG_ONE_ITEM; + } + + /* destroy already created UI for single file rename operation */ + file_op_context_destroy_ui (ctx); + + file_op_context_create_ui (ctx, TRUE, dialog_type); + + return status; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +progress_update_one (file_op_total_context_t * tctx, file_op_context_t * ctx, off_t add) +{ + gint64 tv_current; + static gint64 tv_start = -1; + + tctx->progress_count++; + tctx->progress_bytes += (uintmax_t) add; + + tv_current = g_get_monotonic_time (); + + if (tv_start < 0) + tv_start = tv_current; + + if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US) + { + if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE); + } + + tv_start = tv_current; + } + + return check_progress_buttons (ctx); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b) +{ + char *msg; + int result = 0; + const char *head_msg; + int width_a, width_b, width; + + head_msg = mode == Foreground ? MSG_ERROR : _("Background process error"); + + width_a = str_term_width1 (a); + width_b = str_term_width1 (b); + width = COLS - 8; + + if (width_a > width) + { + if (width_b > width) + { + char *s; + + s = g_strndup (str_trunc (a, width), width); + b = str_trunc (b, width); + msg = g_strdup_printf (fmt, s, b); + g_free (s); + } + else + { + a = str_trunc (a, width); + msg = g_strdup_printf (fmt, a, b); + } + } + else + { + if (width_b > width) + b = str_trunc (b, width); + + msg = g_strdup_printf (fmt, a, b); + } + + result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort")); + g_free (msg); + do_refresh (); + + return (result == 1) ? FILE_ABORT : FILE_SKIP; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +warn_same_file (const char *fmt, const char *a, const char *b) +{ +#ifdef ENABLE_BACKGROUND +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b); + } pntr; +/* *INDENT-ON* */ + + pntr.f = real_warn_same_file; + + if (mc_global.we_are_background) + return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b); +#endif + return real_warn_same_file (Foreground, fmt, a, b); +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +check_same_file (const char *a, const struct stat *ast, const char *b, const struct stat *bst, + FileProgressStatus * status) +{ + if (ast->st_dev != bst->st_dev || ast->st_ino != bst->st_ino) + return FALSE; + + if (S_ISDIR (ast->st_mode)) + *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a, b); + else + *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a, b); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +get_times (const struct stat *sb, mc_timesbuf_t * times) +{ +#ifdef HAVE_UTIMENSAT + (*times)[0] = sb->st_atim; + (*times)[1] = sb->st_mtim; +#else + times->actime = sb->st_atime; + times->modtime = sb->st_mtime; +#endif +} + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Query/status report routines */ + +static FileProgressStatus +real_do_file_error (enum OperationMode mode, gboolean allow_retry, const char *error) +{ + int result; + const char *msg; + + msg = mode == Foreground ? MSG_ERROR : _("Background process error"); + + if (allow_retry) + result = + query_dialog (msg, error, D_ERROR, 4, _("&Skip"), _("Ski&p all"), _("&Retry"), + _("&Abort")); + else + result = query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("Ski&p all"), _("&Abort")); + + switch (result) + { + case 0: + do_refresh (); + return FILE_SKIP; + + case 1: + do_refresh (); + return FILE_SKIPALL; + + case 2: + if (allow_retry) + { + do_refresh (); + return FILE_RETRY; + } + MC_FALLTHROUGH; + + case 3: + default: + return FILE_ABORT; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +real_query_recursive (file_op_context_t * ctx, enum OperationMode mode, const char *s) +{ + if (ctx->recursive_result < RECURSIVE_ALWAYS) + { + const char *msg; + char *text; + + msg = mode == Foreground + ? _("Directory \"%s\" not empty.\nDelete it recursively?") + : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?"); + text = g_strdup_printf (msg, path_trunc (s, 30)); + + if (safe_delete) + query_set_sel (1); + + ctx->recursive_result = + query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"), + _("Non&e"), _("&Abort")); + g_free (text); + + if (ctx->recursive_result != RECURSIVE_ABORT) + do_refresh (); + } + + switch (ctx->recursive_result) + { + case RECURSIVE_YES: + case RECURSIVE_ALWAYS: + return FILE_CONT; + + case RECURSIVE_NO: + case RECURSIVE_NEVER: + return FILE_SKIP; + + case RECURSIVE_ABORT: + default: + return FILE_ABORT; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_BACKGROUND +static FileProgressStatus +do_file_error (gboolean allow_retry, const char *str) +{ +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (enum OperationMode, gboolean, const char *); + } pntr; +/* *INDENT-ON* */ + + pntr.f = real_do_file_error; + + if (mc_global.we_are_background) + return parent_call (pntr.p, NULL, 2, sizeof (allow_retry), allow_retry, strlen (str), str); + else + return real_do_file_error (Foreground, allow_retry, str); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_recursive (file_op_context_t * ctx, const char *s) +{ +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *); + } pntr; +/* *INDENT-ON* */ + + pntr.f = real_query_recursive; + + if (mc_global.we_are_background) + return parent_call (pntr.p, ctx, 1, strlen (s), s); + else + return real_query_recursive (ctx, Foreground, s); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst, + struct stat *dst_stat) +{ +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *, + struct stat *, const char *, struct stat *); + } pntr; +/* *INDENT-ON* */ + + pntr.f = file_progress_real_query_replace; + + if (mc_global.we_are_background) + return parent_call (pntr.p, ctx, 4, strlen (src), src, sizeof (struct stat), src_stat, + strlen (dst), dst, sizeof (struct stat), dst_stat); + else + return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat); +} + +#else +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +do_file_error (gboolean allow_retry, const char *str) +{ + return real_do_file_error (Foreground, allow_retry, str); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_recursive (file_op_context_t * ctx, const char *s) +{ + return real_query_recursive (ctx, Foreground, s); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst, + struct stat *dst_stat) +{ + return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat); +} + +#endif /* !ENABLE_BACKGROUND */ + +/* --------------------------------------------------------------------------------------------- */ +/** Report error with two files */ + +static FileProgressStatus +files_error (const char *format, const char *file1, const char *file2) +{ + char buf[BUF_MEDIUM]; + char *nfile1 = g_strdup (path_trunc (file1, 15)); + char *nfile2 = g_strdup (path_trunc (file2, 15)); + + g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno)); + + g_free (nfile1); + g_free (nfile2); + + return do_file_error (TRUE, buf); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ + +static void +copy_file_file_display_progress (file_op_total_context_t * tctx, file_op_context_t * ctx, + gint64 tv_current, gint64 tv_transfer_start, off_t file_size, + off_t file_part) +{ + gint64 dt; + + /* Update rotating dash after some time */ + rotate_dash (TRUE); + + /* Compute ETA */ + dt = (tv_current - tv_transfer_start) / G_USEC_PER_SEC; + + if (file_part == 0) + ctx->eta_secs = 0.0; + else + ctx->eta_secs = ((dt / (double) file_part) * file_size) - dt; + + /* Compute BPS rate */ + ctx->bps_time = MAX (1, dt); + ctx->bps = file_part / ctx->bps_time; + + /* Compute total ETA and BPS */ + if (ctx->progress_bytes != 0) + { + uintmax_t remain_bytes; + + remain_bytes = ctx->progress_bytes - tctx->copied_bytes; +#if 1 + { + gint64 total_secs; + + total_secs = (tv_current - tctx->transfer_start) / G_USEC_PER_SEC; + total_secs = MAX (1, total_secs); + + tctx->bps = tctx->copied_bytes / total_secs; + tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0; + } +#else + /* broken on lot of little files */ + tctx->bps_count++; + tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count; + tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0; +#endif + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +try_remove_file (file_op_context_t * ctx, const vfs_path_t * vpath, FileProgressStatus * status) +{ + while (mc_unlink (vpath) != 0 && !ctx->skip_all) + { + *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath)); + if (*status == FILE_RETRY) + continue; + if (*status == FILE_SKIPALL) + ctx->skip_all = TRUE; + return FALSE; + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* {{{ Move routines */ + +/** + * Move single file or one of many files from one location to another. + * + * @panel pointer to panel in case of single file, NULL otherwise + * @tctx file operation total context object + * @ctx file operation context object + * @s source file name + * @d destination file name + * + * @return operation result + */ +static FileProgressStatus +move_file_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *s, const char *d) +{ + struct stat src_stat, dst_stat; + FileProgressStatus return_status = FILE_CONT; + gboolean copy_done = FALSE; + gboolean old_ask_overwrite; + vfs_path_t *src_vpath, *dst_vpath; + + src_vpath = vfs_path_from_str (s); + dst_vpath = vfs_path_from_str (d); + + file_progress_show_source (ctx, src_vpath); + file_progress_show_target (ctx, dst_vpath); + + /* FIXME: do we really need to check buttons in case of single file? */ + if (check_progress_buttons (ctx) == FILE_ABORT) + { + return_status = FILE_ABORT; + goto ret; + } + + mc_refresh (); + + while (mc_lstat (src_vpath, &src_stat) != 0) + { + /* Source doesn't exist */ + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + + if (return_status != FILE_RETRY) + goto ret; + } + + if (mc_lstat (dst_vpath, &dst_stat) == 0) + { + if (check_same_file (s, &src_stat, d, &dst_stat, &return_status)) + goto ret; + + if (S_ISDIR (dst_stat.st_mode)) + { + message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d); + do_refresh (); + return_status = FILE_SKIP; + goto ret; + } + + if (confirm_overwrite) + { + return_status = query_replace (ctx, s, &src_stat, d, &dst_stat); + if (return_status != FILE_CONT) + goto ret; + } + /* Ok to overwrite */ + } + + if (!ctx->do_append) + { + if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks) + { + return_status = make_symlink (ctx, src_vpath, dst_vpath); + if (return_status == FILE_CONT) + { + if (ctx->preserve) + { + mc_timesbuf_t times; + + get_times (&src_stat, ×); + mc_utime (dst_vpath, ×); + } + goto retry_src_remove; + } + goto ret; + } + + if (mc_rename (src_vpath, dst_vpath) == 0) + { + /* FIXME: do we really need to update progress in case of single file? */ + return_status = progress_update_one (tctx, ctx, src_stat.st_size); + goto ret; + } + } +#if 0 + /* Comparison to EXDEV seems not to work in nfs if you're moving from + one nfs to the same, but on the server it is on two different + filesystems. Then nfs returns EIO instead of EXDEV. + Hope it will not hurt if we always in case of error try to copy/delete. */ + else + errno = EXDEV; /* Hack to copy (append) the file and then delete it */ + + if (errno != EXDEV) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_rename; + } + + goto ret; + } +#endif + + /* Failed rename -> copy the file instead */ + if (panel != NULL) + { + /* In case of single file, calculate totals. In case of many files, + totals are calcuated already. */ + return_status = + panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE, + FILEGUI_DIALOG_ONE_ITEM); + if (return_status != FILE_CONT) + goto ret; + } + + old_ask_overwrite = tctx->ask_overwrite; + tctx->ask_overwrite = FALSE; + return_status = copy_file_file (tctx, ctx, s, d); + tctx->ask_overwrite = old_ask_overwrite; + if (return_status != FILE_CONT) + goto ret; + + copy_done = TRUE; + + /* FIXME: there is no need to update progress and check buttons + at the finish of single file operation. */ + if (panel == NULL) + { + file_progress_show_source (ctx, NULL); + file_progress_show (ctx, 0, 0, "", FALSE); + + return_status = check_progress_buttons (ctx); + if (return_status != FILE_CONT) + goto ret; + } + + mc_refresh (); + + retry_src_remove: + if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL) + goto ret; + + if (!copy_done) + return_status = progress_update_one (tctx, ctx, src_stat.st_size); + + ret: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + + return return_status; +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Erase routines */ +/** Don't update progress status if progress_count==NULL */ + +static FileProgressStatus +erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath) +{ + struct stat buf; + FileProgressStatus return_status; + + /* check buttons if deleting info was changed */ + if (file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count)) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + } + + if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0) + { + /* ignore, most likely the mc_unlink fails, too */ + buf.st_size = 0; + } + + if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT) + return FILE_ABORT; + + if (tctx->progress_count == 0) + return FILE_CONT; + + return check_progress_buttons (ctx); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +try_erase_dir (file_op_context_t * ctx, const char *dir) +{ + FileProgressStatus return_status = FILE_CONT; + + while (my_rmdir (dir) != 0 && !ctx->skip_all) + { + return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status != FILE_RETRY) + break; + } + + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + Recursive remove of files + abort->cancel stack + skip ->warn every level, gets default + skipall->remove as much as possible +*/ +static FileProgressStatus +recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath) +{ + struct vfs_dirent *next; + DIR *reading; + const char *s; + FileProgressStatus return_status = FILE_CONT; + + reading = mc_opendir (vpath); + if (reading == NULL) + return FILE_RETRY; + + while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) + { + vfs_path_t *tmp_vpath; + struct stat buf; + + if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name)) + continue; + + tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL); + if (mc_lstat (tmp_vpath, &buf) != 0) + { + mc_closedir (reading); + vfs_path_free (tmp_vpath, TRUE); + return FILE_RETRY; + } + if (S_ISDIR (buf.st_mode)) + return_status = recursive_erase (tctx, ctx, tmp_vpath); + else + return_status = erase_file (tctx, ctx, tmp_vpath); + vfs_path_free (tmp_vpath, TRUE); + } + mc_closedir (reading); + + if (return_status == FILE_ABORT) + return FILE_ABORT; + + s = vfs_path_as_str (vpath); + + file_progress_show_deleting (ctx, s, NULL); + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + + return try_erase_dir (ctx, s); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Check if directory is empty or not. + * + * @param vpath directory handler + * + * @returns -1 on error, + * 1 if there are no entries besides "." and ".." in the directory path points to, + * 0 else. + * + * ATTENTION! Be carefull when modifying this function (like commit 25e419ba0886f)! + * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), whuch is uded + * in FISH) don't return "." and ".." entries. + */ +static int +check_dir_is_empty (const vfs_path_t * vpath) +{ + DIR *dir; + struct vfs_dirent *d; + int i = 1; + + dir = mc_opendir (vpath); + if (dir == NULL) + return -1; + + for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir)) + if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name)) + { + i = 0; + break; + } + + mc_closedir (dir); + return i; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count) +{ + const char *s; + + s = vfs_path_as_str (vpath); + + file_progress_show_deleting (ctx, s, NULL); + file_progress_show_count (ctx, count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + + if (check_dir_is_empty (vpath) != 1) + return FILE_CONT; + + /* not empty or error */ + return try_erase_dir (ctx, s); +} + + +/* --------------------------------------------------------------------------------------------- */ + +static void +erase_dir_after_copy (file_op_total_context_t * tctx, file_op_context_t * ctx, + const vfs_path_t * vpath, FileProgressStatus * status) +{ + if (ctx->erase_at_end) + { + /* Reset progress count before delete to avoid counting files twice */ + tctx->progress_count = tctx->prev_progress_count; + + while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT) + { + struct link *lp; + + lp = (struct link *) g_queue_pop_head (erase_list); + + if (S_ISDIR (lp->st_mode)) + *status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count); + else + *status = erase_file (tctx, ctx, lp->src_vpath); + + free_link (lp); + } + + /* Save progress counter before move next directory */ + tctx->prev_progress_count = tctx->progress_count; + } + + erase_dir_iff_empty (ctx, vpath, tctx->progress_count); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Move single directory or one of many directories from one location to another. + * + * @panel pointer to panel in case of single directory, NULL otherwise + * @tctx file operation total context object + * @ctx file operation context object + * @s source directory name + * @d destination directory name + * + * @return operation result + */ +static FileProgressStatus +do_move_dir_dir (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *s, const char *d) +{ + struct stat src_stat, dst_stat; + FileProgressStatus return_status = FILE_CONT; + gboolean move_over = FALSE; + gboolean dstat_ok; + vfs_path_t *src_vpath, *dst_vpath; + + src_vpath = vfs_path_from_str (s); + dst_vpath = vfs_path_from_str (d); + + file_progress_show_source (ctx, src_vpath); + file_progress_show_target (ctx, dst_vpath); + + /* FIXME: do we really need to check buttons in case of single directory? */ + if (panel != NULL && check_progress_buttons (ctx) == FILE_ABORT) + { + return_status = FILE_ABORT; + goto ret_fast; + } + + mc_refresh (); + + mc_stat (src_vpath, &src_stat); + + dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0); + + if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status)) + goto ret_fast; + + if (!dstat_ok) + ; /* destination doesn't exist */ + else if (!ctx->dive_into_subdirs) + move_over = TRUE; + else + { + vfs_path_t *tmp; + + tmp = dst_vpath; + dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL); + vfs_path_free (tmp, TRUE); + } + + d = vfs_path_as_str (dst_vpath); + + /* Check if the user inputted an existing dir */ + retry_dst_stat: + if (mc_stat (dst_vpath, &dst_stat) == 0) + { + if (move_over) + { + if (panel != NULL) + { + /* In case of single directory, calculate totals. In case of many directories, + totals are calcuated already. */ + return_status = + panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE, + FILEGUI_DIALOG_MULTI_ITEM); + if (return_status != FILE_CONT) + goto ret; + } + + return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL); + + if (return_status != FILE_CONT) + goto ret; + goto oktoret; + } + else if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + if (S_ISDIR (dst_stat.st_mode)) + return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d); + else + return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_dst_stat; + } + + goto ret_fast; + } + + retry_rename: + if (mc_rename (src_vpath, dst_vpath) == 0) + { + return_status = FILE_CONT; + goto ret; + } + + if (errno != EXDEV) + { + if (!ctx->skip_all) + { + return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_rename; + } + goto ret; + } + + /* Failed because of filesystem boundary -> copy dir instead */ + if (panel != NULL) + { + /* In case of single directory, calculate totals. In case of many directories, + totals are calcuated already. */ + return_status = + panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE, + FILEGUI_DIALOG_MULTI_ITEM); + if (return_status != FILE_CONT) + goto ret; + } + + return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL); + + if (return_status != FILE_CONT) + goto ret; + + oktoret: + /* FIXME: there is no need to update progress and check buttons + at the finish of single directory operation. */ + if (panel == NULL) + { + file_progress_show_source (ctx, NULL); + file_progress_show_target (ctx, NULL); + file_progress_show (ctx, 0, 0, "", FALSE); + + return_status = check_progress_buttons (ctx); + if (return_status != FILE_CONT) + goto ret; + } + + mc_refresh (); + + erase_dir_after_copy (tctx, ctx, src_vpath, &return_status); + + ret: + erase_list = free_erase_list (erase_list); + ret_fast: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* {{{ Panel operate routines */ + +/** + * Return currently selected entry name or the name of the first marked + * entry if there is one. + */ + +static const char * +panel_get_file (const WPanel * panel) +{ + if (get_current_type () == view_tree) + { + WTree *tree; + const vfs_path_t *selected_name; + + tree = (WTree *) get_panel_widget (get_current_index ()); + selected_name = tree_selected_name (tree); + return vfs_path_as_str (selected_name); + } + + if (panel->marked != 0) + { + int i; + + for (i = 0; i < panel->dir.len; i++) + if (panel->dir.list[i].f.marked) + return panel->dir.list[i].fname->str; + } + + return panel->dir.list[panel->selected].fname->str; +} + +/* --------------------------------------------------------------------------------------------- */ + +static const char * +check_single_entry (const WPanel * panel, gboolean force_single, struct stat *src_stat) +{ + const char *source; + gboolean ok; + + if (force_single) + source = selection (panel)->fname->str; + else + source = panel_get_file (panel); + + ok = !DIR_IS_DOTDOT (source); + + if (!ok) + message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!")); + else + { + vfs_path_t *source_vpath; + + source_vpath = vfs_path_from_str (source); + + /* Update stat to get actual info */ + ok = mc_lstat (source_vpath, src_stat) == 0; + if (!ok) + { + message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"), + path_trunc (source, 30), unix_error_string (errno)); + + /* Directory was changed outside MC. Reload it forced */ + if (!panel->is_panelized) + { + panel_update_flags_t flags = UP_RELOAD; + + /* don't update panelized panel */ + if (get_other_type () == view_listing && other_panel->is_panelized) + flags |= UP_ONLY_CURRENT; + + update_panels (flags, UP_KEEPSEL); + } + } + + vfs_path_free (source_vpath, TRUE); + } + + return ok ? source : NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Generate user prompt for panel operation. + * src_stat must be not NULL for single source, and NULL for multiple sources + */ + +static char * +panel_operate_generate_prompt (const WPanel * panel, FileOperation operation, + const struct stat *src_stat) +{ + char *sp; + char *format_string; + const char *cp; + + static gboolean i18n_flag = FALSE; + if (!i18n_flag) + { + size_t i; + + for (i = G_N_ELEMENTS (op_names1); i-- != 0;) + op_names1[i] = Q_ (op_names1[i]); + +#ifdef ENABLE_NLS + for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;) + prompt_parts[i] = _(prompt_parts[i]); + + one_format = _(one_format); + many_format = _(many_format); +#endif /* ENABLE_NLS */ + i18n_flag = TRUE; + } + + /* Possible prompts: + * OP_COPY: + * "Copy file \"%s\" with source mask:" + * "Copy %d files with source mask:" + * "Copy directory \"%s\" with source mask:" + * "Copy %d directories with source mask:" + * "Copy %d files/directories with source mask:" + * OP_MOVE: + * "Move file \"%s\" with source mask:" + * "Move %d files with source mask:" + * "Move directory \"%s\" with source mask:" + * "Move %d directories with source mask:" + * "Move %d files/directories with source mask:" + * OP_DELETE: + * "Delete file \"%s\"?" + * "Delete %d files?" + * "Delete directory \"%s\"?" + * "Delete %d directories?" + * "Delete %d files/directories?" + */ + + cp = (src_stat != NULL ? one_format : many_format); + + /* 1. Substitute %o */ + format_string = str_replace_all (cp, "%o", op_names1[(int) operation]); + + /* 2. Substitute %n */ + cp = operation == OP_DELETE ? "\n" : " "; + sp = format_string; + format_string = str_replace_all (sp, "%n", cp); + g_free (sp); + + /* 3. Substitute %f */ + if (src_stat != NULL) + cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0]; + else if (panel->marked == panel->dirs_marked) + cp = prompt_parts[3]; + else + cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1]; + + sp = format_string; + format_string = str_replace_all (sp, "%f", cp); + g_free (sp); + + /* 4. Substitute %m */ + cp = operation == OP_DELETE ? "?" : prompt_parts[5]; + sp = format_string; + format_string = str_replace_all (sp, "%m", cp); + g_free (sp); + + return format_string; +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +do_confirm_copy_move (const WPanel * panel, FileOperation operation, gboolean force_single, + const char *source, struct stat *src_stat, file_op_context_t * ctx, + gboolean * do_bg) +{ + const char *tmp_dest_dir; + char *dest_dir; + char *format; + char *ret; + + /* Forced single operations default to the original name */ + if (force_single) + tmp_dest_dir = source; + else if (get_other_type () == view_listing) + tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath); + else + tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath); + + /* + * Add trailing backslash only when do non-local ops. + * It saves user from occasional file renames (when destination + * dir is deleted) + */ + if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0' + && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1])) + { + /* add trailing separator */ + dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL); + } + else + { + /* just copy */ + dest_dir = g_strdup (tmp_dest_dir); + } + + if (dest_dir == NULL) + return NULL; + + if (source == NULL) + src_stat = NULL; + + /* Generate confirmation prompt */ + format = panel_operate_generate_prompt (panel, operation, src_stat); + + ret = file_mask_dialog (ctx, operation, source != NULL, format, + source != NULL ? source : (const void *) &panel->marked, dest_dir, + do_bg); + + g_free (format); + g_free (dest_dir); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +do_confirm_erase (const WPanel * panel, const char *source, struct stat *src_stat) +{ + int i; + char *format; + char fmd_buf[BUF_MEDIUM]; + + if (source == NULL) + src_stat = NULL; + + /* Generate confirmation prompt */ + format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat); + + if (source == NULL) + g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked); + else + { + const int fmd_xlen = 64; + + i = fmd_xlen - str_term_width1 (format) - 4; + g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i)); + } + + g_free (format); + + if (safe_delete) + query_set_sel (1); + + i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No")); + + return (i == 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +operate_single_file (const WPanel * panel, FileOperation operation, file_op_total_context_t * tctx, + file_op_context_t * ctx, const char *src, struct stat *src_stat, + const char *dest, filegui_dialog_type_t dialog_type) +{ + FileProgressStatus value; + vfs_path_t *src_vpath; + gboolean is_file; + + if (g_path_is_absolute (src)) + src_vpath = vfs_path_from_str (src); + else + src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL); + + is_file = !S_ISDIR (src_stat->st_mode); + /* Is link to directory? */ + if (is_file) + { + gboolean is_link; + + is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL); + is_file = !(is_link && ctx->follow_links); + } + + + if (operation == OP_DELETE) + { + value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type); + if (value == FILE_CONT) + { + if (is_file) + value = erase_file (tctx, ctx, src_vpath); + else + value = erase_dir (tctx, ctx, src_vpath); + } + } + else + { + char *temp; + + src = vfs_path_as_str (src_vpath); + + temp = build_dest (ctx, src, dest, &value); + if (temp != NULL) + { + dest = temp; + + switch (operation) + { + case OP_COPY: + /* we use file_mask_op_follow_links only with OP_COPY */ + ctx->stat_func (src_vpath, src_stat); + + value = + panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, + dialog_type); + if (value == FILE_CONT) + { + is_file = !S_ISDIR (src_stat->st_mode); + /* Is link to directory? */ + if (is_file) + { + gboolean is_link; + + is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL); + is_file = !(is_link && ctx->follow_links); + } + + if (is_file) + value = copy_file_file (tctx, ctx, src, dest); + else + value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL); + } + break; + + case OP_MOVE: +#ifdef ENABLE_BACKGROUND + /* create UI to show confirmation dialog */ + if (!mc_global.we_are_background) + file_op_context_create_ui (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM); +#endif + if (is_file) + value = move_file_file (panel, tctx, ctx, src, dest); + else + value = do_move_dir_dir (panel, tctx, ctx, src, dest); + break; + + default: + /* Unknown file operation */ + abort (); + } + + g_free (temp); + } + } + + vfs_path_free (src_vpath, TRUE); + + return value; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +operate_one_file (const WPanel * panel, FileOperation operation, file_op_total_context_t * tctx, + file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dest) +{ + FileProgressStatus value = FILE_CONT; + vfs_path_t *src_vpath; + gboolean is_file; + + if (g_path_is_absolute (src)) + src_vpath = vfs_path_from_str (src); + else + src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL); + + is_file = !S_ISDIR (src_stat->st_mode); + + if (operation == OP_DELETE) + { + if (is_file) + value = erase_file (tctx, ctx, src_vpath); + else + value = erase_dir (tctx, ctx, src_vpath); + } + else + { + char *temp; + + src = vfs_path_as_str (src_vpath); + + temp = build_dest (ctx, src, dest, &value); + if (temp != NULL) + { + dest = temp; + + switch (operation) + { + case OP_COPY: + /* we use file_mask_op_follow_links only with OP_COPY */ + ctx->stat_func (src_vpath, src_stat); + is_file = !S_ISDIR (src_stat->st_mode); + + if (is_file) + value = copy_file_file (tctx, ctx, src, dest); + else + value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL); + dest_dirs = free_linklist (dest_dirs); + break; + + case OP_MOVE: + if (is_file) + value = move_file_file (NULL, tctx, ctx, src, dest); + else + value = do_move_dir_dir (NULL, tctx, ctx, src, dest); + break; + + default: + /* Unknown file operation */ + abort (); + } + + g_free (temp); + } + } + + vfs_path_free (src_vpath, TRUE); + + return value; +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_BACKGROUND +static int +end_bg_process (file_op_context_t * ctx, enum OperationMode mode) +{ + int pid = ctx->pid; + + (void) mode; + ctx->pid = 0; + + unregister_task_with_pid (pid); + /* file_op_context_destroy(ctx); */ + return 1; +} +#endif +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* Is file symlink to directory or not. + * + * @param path file or directory + * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here + * @param stale_link TRUE if file is stale link to directory + * + * @return TRUE if file symlink to directory, ELSE otherwise. + */ +gboolean +file_is_symlink_to_dir (const vfs_path_t * vpath, struct stat * st, gboolean * stale_link) +{ + struct stat st2; + gboolean stale = FALSE; + gboolean res = FALSE; + + if (st == NULL) + { + st = &st2; + + if (mc_lstat (vpath, st) != 0) + goto ret; + } + + if (S_ISLNK (st->st_mode)) + { + struct stat st3; + + stale = (mc_stat (vpath, &st3) != 0); + + if (!stale) + res = (S_ISDIR (st3.st_mode) != 0); + } + + ret: + if (stale_link != NULL) + *stale_link = stale; + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ + +FileProgressStatus +copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *src_path, const char *dst_path) +{ + uid_t src_uid = (uid_t) (-1); + gid_t src_gid = (gid_t) (-1); + + int src_desc, dest_desc = -1; + mode_t src_mode = 0; /* The mode of the source file */ + struct stat src_stat, dst_stat; + mc_timesbuf_t times; + gboolean dst_exists = FALSE, appending = FALSE; + off_t file_size = -1; + FileProgressStatus return_status, temp_status; + gint64 tv_transfer_start; + dest_status_t dst_status = DEST_NONE; + int open_flags; + vfs_path_t *src_vpath = NULL, *dst_vpath = NULL; + char *buf = NULL; + + /* FIXME: We should not be using global variables! */ + ctx->do_reget = 0; + return_status = FILE_RETRY; + + dst_vpath = vfs_path_from_str (dst_path); + src_vpath = vfs_path_from_str (src_path); + + file_progress_show_source (ctx, src_vpath); + file_progress_show_target (ctx, dst_vpath); + + if (check_progress_buttons (ctx) == FILE_ABORT) + { + return_status = FILE_ABORT; + goto ret_fast; + } + + mc_refresh (); + + while (mc_stat (dst_vpath, &dst_stat) == 0) + { + if (S_ISDIR (dst_stat.st_mode)) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + continue; + } + goto ret_fast; + } + + dst_exists = TRUE; + break; + } + + while ((*ctx->stat_func) (src_vpath, &src_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + + if (return_status != FILE_RETRY) + goto ret_fast; + } + + if (dst_exists) + { + /* Destination already exists */ + if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status)) + goto ret_fast; + + /* Should we replace destination? */ + if (tctx->ask_overwrite) + { + ctx->do_reget = 0; + return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat); + if (return_status != FILE_CONT) + goto ret_fast; + } + } + + get_times (&src_stat, ×); + + if (!ctx->do_append) + { + /* Check the hardlinks */ + if (!ctx->follow_links) + { + switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all)) + { + case HARDLINK_OK: + /* We have made a hardlink - no more processing is necessary */ + return_status = FILE_CONT; + goto ret_fast; + + case HARDLINK_ABORT: + return_status = FILE_ABORT; + goto ret_fast; + + default: + break; + } + } + + if (S_ISLNK (src_stat.st_mode)) + { + return_status = make_symlink (ctx, src_vpath, dst_vpath); + if (return_status == FILE_CONT && ctx->preserve) + mc_utime (dst_vpath, ×); + goto ret_fast; + } + + if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode) + || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode)) + { + dev_t rdev = 0; + +#ifdef HAVE_STRUCT_STAT_ST_RDEV + rdev = src_stat.st_rdev; +#endif + + while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0 + && !ctx->skip_all) + { + return_status = + file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + goto ret_fast; + } + /* Success */ + + while (ctx->preserve_uidgid + && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_SKIP) + break; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (temp_status != FILE_RETRY) + { + return_status = temp_status; + goto ret_fast; + } + } + + while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0 + && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_SKIP) + break; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (temp_status != FILE_RETRY) + { + return_status = temp_status; + goto ret_fast; + } + } + + return_status = FILE_CONT; + mc_utime (dst_vpath, ×); + goto ret_fast; + } + } + + tv_transfer_start = g_get_monotonic_time (); + + while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all) + { + return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_SKIP) + break; + ctx->do_append = FALSE; + goto ret_fast; + } + + if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) + { + message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file")); + ctx->do_reget = 0; + ctx->do_append = FALSE; + } + + while (mc_fstat (src_desc, &src_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + ctx->do_append = FALSE; + } + goto ret; + } + + src_mode = src_stat.st_mode; + src_uid = src_stat.st_uid; + src_gid = src_stat.st_gid; + file_size = src_stat.st_size; + + open_flags = O_WRONLY; + if (!dst_exists) + open_flags |= O_CREAT | O_EXCL; + else if (ctx->do_append) + open_flags |= O_APPEND; + else + open_flags |= O_CREAT | O_TRUNC; + + while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0) + { + if (errno != EEXIST) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + ctx->do_append = FALSE; + } + } + goto ret; + } + + /* file opened, but not fully copied */ + dst_status = DEST_SHORT_QUERY; + + appending = ctx->do_append; + ctx->do_append = FALSE; + + /* Try clone the file first. */ + if (vfs_clone_file (dest_desc, src_desc) == 0) + { + dst_status = DEST_FULL; + return_status = FILE_CONT; + goto ret; + } + + /* Find out the optimal buffer size. */ + while (mc_fstat (dest_desc, &dst_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + goto ret; + } + + /* try preallocate space; if fail, try copy anyway */ + while (mc_global.vfs.preallocate_space && + vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0) + { + if (ctx->skip_all) + { + /* cannot allocate, start the file copying anyway */ + return_status = FILE_CONT; + break; + } + + return_status = + file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path); + + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + + if (ctx->skip_all || return_status == FILE_SKIP) + { + /* skip the space allocation error, start file copying */ + return_status = FILE_CONT; + break; + } + + if (return_status == FILE_ABORT) + { + mc_close (dest_desc); + dest_desc = -1; + mc_unlink (dst_vpath); + dst_status = DEST_NONE; + goto ret; + } + + /* return_status == FILE_RETRY -- try allocate space again */ + } + + ctx->eta_secs = 0.0; + ctx->bps = 0; + + if (tctx->bps == 0 || (file_size / tctx->bps) > FILEOP_UPDATE_INTERVAL) + file_progress_show (ctx, 0, file_size, "", TRUE); + else + file_progress_show (ctx, 1, 1, "", TRUE); + return_status = check_progress_buttons (ctx); + mc_refresh (); + + if (return_status == FILE_CONT) + { + size_t bufsize; + off_t file_part = 0; + gint64 tv_current, tv_last_update; + gint64 tv_last_input = 0; + gint64 usecs, update_usecs; + const char *stalled_msg = ""; + gboolean is_first_time = TRUE; + + tv_last_update = tv_transfer_start; + + bufsize = io_blksize (dst_stat); + buf = g_malloc (bufsize); + + while (TRUE) + { + ssize_t n_read = -1, n_written; + gboolean force_update; + + /* src_read */ + if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0) + while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all) + { + return_status = + file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + goto ret; + } + + if (n_read == 0) + break; + + tv_current = g_get_monotonic_time (); + + if (n_read > 0) + { + char *t = buf; + + file_part += n_read; + + tv_last_input = tv_current; + + /* dst_write */ + while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read) + { + gboolean write_errno_nospace; + + if (n_written > 0) + { + n_read -= n_written; + t += n_written; + continue; + } + + write_errno_nospace = (n_written < 0 && errno == ENOSPC); + + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + return_status = + file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path); + + if (return_status == FILE_SKIP) + { + if (write_errno_nospace) + goto ret; + break; + } + if (return_status == FILE_SKIPALL) + { + ctx->skip_all = TRUE; + if (write_errno_nospace) + goto ret; + } + if (return_status != FILE_RETRY) + goto ret; + } + } + + tctx->copied_bytes = tctx->progress_bytes + file_part + ctx->do_reget; + + usecs = tv_current - tv_last_update; + update_usecs = tv_current - tv_last_input; + + if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US) + { + copy_file_file_display_progress (tctx, ctx, tv_current, tv_transfer_start, + file_size, file_part); + tv_last_update = tv_current; + } + + is_first_time = FALSE; + + if (update_usecs > FILEOP_STALLING_INTERVAL_US) + stalled_msg = _("(stalled)"); + + force_update = (tv_current - tctx->transfer_start) > FILEOP_UPDATE_INTERVAL_US; + + if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update); + } + + file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg, + force_update); + mc_refresh (); + + return_status = check_progress_buttons (ctx); + if (return_status != FILE_CONT) + { + int query_res; + + query_res = + query_dialog (Q_ ("DialogTitle|Copy"), + _("Incomplete file was retrieved"), D_ERROR, 3, + _("&Delete"), _("&Keep"), _("&Continue copy")); + + switch (query_res) + { + case 0: + /* delete */ + dst_status = DEST_SHORT_DELETE; + goto ret; + + case 1: + /* keep */ + dst_status = DEST_SHORT_KEEP; + goto ret; + + default: + /* continue copy */ + break; + } + } + } + + /* copy successful */ + dst_status = DEST_FULL; + } + + ret: + g_free (buf); + + rotate_dash (FALSE); + while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_ABORT) + return_status = temp_status; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + break; + } + + while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + return_status = temp_status; + break; + } + + if (dst_status == DEST_SHORT_QUERY) + { + /* Query to remove short file */ + if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"), + D_ERROR, 2, _("&Delete"), _("&Keep")) == 0) + mc_unlink (dst_vpath); + } + else if (dst_status == DEST_SHORT_DELETE) + mc_unlink (dst_vpath); + else if (dst_status == DEST_FULL && !appending) + { + /* Copy has succeeded */ + + while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0 + && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_SKIPALL) + { + ctx->skip_all = TRUE; + return_status = FILE_CONT; + } + if (temp_status == FILE_SKIP) + return_status = FILE_CONT; + break; + } + + while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0 + && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_SKIPALL) + { + ctx->skip_all = TRUE; + return_status = FILE_CONT; + } + if (temp_status == FILE_SKIP) + return_status = FILE_CONT; + break; + } + + if (!ctx->preserve && !dst_exists) + { + src_mode = umask (-1); + umask (src_mode); + src_mode = 0100666 & ~src_mode; + mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)); + } + + mc_utime (dst_vpath, ×); + } + + if (return_status == FILE_CONT) + return_status = progress_update_one (tctx, ctx, file_size); + + ret_fast: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * I think these copy_*_* functions should have a return type. + * anyway, this function *must* have two directories as arguments. + */ +/* FIXME: This function needs to check the return values of the + function calls */ + +FileProgressStatus +copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d, + gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs) +{ + struct vfs_dirent *next; + struct stat dst_stat, src_stat; + DIR *reading; + FileProgressStatus return_status = FILE_CONT; + struct link *lp; + vfs_path_t *src_vpath, *dst_vpath; + gboolean do_mkdir = TRUE; + + src_vpath = vfs_path_from_str (s); + dst_vpath = vfs_path_from_str (d); + + /* First get the mode of the source dir */ + + retry_src_stat: + if ((*ctx->stat_func) (src_vpath, &src_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s); + if (return_status == FILE_RETRY) + goto retry_src_stat; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + goto ret_fast; + } + + if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL) + { + /* Don't copy a directory we created before (we don't want to copy + infinitely if a directory is copied into itself) */ + /* FIXME: should there be an error message and FILE_SKIP? - Norbert */ + return_status = FILE_CONT; + goto ret_fast; + } + + /* Hmm, hardlink to directory??? - Norbert */ + /* FIXME: In this step we should do something in case the destination already exist */ + /* Check the hardlinks */ + if (ctx->preserve) + { + switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all)) + { + case HARDLINK_OK: + /* We have made a hardlink - no more processing is necessary */ + goto ret_fast; + + case HARDLINK_ABORT: + return_status = FILE_ABORT; + goto ret_fast; + + default: + break; + } + } + + if (!S_ISDIR (src_stat.st_mode)) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s); + if (return_status == FILE_RETRY) + goto retry_src_stat; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + goto ret_fast; + } + + if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL) + { + /* we found a cyclic symbolic link */ + message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s); + return_status = FILE_SKIP; + goto ret_fast; + } + + lp = g_new0 (struct link, 1); + lp->vfs = vfs_path_get_by_index (src_vpath, -1)->class; + lp->ino = src_stat.st_ino; + lp->dev = src_stat.st_dev; + parent_dirs = g_slist_prepend (parent_dirs, lp); + + retry_dst_stat: + /* Now, check if the dest dir exists, if not, create it. */ + if (mc_stat (dst_vpath, &dst_stat) != 0) + { + /* Here the dir doesn't exist : make it ! */ + if (move_over && mc_rename (src_vpath, dst_vpath) == 0) + { + return_status = FILE_CONT; + goto ret; + } + } + else + { + /* + * If the destination directory exists, we want to copy the whole + * directory, but we only want this to happen once. + * + * Escape sequences added to the * to compiler warnings. + * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\* + * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\* + */ + if (!S_ISDIR (dst_stat.st_mode)) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_dst_stat; + } + goto ret; + } + /* Dive into subdir if exists */ + if (toplevel && ctx->dive_into_subdirs) + { + vfs_path_t *tmp; + + tmp = dst_vpath; + dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL); + vfs_path_free (tmp, TRUE); + + } + else + do_mkdir = FALSE; + } + + d = vfs_path_as_str (dst_vpath); + + if (do_mkdir) + { + while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + if (return_status != FILE_RETRY) + goto ret; + } + + lp = g_new0 (struct link, 1); + mc_stat (dst_vpath, &dst_stat); + lp->vfs = vfs_path_get_by_index (dst_vpath, -1)->class; + lp->ino = dst_stat.st_ino; + lp->dev = dst_stat.st_dev; + dest_dirs = g_slist_prepend (dest_dirs, lp); + } + + if (ctx->preserve_uidgid) + { + while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + if (return_status != FILE_RETRY) + goto ret; + } + } + + /* open the source dir for reading */ + reading = mc_opendir (src_vpath); + if (reading == NULL) + goto ret; + + while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) + { + char *path; + vfs_path_t *tmp_vpath; + + /* + * Now, we don't want '.' and '..' to be created / copied at any time + */ + if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name)) + continue; + + /* get the filename and add it to the src directory */ + path = mc_build_filename (s, next->d_name, (char *) NULL); + tmp_vpath = vfs_path_from_str (path); + + (*ctx->stat_func) (tmp_vpath, &dst_stat); + if (S_ISDIR (dst_stat.st_mode)) + { + char *mdpath; + + mdpath = mc_build_filename (d, next->d_name, (char *) NULL); + /* + * From here, we just intend to recursively copy subdirs, not + * the double functionality of copying different when the target + * dir already exists. So, we give the recursive call the flag 0 + * meaning no toplevel. + */ + return_status = + copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs); + g_free (mdpath); + } + else + { + char *dest_file; + + dest_file = mc_build_filename (d, x_basename (path), (char *) NULL); + return_status = copy_file_file (tctx, ctx, path, dest_file); + g_free (dest_file); + } + + g_free (path); + + if (do_delete && return_status == FILE_CONT) + { + if (ctx->erase_at_end) + { + if (erase_list == NULL) + erase_list = g_queue_new (); + + lp = g_new0 (struct link, 1); + lp->src_vpath = tmp_vpath; + lp->st_mode = dst_stat.st_mode; + g_queue_push_tail (erase_list, lp); + tmp_vpath = NULL; + } + else if (S_ISDIR (dst_stat.st_mode)) + return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count); + else + return_status = erase_file (tctx, ctx, tmp_vpath); + } + vfs_path_free (tmp_vpath, TRUE); + } + mc_closedir (reading); + + if (ctx->preserve) + { + mc_timesbuf_t times; + + mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill); + get_times (&src_stat, ×); + mc_utime (dst_vpath, ×); + } + else + { + src_stat.st_mode = umask (-1); + umask (src_stat.st_mode); + src_stat.st_mode = 0100777 & ~src_stat.st_mode; + mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill); + } + + ret: + free_link (parent_dirs->data); + g_slist_free_1 (parent_dirs); + ret_fast: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + return return_status; +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Move routines */ + +FileProgressStatus +move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d) +{ + return do_move_dir_dir (NULL, tctx, ctx, s, d); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Erase routines */ + +FileProgressStatus +erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath) +{ + file_progress_show_deleting (ctx, vfs_path_as_str (vpath), NULL); + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + + /* The old way to detect a non empty directory was: + error = my_rmdir (s); + if (error && (errno == ENOTEMPTY || errno == EEXIST))){ + For the linux user space nfs server (nfs-server-2.2beta29-2) + we would have to check also for EIO. I hope the new way is + fool proof. (Norbert) + */ + if (check_dir_is_empty (vpath) == 0) + { /* not empty */ + FileProgressStatus error; + + error = query_recursive (ctx, vfs_path_as_str (vpath)); + if (error == FILE_CONT) + error = recursive_erase (tctx, ctx, vpath); + return error; + } + + return try_erase_dir (ctx, vfs_path_as_str (vpath)); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Panel operate routines */ + +void +dirsize_status_init_cb (status_msg_t * sm) +{ + dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm; + WGroup *gd = GROUP (sm->dlg); + Widget *wd = WIDGET (sm->dlg); + WRect r = wd->rect; + + const char *b1_name = N_("&Abort"); + const char *b2_name = N_("&Skip"); + int b_width, ui_width; + +#ifdef ENABLE_NLS + b1_name = _(b1_name); + b2_name = _(b2_name); +#endif + + b_width = str_term_width1 (b1_name) + 4; + if (dsm->allow_skip) + b_width += str_term_width1 (b2_name) + 4 + 1; + + ui_width = MAX (COLS / 2, b_width + 6); + dsm->dirname = label_new (2, 3, ""); + group_add_widget (gd, dsm->dirname); + dsm->count_size = label_new (3, 3, ""); + group_add_widget (gd, dsm->count_size); + group_add_widget (gd, hline_new (4, -1, -1)); + + dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL)); + group_add_widget (gd, dsm->abort_button); + if (dsm->allow_skip) + { + dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL)); + group_add_widget (gd, dsm->skip_button); + widget_select (dsm->skip_button); + } + + r.lines = 8; + r.cols = ui_width; + widget_set_size_rect (wd, &r); + dirsize_status_locate_buttons (dsm); +} + +/* --------------------------------------------------------------------------------------------- */ + +int +dirsize_status_update_cb (status_msg_t * sm) +{ + dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm; + Widget *wd = WIDGET (sm->dlg); + WRect r = wd->rect; + + /* update second (longer label) */ + label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"), + dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si)); + + /* enlarge dialog if required */ + if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols) + { + r.cols = WIDGET (dsm->count_size)->rect.cols + 6; + widget_set_size_rect (wd, &r); + dirsize_status_locate_buttons (dsm); + widget_draw (wd); + /* TODO: ret rid of double redraw */ + } + + /* adjust first label */ + label_set_text (dsm->dirname, + str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6)); + + switch (status_msg_common_update (sm)) + { + case B_CANCEL: + case FILE_ABORT: + return FILE_ABORT; + case FILE_SKIP: + return FILE_SKIP; + default: + return FILE_CONT; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +dirsize_status_deinit_cb (status_msg_t * sm) +{ + (void) sm; + + /* schedule to update passive panel */ + if (get_other_type () == view_listing) + other_panel->dirty = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * compute_dir_size: + * + * Computes the number of bytes used by the files in a directory + */ + +FileProgressStatus +compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm, + size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total, + gboolean follow_symlinks) +{ + return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total, + follow_symlinks ? mc_stat : mc_lstat); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * panel_operate: + * + * Performs one of the operations on the selection on the source_panel + * (copy, delete, move). + * + * Returns TRUE if did change the directory + * structure, Returns FALSE if user aborted + * + * force_single forces operation on the current entry and affects + * default destination. Current filename is used as default. + */ + +gboolean +panel_operate (void *source_panel, FileOperation operation, gboolean force_single) +{ + WPanel *panel = PANEL (source_panel); + const gboolean single_entry = force_single || (panel->marked <= 1) + || (get_current_type () == view_tree); + + const char *source = NULL; + char *dest = NULL; + vfs_path_t *dest_vpath = NULL; + char *save_cwd = NULL, *save_dest = NULL; + struct stat src_stat; + gboolean ret_val = TRUE; + int i; + FileProgressStatus value; + file_op_context_t *ctx; + file_op_total_context_t *tctx; + vfs_path_t *tmp_vpath; + filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM; + + gboolean do_bg = FALSE; /* do background operation? */ + + static gboolean i18n_flag = FALSE; + if (!i18n_flag) + { + for (i = G_N_ELEMENTS (op_names); i-- != 0;) + op_names[i] = Q_ (op_names[i]); + i18n_flag = TRUE; + } + + linklist = free_linklist (linklist); + dest_dirs = free_linklist (dest_dirs); + + save_cwds_stat (); + + if (single_entry) + { + source = check_single_entry (panel, force_single, &src_stat); + + if (source == NULL) + return FALSE; + } + + ctx = file_op_context_new (operation); + + /* Show confirmation dialog */ + if (operation != OP_DELETE) + { + dest = + do_confirm_copy_move (panel, operation, force_single, source, &src_stat, ctx, &do_bg); + + if (dest == NULL) + { + ret_val = FALSE; + goto ret_fast; + } + + dest_vpath = vfs_path_from_str (dest); + } + else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat)) + { + ret_val = FALSE; + goto ret_fast; + } + + tctx = file_op_total_context_new (); + tctx->transfer_start = g_get_monotonic_time (); + +#ifdef ENABLE_BACKGROUND + /* Did the user select to do a background operation? */ + if (do_bg) + { + int v; + + v = do_background (ctx, + g_strconcat (op_names[operation], ": ", + vfs_path_as_str (panel->cwd_vpath), (char *) NULL)); + if (v == -1) + message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background")); + + /* If we are the parent */ + if (v == 1) + { + mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL); + + mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL); + vfs_path_free (dest_vpath, TRUE); + g_free (dest); + /* file_op_context_destroy (ctx); */ + return FALSE; + } + } + else +#endif /* ENABLE_BACKGROUND */ + { + if (operation == OP_DELETE) + dialog_type = FILEGUI_DIALOG_DELETE_ITEM; + else if (single_entry && S_ISDIR (selection (panel)->st.st_mode)) + dialog_type = FILEGUI_DIALOG_MULTI_ITEM; + else if (single_entry || force_single) + dialog_type = FILEGUI_DIALOG_ONE_ITEM; + else + dialog_type = FILEGUI_DIALOG_MULTI_ITEM; + } + + /* Initialize things */ + /* We do not want to trash cache every time file is + created/touched. However, this will make our cache contain + invalid data. */ + if ((dest != NULL) + && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0)) + save_dest = g_strdup (dest); + + if ((vfs_path_tokens_count (panel->cwd_vpath) != 0) + && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0)) + save_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath)); + + /* Now, let's do the job */ + + /* This code is only called by the tree and panel code */ + if (single_entry) + { + /* We now have ETA in all cases */ + + /* One file: FIXME mc_chdir will take user out of any vfs */ + if ((operation != OP_COPY) && (get_current_type () == view_tree)) + { + vfs_path_t *vpath; + int chdir_retcode; + + vpath = vfs_path_from_str (PATH_SEP_STR); + chdir_retcode = mc_chdir (vpath); + vfs_path_free (vpath, TRUE); + if (chdir_retcode < 0) + { + ret_val = FALSE; + goto clean_up; + } + } + + value = + operate_single_file (panel, operation, tctx, ctx, source, &src_stat, dest, dialog_type); + + if ((value == FILE_CONT) && !force_single) + unmark_files (panel); + } + else + { + /* Many files */ + + /* Check destination for copy or move operation */ + while (operation != OP_DELETE) + { + int dst_result; + struct stat dst_stat; + + dst_result = mc_stat (dest_vpath, &dst_stat); + + if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode)) + break; + + if (ctx->skip_all + || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), + dest) != FILE_RETRY) + goto clean_up; + } + + /* TODO: the good way is required to skip directories scanning in case of rename/move + * of several directories. Since reqular expression can be used for destination, + * some directory movements can be a cross-filesystem and directory scanning is useful + * for those directories only. */ + + if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type) + == FILE_CONT) + { + /* Loop for every file, perform the actual copy operation */ + for (i = 0; i < panel->dir.len; i++) + { + const char *source2; + + if (!panel->dir.list[i].f.marked) + continue; /* Skip the unmarked ones */ + + source2 = panel->dir.list[i].fname->str; + src_stat = panel->dir.list[i].st; + + value = operate_one_file (panel, operation, tctx, ctx, source2, &src_stat, dest); + + if (value == FILE_ABORT) + break; + + if (value == FILE_CONT) + do_file_mark (panel, i, 0); + + if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE); + } + + if (operation != OP_DELETE) + file_progress_show (ctx, 0, 0, "", FALSE); + + if (check_progress_buttons (ctx) == FILE_ABORT) + break; + + mc_refresh (); + } /* Loop for every file */ + } + } /* Many entries */ + + clean_up: + /* Clean up */ + if (save_cwd != NULL) + { + tmp_vpath = vfs_path_from_str (save_cwd); + mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL); + vfs_path_free (tmp_vpath, TRUE); + g_free (save_cwd); + } + + if (save_dest != NULL) + { + tmp_vpath = vfs_path_from_str (save_dest); + mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL); + vfs_path_free (tmp_vpath, TRUE); + g_free (save_dest); + } + + linklist = free_linklist (linklist); + dest_dirs = free_linklist (dest_dirs); + g_free (dest); + vfs_path_free (dest_vpath, TRUE); + MC_PTR_FREE (ctx->dest_mask); + +#ifdef ENABLE_BACKGROUND + /* Let our parent know we are saying bye bye */ + if (mc_global.we_are_background) + { + int cur_pid = getpid (); + /* Send pid to parent with child context, it is fork and + don't modify real parent ctx */ + ctx->pid = cur_pid; + parent_call ((void *) end_bg_process, ctx, 0); + + vfs_shut (); + my_exit (EXIT_SUCCESS); + } +#endif /* ENABLE_BACKGROUND */ + + file_op_total_context_destroy (tctx); + ret_fast: + /* update panels before redraw screen in file_op_total_context_destroy() */ + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + file_op_context_destroy (ctx); + + return ret_val; +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Query/status report routines */ +/** Report error with one file */ +FileProgressStatus +file_error (gboolean allow_retry, const char *format, const char *file) +{ + char buf[BUF_MEDIUM]; + + g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno)); + + return do_file_error (allow_retry, buf); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + Cause emacs to enter folding mode for this file: + Local variables: + end: + */ diff --git a/src/filemanager/file.h b/src/filemanager/file.h new file mode 100644 index 0000000..ae12e15 --- /dev/null +++ b/src/filemanager/file.h @@ -0,0 +1,72 @@ +/** \file file.h + * \brief Header: File and directory operation routines + */ + +#ifndef MC__FILE_H +#define MC__FILE_H + +#include <inttypes.h> /* off_t, uintmax_t */ + +#include "lib/global.h" +#include "lib/widget.h" + +#include "fileopctx.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +typedef struct dirsize_status_msg_t dirsize_status_msg_t; + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/* status dialog of directory size computing */ +struct dirsize_status_msg_t +{ + status_msg_t status_msg; /* base class */ + + gboolean allow_skip; + WLabel *dirname; + WLabel *count_size; + Widget *abort_button; + Widget *skip_button; + const vfs_path_t *dirname_vpath; + size_t dir_count; + uintmax_t total_size; +}; + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +gboolean file_is_symlink_to_dir (const vfs_path_t * path, struct stat *st, gboolean * stale_link); + +FileProgressStatus copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *src_path, const char *dst_path); +FileProgressStatus move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *s, const char *d); +FileProgressStatus copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *s, const char *d, + gboolean toplevel, gboolean move_over, gboolean do_delete, + GSList * parent_dirs); +FileProgressStatus erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, + const vfs_path_t * vpath); + +gboolean panel_operate (void *source_panel, FileOperation op, gboolean force_single); + +/* Error reporting routines */ + +/* Report error with one file */ +FileProgressStatus file_error (gboolean allow_retry, const char *format, const char *file); + +/* return value is FILE_CONT or FILE_ABORT */ +FileProgressStatus compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm, + size_t * ret_dir_count, size_t * ret_marked_count, + uintmax_t * ret_total, gboolean follow_symlinks); + +void dirsize_status_init_cb (status_msg_t * sm); +int dirsize_status_update_cb (status_msg_t * sm); +void dirsize_status_deinit_cb (status_msg_t * sm); + +/*** inline functions ****************************************************************************/ +#endif /* MC__FILE_H */ diff --git a/src/filemanager/filegui.c b/src/filemanager/filegui.c new file mode 100644 index 0000000..6d21347 --- /dev/null +++ b/src/filemanager/filegui.c @@ -0,0 +1,1496 @@ +/* + File management GUI for the text mode edition + + The copy code was based in GNU's cp, and was written by: + Torbjorn Granlund, David MacKenzie, and Jim Meyering. + + The move code was based in GNU's mv, and was written by: + Mike Parker and David MacKenzie. + + Janne Kukonlehto added much error recovery to them for being used + in an interactive program. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Janne Kukonlehto, 1994, 1995 + Fred Leeflang, 1994, 1995 + Miguel de Icaza, 1994, 1995, 1996 + Jakub Jelinek, 1995, 1996 + Norbert Warmuth, 1997 + Pavel Machek, 1998 + Slava Zanko, 2009, 2010, 2011, 2012, 2013 + 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/>. + */ + +/* + * Please note that all dialogs used here must be safe for background + * operations. + */ + +/** \file filegui.c + * \brief Source: file management GUI for the text mode edition + */ + +/* {{{ Include files */ + +#include <config.h> + +#if ((defined STAT_STATVFS || defined STAT_STATVFS64) \ + && (defined HAVE_STRUCT_STATVFS_F_BASETYPE || defined HAVE_STRUCT_STATVFS_F_FSTYPENAME \ + || (! defined HAVE_STRUCT_STATFS_F_FSTYPENAME))) +#define USE_STATVFS 1 +#else +#define USE_STATVFS 0 +#endif + +#include <errno.h> +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +#if USE_STATVFS +#include <sys/statvfs.h> +#elif defined HAVE_SYS_VFS_H +#include <sys/vfs.h> +#elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H +/* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs. + It does have statvfs.h, but shouldn't use it, since it doesn't + HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */ +/* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */ +#include <sys/param.h> +#include <sys/mount.h> +#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */ +#include <fs_info.h> +#endif + +#if USE_STATVFS +#if ! defined STAT_STATVFS && defined STAT_STATVFS64 +#define STRUCT_STATVFS struct statvfs64 +#define STATFS statvfs64 +#else +#define STRUCT_STATVFS struct statvfs +#define STATFS statvfs + +#if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__) +#include <sys/utsname.h> +#include <sys/statfs.h> +#define STAT_STATFS2_BSIZE 1 +#endif +#endif + +#else +#define STATFS statfs +#define STRUCT_STATVFS struct statfs +#ifdef HAVE_OS_H /* Haiku, also (obsolete) BeOS */ +/* BeOS has a statvfs function, but it does not return sensible values + for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and + f_fstypename. Use 'struct fs_info' instead. */ +static int +statfs (char const *filename, struct fs_info *buf) +{ + dev_t device; + + device = dev_for_path (filename); + + if (device < 0) + { + errno = (device == B_ENTRY_NOT_FOUND ? ENOENT + : device == B_BAD_VALUE ? EINVAL + : device == B_NAME_TOO_LONG ? ENAMETOOLONG + : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0); + return -1; + } + /* If successful, buf->dev will be == device. */ + return fs_stat_dev (device, buf); +} + +#define STRUCT_STATVFS struct fs_info +#else +#define STRUCT_STATVFS struct statfs +#endif +#endif + +#ifdef HAVE_STRUCT_STATVFS_F_BASETYPE +#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype +#else +#if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME +#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename +#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */ +#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name +#endif +#endif + +#include <unistd.h> + +#include "lib/global.h" + +#include "lib/tty/key.h" /* tty_get_event */ +#include "lib/mcconfig.h" +#include "lib/search.h" +#include "lib/vfs/vfs.h" +#include "lib/strescape.h" +#include "lib/strutil.h" +#include "lib/timefmt.h" /* file_date() */ +#include "lib/util.h" +#include "lib/widget.h" + +#include "src/setup.h" /* verbose, safe_overwrite */ + +#include "filemanager.h" +#include "fileopctx.h" /* FILE_CONT */ + +#include "filegui.h" + +/* }}} */ + +/*** global variables ****************************************************************************/ + +gboolean classic_progressbar = TRUE; + +/*** file scope macro definitions ****************************************************************/ + +#define truncFileString(dlg, s) str_trunc (s, WIDGET (dlg)->rect.cols - 10) +#define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10) + +/*** file scope type declarations ****************************************************************/ + +/* *INDENT-OFF* */ +typedef enum { + MSDOS_SUPER_MAGIC = 0x4d44, + NTFS_SB_MAGIC = 0x5346544e, + FUSE_MAGIC = 0x65735546, + PROC_SUPER_MAGIC = 0x9fa0, + SMB_SUPER_MAGIC = 0x517B, + NCP_SUPER_MAGIC = 0x564c, + USBDEVICE_SUPER_MAGIC = 0x9fa2 +} filegui_nonattrs_fs_t; +/* *INDENT-ON* */ + +/* Used for button result values */ +typedef enum +{ + REPLACE_YES = B_USER, + REPLACE_NO, + REPLACE_APPEND, + REPLACE_REGET, + REPLACE_ALL, + REPLACE_OLDER, + REPLACE_NONE, + REPLACE_SMALLER, + REPLACE_SIZE, + REPLACE_ABORT +} replace_action_t; + +/* This structure describes the UI and internal data required by a file + * operation context. + */ +typedef struct +{ + /* ETA and bps */ + gboolean showing_eta; + gboolean showing_bps; + + /* Dialog and widgets for the operation progress window */ + WDialog *op_dlg; + /* Source file: label and name */ + WLabel *src_file_label; + WLabel *src_file; + /* Target file: label and name */ + WLabel *tgt_file_label; + WLabel *tgt_file; + + WGauge *progress_file_gauge; + WLabel *progress_file_label; + + WGauge *progress_total_gauge; + + WLabel *total_files_processed_label; + WLabel *time_label; + WHLine *total_bytes_label; + + /* Query replace dialog */ + WDialog *replace_dlg; + const char *src_filename; + const char *tgt_filename; + replace_action_t replace_result; + gboolean dont_overwrite_with_zero; + + struct stat *src_stat, *dst_stat; +} file_op_context_ui_t; + +/*** file scope variables ************************************************************************/ + +static struct +{ + Widget *w; + FileProgressStatus action; + const char *text; + button_flags_t flags; + int len; +} progress_buttons[] = +{ + /* *INDENT-OFF* */ + { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 }, + { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 }, + { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 }, + { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 } + /* *INDENT-ON* */ +}; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* Return true if statvfs works. This is false for statvfs on systems + with GNU libc on Linux kernels before 2.6.36, which stats all + preceding entries in /proc/mounts; that makes df hang if even one + of the corresponding file systems is hard-mounted but not available. */ + +#if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64) +static int +statvfs_works (void) +{ +#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)) + return 1; +#else + static int statvfs_works_cache = -1; + struct utsname name; + + if (statvfs_works_cache < 0) + statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36")); + return statvfs_works_cache; +#endif +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +filegui__check_attrs_on_fs (const char *fs_path) +{ + STRUCT_STATVFS stfs; + +#if USE_STATVFS && defined(STAT_STATVFS) + if (statvfs_works () && statvfs (fs_path, &stfs) != 0) + return TRUE; +#else + if (STATFS (fs_path, &stfs) != 0) + return TRUE; +#endif + +#if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \ + (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE)) + switch ((filegui_nonattrs_fs_t) stfs.f_type) + { + case MSDOS_SUPER_MAGIC: + case NTFS_SB_MAGIC: + case PROC_SUPER_MAGIC: + case SMB_SUPER_MAGIC: + case NCP_SUPER_MAGIC: + case USBDEVICE_SUPER_MAGIC: + return FALSE; + default: + break; + } +#elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) + if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0 + || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL) + return FALSE; +#elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE) + if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0 + || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0) + return FALSE; +#endif + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +file_frmt_time (char *buffer, double eta_secs) +{ + int eta_hours, eta_mins, eta_s; + + eta_hours = (int) (eta_secs / (60 * 60)); + eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60); + eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60)); + g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show) +{ + char _fmt_buff[BUF_TINY]; + + if (eta_secs <= 0.5 && !always_show) + { + *buffer = '\0'; + return; + } + + if (eta_secs <= 0.5) + eta_secs = 1; + file_frmt_time (_fmt_buff, eta_secs); + g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +file_bps_prepare_for_show (char *buffer, long bps) +{ + if (bps > 1024 * 1024) + g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0)); + else if (bps > 1024) + g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0); + else if (bps > 1) + g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps); + else + *buffer = '\0'; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +file_ui_op_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_ACTION: + /* Do not close the dialog because the query dialog will be shown */ + if (parm == CK_Cancel) + { + DIALOG (w)->ret_value = FILE_ABORT; /* for check_progress_buttons() */ + return MSG_HANDLED; + } + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +/* The dialog layout: + * + * +---------------------- File exists -----------------------+ + * | New : /path/to/original_file_name | // 0, 1 + * | 1234567 feb 4 2017 13:38 | // 2, 3 + * | Existing: /path/to/target_file_name | // 4, 5 + * | 1234567890 feb 4 2017 13:37 | // 6, 7 + * +----------------------------------------------------------+ + * | Overwrite this file? | // 8 + * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12 + * +----------------------------------------------------------+ + * | Overwrite all files? | // 13 + * | [ ] Don't overwrite with zero length file | // 14 + * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19 + * +----------------------------------------------------------| + * | [ Abort ] | // 20 + * +----------------------------------------------------------+ + */ + +static replace_action_t +overwrite_query_dialog (file_op_context_t * ctx, enum OperationMode mode) +{ +#define W(i) dlg_widgets[i].widget +#define WX(i) W(i)->rect.x +#define WY(i) W(i)->rect.y +#define WCOLS(i) W(i)->rect.cols + +#define NEW_LABEL(i, text) \ + W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text)) + +#define ADD_LABEL(i) \ + group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \ + g->current != NULL ? g->current->data : NULL) + +#define NEW_BUTTON(i) \ + W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \ + dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL)) + +#define ADD_BUTTON(i) \ + group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data) + + /* dialog sizes */ + const int dlg_height = 17; + int dlg_width = 60; + + struct + { + Widget *widget; + const char *text; + int y; + int x; + widget_pos_flags_t pos_flags; + int value; /* 0 for labels and checkbox */ + } dlg_widgets[] = + { + /* *INDENT-OFF* */ + /* 0 - label */ + { NULL, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT, 0 }, + /* 1 - label - name */ + { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 }, + /* 2 - label - size */ + { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 }, + /* 3 - label - date & time */ + { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 }, + /* 4 - label */ + { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 }, + /* 5 - label - name */ + { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 }, + /* 6 - label - size */ + { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 }, + /* 7 - label - date & time */ + { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 }, + /* --------------------------------------------------- */ + /* 8 - label */ + { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 }, + /* 9 - button */ + { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES }, + /* 10 - button */ + { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO }, + /* 11 - button */ + { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND }, + /* 12 - button */ + { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET }, + /* --------------------------------------------------- */ + /* 13 - label */ + { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 }, + /* 14 - checkbox */ + { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 }, + /* 15 - button */ + { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL }, + /* 16 - button */ + { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER }, + /* 17 - button */ + { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE }, + /* 18 - button */ + { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER }, + /* 19 - button */ + { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE }, + /* --------------------------------------------------- */ + /* 20 - button */ + { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT } + /* *INDENT-ON* */ + }; + + const int gap = 1; + + file_op_context_ui_t *ui = ctx->ui; + Widget *wd; + WGroup *g; + const char *title; + + vfs_path_t *p; + char *s1; + const char *cs1; + char s2[BUF_SMALL]; + int w, bw1, bw2; + unsigned short i; + + gboolean do_append = FALSE, do_reget = FALSE; + unsigned long yes_id, no_id; + int result; + + if (mode == Foreground) + title = _("File exists"); + else + title = _("Background process: File exists"); + +#ifdef ENABLE_NLS + { + const unsigned short num = G_N_ELEMENTS (dlg_widgets); + + for (i = 0; i < num; i++) + if (dlg_widgets[i].text != NULL) + dlg_widgets[i].text = _(dlg_widgets[i].text); + } +#endif /* ENABLE_NLS */ + + /* create widgets to get their real widths */ + /* new file */ + NEW_LABEL (0, dlg_widgets[0].text); + /* new file name */ + p = vfs_path_from_str (ui->src_filename); + s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD); + NEW_LABEL (1, s1); + vfs_path_free (p, TRUE); + g_free (s1); + /* new file size */ + size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si); + NEW_LABEL (2, s2); + /* new file modification date & time */ + cs1 = file_date (ui->src_stat->st_mtime); + NEW_LABEL (3, cs1); + + /* existing file */ + NEW_LABEL (4, dlg_widgets[4].text); + /* existing file name */ + p = vfs_path_from_str (ui->tgt_filename); + s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD); + NEW_LABEL (5, s1); + vfs_path_free (p, TRUE); + g_free (s1); + /* existing file size */ + size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si); + NEW_LABEL (6, s2); + /* existing file modification date & time */ + cs1 = file_date (ui->dst_stat->st_mtime); + NEW_LABEL (7, cs1); + + /* will "Append" and "Reget" buttons be in the dialog? */ + do_append = !S_ISDIR (ui->dst_stat->st_mode); + do_reget = do_append && ctx->operation == OP_COPY && ui->dst_stat->st_size != 0 + && ui->src_stat->st_size > ui->dst_stat->st_size; + + NEW_LABEL (8, dlg_widgets[8].text); + NEW_BUTTON (9); + NEW_BUTTON (10); + if (do_append) + NEW_BUTTON (11); + if (do_reget) + NEW_BUTTON (12); + + NEW_LABEL (13, dlg_widgets[13].text); + dlg_widgets[14].widget = + WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text)); + for (i = 15; i <= 20; i++) + NEW_BUTTON (i); + + /* place widgets */ + dlg_width -= 2 * (2 + gap); /* inside frame */ + + /* perhaps longest line is buttons */ + bw1 = WCOLS (9) + gap + WCOLS (10); + if (do_append) + bw1 += gap + WCOLS (11); + if (do_reget) + bw1 += gap + WCOLS (12); + dlg_width = MAX (dlg_width, bw1); + + bw2 = WCOLS (15); + for (i = 16; i <= 19; i++) + bw2 += gap + WCOLS (i); + dlg_width = MAX (dlg_width, bw2); + + dlg_width = MAX (dlg_width, WCOLS (8)); + dlg_width = MAX (dlg_width, WCOLS (13)); + dlg_width = MAX (dlg_width, WCOLS (14)); + + /* truncate file names */ + w = WCOLS (0) + gap + WCOLS (1); + if (w > dlg_width) + { + WLabel *l = LABEL (W (1)); + + w = dlg_width - gap - WCOLS (0); + label_set_text (l, str_trunc (l->text, w)); + } + + w = WCOLS (4) + gap + WCOLS (5); + if (w > dlg_width) + { + WLabel *l = LABEL (W (5)); + + w = dlg_width - gap - WCOLS (4); + label_set_text (l, str_trunc (l->text, w)); + } + + /* real dlalog width */ + dlg_width += 2 * (2 + gap); + + WX (1) = WX (0) + WCOLS (0) + gap; + WX (5) = WX (4) + WCOLS (4) + gap; + + /* sizes: right alignment */ + WX (2) = dlg_width / 2 - WCOLS (2); + WX (6) = dlg_width / 2 - WCOLS (6); + + w = dlg_width - (2 + gap); /* right bound */ + + /* date & time */ + WX (3) = w - WCOLS (3); + WX (7) = w - WCOLS (7); + + /* buttons: center alignment */ + WX (9) = dlg_width / 2 - bw1 / 2; + WX (10) = WX (9) + WCOLS (9) + gap; + if (do_append) + WX (11) = WX (10) + WCOLS (10) + gap; + if (do_reget) + WX (12) = WX (11) + WCOLS (11) + gap; + + WX (15) = dlg_width / 2 - bw2 / 2; + for (i = 16; i <= 19; i++) + WX (i) = WX (i - 1) + WCOLS (i - 1) + gap; + + /* TODO: write help (ticket #3970) */ + ui->replace_dlg = + dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL, + "[Replace]", title); + wd = WIDGET (ui->replace_dlg); + g = GROUP (ui->replace_dlg); + + /* file info */ + for (i = 0; i <= 7; i++) + ADD_LABEL (i); + group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1)); + + /* label & buttons */ + ADD_LABEL (8); /* Overwrite this file? */ + yes_id = ADD_BUTTON (9); /* Yes */ + no_id = ADD_BUTTON (10); /* No */ + if (do_append) + ADD_BUTTON (11); /* Append */ + if (do_reget) + ADD_BUTTON (12); /* Reget */ + group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1)); + + /* label & buttons */ + ADD_LABEL (13); /* Overwrite all files? */ + group_add_widget (g, dlg_widgets[14].widget); + for (i = 15; i <= 19; i++) + ADD_BUTTON (i); + group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1)); + + ADD_BUTTON (20); /* Abort */ + + group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id); + + result = dlg_run (ui->replace_dlg); + + if (result != B_CANCEL) + ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state; + + widget_destroy (wd); + + return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result; + +#undef ADD_BUTTON +#undef NEW_BUTTON +#undef ADD_LABEL +#undef NEW_LABEL +#undef WCOLS +#undef WX +#undef W +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +is_wildcarded (const char *p) +{ + gboolean escaped = FALSE; + + for (; *p != '\0'; p++) + { + if (*p == '\\') + { + if (p[1] >= '1' && p[1] <= '9' && !escaped) + return TRUE; + escaped = !escaped; + } + else + { + if ((*p == '*' || *p == '?') && !escaped) + return TRUE; + escaped = FALSE; + } + } + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +place_progress_buttons (WDialog * h, gboolean suspended) +{ + const size_t i = suspended ? 2 : 1; + Widget *w = WIDGET (h); + int buttons_width; + + buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len; + buttons_width += progress_buttons[i].len; + button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text); + + progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2; + progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1; + progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +progress_button_callback (WButton * button, int action) +{ + (void) button; + (void) action; + + /* don't close dialog in any case */ + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +FileProgressStatus +check_progress_buttons (file_op_context_t * ctx) +{ + int c; + Gpm_Event event; + file_op_context_ui_t *ui; + + if (ctx == NULL || ctx->ui == NULL) + return FILE_CONT; + + ui = ctx->ui; + + get_event: + event.x = -1; /* Don't show the GPM cursor */ + c = tty_get_event (&event, FALSE, ctx->suspended); + if (c == EV_NONE) + return FILE_CONT; + + /* Reinitialize to avoid old values after events other than selecting a button */ + ui->op_dlg->ret_value = FILE_CONT; + + dlg_process_event (ui->op_dlg, c, &event); + switch (ui->op_dlg->ret_value) + { + case FILE_SKIP: + if (ctx->suspended) + { + /* redraw dialog in case of Skip after Suspend */ + place_progress_buttons (ui->op_dlg, FALSE); + widget_draw (WIDGET (ui->op_dlg)); + } + ctx->suspended = FALSE; + return FILE_SKIP; + case B_CANCEL: + case FILE_ABORT: + ctx->suspended = FALSE; + return FILE_ABORT; + case FILE_SUSPEND: + ctx->suspended = !ctx->suspended; + place_progress_buttons (ui->op_dlg, ctx->suspended); + widget_draw (WIDGET (ui->op_dlg)); + MC_FALLTHROUGH; + default: + if (ctx->suspended) + goto get_event; + return FILE_CONT; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ File progress display routines */ + +void +file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta, + filegui_dialog_type_t dialog_type) +{ + file_op_context_ui_t *ui; + Widget *w; + WGroup *g; + int buttons_width; + int dlg_width = 58, dlg_height = 17; + int y = 2, x = 3; + WRect r; + + if (ctx == NULL || ctx->ui != NULL) + return; + +#ifdef ENABLE_NLS + if (progress_buttons[0].len == -1) + { + size_t i; + + for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++) + progress_buttons[i].text = _(progress_buttons[i].text); + } +#endif + + ctx->dialog_type = dialog_type; + ctx->recursive_result = RECURSIVE_YES; + ctx->ui = g_new0 (file_op_context_ui_t, 1); + + ui = ctx->ui; + ui->replace_result = REPLACE_YES; + + ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors, + file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]); + w = WIDGET (ui->op_dlg); + g = GROUP (ui->op_dlg); + + if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM) + { + ui->showing_eta = with_eta && ctx->progress_totals_computed; + ui->showing_bps = with_eta; + + ui->src_file_label = label_new (y++, x, ""); + group_add_widget (g, ui->src_file_label); + + ui->src_file = label_new (y++, x, ""); + group_add_widget (g, ui->src_file); + + ui->tgt_file_label = label_new (y++, x, ""); + group_add_widget (g, ui->tgt_file_label); + + ui->tgt_file = label_new (y++, x, ""); + group_add_widget (g, ui->tgt_file); + + ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0); + if (!classic_progressbar && (current_panel == right_panel)) + ui->progress_file_gauge->from_left_to_right = FALSE; + group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL); + + ui->progress_file_label = label_new (y++, x, ""); + group_add_widget (g, ui->progress_file_label); + + if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM) + { + ui->total_bytes_label = hline_new (y++, -1, -1); + group_add_widget (g, ui->total_bytes_label); + + if (ctx->progress_totals_computed) + { + ui->progress_total_gauge = + gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0); + if (!classic_progressbar && (current_panel == right_panel)) + ui->progress_total_gauge->from_left_to_right = FALSE; + group_add_widget_autopos (g, ui->progress_total_gauge, + WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL); + } + + ui->total_files_processed_label = label_new (y++, x, ""); + group_add_widget (g, ui->total_files_processed_label); + + ui->time_label = label_new (y++, x, ""); + group_add_widget (g, ui->time_label); + } + } + else + { + ui->src_file = label_new (y++, x, ""); + group_add_widget (g, ui->src_file); + + ui->total_files_processed_label = label_new (y++, x, ""); + group_add_widget (g, ui->total_files_processed_label); + } + + group_add_widget (g, hline_new (y++, -1, -1)); + + progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action, + progress_buttons[0].flags, progress_buttons[0].text, + progress_button_callback)); + if (progress_buttons[0].len == -1) + progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w)); + + progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action, + progress_buttons[1].flags, progress_buttons[1].text, + progress_button_callback)); + if (progress_buttons[1].len == -1) + progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w)); + + if (progress_buttons[2].len == -1) + { + /* create and destroy button to get it length */ + progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action, + progress_buttons[2].flags, + progress_buttons[2].text, + progress_button_callback)); + progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w)); + widget_destroy (progress_buttons[2].w); + } + progress_buttons[2].w = progress_buttons[1].w; + + progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action, + progress_buttons[3].flags, progress_buttons[3].text, + NULL)); + if (progress_buttons[3].len == -1) + progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w)); + + group_add_widget (g, progress_buttons[0].w); + group_add_widget (g, progress_buttons[1].w); + group_add_widget (g, progress_buttons[3].w); + + buttons_width = 2 + + progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) + + progress_buttons[3].len; + + /* adjust dialog sizes */ + r = w->rect; + r.lines = y + 3; + r.cols = MAX (COLS * 2 / 3, buttons_width + 6); + widget_set_size_rect (w, &r); + + place_progress_buttons (ui->op_dlg, FALSE); + + widget_select (progress_buttons[0].w); + + /* We will manage the dialog without any help, that's why + we have to call dlg_init */ + dlg_init (ui->op_dlg); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +file_op_context_destroy_ui (file_op_context_t * ctx) +{ + if (ctx != NULL && ctx->ui != NULL) + { + file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui; + + dlg_run_done (ui->op_dlg); + widget_destroy (WIDGET (ui->op_dlg)); + MC_PTR_FREE (ctx->ui); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + show progressbar for file + */ + +void +file_progress_show (file_op_context_t * ctx, off_t done, off_t total, + const char *stalled_msg, gboolean force_update) +{ + file_op_context_ui_t *ui; + + if (!verbose || ctx == NULL || ctx->ui == NULL) + return; + + ui = ctx->ui; + + if (total == 0) + { + gauge_show (ui->progress_file_gauge, FALSE); + return; + } + + gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total)); + gauge_show (ui->progress_file_gauge, TRUE); + + if (!force_update) + return; + + if (!ui->showing_eta || ctx->eta_secs <= 0.5) + label_set_text (ui->progress_file_label, stalled_msg); + else + { + char buffer2[BUF_TINY]; + + file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE); + if (ctx->bps == 0) + label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg); + else + { + char buffer3[BUF_TINY]; + + file_bps_prepare_for_show (buffer3, ctx->bps); + label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg); + } + + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total) +{ + file_op_context_ui_t *ui; + + if (ctx == NULL || ctx->ui == NULL) + return; + + ui = ctx->ui; + + if (ui->total_files_processed_label == NULL) + return; + + if (ctx->progress_totals_computed) + label_set_textv (ui->total_files_processed_label, _("Files processed: %zu / %zu"), done, + total); + else + label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"), done); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx, + uintmax_t copied_bytes, gboolean show_summary) +{ + char buffer2[BUF_TINY]; + char buffer3[BUF_TINY]; + file_op_context_ui_t *ui; + + if (ctx == NULL || ctx->ui == NULL) + return; + + ui = ctx->ui; + + if (ui->progress_total_gauge != NULL) + { + if (ctx->progress_bytes == 0) + gauge_show (ui->progress_total_gauge, FALSE); + else + { + gauge_set_value (ui->progress_total_gauge, 1024, + (int) (1024 * copied_bytes / ctx->progress_bytes)); + gauge_show (ui->progress_total_gauge, TRUE); + } + } + + if (!show_summary && tctx->bps == 0) + return; + + if (ui->time_label != NULL) + { + gint64 tv_current; + char buffer4[BUF_TINY]; + + tv_current = g_get_monotonic_time (); + file_frmt_time (buffer2, (tv_current - tctx->transfer_start) / G_USEC_PER_SEC); + + if (ctx->progress_totals_computed) + { + file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE); + if (tctx->bps == 0) + label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3); + else + { + file_bps_prepare_for_show (buffer4, (long) tctx->bps); + label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4); + } + } + else + { + if (tctx->bps == 0) + label_set_textv (ui->time_label, _("Time: %s"), buffer2); + else + { + file_bps_prepare_for_show (buffer4, (long) tctx->bps); + label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4); + } + } + } + + if (ui->total_bytes_label != NULL) + { + size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si); + + if (!ctx->progress_totals_computed) + hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2); + else + { + size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si); + hline_set_textv (ui->total_bytes_label, _(" Total: %s / %s "), buffer2, buffer3); + } + } +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ + +void +file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath) +{ + file_op_context_ui_t *ui; + + if (ctx == NULL || ctx->ui == NULL) + return; + + ui = ctx->ui; + + if (vpath != NULL) + { + char *s; + + s = vfs_path_tokens_get (vpath, -1, 1); + label_set_text (ui->src_file_label, _("Source")); + label_set_text (ui->src_file, truncFileString (ui->op_dlg, s)); + g_free (s); + } + else + { + label_set_text (ui->src_file_label, ""); + label_set_text (ui->src_file, ""); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath) +{ + file_op_context_ui_t *ui; + + if (ctx == NULL || ctx->ui == NULL) + return; + + ui = ctx->ui; + + if (vpath != NULL) + { + label_set_text (ui->tgt_file_label, _("Target")); + label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath))); + } + else + { + label_set_text (ui->tgt_file_label, ""); + label_set_text (ui->tgt_file, ""); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +gboolean +file_progress_show_deleting (file_op_context_t * ctx, const char *s, size_t * count) +{ + static gint64 timestamp = 0; + /* update with 25 FPS rate */ + static const gint64 delay = G_USEC_PER_SEC / 25; + + gboolean ret; + + if (ctx == NULL || ctx->ui == NULL) + return FALSE; + + ret = mc_time_elapsed (×tamp, delay); + + if (ret) + { + file_op_context_ui_t *ui; + + ui = ctx->ui; + + if (ui->src_file_label != NULL) + label_set_text (ui->src_file_label, _("Deleting")); + + label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s)); + } + + if (count != NULL) + (*count)++; + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +FileProgressStatus +file_progress_real_query_replace (file_op_context_t * ctx, enum OperationMode mode, + const char *src, struct stat * src_stat, + const char *dst, struct stat * dst_stat) +{ + file_op_context_ui_t *ui; + FileProgressStatus replace_with_zero; + + if (ctx == NULL || ctx->ui == NULL) + return FILE_CONT; + + ui = ctx->ui; + + if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO + || ui->replace_result == REPLACE_APPEND) + { + ui->src_filename = src; + ui->src_stat = src_stat; + ui->tgt_filename = dst; + ui->dst_stat = dst_stat; + ui->replace_result = overwrite_query_dialog (ctx, mode); + } + + replace_with_zero = (src_stat->st_size == 0 + && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT; + + switch (ui->replace_result) + { + case REPLACE_OLDER: + do_refresh (); + if (src_stat->st_mtime > dst_stat->st_mtime) + return replace_with_zero; + else + return FILE_SKIP; + + case REPLACE_SIZE: + do_refresh (); + if (src_stat->st_size == dst_stat->st_size) + return FILE_SKIP; + else + return replace_with_zero; + + case REPLACE_SMALLER: + do_refresh (); + if (src_stat->st_size > dst_stat->st_size) + return FILE_CONT; + else + return FILE_SKIP; + + case REPLACE_ALL: + do_refresh (); + return replace_with_zero; + + case REPLACE_REGET: + /* Careful: we fall through and set do_append */ + ctx->do_reget = dst_stat->st_size; + MC_FALLTHROUGH; + + case REPLACE_APPEND: + ctx->do_append = TRUE; + MC_FALLTHROUGH; + + case REPLACE_YES: + do_refresh (); + return FILE_CONT; + + case REPLACE_NO: + case REPLACE_NONE: + do_refresh (); + return FILE_SKIP; + + case REPLACE_ABORT: + default: + return FILE_ABORT; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +char * +file_mask_dialog (file_op_context_t * ctx, FileOperation operation, gboolean only_one, + const char *format, const void *text, const char *def_text, gboolean * do_bg) +{ + size_t fmd_xlen; + vfs_path_t *vpath; + gboolean source_easy_patterns = easy_patterns; + char fmd_buf[BUF_MEDIUM]; + char *dest_dir = NULL; + char *tmp; + char *def_text_secure; + + if (ctx == NULL) + return NULL; + + /* unselect checkbox if target filesystem doesn't support attributes */ + ctx->op_preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text); + ctx->stable_symlinks = FALSE; + *do_bg = FALSE; + + /* filter out a possible password from def_text */ + vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE); + tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD); + vfs_path_free (vpath, TRUE); + + if (source_easy_patterns) + def_text_secure = strutils_glob_escape (tmp); + else + def_text_secure = strutils_regex_escape (tmp); + g_free (tmp); + + if (only_one) + { + int format_len, text_len; + int max_len; + + format_len = str_term_width1 (format); + text_len = str_term_width1 (text); + max_len = COLS - 2 - 6; + + if (format_len + text_len <= max_len) + { + fmd_xlen = format_len + text_len + 6; + fmd_xlen = MAX (fmd_xlen, 68); + } + else + { + text = str_trunc ((const char *) text, max_len - format_len); + fmd_xlen = max_len + 6; + } + + g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text); + } + else + { + fmd_xlen = COLS * 2 / 3; + fmd_xlen = MAX (fmd_xlen, 68); + g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text); + } + + { + char *source_mask = NULL; + char *orig_mask; + int val; + struct stat buf; + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$", + "input-def", &source_mask, NULL, FALSE, FALSE, + INPUT_COMPLETE_FILENAMES), + QUICK_START_COLUMNS, + QUICK_SEPARATOR (FALSE), + QUICK_NEXT_COLUMN, + QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL), + QUICK_STOP_COLUMNS, + QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir, + NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES), + QUICK_SEPARATOR (TRUE), + QUICK_START_COLUMNS, + QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL), + QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL), + QUICK_NEXT_COLUMN, + QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL), + QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL), + QUICK_STOP_COLUMNS, + QUICK_START_BUTTONS (TRUE, TRUE), + QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL), +#ifdef ENABLE_BACKGROUND + QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL), +#endif /* ENABLE_BACKGROUND */ + QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL), + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, fmd_xlen }; + + quick_dialog_t qdlg = { + r, op_names[operation], "[Mask Copy/Rename]", + quick_widgets, NULL, NULL + }; + + while (TRUE) + { + val = quick_dialog_skip (&qdlg, 4); + + if (val == B_CANCEL) + { + g_free (def_text_secure); + return NULL; + } + + ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat; + + if (ctx->op_preserve) + { + ctx->preserve = TRUE; + ctx->umask_kill = 0777777; + ctx->preserve_uidgid = (geteuid () == 0); + } + else + { + mode_t i2; + + ctx->preserve = ctx->preserve_uidgid = FALSE; + i2 = umask (0); + umask (i2); + ctx->umask_kill = i2 ^ 0777777; + } + + if (*dest_dir == '\0') + { + g_free (def_text_secure); + g_free (source_mask); + g_free (dest_dir); + return NULL; + } + + ctx->search_handle = mc_search_new (source_mask, NULL); + if (ctx->search_handle != NULL) + break; + + message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask); + MC_PTR_FREE (dest_dir); + MC_PTR_FREE (source_mask); + } + + g_free (def_text_secure); + g_free (source_mask); + + ctx->search_handle->is_case_sensitive = TRUE; + if (source_easy_patterns) + ctx->search_handle->search_type = MC_SEARCH_T_GLOB; + else + ctx->search_handle->search_type = MC_SEARCH_T_REGEX; + + tmp = dest_dir; + dest_dir = tilde_expand (tmp); + g_free (tmp); + vpath = vfs_path_from_str (dest_dir); + + ctx->dest_mask = strrchr (dest_dir, PATH_SEP); + if (ctx->dest_mask == NULL) + ctx->dest_mask = dest_dir; + else + ctx->dest_mask++; + + orig_mask = ctx->dest_mask; + + if (*ctx->dest_mask == '\0' + || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask) + && (!only_one + || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))) + || (ctx->dive_into_subdirs + && ((!only_one && !is_wildcarded (ctx->dest_mask)) + || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))) + ctx->dest_mask = g_strdup ("\\0"); + else + { + ctx->dest_mask = g_strdup (ctx->dest_mask); + *orig_mask = '\0'; + } + + if (*dest_dir == '\0') + { + g_free (dest_dir); + dest_dir = g_strdup ("./"); + } + + vfs_path_free (vpath, TRUE); + + if (val == B_USER) + *do_bg = TRUE; + } + + return dest_dir; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/filegui.h b/src/filemanager/filegui.h new file mode 100644 index 0000000..3fe6fe7 --- /dev/null +++ b/src/filemanager/filegui.h @@ -0,0 +1,41 @@ +/** \file filegui.h + * \brief Header: file management GUI for the text mode edition + */ + +#ifndef MC__FILEGUI_H +#define MC__FILEGUI_H + +#include "lib/global.h" +#include "fileopctx.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 file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta, + filegui_dialog_type_t dialog_type); +void file_op_context_destroy_ui (file_op_context_t * ctx); + +char *file_mask_dialog (file_op_context_t * ctx, FileOperation operation, gboolean only_one, + const char *format, const void *text, const char *def_text, + gboolean * do_bg); + +FileProgressStatus check_progress_buttons (file_op_context_t * ctx); + +void file_progress_show (file_op_context_t * ctx, off_t done, off_t total, + const char *stalled_msg, gboolean force_update); +void file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total); +void file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx, + uintmax_t copied_bytes, gboolean show_summary); +void file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath); +void file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath); +gboolean file_progress_show_deleting (file_op_context_t * ctx, const char *path, size_t * count); + +/*** inline functions ****************************************************************************/ +#endif /* MC__FILEGUI_H */ diff --git a/src/filemanager/filemanager.c b/src/filemanager/filemanager.c new file mode 100644 index 0000000..46de006 --- /dev/null +++ b/src/filemanager/filemanager.c @@ -0,0 +1,1860 @@ +/* + Main dialog (file panels) of the Midnight Commander + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Miguel de Icaza, 1994, 1995, 1996, 1997 + Janne Kukonlehto, 1994, 1995 + Norbert Warmuth, 1997 + Andrew Borodin <aborodin@vmail.ru>, 2009-2022 + 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 filemanager.c + * \brief Source: main dialog (file panels) of the Midnight Commander + */ + +#include <config.h> + +#include <ctype.h> +#include <errno.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <pwd.h> /* for username in xterm title */ + +#include "lib/global.h" +#include "lib/fileloc.h" /* MC_HINT */ + +#include "lib/tty/tty.h" +#include "lib/tty/key.h" /* KEY_M_* masks */ +#include "lib/skin.h" +#include "lib/util.h" + +#include "lib/vfs/vfs.h" + +#include "src/args.h" +#ifdef ENABLE_SUBSHELL +#include "src/subshell/subshell.h" +#endif +#include "src/execute.h" /* toggle_subshell */ +#include "src/setup.h" /* variables */ +#include "src/learn.h" /* learn_keys() */ +#include "src/keymap.h" +#include "lib/fileloc.h" /* MC_FILEPOS_FILE */ +#include "lib/keybind.h" +#include "lib/event.h" + +#include "tree.h" +#include "boxes.h" /* sort_box(), tree_box() */ +#include "layout.h" +#include "cmd.h" /* commands */ +#include "hotlist.h" +#include "panelize.h" +#include "command.h" /* cmdline */ +#include "dir.h" /* dir_list_clean() */ + +#ifdef USE_INTERNAL_EDIT +#include "src/editor/edit.h" +#endif + +#ifdef USE_DIFF_VIEW +#include "src/diffviewer/ydiff.h" +#endif + +#include "src/consaver/cons.saver.h" /* show_console_contents */ +#include "src/file_history.h" /* show_file_history() */ + +#include "filemanager.h" + +/*** global variables ****************************************************************************/ + +/* When the modes are active, left_panel, right_panel and tree_panel */ +/* point to a proper data structure. You should check with the functions */ +/* get_current_type and get_other_type the types of the panels before using */ +/* this pointer variables */ + +/* The structures for the panels */ +WPanel *left_panel = NULL; +WPanel *right_panel = NULL; +/* Pointer to the selected and unselected panel */ +WPanel *current_panel = NULL; + +/* The Menubar */ +WMenuBar *the_menubar = NULL; +/* The widget where we draw the prompt */ +WLabel *the_prompt; +/* The hint bar */ +WLabel *the_hint; +/* The button bar */ +WButtonBar *the_bar; + +/* The prompt */ +const char *mc_prompt = NULL; + +/*** file scope macro definitions ****************************************************************/ + +#ifdef HAVE_CHARSET +/* + * Don't restrict the output on the screen manager level, + * the translation tables take care of it. + */ +#endif /* !HAVE_CHARSET */ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static menu_t *left_menu, *right_menu; + +/*** file scope functions ************************************************************************/ + +/** Stop MC main dialog and the current dialog if it exists. + * Needed to provide fast exit from MC viewer or editor on shell exit */ +static void +stop_dialogs (void) +{ + dlg_stop (filemanager); + + if (top_dlg != NULL) + dlg_stop (DIALOG (top_dlg->data)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +treebox_cmd (void) +{ + char *sel_dir; + + sel_dir = tree_box (selection (current_panel)->fname->str); + if (sel_dir != NULL) + { + vfs_path_t *sel_vdir; + + sel_vdir = vfs_path_from_str (sel_dir); + panel_cd (current_panel, sel_vdir, cd_exact); + vfs_path_free (sel_vdir, TRUE); + g_free (sel_dir); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef LISTMODE_EDITOR +static void +listmode_cmd (void) +{ + char *newmode; + + if (get_current_type () != view_listing) + return; + + newmode = listmode_edit (current_panel->user_format); + if (!newmode) + return; + + g_free (current_panel->user_format); + current_panel->list_format = list_user; + current_panel->user_format = newmode; + set_panel_formats (current_panel); + + do_refresh (); +} +#endif /* LISTMODE_EDITOR */ + +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_panel_menu (void) +{ + GList *entries = NULL; + + entries = g_list_prepend (entries, menu_entry_create (_("File listin&g"), CK_PanelListing)); + entries = g_list_prepend (entries, menu_entry_create (_("&Quick view"), CK_PanelQuickView)); + entries = g_list_prepend (entries, menu_entry_create (_("&Info"), CK_PanelInfo)); + entries = g_list_prepend (entries, menu_entry_create (_("&Tree"), CK_PanelTree)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = + g_list_prepend (entries, + menu_entry_create (_("&Listing format..."), CK_SetupListingFormat)); + entries = g_list_prepend (entries, menu_entry_create (_("&Sort order..."), CK_Sort)); + entries = g_list_prepend (entries, menu_entry_create (_("&Filter..."), CK_Filter)); +#ifdef HAVE_CHARSET + entries = g_list_prepend (entries, menu_entry_create (_("&Encoding..."), CK_SelectCodepage)); +#endif + entries = g_list_prepend (entries, menu_separator_create ()); +#ifdef ENABLE_VFS_FTP + entries = g_list_prepend (entries, menu_entry_create (_("FT&P link..."), CK_ConnectFtp)); +#endif +#ifdef ENABLE_VFS_FISH + entries = g_list_prepend (entries, menu_entry_create (_("S&hell link..."), CK_ConnectFish)); +#endif +#ifdef ENABLE_VFS_SFTP + entries = g_list_prepend (entries, menu_entry_create (_("SFTP li&nk..."), CK_ConnectSftp)); +#endif + entries = g_list_prepend (entries, menu_entry_create (_("Paneli&ze"), CK_Panelize)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Rescan"), CK_Reread)); + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_file_menu (void) +{ + GList *entries = NULL; + + entries = g_list_prepend (entries, menu_entry_create (_("&View"), CK_View)); + entries = g_list_prepend (entries, menu_entry_create (_("Vie&w file..."), CK_ViewFile)); + entries = g_list_prepend (entries, menu_entry_create (_("&Filtered view"), CK_ViewFiltered)); + entries = g_list_prepend (entries, menu_entry_create (_("&Edit"), CK_Edit)); + entries = g_list_prepend (entries, menu_entry_create (_("&Copy"), CK_Copy)); + entries = g_list_prepend (entries, menu_entry_create (_("C&hmod"), CK_ChangeMode)); + entries = g_list_prepend (entries, menu_entry_create (_("&Link"), CK_Link)); + entries = g_list_prepend (entries, menu_entry_create (_("&Symlink"), CK_LinkSymbolic)); + entries = + g_list_prepend (entries, + menu_entry_create (_("Relative symlin&k"), CK_LinkSymbolicRelative)); + entries = g_list_prepend (entries, menu_entry_create (_("Edit s&ymlink"), CK_LinkSymbolicEdit)); + entries = g_list_prepend (entries, menu_entry_create (_("Ch&own"), CK_ChangeOwn)); + entries = + g_list_prepend (entries, menu_entry_create (_("&Advanced chown"), CK_ChangeOwnAdvanced)); +#ifdef ENABLE_EXT2FS_ATTR + entries = g_list_prepend (entries, menu_entry_create (_("Cha&ttr"), CK_ChangeAttributes)); +#endif + entries = g_list_prepend (entries, menu_entry_create (_("&Rename/Move"), CK_Move)); + entries = g_list_prepend (entries, menu_entry_create (_("&Mkdir"), CK_MakeDir)); + entries = g_list_prepend (entries, menu_entry_create (_("&Delete"), CK_Delete)); + entries = g_list_prepend (entries, menu_entry_create (_("&Quick cd"), CK_CdQuick)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("Select &group"), CK_Select)); + entries = g_list_prepend (entries, menu_entry_create (_("U&nselect group"), CK_Unselect)); + entries = g_list_prepend (entries, menu_entry_create (_("&Invert selection"), CK_SelectInvert)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("E&xit"), CK_Quit)); + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_command_menu (void) +{ + /* I know, I'm lazy, but the tree widget when it's not running + * as a panel still has some problems, I have not yet finished + * the WTree widget port, sorry. + */ + GList *entries = NULL; + + entries = g_list_prepend (entries, menu_entry_create (_("&User menu"), CK_UserMenu)); + entries = g_list_prepend (entries, menu_entry_create (_("&Directory tree"), CK_Tree)); + entries = g_list_prepend (entries, menu_entry_create (_("&Find file"), CK_Find)); + entries = g_list_prepend (entries, menu_entry_create (_("S&wap panels"), CK_Swap)); + entries = g_list_prepend (entries, menu_entry_create (_("Switch &panels on/off"), CK_Shell)); + entries = + g_list_prepend (entries, menu_entry_create (_("&Compare directories"), CK_CompareDirs)); +#ifdef USE_DIFF_VIEW + entries = g_list_prepend (entries, menu_entry_create (_("C&ompare files"), CK_CompareFiles)); +#endif + entries = + g_list_prepend (entries, menu_entry_create (_("E&xternal panelize"), CK_ExternalPanelize)); + entries = g_list_prepend (entries, menu_entry_create (_("Show directory s&izes"), CK_DirSize)); + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("Command &history"), CK_History)); + entries = + g_list_prepend (entries, + menu_entry_create (_("Viewed/edited files hi&story"), + CK_EditorViewerHistory)); + entries = g_list_prepend (entries, menu_entry_create (_("Di&rectory hotlist"), CK_HotList)); +#ifdef ENABLE_VFS + entries = g_list_prepend (entries, menu_entry_create (_("&Active VFS list"), CK_VfsList)); +#endif +#ifdef ENABLE_BACKGROUND + entries = g_list_prepend (entries, menu_entry_create (_("&Background jobs"), CK_Jobs)); +#endif + entries = g_list_prepend (entries, menu_entry_create (_("Screen lis&t"), CK_ScreenList)); + entries = g_list_prepend (entries, menu_separator_create ()); +#ifdef ENABLE_VFS_UNDELFS + entries = + g_list_prepend (entries, + menu_entry_create (_("&Undelete files (ext2fs only)"), CK_Undelete)); +#endif +#ifdef LISTMODE_EDITOR + entries = g_list_prepend (entries, menu_entry_create (_("&Listing format edit"), CK_ListMode)); +#endif +#if defined (ENABLE_VFS_UNDELFS) || defined (LISTMODE_EDITOR) + entries = g_list_prepend (entries, menu_separator_create ()); +#endif + entries = + g_list_prepend (entries, + menu_entry_create (_("Edit &extension file"), CK_EditExtensionsFile)); + entries = g_list_prepend (entries, menu_entry_create (_("Edit &menu file"), CK_EditUserMenu)); + entries = + g_list_prepend (entries, + menu_entry_create (_("Edit hi&ghlighting group file"), + CK_EditFileHighlightFile)); + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static GList * +create_options_menu (void) +{ + GList *entries = NULL; + + entries = g_list_prepend (entries, menu_entry_create (_("&Configuration..."), CK_Options)); + entries = g_list_prepend (entries, menu_entry_create (_("&Layout..."), CK_OptionsLayout)); + entries = g_list_prepend (entries, menu_entry_create (_("&Panel options..."), CK_OptionsPanel)); + entries = + g_list_prepend (entries, menu_entry_create (_("C&onfirmation..."), CK_OptionsConfirm)); + entries = + g_list_prepend (entries, menu_entry_create (_("&Appearance..."), CK_OptionsAppearance)); + entries = + g_list_prepend (entries, menu_entry_create (_("&Display bits..."), CK_OptionsDisplayBits)); + entries = g_list_prepend (entries, menu_entry_create (_("Learn &keys..."), CK_LearnKeys)); +#ifdef ENABLE_VFS + entries = g_list_prepend (entries, menu_entry_create (_("&Virtual FS..."), CK_OptionsVfs)); +#endif + entries = g_list_prepend (entries, menu_separator_create ()); + entries = g_list_prepend (entries, menu_entry_create (_("&Save setup"), CK_SaveSetup)); + + return g_list_reverse (entries); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +init_menu (void) +{ + left_menu = create_menu ("", create_panel_menu (), "[Left and Right Menus]"); + menubar_add_menu (the_menubar, left_menu); + menubar_add_menu (the_menubar, create_menu (_("&File"), create_file_menu (), "[File Menu]")); + menubar_add_menu (the_menubar, + create_menu (_("&Command"), create_command_menu (), "[Command Menu]")); + menubar_add_menu (the_menubar, + create_menu (_("&Options"), create_options_menu (), "[Options Menu]")); + right_menu = create_menu ("", create_panel_menu (), "[Left and Right Menus]"); + menubar_add_menu (the_menubar, right_menu); + update_menu (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +menu_last_selected_cmd (void) +{ + menubar_activate (the_menubar, drop_menus, -1); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +menu_cmd (void) +{ + int selected; + + if ((get_current_index () == 0) == current_panel->active) + selected = 0; + else + selected = g_list_length (the_menubar->menu) - 1; + + menubar_activate (the_menubar, drop_menus, selected); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +sort_cmd (void) +{ + WPanel *p; + const panel_field_t *sort_order; + + if (!SELECTED_IS_PANEL) + return; + + p = MENU_PANEL; + sort_order = sort_box (&p->sort_info, p->sort_field); + panel_set_sort_order (p, sort_order); +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +midnight_get_shortcut (long command) +{ + const char *ext_map; + const char *shortcut = NULL; + + shortcut = keybind_lookup_keymap_shortcut (filemanager_map, command); + if (shortcut != NULL) + return g_strdup (shortcut); + + shortcut = keybind_lookup_keymap_shortcut (panel_map, command); + if (shortcut != NULL) + return g_strdup (shortcut); + + ext_map = keybind_lookup_keymap_shortcut (filemanager_map, CK_ExtendedKeyMap); + if (ext_map != NULL) + shortcut = keybind_lookup_keymap_shortcut (filemanager_x_map, command); + if (shortcut != NULL) + return g_strdup_printf ("%s %s", ext_map, shortcut); + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +midnight_get_title (const WDialog * h, size_t len) +{ + char *path; + char *login; + char *p; + + (void) h; + + title_path_prepare (&path, &login); + + p = g_strdup_printf ("%s [%s]:%s", _("Panels:"), login, path); + g_free (path); + g_free (login); + path = g_strdup (str_trunc (p, len - 4)); + g_free (p); + + return path; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +toggle_panels_split (void) +{ + panels_layout.horizontal_split = !panels_layout.horizontal_split; + layout_change (); + do_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS +/* event helper */ +static gboolean +check_panel_timestamp (const WPanel * panel, panel_view_mode_t mode, struct vfs_class *vclass, + vfsid id) +{ + if (mode == view_listing) + { + const vfs_path_element_t *path_element; + + path_element = vfs_path_get_by_index (panel->cwd_vpath, -1); + + if (path_element->class != vclass) + return FALSE; + + if (vfs_getid (panel->cwd_vpath) != id) + return FALSE; + } + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* event callback */ +static gboolean +check_current_panel_timestamp (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + ev_vfs_stamp_create_t *event_data = (ev_vfs_stamp_create_t *) data; + + (void) event_group_name; + (void) event_name; + (void) init_data; + + event_data->ret = + check_panel_timestamp (current_panel, get_current_type (), event_data->vclass, + event_data->id); + return !event_data->ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* event callback */ +static gboolean +check_other_panel_timestamp (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + ev_vfs_stamp_create_t *event_data = (ev_vfs_stamp_create_t *) data; + + (void) event_group_name; + (void) event_name; + (void) init_data; + + event_data->ret = + check_panel_timestamp (other_panel, get_other_type (), event_data->vclass, event_data->id); + return !event_data->ret; +} +#endif /* ENABLE_VFS */ + +/* --------------------------------------------------------------------------------------------- */ + +/* event callback */ +static gboolean +print_vfs_message (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + ev_vfs_print_message_t *event_data = (ev_vfs_print_message_t *) data; + + (void) event_group_name; + (void) event_name; + (void) init_data; + + if (mc_global.midnight_shutdown) + goto ret; + + if (!mc_global.message_visible || the_hint == NULL || WIDGET (the_hint)->owner == NULL) + { + int col, row; + + if (!nice_rotating_dash || (ok_to_refresh <= 0)) + goto ret; + + /* Preserve current cursor position */ + tty_getyx (&row, &col); + + tty_gotoyx (0, 0); + tty_setcolor (NORMAL_COLOR); + tty_print_string (str_fit_to_term (event_data->msg, COLS - 1, J_LEFT)); + + /* Restore cursor position */ + tty_gotoyx (row, col); + mc_refresh (); + goto ret; + } + + if (mc_global.message_visible) + set_hintbar (event_data->msg); + + ret: + MC_PTR_FREE (event_data->msg); + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +create_panels (void) +{ + int current_index, other_index; + panel_view_mode_t current_mode, other_mode; + char *current_dir, *other_dir; + vfs_path_t *original_dir; + + /* + * Following cases from command line are possible: + * 'mc' (no arguments): mc_run_param0 == NULL, mc_run_param1 == NULL + * active panel uses current directory + * passive panel uses "other_dir" from panels.ini + * + * 'mc dir1 dir2' (two arguments): mc_run_param0 != NULL, mc_run_param1 != NULL + * active panel uses mc_run_param0 + * passive panel uses mc_run_param1 + * + * 'mc dir1' (single argument): mc_run_param0 != NULL, mc_run_param1 == NULL + * active panel uses mc_run_param0 + * passive panel uses "other_dir" from panels.ini + */ + + /* Set up panel directories */ + if (boot_current_is_left) + { + /* left panel is active */ + current_index = 0; + other_index = 1; + current_mode = startup_left_mode; + other_mode = startup_right_mode; + + if (mc_run_param0 == NULL && mc_run_param1 == NULL) + { + /* no arguments */ + current_dir = NULL; /* assume current dir */ + other_dir = saved_other_dir; /* from ini */ + } + else if (mc_run_param0 != NULL && mc_run_param1 != NULL) + { + /* two arguments */ + current_dir = (char *) mc_run_param0; + other_dir = mc_run_param1; + } + else /* mc_run_param0 != NULL && mc_run_param1 == NULL */ + { + /* one argument */ + current_dir = (char *) mc_run_param0; + other_dir = saved_other_dir; /* from ini */ + } + } + else + { + /* right panel is active */ + current_index = 1; + other_index = 0; + current_mode = startup_right_mode; + other_mode = startup_left_mode; + + if (mc_run_param0 == NULL && mc_run_param1 == NULL) + { + /* no arguments */ + current_dir = NULL; /* assume current dir */ + other_dir = saved_other_dir; /* from ini */ + } + else if (mc_run_param0 != NULL && mc_run_param1 != NULL) + { + /* two arguments */ + current_dir = (char *) mc_run_param0; + other_dir = mc_run_param1; + } + else /* mc_run_param0 != NULL && mc_run_param1 == NULL */ + { + /* one argument */ + current_dir = (char *) mc_run_param0; + other_dir = saved_other_dir; /* from ini */ + } + } + + /* 1. Get current dir */ + original_dir = vfs_path_clone (vfs_get_raw_current_dir ()); + + /* 2. Create passive panel */ + if (other_dir != NULL) + { + vfs_path_t *vpath; + + if (g_path_is_absolute (other_dir)) + vpath = vfs_path_from_str (other_dir); + else + vpath = vfs_path_append_new (original_dir, other_dir, (char *) NULL); + mc_chdir (vpath); + vfs_path_free (vpath, TRUE); + } + create_panel (other_index, other_mode); + + /* 3. Create active panel */ + if (current_dir == NULL) + mc_chdir (original_dir); + else + { + vfs_path_t *vpath; + + if (g_path_is_absolute (current_dir)) + vpath = vfs_path_from_str (current_dir); + else + vpath = vfs_path_append_new (original_dir, current_dir, (char *) NULL); + mc_chdir (vpath); + vfs_path_free (vpath, TRUE); + } + create_panel (current_index, current_mode); + + if (startup_left_mode == view_listing) + current_panel = left_panel; + else if (right_panel != NULL) + current_panel = right_panel; + else + current_panel = left_panel; + + vfs_path_free (original_dir, TRUE); + +#ifdef ENABLE_VFS + mc_event_add (MCEVENT_GROUP_CORE, "vfs_timestamp", check_other_panel_timestamp, NULL, NULL); + mc_event_add (MCEVENT_GROUP_CORE, "vfs_timestamp", check_current_panel_timestamp, NULL, NULL); +#endif /* ENABLE_VFS */ + + mc_event_add (MCEVENT_GROUP_CORE, "vfs_print_message", print_vfs_message, NULL, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +midnight_put_panel_path (WPanel * panel) +{ + vfs_path_t *cwd_vpath; + const char *cwd_vpath_str; + + if (!command_prompt) + return; + +#ifdef HAVE_CHARSET + cwd_vpath = remove_encoding_from_path (panel->cwd_vpath); +#else + cwd_vpath = vfs_path_clone (panel->cwd_vpath); +#endif + + cwd_vpath_str = vfs_path_as_str (cwd_vpath); + + command_insert (cmdline, cwd_vpath_str, FALSE); + + if (!IS_PATH_SEP (cwd_vpath_str[strlen (cwd_vpath_str) - 1])) + command_insert (cmdline, PATH_SEP_STR, FALSE); + + vfs_path_free (cwd_vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +put_link (WPanel * panel) +{ + if (!command_prompt) + return; + if (S_ISLNK (selection (panel)->st.st_mode)) + { + char buffer[MC_MAXPATHLEN]; + vfs_path_t *vpath; + int i; + + vpath = + vfs_path_append_new (panel->cwd_vpath, selection (panel)->fname->str, (char *) NULL); + i = mc_readlink (vpath, buffer, sizeof (buffer) - 1); + vfs_path_free (vpath, TRUE); + + if (i > 0) + { + buffer[i] = '\0'; + command_insert (cmdline, buffer, TRUE); + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +put_current_link (void) +{ + put_link (current_panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +put_other_link (void) +{ + if (get_other_type () == view_listing) + put_link (other_panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Insert the selected file name into the input line */ +static void +put_current_selected (void) +{ + const char *tmp; + + if (!command_prompt) + return; + + if (get_current_type () == view_tree) + { + WTree *tree; + const vfs_path_t *selected_name; + + tree = (WTree *) get_panel_widget (get_current_index ()); + selected_name = tree_selected_name (tree); + tmp = vfs_path_as_str (selected_name); + } + else + tmp = selection (current_panel)->fname->str; + + command_insert (cmdline, tmp, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +put_tagged (WPanel * panel) +{ + if (!command_prompt) + return; + input_disable_update (cmdline); + if (panel->marked) + { + int i; + + for (i = 0; i < panel->dir.len; i++) + { + if (panel->dir.list[i].f.marked) + command_insert (cmdline, panel->dir.list[i].fname->str, TRUE); + } + } + else + { + command_insert (cmdline, panel->dir.list[panel->selected].fname->str, TRUE); + } + input_enable_update (cmdline); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +put_current_tagged (void) +{ + put_tagged (current_panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +put_other_tagged (void) +{ + if (get_other_type () == view_listing) + put_tagged (other_panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +setup_mc (void) +{ +#ifdef HAVE_SLANG +#ifdef HAVE_CHARSET + tty_display_8bit (TRUE); +#else + tty_display_8bit (mc_global.full_eight_bits); +#endif /* HAVE_CHARSET */ + +#else /* HAVE_SLANG */ + +#ifdef HAVE_CHARSET + tty_display_8bit (TRUE); +#else + tty_display_8bit (mc_global.eight_bit_clean); +#endif /* HAVE_CHARSET */ +#endif /* HAVE_SLANG */ + + if ((tty_baudrate () < 9600) || mc_global.tty.slow_terminal) + verbose = FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +setup_dummy_mc (void) +{ + vfs_path_t *vpath; + char *d; + int ret; + + d = _vfs_get_cwd (); + setup_mc (); + vpath = vfs_path_from_str (d); + ret = mc_chdir (vpath); + (void) ret; + vfs_path_free (vpath, TRUE); + g_free (d); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +done_mc (void) +{ + /* Setup shutdown + * + * We sync the profiles since the hotlist may have changed, while + * we only change the setup data if we have the auto save feature set + */ + + save_setup (auto_save_setup, panels_options.auto_save_setup); + + vfs_stamp_path (vfs_get_raw_current_dir ()); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +create_file_manager (void) +{ + Widget *w = WIDGET (filemanager); + WGroup *g = GROUP (filemanager); + + w->keymap = filemanager_map; + w->ext_keymap = filemanager_x_map; + + filemanager->get_shortcut = midnight_get_shortcut; + filemanager->get_title = midnight_get_title; + /* allow rebind tab */ + widget_want_tab (w, TRUE); + + the_menubar = menubar_new (NULL); + group_add_widget (g, the_menubar); + init_menu (); + + create_panels (); + group_add_widget (g, get_panel_widget (0)); + group_add_widget (g, get_panel_widget (1)); + + the_hint = label_new (0, 0, 0); + the_hint->transparent = TRUE; + the_hint->auto_adjust_cols = 0; + WIDGET (the_hint)->rect.cols = COLS; + group_add_widget (g, the_hint); + + cmdline = command_new (0, 0, 0); + group_add_widget (g, cmdline); + + the_prompt = label_new (0, 0, mc_prompt); + the_prompt->transparent = TRUE; + group_add_widget (g, the_prompt); + + the_bar = buttonbar_new (); + group_add_widget (g, the_bar); + midnight_set_buttonbar (the_bar); + +#ifdef ENABLE_SUBSHELL + /* Must be done after creation of cmdline and promt widgets to avoid potential + NULL dereference in load_prompt() -> ... -> setup_cmdline() -> label_set_text(). */ + if (mc_global.tty.use_subshell) + add_select_channel (mc_global.tty.subshell_pty, load_prompt, NULL); +#endif /* !ENABLE_SUBSHELL */ +} + +/* --------------------------------------------------------------------------------------------- */ + +/** result must be free'd (I think this should go in util.c) */ +static vfs_path_t * +prepend_cwd_on_local (const char *filename) +{ + vfs_path_t *vpath; + + vpath = vfs_path_from_str (filename); + if (!vfs_file_is_local (vpath) || g_path_is_absolute (filename)) + return vpath; + + vfs_path_free (vpath, TRUE); + + return vfs_path_append_new (vfs_get_raw_current_dir (), filename, (char *) NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Invoke the internal view/edit routine with: + * the default processing and forcing the internal viewer/editor + */ +static gboolean +mc_maybe_editor_or_viewer (void) +{ + gboolean ret; + + switch (mc_global.mc_run_mode) + { +#ifdef USE_INTERNAL_EDIT + case MC_RUN_EDITOR: + ret = edit_files ((GList *) mc_run_param0); + break; +#endif /* USE_INTERNAL_EDIT */ + case MC_RUN_VIEWER: + { + vfs_path_t *vpath = NULL; + + if (mc_run_param0 != NULL && *(char *) mc_run_param0 != '\0') + vpath = prepend_cwd_on_local ((char *) mc_run_param0); + + ret = view_file (vpath, FALSE, TRUE); + vfs_path_free (vpath, TRUE); + break; + } +#ifdef USE_DIFF_VIEW + case MC_RUN_DIFFVIEWER: + ret = dview_diff_cmd (mc_run_param0, mc_run_param1); + break; +#endif /* USE_DIFF_VIEW */ + default: + ret = FALSE; + } + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +show_editor_viewer_history (void) +{ + char *s; + int act; + + s = show_file_history (WIDGET (filemanager), &act); + if (s != NULL) + { + vfs_path_t *s_vpath; + + switch (act) + { + case CK_Edit: + s_vpath = vfs_path_from_str (s); + edit_file_at_line (s_vpath, use_internal_edit, 0); + break; + + case CK_View: + s_vpath = vfs_path_from_str (s); + view_file (s_vpath, use_internal_view, FALSE); + break; + + default: + { + char *d; + + d = g_path_get_dirname (s); + s_vpath = vfs_path_from_str (d); + panel_cd (current_panel, s_vpath, cd_exact); + try_to_select (current_panel, s); + g_free (d); + } + } + + g_free (s); + vfs_path_free (s_vpath, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +quit_cmd_internal (int quiet) +{ + int q = quit; + size_t n; + + n = dialog_switch_num () - 1; + if (n != 0) + { + char msg[BUF_MEDIUM]; + + g_snprintf (msg, sizeof (msg), + ngettext ("You have %zu opened screen. Quit anyway?", + "You have %zu opened screens. Quit anyway?", n), n); + + if (query_dialog (_("The Midnight Commander"), msg, D_NORMAL, 2, _("&Yes"), _("&No")) != 0) + return FALSE; + q = 1; + } + else if (quiet || !confirm_exit) + q = 1; + else if (query_dialog (_("The Midnight Commander"), + _("Do you really want to quit the Midnight Commander?"), + D_NORMAL, 2, _("&Yes"), _("&No")) == 0) + q = 1; + + if (q != 0) + { +#ifdef ENABLE_SUBSHELL + if (!mc_global.tty.use_subshell) + stop_dialogs (); + else if ((q = exit_subshell ()? 1 : 0) != 0) +#endif + stop_dialogs (); + } + + if (q != 0) + quit |= 1; + return (quit != 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +quit_cmd (void) +{ + return quit_cmd_internal (0); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Repaint the contents of the panels without frames. To schedule panel + * for repainting, set panel->dirty to TRUE. There are many reasons why + * the panels need to be repainted, and this is a costly operation, so + * it's done once per event. + */ + +static void +update_dirty_panels (void) +{ + if (get_current_type () == view_listing && current_panel->dirty) + widget_draw (WIDGET (current_panel)); + + if (get_other_type () == view_listing && other_panel->dirty) + widget_draw (WIDGET (other_panel)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +toggle_show_hidden (void) +{ + panels_options.show_dot_files = !panels_options.show_dot_files; + update_panels (UP_RELOAD, UP_KEEPSEL); + /* redraw panels forced */ + update_dirty_panels (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +midnight_execute_cmd (Widget * sender, long command) +{ + cb_ret_t res = MSG_HANDLED; + + (void) sender; + + /* stop quick search before executing any command */ + send_message (current_panel, NULL, MSG_ACTION, CK_SearchStop, NULL); + + switch (command) + { + case CK_ChangePanel: + (void) change_panel (); + break; + case CK_HotListAdd: + add2hotlist_cmd (current_panel); + break; + case CK_SetupListingFormat: + setup_listing_format_cmd (); + break; + case CK_ChangeMode: + chmod_cmd (current_panel); + break; + case CK_ChangeOwn: + chown_cmd (current_panel); + break; + case CK_ChangeOwnAdvanced: + advanced_chown_cmd (current_panel); + break; +#ifdef ENABLE_EXT2FS_ATTR + case CK_ChangeAttributes: + chattr_cmd (current_panel); + break; +#endif + case CK_CompareDirs: + compare_dirs_cmd (); + break; + case CK_Options: + configure_box (); + break; +#ifdef ENABLE_VFS + case CK_OptionsVfs: + configure_vfs_box (); + break; +#endif + case CK_OptionsConfirm: + confirm_box (); + break; + case CK_Copy: + copy_cmd (current_panel); + break; + case CK_PutCurrentPath: + midnight_put_panel_path (current_panel); + break; + case CK_PutCurrentSelected: + put_current_selected (); + break; + case CK_PutCurrentFullSelected: + midnight_put_panel_path (current_panel); + put_current_selected (); + break; + case CK_PutCurrentLink: + put_current_link (); + break; + case CK_PutCurrentTagged: + put_current_tagged (); + break; + case CK_PutOtherPath: + if (get_other_type () == view_listing) + midnight_put_panel_path (other_panel); + break; + case CK_PutOtherLink: + put_other_link (); + break; + case CK_PutOtherTagged: + put_other_tagged (); + break; + case CK_Delete: + delete_cmd (current_panel); + break; + case CK_ScreenList: + dialog_switch_list (); + break; +#ifdef USE_DIFF_VIEW + case CK_CompareFiles: + diff_view_cmd (); + break; +#endif + case CK_OptionsDisplayBits: + display_bits_box (); + break; + case CK_Edit: + edit_cmd (current_panel); + break; +#ifdef USE_INTERNAL_EDIT + case CK_EditForceInternal: + edit_cmd_force_internal (current_panel); + break; +#endif + case CK_EditExtensionsFile: + ext_cmd (); + break; + case CK_EditFileHighlightFile: + edit_fhl_cmd (); + break; + case CK_EditUserMenu: + edit_mc_menu_cmd (); + break; + case CK_LinkSymbolicEdit: + edit_symlink_cmd (); + break; + case CK_ExternalPanelize: + external_panelize (); + break; + case CK_ViewFiltered: + view_filtered_cmd (current_panel); + break; + case CK_Find: + find_cmd (current_panel); + break; +#ifdef ENABLE_VFS_FISH + case CK_ConnectFish: + fishlink_cmd (); + break; +#endif +#ifdef ENABLE_VFS_FTP + case CK_ConnectFtp: + ftplink_cmd (); + break; +#endif +#ifdef ENABLE_VFS_SFTP + case CK_ConnectSftp: + sftplink_cmd (); + break; +#endif + case CK_Panelize: + cd_panelize_cmd (); + break; + case CK_Help: + help_cmd (); + break; + case CK_History: + /* show the history of command line widget */ + send_message (cmdline, NULL, MSG_ACTION, CK_History, NULL); + break; + case CK_PanelInfo: + if (sender == WIDGET (the_menubar)) + info_cmd (); /* menu */ + else + info_cmd_no_menu (); /* shortcut or buttonbar */ + break; +#ifdef ENABLE_BACKGROUND + case CK_Jobs: + jobs_box (); + break; +#endif + case CK_OptionsLayout: + layout_box (); + break; + case CK_OptionsAppearance: + appearance_box (); + break; + case CK_LearnKeys: + learn_keys (); + break; + case CK_Link: + link_cmd (LINK_HARDLINK); + break; + case CK_PanelListing: + listing_cmd (); + break; +#ifdef LISTMODE_EDITOR + case CK_ListMode: + listmode_cmd (); + break; +#endif + case CK_Menu: + menu_cmd (); + break; + case CK_MenuLastSelected: + menu_last_selected_cmd (); + break; + case CK_MakeDir: + mkdir_cmd (current_panel); + break; + case CK_OptionsPanel: + panel_options_box (); + break; +#ifdef HAVE_CHARSET + case CK_SelectCodepage: + encoding_cmd (); + break; +#endif + case CK_CdQuick: + quick_cd_cmd (current_panel); + break; + case CK_HotList: + hotlist_cmd (current_panel); + break; + case CK_PanelQuickView: + if (sender == WIDGET (the_menubar)) + quick_view_cmd (); /* menu */ + else + quick_cmd_no_menu (); /* shortcut or buttonabr */ + break; + case CK_QuitQuiet: + quiet_quit_cmd (); + break; + case CK_Quit: + quit_cmd (); + break; + case CK_LinkSymbolicRelative: + link_cmd (LINK_SYMLINK_RELATIVE); + break; + case CK_Move: + rename_cmd (current_panel); + break; + case CK_Reread: + reread_cmd (); + break; +#ifdef ENABLE_VFS + case CK_VfsList: + vfs_list (current_panel); + break; +#endif + case CK_SaveSetup: + save_setup_cmd (); + break; + case CK_Select: + case CK_Unselect: + case CK_SelectInvert: + case CK_Filter: + res = send_message (current_panel, filemanager, MSG_ACTION, command, NULL); + break; + case CK_Shell: + toggle_subshell (); + break; + case CK_DirSize: + smart_dirsize_cmd (current_panel); + break; + case CK_Sort: + sort_cmd (); + break; + case CK_ExtendedKeyMap: + WIDGET (filemanager)->ext_mode = TRUE; + break; + case CK_Suspend: + mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL); + break; + case CK_Swap: + swap_cmd (); + break; + case CK_LinkSymbolic: + link_cmd (LINK_SYMLINK_ABSOLUTE); + break; + case CK_ShowHidden: + toggle_show_hidden (); + break; + case CK_SplitVertHoriz: + toggle_panels_split (); + break; + case CK_SplitEqual: + panels_split_equal (); + break; + case CK_SplitMore: + panels_split_more (); + break; + case CK_SplitLess: + panels_split_less (); + break; + case CK_PanelTree: + panel_tree_cmd (); + break; + case CK_Tree: + treebox_cmd (); + break; +#ifdef ENABLE_VFS_UNDELFS + case CK_Undelete: + undelete_cmd (); + break; +#endif + case CK_UserMenu: + user_file_menu_cmd (); + break; + case CK_View: + view_cmd (current_panel); + break; + case CK_ViewFile: + view_file_cmd (current_panel); + break; + case CK_EditorViewerHistory: + show_editor_viewer_history (); + break; + case CK_Cancel: + /* don't close panels due to SIGINT */ + break; + default: + res = MSG_NOT_HANDLED; + } + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Whether the command-line should not respond to key events. + * + * This is TRUE if a QuickView or TreeView have the focus, as they're going + * to consume some keys and there's no sense in passing to the command-line + * just the leftovers. + */ +static gboolean +is_cmdline_mute (void) +{ + /* When one of panels is other than view_listing, + current_panel points to view_listing panel all time independently of + it's activity. Thus, we can't use get_current_type() here. + current_panel should point to actualy current active panel + independently of it's type. */ + return (!current_panel->active + && (get_other_type () == view_quick || get_other_type () == view_tree)); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Handles the Enter key on the command-line. + * + * Returns TRUE if non-whitespace was indeed processed. + */ +static gboolean +handle_cmdline_enter (void) +{ + const char *s; + + for (s = input_get_ctext (cmdline); *s != '\0' && whitespace (*s); s++) + ; + + if (*s != '\0') + { + send_message (cmdline, NULL, MSG_KEY, '\n', NULL); + return TRUE; + } + + input_insert (cmdline, "", FALSE); + cmdline->point = 0; + + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +midnight_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + long command; + + switch (msg) + { + case MSG_INIT: + panel_init (); + setup_panels (); + return MSG_HANDLED; + + case MSG_DRAW: + load_hint (TRUE); + group_default_callback (w, NULL, MSG_DRAW, 0, NULL); + /* We handle the special case of the output lines */ + if (mc_global.tty.console_flag != '\0' && output_lines != 0) + { + unsigned char end_line; + + end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1; + show_console_contents (output_start_y, end_line - output_lines, end_line); + } + return MSG_HANDLED; + + case MSG_RESIZE: + widget_adjust_position (w->pos_flags, &w->rect); + setup_panels (); + menubar_arrange (the_menubar); + return MSG_HANDLED; + + case MSG_IDLE: + /* We only need the first idle event to show user menu after start */ + widget_idle (w, FALSE); + + if (boot_current_is_left) + widget_select (get_panel_widget (0)); + else + widget_select (get_panel_widget (1)); + + if (auto_menu) + midnight_execute_cmd (NULL, CK_UserMenu); + return MSG_HANDLED; + + case MSG_KEY: + if (w->ext_mode) + { + command = widget_lookup_key (w, parm); + if (command != CK_IgnoreKey) + return midnight_execute_cmd (NULL, command); + } + + /* FIXME: should handle all menu shortcuts before this point */ + if (widget_get_state (WIDGET (the_menubar), WST_FOCUSED)) + return MSG_NOT_HANDLED; + + if (parm == '\n' && !is_cmdline_mute ()) + { + if (handle_cmdline_enter ()) + return MSG_HANDLED; + /* Else: the panel will handle it. */ + } + + if ((!mc_global.tty.alternate_plus_minus + || !(mc_global.tty.console_flag != '\0' || mc_global.tty.xterm_flag)) && !quote + && !current_panel->quick_search.active) + { + if (!only_leading_plus_minus) + { + /* Special treatement, since the input line will eat them */ + if (parm == '+') + return send_message (current_panel, filemanager, MSG_ACTION, CK_Select, NULL); + + if (parm == '\\' || parm == '-') + return send_message (current_panel, filemanager, MSG_ACTION, CK_Unselect, NULL); + + if (parm == '*') + return send_message (current_panel, filemanager, MSG_ACTION, CK_SelectInvert, + NULL); + } + else if (!command_prompt || input_is_empty (cmdline)) + { + /* Special treatement '+', '-', '\', '*' only when this is + * first char on input line + */ + if (parm == '+') + return send_message (current_panel, filemanager, MSG_ACTION, CK_Select, NULL); + + if (parm == '\\' || parm == '-') + return send_message (current_panel, filemanager, MSG_ACTION, CK_Unselect, NULL); + + if (parm == '*') + return send_message (current_panel, filemanager, MSG_ACTION, CK_SelectInvert, + NULL); + } + } + return MSG_NOT_HANDLED; + + case MSG_HOTKEY_HANDLED: + if ((get_current_type () == view_listing) && current_panel->quick_search.active) + { + current_panel->dirty = TRUE; /* FIXME: unneeded? */ + send_message (current_panel, NULL, MSG_ACTION, CK_SearchStop, NULL); + } + return MSG_HANDLED; + + case MSG_UNHANDLED_KEY: + { + cb_ret_t v = MSG_NOT_HANDLED; + + command = widget_lookup_key (w, parm); + if (command != CK_IgnoreKey) + v = midnight_execute_cmd (NULL, command); + + if (v == MSG_NOT_HANDLED && command_prompt && !is_cmdline_mute ()) + v = send_message (cmdline, NULL, MSG_KEY, parm, NULL); + + return v; + } + + case MSG_POST_KEY: + if (!widget_get_state (WIDGET (the_menubar), WST_FOCUSED)) + update_dirty_panels (); + return MSG_HANDLED; + + case MSG_ACTION: + /* Handle shortcuts, menu, and buttonbar. */ + return midnight_execute_cmd (sender, parm); + + case MSG_DESTROY: + panel_deinit (); + return MSG_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +update_menu (void) +{ + menu_set_name (left_menu, panels_layout.horizontal_split ? _("&Above") : _("&Left")); + menu_set_name (right_menu, panels_layout.horizontal_split ? _("&Below") : _("&Right")); + menubar_arrange (the_menubar); + widget_set_visibility (WIDGET (the_menubar), menubar_visible); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +midnight_set_buttonbar (WButtonBar * b) +{ + Widget *w = WIDGET (filemanager); + + buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, NULL); + buttonbar_set_label (b, 2, Q_ ("ButtonBar|Menu"), w->keymap, NULL); + buttonbar_set_label (b, 3, Q_ ("ButtonBar|View"), w->keymap, NULL); + buttonbar_set_label (b, 4, Q_ ("ButtonBar|Edit"), w->keymap, NULL); + buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, NULL); + buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, NULL); + buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, NULL); + buttonbar_set_label (b, 8, Q_ ("ButtonBar|Delete"), w->keymap, NULL); + buttonbar_set_label (b, 9, Q_ ("ButtonBar|PullDn"), w->keymap, NULL); + buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), w->keymap, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Return a random hint. If force is TRUE, ignore the timeout. + */ + +char * +get_random_hint (gboolean force) +{ + static const gint64 update_period = 60 * G_USEC_PER_SEC; + static gint64 tv = 0; + + char *data, *result, *eop; + size_t len, start; + GIConv conv; + + /* Do not change hints more often than one minute */ + if (!force && !mc_time_elapsed (&tv, update_period)) + return g_strdup (""); + + data = load_mc_home_file (mc_global.share_data_dir, MC_HINT, NULL, &len); + if (data == NULL) + return NULL; + + /* get a random entry */ + srand ((unsigned int) (tv / G_USEC_PER_SEC)); + start = ((size_t) rand ()) % (len - 1); + + /* Search the start of paragraph */ + for (; start != 0; start--) + if (data[start] == '\n' && data[start + 1] == '\n') + { + start += 2; + break; + } + + /* Search the end of paragraph */ + for (eop = data + start; *eop != '\0'; eop++) + { + if (*eop == '\n' && *(eop + 1) == '\n') + { + *eop = '\0'; + break; + } + if (*eop == '\n') + *eop = ' '; + } + + /* hint files are stored in utf-8 */ + /* try convert hint file from utf-8 to terminal encoding */ + conv = str_crt_conv_from ("UTF-8"); + if (conv == INVALID_CONV) + result = g_strndup (data + start, len - start); + else + { + GString *buffer; + gboolean nok; + + buffer = g_string_sized_new (len - start); + nok = (str_convert (conv, data + start, buffer) == ESTR_FAILURE); + result = g_string_free (buffer, nok); + str_close_conv (conv); + } + + g_free (data); + return result; +} + + +/* --------------------------------------------------------------------------------------------- */ +/** + * Load new hint and display it. + * IF force is not 0, ignore the timeout. + */ + +void +load_hint (gboolean force) +{ + char *hint; + + if (WIDGET (the_hint)->owner == NULL) + return; + + if (!mc_global.message_visible) + { + label_set_text (the_hint, NULL); + return; + } + + hint = get_random_hint (force); + + if (hint != NULL) + { + if (*hint != '\0') + set_hintbar (hint); + g_free (hint); + } + else + { + char text[BUF_SMALL]; + + g_snprintf (text, sizeof (text), _("GNU Midnight Commander %s\n"), mc_global.mc_version); + set_hintbar (text); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Change current panel in the file manager. + * + * @return current_panel + */ + +WPanel * +change_panel (void) +{ + input_complete_free (cmdline); + group_select_next_widget (GROUP (filemanager)); + return current_panel; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Save current stat of directories to avoid reloading the panels + * when no modifications have taken place + */ +void +save_cwds_stat (void) +{ + if (panels_options.fast_reload) + { + mc_stat (current_panel->cwd_vpath, &(current_panel->dir_stat)); + if (get_other_type () == view_listing) + mc_stat (other_panel->cwd_vpath, &(other_panel->dir_stat)); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +gboolean +quiet_quit_cmd (void) +{ + print_last_revert = TRUE; + return quit_cmd_internal (1); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Run the main dialog that occupies the whole screen */ +gboolean +do_nc (void) +{ + gboolean ret; + +#ifdef USE_INTERNAL_EDIT + edit_stack_init (); +#endif + + filemanager = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, dialog_colors, + midnight_callback, NULL, "[main]", NULL); + + /* Check if we were invoked as an editor or file viewer */ + if (mc_global.mc_run_mode != MC_RUN_FULL) + { + setup_dummy_mc (); + ret = mc_maybe_editor_or_viewer (); + } + else + { + /* We only need the first idle event to show user menu after start */ + widget_idle (WIDGET (filemanager), TRUE); + + setup_mc (); + mc_filehighlight = mc_fhl_new (TRUE); + + create_file_manager (); + (void) dlg_run (filemanager); + + mc_fhl_free (&mc_filehighlight); + + ret = TRUE; + + /* widget_destroy destroys even current_panel->cwd_vpath, so we have to save a copy :) */ + if (mc_args__last_wd_file != NULL && vfs_current_is_local ()) + last_wd_string = g_strdup (vfs_path_as_str (current_panel->cwd_vpath)); + + /* don't handle VFS timestamps for dirs opened in panels */ + mc_event_destroy (MCEVENT_GROUP_CORE, "vfs_timestamp"); + + dir_list_free_list (&panelized_panel.list); + } + + /* Program end */ + mc_global.midnight_shutdown = TRUE; + dialog_switch_shutdown (); + done_mc (); + widget_destroy (WIDGET (filemanager)); + current_panel = NULL; + +#ifdef USE_INTERNAL_EDIT + edit_stack_free (); +#endif + + if ((quit & SUBSHELL_EXIT) == 0) + tty_clear_screen (); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/filemanager.h b/src/filemanager/filemanager.h new file mode 100644 index 0000000..ca607ce --- /dev/null +++ b/src/filemanager/filemanager.h @@ -0,0 +1,53 @@ +/** \file filemanager.h + * \brief Header: main dialog (file panels) for Midnight Commander + */ + +#ifndef MC__FILEMANAGER_H +#define MC__FILEMANAGER_H + +#include "lib/widget.h" + +#include "panel.h" +#include "layout.h" + +/* TODO: merge content of layout.h here */ + +/*** typedefs(not structures) and defined constants **********************************************/ + +#define MENU_PANEL (mc_global.widget.is_right ? right_panel : left_panel) +#define MENU_PANEL_IDX (mc_global.widget.is_right ? 1 : 0) +#define SELECTED_IS_PANEL (get_panel_type (MENU_PANEL_IDX) == view_listing) + +#define other_panel get_other_panel() + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +extern WMenuBar *the_menubar; +extern WLabel *the_prompt; +extern WLabel *the_hint; +extern WButtonBar *the_bar; + +extern WPanel *left_panel; +extern WPanel *right_panel; +extern WPanel *current_panel; + +extern const char *mc_prompt; + +/*** declarations of public functions ************************************************************/ + +void update_menu (void); +void midnight_set_buttonbar (WButtonBar * b); +char *get_random_hint (gboolean force); +void load_hint (gboolean force); +WPanel *change_panel (void); +void save_cwds_stat (void); +gboolean quiet_quit_cmd (void); +gboolean do_nc (void); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__FILEMANAGER_H */ diff --git a/src/filemanager/filenot.c b/src/filemanager/filenot.c new file mode 100644 index 0000000..75df197 --- /dev/null +++ b/src/filemanager/filenot.c @@ -0,0 +1,147 @@ +/* + Wrapper for routines to notify the + tree about the changes made to the directory + structure. + + Copyright (C) 2011-2022 + Free Software Foundation, Inc. + + Author: + Janne Kukonlehto + Miguel de Icaza + 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 filenot.c + * \brief Source: wrapper for routines to notify the + * tree about the changes made to the directory + * structure. + */ + +#include <config.h> + +#include <errno.h> +#include <string.h> + +#include "lib/global.h" +#include "lib/fs.h" +#include "lib/util.h" +#include "lib/vfs/vfs.h" + +#include "filenot.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static vfs_path_t * +get_absolute_name (const vfs_path_t * vpath) +{ + if (vpath == NULL) + return NULL; + + if (IS_PATH_SEP (*vfs_path_get_by_index (vpath, 0)->path)) + return vfs_path_clone (vpath); + + return vfs_path_append_vpath_new (vfs_get_raw_current_dir (), vpath, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +my_mkdir_rec (const vfs_path_t * vpath, mode_t mode) +{ + vfs_path_t *q; + int result; + + if (mc_mkdir (vpath, mode) == 0) + return 0; + if (errno != ENOENT) + return (-1); + + /* FIXME: should check instead if vpath is at the root of that filesystem */ + if (!vfs_file_is_local (vpath)) + return (-1); + + if (strcmp (vfs_path_as_str (vpath), PATH_SEP_STR) == 0) + { + errno = ENOTDIR; + return (-1); + } + + q = vfs_path_append_new (vpath, "..", (char *) NULL); + result = my_mkdir_rec (q, mode); + vfs_path_free (q, TRUE); + + if (result == 0) + result = mc_mkdir (vpath, mode); + + return result; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +int +my_mkdir (const vfs_path_t * vpath, mode_t mode) +{ + int result; + + result = my_mkdir_rec (vpath, mode); + if (result == 0) + { + vfs_path_t *my_s; + + my_s = get_absolute_name (vpath); + vfs_path_free (my_s, TRUE); + } + return result; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +my_rmdir (const char *path) +{ + int result; + vfs_path_t *vpath; + + vpath = vfs_path_from_str_flags (path, VPF_NO_CANON); + /* FIXME: Should receive a Wtree! */ + result = mc_rmdir (vpath); + if (result == 0) + { + vfs_path_t *my_s; + + my_s = get_absolute_name (vpath); + vfs_path_free (my_s, TRUE); + } + vfs_path_free (vpath, TRUE); + return result; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/filenot.h b/src/filemanager/filenot.h new file mode 100644 index 0000000..33991e8 --- /dev/null +++ b/src/filemanager/filenot.h @@ -0,0 +1,26 @@ +/** \file file.h + * \brief Header: File and directory operation routines + */ + +#ifndef MC__FILENOT_H +#define MC__FILENOT_H + +#include "lib/global.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +/* Misc Unix functions */ +int my_mkdir (const vfs_path_t * vpath, mode_t mode); +int my_rmdir (const char *path); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__FILE_H */ diff --git a/src/filemanager/fileopctx.c b/src/filemanager/fileopctx.c new file mode 100644 index 0000000..c6db22d --- /dev/null +++ b/src/filemanager/fileopctx.c @@ -0,0 +1,128 @@ +/* + File operation contexts for the Midnight Commander + + Copyright (C) 1999-2022 + Free Software Foundation, Inc. + + Written by: + Federico Mena <federico@nuclecu.unam.mx> + Miguel de Icaza <miguel@nuclecu.unam.mx> + + 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 fileopctx.c + * \brief Source: file operation contexts + * \date 1998-2007 + * \author Federico Mena <federico@nuclecu.unam.mx> + * \author Miguel de Icaza <miguel@nuclecu.unam.mx> + */ + +#include <config.h> + +#include <unistd.h> + +#include "lib/global.h" +#include "fileopctx.h" +#include "filegui.h" +#include "lib/search.h" +#include "lib/vfs/vfs.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** + * \fn file_op_context_t * file_op_context_new (FileOperation op) + * \param op file operation struct + * \return The newly-created context, filled with the default file mask values. + * + * Creates a new file operation context with the default values. If you later want + * to have a user interface for this, call file_op_context_create_ui(). + */ + +file_op_context_t * +file_op_context_new (FileOperation op) +{ + file_op_context_t *ctx; + + ctx = g_new0 (file_op_context_t, 1); + ctx->operation = op; + ctx->eta_secs = 0.0; + ctx->progress_bytes = 0; + ctx->op_preserve = TRUE; + ctx->do_reget = 1; + ctx->stat_func = mc_lstat; + ctx->preserve = TRUE; + ctx->preserve_uidgid = (geteuid () == 0); + ctx->umask_kill = 0777777; + ctx->erase_at_end = TRUE; + ctx->skip_all = FALSE; + + return ctx; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * \fn void file_op_context_destroy (file_op_context_t *ctx) + * \param ctx The file operation context to destroy. + * + * Destroys the specified file operation context and its associated UI data, if + * it exists. + */ + +void +file_op_context_destroy (file_op_context_t * ctx) +{ + if (ctx != NULL) + { + file_op_context_destroy_ui (ctx); + mc_search_free (ctx->search_handle); + g_free (ctx); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +file_op_total_context_t * +file_op_total_context_new (void) +{ + file_op_total_context_t *tctx; + tctx = g_new0 (file_op_total_context_t, 1); + tctx->ask_overwrite = TRUE; + return tctx; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +file_op_total_context_destroy (file_op_total_context_t * tctx) +{ + g_free (tctx); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/fileopctx.h b/src/filemanager/fileopctx.h new file mode 100644 index 0000000..348507e --- /dev/null +++ b/src/filemanager/fileopctx.h @@ -0,0 +1,198 @@ +/** \file fileopctx.h + * \brief Header: file operation contexts + * \date 1998 + * \author Federico Mena <federico@nuclecu.unam.mx> + * \author Miguel de Icaza <miguel@nuclecu.unam.mx> + */ + +#ifndef MC__FILEOPCTX_H +#define MC__FILEOPCTX_H + +#include <sys/stat.h> +#include <sys/types.h> +#include <inttypes.h> /* uintmax_t */ + +#include "lib/global.h" +#include "lib/vfs/vfs.h" + + +/*** typedefs(not structures) and defined constants **********************************************/ + +typedef int (*mc_stat_fn) (const vfs_path_t * vpath, struct stat * buf); + +/*** enums ***************************************************************************************/ + +typedef enum +{ + FILEGUI_DIALOG_ONE_ITEM, + FILEGUI_DIALOG_MULTI_ITEM, + FILEGUI_DIALOG_DELETE_ITEM +} filegui_dialog_type_t; + +typedef enum +{ + OP_COPY = 0, + OP_MOVE = 1, + OP_DELETE = 2 +} FileOperation; + +typedef enum +{ + RECURSIVE_YES = 0, + RECURSIVE_NO = 1, + RECURSIVE_ALWAYS = 2, + RECURSIVE_NEVER = 3, + RECURSIVE_ABORT = 4 +} FileCopyMode; + +/* ATTENTION: avoid overlapping with B_* values (lib/widget/dialog.h) */ +typedef enum +{ + FILE_CONT = 10, + FILE_RETRY, + FILE_SKIP, + FILE_ABORT, + FILE_SKIPALL, + FILE_SUSPEND +} FileProgressStatus; + +/* First argument passed to real functions */ +enum OperationMode +{ + Foreground, + Background +}; + +/*** structures declarations (and typedefs of structures)*****************************************/ + +struct mc_search_struct; + +/* This structure describes a context for file operations. It is used to update + * the progress windows and pass around options. + */ +typedef struct +{ + /* Operation type (copy, move, delete) */ + FileOperation operation; + + /* The estimated time of arrival in seconds */ + double eta_secs; + + /* Transferred bytes per second */ + long bps; + + /* Transferred seconds */ + long bps_time; + + /* Whether the panel total has been computed */ + gboolean progress_totals_computed; + filegui_dialog_type_t dialog_type; + + /* Counters for progress indicators */ + size_t progress_count; + uintmax_t progress_bytes; + + /* The value of the "preserve Attributes" checkbox in the copy file dialog. + * We can't use the value of "ctx->preserve" because it can change in order + * to preserve file attributs when moving files across filesystem boundaries + * (we want to keep the value of the checkbox between copy operations). + */ + gboolean op_preserve; + + /* Result from the recursive query */ + FileCopyMode recursive_result; + + /* Whether to do a reget */ + off_t do_reget; + + /* Controls appending to files */ + gboolean do_append; + + /* Whether to stat or lstat */ + gboolean follow_links; + + /* Pointer to the stat function we will use */ + mc_stat_fn stat_func; + + /* Whether to recompute symlinks */ + gboolean stable_symlinks; + + /* Preserve the original files' owner, group, permissions, and + * timestamps (owner, group only as root). + */ + gboolean preserve; + + /* If running as root, preserve the original uid/gid (we don't want to + * try chown for non root) preserve_uidgid = preserve && uid == 0 + */ + gboolean preserve_uidgid; + + /* The bits to preserve in created files' modes on file copy */ + mode_t umask_kill; + + /* The mask of files to actually operate on */ + char *dest_mask; + + /* search handler */ + struct mc_search_struct *search_handle; + + /* Whether to dive into subdirectories for recursive operations */ + gboolean dive_into_subdirs; + + /* When moving directories cross filesystem boundaries delete the + * successfully copied files when all files below the directory and its + * subdirectories were processed. + * + * If erase_at_end is FALSE files will be deleted immediately after their + * successful copy (Note: this behavior is not tested and at the moment + * it can't be changed at runtime). + */ + gboolean erase_at_end; + + /* PID of the child for background operations */ + pid_t pid; + + /* toggle if all errors should be ignored */ + gboolean skip_all; + + /* Whether the file operation is in pause */ + gboolean suspended; + + /* User interface data goes here */ + void *ui; +} file_op_context_t; + +typedef struct +{ + size_t progress_count; + size_t prev_progress_count; /* Used in OP_MOVE between copy and remove directories */ + uintmax_t progress_bytes; + uintmax_t copied_bytes; + size_t bps; + size_t bps_count; + gint64 transfer_start; + double eta_secs; + + gboolean ask_overwrite; +} file_op_total_context_t; + +/*** global variables defined in .c file *********************************************************/ + +extern const char *op_names[3]; + +/*** declarations of public functions ************************************************************/ + +file_op_context_t *file_op_context_new (FileOperation op); +void file_op_context_destroy (file_op_context_t * ctx); + +file_op_total_context_t *file_op_total_context_new (void); +void file_op_total_context_destroy (file_op_total_context_t * tctx); + +/* The following functions are implemented separately by each port */ +FileProgressStatus file_progress_real_query_replace (file_op_context_t * ctx, + enum OperationMode mode, const char *src, + struct stat *src_stat, const char *dst, + struct stat *dst_stat); + +/*** inline functions ****************************************************************************/ +#endif /* MC__FILEOPCTX_H */ diff --git a/src/filemanager/find.c b/src/filemanager/find.c new file mode 100644 index 0000000..b9d5320 --- /dev/null +++ b/src/filemanager/find.c @@ -0,0 +1,1968 @@ +/* + Find file command for the Midnight Commander + + Copyright (C) 1995-2022 + Free Software Foundation, Inc. + + Written by: + Miguel de Icaza, 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 find.c + * \brief Source: Find file command + */ + +#include <config.h> + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" +#include "lib/tty/key.h" +#include "lib/skin.h" +#include "lib/search.h" +#include "lib/mcconfig.h" +#include "lib/vfs/vfs.h" +#include "lib/strutil.h" +#include "lib/widget.h" +#include "lib/util.h" /* canonicalize_pathname() */ + +#include "src/setup.h" /* verbose */ +#include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */ + +#include "dir.h" +#include "cmd.h" /* find_cmd(), view_file_at_line() */ +#include "boxes.h" +#include "panelize.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define MAX_REFRESH_INTERVAL (G_USEC_PER_SEC / 20) /* 50 ms */ +#define MIN_REFRESH_FILE_SIZE (256 * 1024) /* 256 KB */ + +/*** file scope type declarations ****************************************************************/ + +/* A couple of extra messages we need */ +enum +{ + B_STOP = B_USER + 1, + B_AGAIN, + B_PANELIZE, + B_TREE, + B_VIEW +}; + +typedef enum +{ + FIND_CONT = 0, + FIND_SUSPEND, + FIND_ABORT +} FindProgressStatus; + +/* find file options */ +typedef struct +{ + /* file name options */ + gboolean file_case_sens; + gboolean file_pattern; + gboolean find_recurs; + gboolean follow_symlinks; + gboolean skip_hidden; + gboolean file_all_charsets; + + /* file content options */ + gboolean content_case_sens; + gboolean content_regexp; + gboolean content_first_hit; + gboolean content_whole_words; + gboolean content_all_charsets; + + /* whether use ignore dirs or not */ + gboolean ignore_dirs_enable; + /* list of directories to be ignored, separated by ':' */ + char *ignore_dirs; +} find_file_options_t; + +typedef struct +{ + char *dir; + gsize start; + gsize end; +} find_match_location_t; + +/*** file scope variables ************************************************************************/ + +/* button callbacks */ +static int start_stop (WButton * button, int action); +static int find_do_view_file (WButton * button, int action); +static int find_do_edit_file (WButton * button, int action); + +/* Parsed ignore dirs */ +static char **find_ignore_dirs = NULL; + +/* static variables to remember find parameters */ +static WInput *in_start; /* Start path */ +static WInput *in_name; /* Filename */ +static WInput *in_with; /* Text */ +static WInput *in_ignore; +static WLabel *content_label; /* 'Content:' label */ +static WCheck *file_case_sens_cbox; /* "case sensitive" checkbox */ +static WCheck *file_pattern_cbox; /* File name is glob or regexp */ +static WCheck *recursively_cbox; +static WCheck *follow_sym_cbox; +static WCheck *skip_hidden_cbox; +static WCheck *content_case_sens_cbox; /* "case sensitive" checkbox */ +static WCheck *content_regexp_cbox; /* "find regular expression" checkbox */ +static WCheck *content_first_hit_cbox; /* "First hit" checkbox" */ +static WCheck *content_whole_words_cbox; /* "whole words" checkbox */ +#ifdef HAVE_CHARSET +static WCheck *file_all_charsets_cbox; +static WCheck *content_all_charsets_cbox; +#endif +static WCheck *ignore_dirs_cbox; + +static gboolean running = FALSE; /* nice flag */ +static char *find_pattern = NULL; /* Pattern to search */ +static char *content_pattern = NULL; /* pattern to search inside files; if + content_regexp_flag is true, it contains the + regex pattern, else the search string. */ +static gboolean content_is_empty = TRUE; /* remember content field state; initially is empty */ +static unsigned long matches; /* Number of matches */ +static gboolean is_start = FALSE; /* Status of the start/stop toggle button */ +static char *old_dir = NULL; + +static gint64 last_refresh; + +/* Where did we stop */ +static gboolean resuming; +static int last_line; +static int last_pos; +static off_t last_off; +static int last_i; + +static size_t ignore_count = 0; + +static WDialog *find_dlg; /* The dialog */ +static WLabel *status_label; /* Finished, Searching etc. */ +static WLabel *found_num_label; /* Number of found items */ + +/* This keeps track of the directory stack */ +static GQueue dir_queue = G_QUEUE_INIT; + +/* *INDENT-OFF* */ +static struct +{ + int ret_cmd; + button_flags_t flags; + const char *text; + int len; /* length including space and brackets */ + int x; + Widget *button; + bcback_fn callback; +} fbuts[] = +{ + { B_ENTER, DEFPUSH_BUTTON, N_("&Chdir"), 0, 0, NULL, NULL }, + { B_AGAIN, NORMAL_BUTTON, N_("&Again"), 0, 0, NULL, NULL }, + { B_STOP, NORMAL_BUTTON, N_("S&uspend"), 0, 0, NULL, start_stop }, + { B_STOP, NORMAL_BUTTON, N_("Con&tinue"), 0, 0, NULL, NULL }, + { B_CANCEL, NORMAL_BUTTON, N_("&Quit"), 0, 0, NULL, NULL }, + + { B_PANELIZE, NORMAL_BUTTON, N_("Pane&lize"), 0, 0, NULL, NULL }, + { B_VIEW, NORMAL_BUTTON, N_("&View - F3"), 0, 0, NULL, find_do_view_file }, + { B_VIEW, NORMAL_BUTTON, N_("&Edit - F4"), 0, 0, NULL, find_do_edit_file } +}; +/* *INDENT-ON* */ + +static const size_t fbuts_num = G_N_ELEMENTS (fbuts); +static const size_t quit_button = 4; /* index of "Quit" button */ + +static WListbox *find_list; /* Listbox with the file list */ + +static find_file_options_t options = { + TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, + TRUE, FALSE, FALSE, FALSE, FALSE, + FALSE, NULL +}; + +static char *in_start_dir = INPUT_LAST_TEXT; + +static mc_search_t *search_file_handle = NULL; +static mc_search_t *search_content_handle = NULL; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* don't use max macro to avoid double str_term_width1() call in widget length caclulation */ +#undef max + +static int +max (int a, int b) +{ + return (a > b ? a : b); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +parse_ignore_dirs (const char *ignore_dirs) +{ + size_t r = 0, w = 0; /* read and write iterators */ + + if (!options.ignore_dirs_enable || ignore_dirs == NULL || ignore_dirs[0] == '\0') + return; + + find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1); + + /* Values like '/foo::/bar: produce holes in list. + * Find and remove them */ + for (; find_ignore_dirs[r] != NULL; r++) + { + if (find_ignore_dirs[r][0] == '\0') + { + /* empty entry -- skip it */ + MC_PTR_FREE (find_ignore_dirs[r]); + continue; + } + + if (r != w) + { + /* copy entry to the previous free array cell */ + find_ignore_dirs[w] = find_ignore_dirs[r]; + find_ignore_dirs[r] = NULL; + } + + canonicalize_pathname (find_ignore_dirs[w]); + if (find_ignore_dirs[w][0] != '\0') + w++; + else + MC_PTR_FREE (find_ignore_dirs[w]); + } + + if (find_ignore_dirs[0] == NULL) + { + g_strfreev (find_ignore_dirs); + find_ignore_dirs = NULL; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_load_options (void) +{ + static gboolean loaded = FALSE; + + if (loaded) + return; + + loaded = TRUE; + + options.file_case_sens = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_case_sens", TRUE); + options.file_pattern = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_shell_pattern", TRUE); + options.find_recurs = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_find_recurs", TRUE); + options.follow_symlinks = + mc_config_get_bool (mc_global.main_config, "FindFile", "follow_symlinks", FALSE); + options.skip_hidden = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_skip_hidden", FALSE); + options.file_all_charsets = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_all_charsets", FALSE); + options.content_case_sens = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_case_sens", TRUE); + options.content_regexp = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_regexp", FALSE); + options.content_first_hit = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_first_hit", FALSE); + options.content_whole_words = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_whole_words", FALSE); + options.content_all_charsets = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_all_charsets", FALSE); + options.ignore_dirs_enable = + mc_config_get_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", TRUE); + options.ignore_dirs = + mc_config_get_string (mc_global.main_config, "FindFile", "ignore_dirs", ""); + + if (options.ignore_dirs[0] == '\0') + MC_PTR_FREE (options.ignore_dirs); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_save_options (void) +{ + mc_config_set_bool (mc_global.main_config, "FindFile", "file_case_sens", + options.file_case_sens); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_shell_pattern", + options.file_pattern); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_find_recurs", options.find_recurs); + mc_config_set_bool (mc_global.main_config, "FindFile", "follow_symlinks", + options.follow_symlinks); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_skip_hidden", options.skip_hidden); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_all_charsets", + options.file_all_charsets); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_case_sens", + options.content_case_sens); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_regexp", + options.content_regexp); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_first_hit", + options.content_first_hit); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_whole_words", + options.content_whole_words); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_all_charsets", + options.content_all_charsets); + mc_config_set_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", + options.ignore_dirs_enable); + mc_config_set_string (mc_global.main_config, "FindFile", "ignore_dirs", options.ignore_dirs); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline char * +add_to_list (const char *text, void *data) +{ + return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +stop_idle (void *data) +{ + widget_idle (WIDGET (data), FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +status_update (const char *text) +{ + label_set_text (status_label, text); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +found_num_update (void) +{ + label_set_textv (found_num_label, _("Found: %lu"), matches); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +get_list_info (char **file, char **dir, gsize * start, gsize * end) +{ + find_match_location_t *location; + + listbox_get_current (find_list, file, (void **) &location); + if (location != NULL) + { + if (dir != NULL) + *dir = location->dir; + if (start != NULL) + *start = location->start; + if (end != NULL) + *end = location->end; + } + else + { + if (dir != NULL) + *dir = NULL; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** check regular expression */ + +static gboolean +find_check_regexp (const char *r) +{ + mc_search_t *search; + gboolean regexp_ok = FALSE; + + search = mc_search_new (r, NULL); + + if (search != NULL) + { + search->search_type = MC_SEARCH_T_REGEX; + regexp_ok = mc_search_prepare (search); + mc_search_free (search); + } + + return regexp_ok; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_toggle_enable_ignore_dirs (void) +{ + widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_toggle_enable_params (void) +{ + gboolean disable = input_is_empty (in_name); + + widget_disable (WIDGET (file_pattern_cbox), disable); + widget_disable (WIDGET (file_case_sens_cbox), disable); +#ifdef HAVE_CHARSET + widget_disable (WIDGET (file_all_charsets_cbox), disable); +#endif +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_toggle_enable_content (void) +{ + widget_disable (WIDGET (content_regexp_cbox), content_is_empty); + widget_disable (WIDGET (content_case_sens_cbox), content_is_empty); +#ifdef HAVE_CHARSET + widget_disable (WIDGET (content_all_charsets_cbox), content_is_empty); +#endif + widget_disable (WIDGET (content_whole_words_cbox), content_is_empty); + widget_disable (WIDGET (content_first_hit_cbox), content_is_empty); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Callback for the parameter dialog. + * Validate regex, prevent closing the dialog if it's invalid. + */ + +static cb_ret_t +find_parm_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + /* FIXME: HACK: use first draw of dialog to resolve widget state dependencies. + * Use this time moment to check input field content. We can't do that in MSG_INIT + * because history is not loaded yet. + * Probably, we want new MSG_ACTIVATE message as complement to MSG_VALIDATE one. Or + * we could name it MSG_POST_INIT. + * + * In one or two other places we use MSG_IDLE instead of MSG_DRAW for a similar + * purpose. We should remember to fix those places too when we introduce the new + * message. + */ + static gboolean first_draw = TRUE; + + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_INIT: + group_default_callback (w, NULL, MSG_INIT, 0, NULL); + first_draw = TRUE; + return MSG_HANDLED; + + case MSG_NOTIFY: + if (sender == WIDGET (ignore_dirs_cbox)) + { + find_toggle_enable_ignore_dirs (); + return MSG_HANDLED; + } + + return MSG_NOT_HANDLED; + + case MSG_VALIDATE: + if (h->ret_value != B_ENTER) + return MSG_HANDLED; + + /* check filename regexp */ + if (!file_pattern_cbox->state && !input_is_empty (in_name) + && !find_check_regexp (input_get_ctext (in_name))) + { + /* Don't stop the dialog */ + widget_set_state (w, WST_ACTIVE, TRUE); + message (D_ERROR, MSG_ERROR, _("Malformed regular expression")); + widget_select (WIDGET (in_name)); + return MSG_HANDLED; + } + + /* check content regexp */ + if (content_regexp_cbox->state && !content_is_empty + && !find_check_regexp (input_get_ctext (in_with))) + { + /* Don't stop the dialog */ + widget_set_state (w, WST_ACTIVE, TRUE); + message (D_ERROR, MSG_ERROR, _("Malformed regular expression")); + widget_select (WIDGET (in_with)); + return MSG_HANDLED; + } + + return MSG_HANDLED; + + case MSG_POST_KEY: + if (GROUP (h)->current->data == in_name) + find_toggle_enable_params (); + else if (GROUP (h)->current->data == in_with) + { + content_is_empty = input_is_empty (in_with); + find_toggle_enable_content (); + } + return MSG_HANDLED; + + case MSG_DRAW: + if (first_draw) + { + find_toggle_enable_ignore_dirs (); + find_toggle_enable_params (); + find_toggle_enable_content (); + } + + first_draw = FALSE; + MC_FALLTHROUGH; /* to call MSG_DRAW default handler */ + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * find_parameters: gets information from the user + * + * If the return value is TRUE, then the following holds: + * + * start_dir, ignore_dirs, pattern and content contain the information provided by the user. + * They are newly allocated strings and must be freed when uneeded. + * + * start_dir_len is -1 when user entered an absolute path, otherwise it is a length + * of start_dir (which is absolute). It is used to get a relative pats of find results. + */ + +static gboolean +find_parameters (WPanel * panel, char **start_dir, ssize_t * start_dir_len, + char **ignore_dirs, char **pattern, char **content) +{ + WGroup *g; + + /* Size of the find parameters window */ +#ifdef HAVE_CHARSET + const int lines = 19; +#else + const int lines = 18; +#endif + int cols = 68; + + gboolean return_value; + + /* file name */ + const char *file_name_label = N_("File name:"); + const char *file_recurs_label = N_("&Find recursively"); + const char *file_follow_symlinks = N_("Follow s&ymlinks"); + const char *file_pattern_label = N_("&Using shell patterns"); +#ifdef HAVE_CHARSET + const char *file_all_charsets_label = N_("&All charsets"); +#endif + const char *file_case_label = N_("Cas&e sensitive"); + const char *file_skip_hidden_label = N_("S&kip hidden"); + + /* file content */ + const char *content_content_label = N_("Content:"); + const char *content_use_label = N_("Sea&rch for content"); + const char *content_regexp_label = N_("Re&gular expression"); + const char *content_case_label = N_("Case sens&itive"); +#ifdef HAVE_CHARSET + const char *content_all_charsets_label = N_("A&ll charsets"); +#endif + const char *content_whole_words_label = N_("&Whole words"); + const char *content_first_hit_label = N_("Fir&st hit"); + + const char *buts[] = { N_("&Tree"), N_("&OK"), N_("&Cancel") }; + + /* button lengths */ + int b0, b1, b2, b12; + int y1, y2, x1, x2; + /* column width */ + int cw; + +#ifdef ENABLE_NLS + { + size_t i; + + file_name_label = _(file_name_label); + file_recurs_label = _(file_recurs_label); + file_follow_symlinks = _(file_follow_symlinks); + file_pattern_label = _(file_pattern_label); +#ifdef HAVE_CHARSET + file_all_charsets_label = _(file_all_charsets_label); +#endif + file_case_label = _(file_case_label); + file_skip_hidden_label = _(file_skip_hidden_label); + + /* file content */ + content_content_label = _(content_content_label); + content_use_label = _(content_use_label); + content_regexp_label = _(content_regexp_label); + content_case_label = _(content_case_label); +#ifdef HAVE_CHARSET + content_all_charsets_label = _(content_all_charsets_label); +#endif + content_whole_words_label = _(content_whole_words_label); + content_first_hit_label = _(content_first_hit_label); + + for (i = 0; i < G_N_ELEMENTS (buts); i++) + buts[i] = _(buts[i]); + } +#endif /* ENABLE_NLS */ + + /* caclulate dialog width */ + + /* widget widths */ + cw = str_term_width1 (file_name_label); + cw = max (cw, str_term_width1 (file_recurs_label) + 4); + cw = max (cw, str_term_width1 (file_follow_symlinks) + 4); + cw = max (cw, str_term_width1 (file_pattern_label) + 4); +#ifdef HAVE_CHARSET + cw = max (cw, str_term_width1 (file_all_charsets_label) + 4); +#endif + cw = max (cw, str_term_width1 (file_case_label) + 4); + cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4); + + cw = max (cw, str_term_width1 (content_content_label) + 4); + cw = max (cw, str_term_width1 (content_use_label) + 4); + cw = max (cw, str_term_width1 (content_regexp_label) + 4); + cw = max (cw, str_term_width1 (content_case_label) + 4); +#ifdef HAVE_CHARSET + cw = max (cw, str_term_width1 (content_all_charsets_label) + 4); +#endif + cw = max (cw, str_term_width1 (content_whole_words_label) + 4); + cw = max (cw, str_term_width1 (content_first_hit_label) + 4); + + /* button width */ + b0 = str_term_width1 (buts[0]) + 3; + b1 = str_term_width1 (buts[1]) + 5; /* default button */ + b2 = str_term_width1 (buts[2]) + 3; + b12 = b1 + b2 + 1; + + cols = max (cols, max (b12, cw * 2 + 1) + 6); + + find_load_options (); + + if (in_start_dir == NULL) + in_start_dir = g_strdup ("."); + + find_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_parm_callback, + NULL, "[Find File]", _("Find File")); + g = GROUP (find_dlg); + + x1 = 3; + x2 = cols / 2 + 1; + cw = (cols - 7) / 2; + y1 = 2; + + group_add_widget (g, label_new (y1++, x1, _("Start at:"))); + in_start = + input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start", + INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES); + group_add_widget (g, in_start); + + group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL)); + + ignore_dirs_cbox = + check_new (y1++, x1, options.ignore_dirs_enable, _("Ena&ble ignore directories:")); + group_add_widget (g, ignore_dirs_cbox); + + in_ignore = + input_new (y1++, x1, input_colors, cols - 6, + options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs", + INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES); + group_add_widget (g, in_ignore); + + group_add_widget (g, hline_new (y1++, -1, -1)); + + y2 = y1; + + /* Start 1st column */ + group_add_widget (g, label_new (y1++, x1, file_name_label)); + in_name = + input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name", + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD); + group_add_widget (g, in_name); + + /* Start 2nd column */ + content_label = label_new (y2++, x2, content_content_label); + group_add_widget (g, content_label); + in_with = + input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT, + MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE); + in_with->label = content_label; + group_add_widget (g, in_with); + + /* Continue 1st column */ + recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label); + group_add_widget (g, recursively_cbox); + + follow_sym_cbox = check_new (y1++, x1, options.follow_symlinks, file_follow_symlinks); + group_add_widget (g, follow_sym_cbox); + + file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label); + group_add_widget (g, file_pattern_cbox); + + file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label); + group_add_widget (g, file_case_sens_cbox); + +#ifdef HAVE_CHARSET + file_all_charsets_cbox = + check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label); + group_add_widget (g, file_all_charsets_cbox); +#endif + + skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label); + group_add_widget (g, skip_hidden_cbox); + + /* Continue 2nd column */ + content_whole_words_cbox = + check_new (y2++, x2, options.content_whole_words, content_whole_words_label); + group_add_widget (g, content_whole_words_cbox); + + content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label); + group_add_widget (g, content_regexp_cbox); + + content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label); + group_add_widget (g, content_case_sens_cbox); + +#ifdef HAVE_CHARSET + content_all_charsets_cbox = + check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label); + group_add_widget (g, content_all_charsets_cbox); +#endif + + content_first_hit_cbox = + check_new (y2++, x2, options.content_first_hit, content_first_hit_label); + group_add_widget (g, content_first_hit_cbox); + + /* buttons */ + y1 = max (y1, y2); + x1 = (cols - b12) / 2; + group_add_widget (g, hline_new (y1++, -1, -1)); + group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL)); + group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL)); + + find_par_start: + widget_select (WIDGET (in_name)); + + switch (dlg_run (find_dlg)) + { + case B_CANCEL: + return_value = FALSE; + break; + + case B_TREE: + { + const char *start_cstr; + const char *temp_dir; + + start_cstr = input_get_ctext (in_start); + + if (input_is_empty (in_start) || DIR_IS_DOT (start_cstr)) + temp_dir = vfs_path_as_str (panel->cwd_vpath); + else + temp_dir = start_cstr; + + if (in_start_dir != INPUT_LAST_TEXT) + g_free (in_start_dir); + in_start_dir = tree_box (temp_dir); + if (in_start_dir == NULL) + in_start_dir = g_strdup (temp_dir); + + input_assign_text (in_start, in_start_dir); + + /* Warning: Dreadful goto */ + goto find_par_start; + } + + default: + { + char *s; + +#ifdef HAVE_CHARSET + options.file_all_charsets = file_all_charsets_cbox->state; + options.content_all_charsets = content_all_charsets_cbox->state; +#endif + options.content_case_sens = content_case_sens_cbox->state; + options.content_regexp = content_regexp_cbox->state; + options.content_first_hit = content_first_hit_cbox->state; + options.content_whole_words = content_whole_words_cbox->state; + options.find_recurs = recursively_cbox->state; + options.follow_symlinks = follow_sym_cbox->state; + options.file_pattern = file_pattern_cbox->state; + options.file_case_sens = file_case_sens_cbox->state; + options.skip_hidden = skip_hidden_cbox->state; + options.ignore_dirs_enable = ignore_dirs_cbox->state; + g_free (options.ignore_dirs); + options.ignore_dirs = input_get_text (in_ignore); + + *content = !input_is_empty (in_with) ? input_get_text (in_with) : NULL; + if (input_is_empty (in_name)) + *pattern = g_strdup (options.file_pattern ? "*" : ".*"); + else + *pattern = input_get_text (in_name); + *start_dir = (char *) (!input_is_empty (in_start) ? input_get_ctext (in_start) : "."); + if (in_start_dir != INPUT_LAST_TEXT) + g_free (in_start_dir); + in_start_dir = g_strdup (*start_dir); + + s = tilde_expand (*start_dir); + canonicalize_pathname (s); + + if (DIR_IS_DOT (s)) + { + *start_dir = g_strdup (vfs_path_as_str (panel->cwd_vpath)); + /* FIXME: is panel->cwd_vpath canonicalized? */ + /* relative paths will be used in panelization */ + *start_dir_len = (ssize_t) strlen (*start_dir); + g_free (s); + } + else if (g_path_is_absolute (s)) + { + *start_dir = s; + *start_dir_len = -1; + } + else + { + /* relative paths will be used in panelization */ + *start_dir = + mc_build_filename (vfs_path_as_str (panel->cwd_vpath), s, (char *) NULL); + *start_dir_len = (ssize_t) strlen (vfs_path_as_str (panel->cwd_vpath)); + g_free (s); + } + + if (!options.ignore_dirs_enable || input_is_empty (in_ignore) + || DIR_IS_DOT (input_get_ctext (in_ignore))) + *ignore_dirs = NULL; + else + *ignore_dirs = input_get_text (in_ignore); + + find_save_options (); + + return_value = TRUE; + } + } + + widget_destroy (WIDGET (find_dlg)); + + return return_value; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +push_directory (vfs_path_t * dir) +{ + g_queue_push_head (&dir_queue, (void *) dir); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline vfs_path_t * +pop_directory (void) +{ + return (vfs_path_t *) g_queue_pop_head (&dir_queue); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +queue_dir_free (gpointer data) +{ + vfs_path_free ((vfs_path_t *) data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Remove all the items from the stack */ + +static void +clear_stack (void) +{ + g_queue_clear_full (&dir_queue, queue_dir_free); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +insert_file (const char *dir, const char *file, gsize start, gsize end) +{ + char *tmp_name = NULL; + static char *dirname = NULL; + find_match_location_t *location; + + while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1])) + dir++; + + if (old_dir != NULL) + { + if (strcmp (old_dir, dir) != 0) + { + g_free (old_dir); + old_dir = g_strdup (dir); + dirname = add_to_list (dir, NULL); + } + } + else + { + old_dir = g_strdup (dir); + dirname = add_to_list (dir, NULL); + } + + tmp_name = g_strdup_printf (" %s", file); + location = g_malloc (sizeof (*location)); + location->dir = dirname; + location->start = start; + location->end = end; + add_to_list (tmp_name, location); + g_free (tmp_name); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_add_match (const char *dir, const char *file, gsize start, gsize end) +{ + insert_file (dir, file, start, end); + + /* Don't scroll */ + if (matches == 0) + listbox_select_first (find_list); + widget_draw (WIDGET (find_list)); + + matches++; + found_num_update (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FindProgressStatus +check_find_events (WDialog * h) +{ + Gpm_Event event; + int c; + + event.x = -1; + c = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, FALSE); + if (c != EV_NONE) + { + dlg_process_event (h, c, &event); + if (h->ret_value == B_ENTER + || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE) + { + /* dialog terminated */ + return FIND_ABORT; + } + if (!widget_get_state (WIDGET (h), WST_IDLE)) + { + /* searching suspended */ + return FIND_SUSPEND; + } + } + + return FIND_CONT; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * search_content: + * + * Search the content_pattern string in the DIRECTORY/FILE. + * It will add the found entries to the find listbox. + * + * returns FALSE if do_search should look for another file + * TRUE if do_search should exit and proceed to the event handler + */ + +static gboolean +search_content (WDialog * h, const char *directory, const char *filename) +{ + struct stat s; + char buffer[BUF_4K] = ""; /* raw input buffer */ + int file_fd; + gboolean ret_val = FALSE; + vfs_path_t *vpath; + gint64 tv; + gboolean status_updated = FALSE; + + vpath = vfs_path_build_filename (directory, filename, (char *) NULL); + + if (mc_stat (vpath, &s) != 0 || !S_ISREG (s.st_mode)) + { + vfs_path_free (vpath, TRUE); + return FALSE; + } + + file_fd = mc_open (vpath, O_RDONLY); + vfs_path_free (vpath, TRUE); + + if (file_fd == -1) + return FALSE; + + /* get time elapsed from last refresh */ + tv = g_get_real_time (); + + if (s.st_size >= MIN_REFRESH_FILE_SIZE || (tv - last_refresh) > MAX_REFRESH_INTERVAL) + { + g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), filename); + status_update (str_trunc (buffer, WIDGET (h)->rect.cols - 8)); + mc_refresh (); + last_refresh = tv; + status_updated = TRUE; + } + + tty_enable_interrupt_key (); + tty_got_interrupt (); + + { + int line = 1; + int pos = 0; + int n_read = 0; + off_t off = 0; /* file_fd's offset corresponding to strbuf[0] */ + gboolean found = FALSE; + gsize found_len; + gsize found_start; + char result[BUF_MEDIUM]; + char *strbuf = NULL; /* buffer for fetched string */ + int strbuf_size = 0; + int i = -1; /* compensate for a newline we'll add when we first enter the loop */ + + if (resuming) + { + /* We've been previously suspended, start from the previous position */ + resuming = FALSE; + line = last_line; + pos = last_pos; + off = last_off; + i = last_i; + } + + while (!ret_val) + { + char ch = '\0'; + + off += i + 1; /* the previous line, plus a newline character */ + i = 0; + + /* read to buffer and get line from there */ + while (TRUE) + { + if (pos >= n_read) + { + pos = 0; + n_read = mc_read (file_fd, buffer, sizeof (buffer)); + if (n_read <= 0) + break; + } + + ch = buffer[pos++]; + if (ch == '\0') + { + /* skip possible leading zero(s) */ + if (i == 0) + { + off++; + continue; + } + break; + } + + if (i >= strbuf_size - 1) + { + strbuf_size += 128; + strbuf = g_realloc (strbuf, strbuf_size); + } + + /* Strip newline */ + if (ch == '\n') + break; + + strbuf[i++] = ch; + } + + if (i == 0) + { + if (ch == '\0') + break; + + /* if (ch == '\n'): do not search in empty strings */ + goto skip_search; + } + + strbuf[i] = '\0'; + + if (!found /* Search in binary line once */ + && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len)) + { + if (!status_updated) + { + /* if we add results for a file, we have to ensure that + name of this file is shown in status bar */ + g_snprintf (result, sizeof (result), _("Grepping in %s"), filename); + status_update (str_trunc (result, WIDGET (h)->rect.cols - 8)); + mc_refresh (); + last_refresh = tv; + status_updated = TRUE; + } + + g_snprintf (result, sizeof (result), "%d:%s", line, filename); + found_start = off + search_content_handle->normal_offset + 1; /* off by one: ticket 3280 */ + find_add_match (directory, result, found_start, found_start + found_len); + found = TRUE; + } + + if (found && options.content_first_hit) + break; + + if (ch == '\n') + { + skip_search: + found = FALSE; + line++; + } + + if ((line & 0xff) == 0) + { + FindProgressStatus res; + + res = check_find_events (h); + switch (res) + { + case FIND_ABORT: + stop_idle (h); + ret_val = TRUE; + break; + case FIND_SUSPEND: + resuming = TRUE; + last_line = line; + last_pos = pos; + last_off = off; + last_i = i; + ret_val = TRUE; + break; + default: + break; + } + } + } + + g_free (strbuf); + } + + tty_disable_interrupt_key (); + mc_close (file_fd); + return ret_val; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + If dir is absolute, this means we're within dir and searching file here. + If dir is relative, this means we're going to add dir to the directory stack. +**/ +static gboolean +find_ignore_dir_search (const char *dir) +{ + if (find_ignore_dirs != NULL) + { + const size_t dlen = strlen (dir); + const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0; + + char **ignore_dir; + + for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++) + { + const size_t ilen = strlen (*ignore_dir); + const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0; + + /* ignore dir is too long -- skip it */ + if (dlen < ilen) + continue; + + /* handle absolute and relative paths */ + switch (iabs | dabs) + { + case 0: /* both paths are relative */ + case 3: /* both paths are abolute */ + /* if ignore dir is not a path of dir -- skip it */ + if (strncmp (dir, *ignore_dir, ilen) == 0) + { + /* be sure that ignore dir is not a part of dir like: + ignore dir is "h", dir is "home" */ + if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen])) + return TRUE; + } + break; + case 1: /* dir is absolute, ignore_dir is relative */ + { + char *d; + + d = strstr (dir, *ignore_dir); + if (d != NULL && IS_PATH_SEP (d[-1]) + && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen]))) + return TRUE; + } + break; + case 2: /* dir is relative, ignore_dir is absolute */ + /* FIXME: skip this case */ + break; + default: /* this cannot occurs */ + return FALSE; + } + } + } + + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_rotate_dash (const WDialog * h, gboolean show) +{ + static size_t pos = 0; + static const char rotating_dash[4] = "|/-\\"; + const Widget *w = CONST_WIDGET (h); + const int *colors; + + colors = widget_get_colors (w); + tty_setcolor (colors[DLG_COLOR_NORMAL]); + widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4); + tty_print_char (show ? rotating_dash[pos] : ' '); + pos = (pos + 1) % sizeof (rotating_dash); + mc_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +do_search (WDialog * h) +{ + static struct vfs_dirent *dp = NULL; + static DIR *dirp = NULL; + static char *directory = NULL; + struct stat tmp_stat; + gsize bytes_found; + unsigned short count; + + if (h == NULL) + { /* someone forces me to close dirp */ + if (dirp != NULL) + { + mc_closedir (dirp); + dirp = NULL; + } + MC_PTR_FREE (directory); + dp = NULL; + return 1; + } + + for (count = 0; count < 32; count++) + { + while (dp == NULL) + { + if (dirp != NULL) + { + mc_closedir (dirp); + dirp = NULL; + } + + while (dirp == NULL) + { + vfs_path_t *tmp_vpath = NULL; + + tty_setcolor (REVERSE_COLOR); + + while (TRUE) + { + tmp_vpath = pop_directory (); + if (tmp_vpath == NULL) + { + running = FALSE; + if (ignore_count == 0) + status_update (_("Finished")); + else + { + char msg[BUF_SMALL]; + + g_snprintf (msg, sizeof (msg), + ngettext ("Finished (ignored %zu directory)", + "Finished (ignored %zu directories)", + ignore_count), ignore_count); + status_update (msg); + } + if (verbose) + find_rotate_dash (h, FALSE); + stop_idle (h); + return 0; + } + + /* handle absolute ignore dirs here */ + { + gboolean ok; + + ok = find_ignore_dir_search (vfs_path_as_str (tmp_vpath)); + if (!ok) + break; + } + + vfs_path_free (tmp_vpath, TRUE); + ignore_count++; + } + + g_free (directory); + directory = g_strdup (vfs_path_as_str (tmp_vpath)); + + if (verbose) + { + char buffer[BUF_MEDIUM]; + + g_snprintf (buffer, sizeof (buffer), _("Searching %s"), directory); + status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8)); + } + + dirp = mc_opendir (tmp_vpath); + vfs_path_free (tmp_vpath, TRUE); + } /* while (!dirp) */ + + /* skip invalid filenames */ + while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name)) + ; + } /* while (!dp) */ + + if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name)) + { + /* skip invalid filenames */ + while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name)) + ; + + return 1; + } + + if (!(options.skip_hidden && (dp->d_name[0] == '.'))) + { + gboolean search_ok; + + if (options.find_recurs && (directory != NULL)) + { /* Can directory be NULL ? */ + /* handle relative ignore dirs here */ + if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name)) + ignore_count++; + else + { + vfs_path_t *tmp_vpath; + int stat_res; + + tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL); + + if (options.follow_symlinks) + stat_res = mc_stat (tmp_vpath, &tmp_stat); + else + stat_res = mc_lstat (tmp_vpath, &tmp_stat); + + if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode)) + push_directory (tmp_vpath); + else + vfs_path_free (tmp_vpath, TRUE); + } + } + + search_ok = mc_search_run (search_file_handle, dp->d_name, + 0, strlen (dp->d_name), &bytes_found); + + if (search_ok) + { + if (content_pattern == NULL) + find_add_match (directory, dp->d_name, 0, 0); + else if (search_content (h, directory, dp->d_name)) + return 1; + } + } + + /* skip invalid filenames */ + while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name)) + ; + } /* for */ + + if (verbose) + find_rotate_dash (h, TRUE); + + return 1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +init_find_vars (void) +{ + MC_PTR_FREE (old_dir); + matches = 0; + ignore_count = 0; + + /* Remove all the items from the stack */ + clear_stack (); + + g_strfreev (find_ignore_dirs); + find_ignore_dirs = NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start, + off_t search_end) +{ + const char *filename = NULL; + int line; + vfs_path_t *fullname_vpath; + + if (content_pattern != NULL) + { + filename = strchr (file + 4, ':') + 1; + line = atoi (file + 4); + } + else + { + filename = file + 4; + line = 0; + } + + fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL); + if (edit) + edit_file_at_line (fullname_vpath, use_internal_edit, line); + else + view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start, + search_end); + vfs_path_free (fullname_vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit) +{ + char *text = NULL; + find_match_location_t *location; + + listbox_get_current (find_list, &text, (void **) &location); + + if ((text == NULL) || (location == NULL) || (location->dir == NULL)) + return MSG_NOT_HANDLED; + + find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end); + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_calc_button_locations (const WDialog * h, gboolean all_buttons) +{ + const int cols = CONST_WIDGET (h)->rect.cols; + + int l1, l2; + + l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3; + l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2; + + fbuts[0].x = (cols - l1) / 2; + fbuts[1].x = fbuts[0].x + fbuts[0].len + 1; + fbuts[2].x = fbuts[1].x + fbuts[1].len + 1; + fbuts[3].x = fbuts[2].x; + fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1; + + if (all_buttons) + { + fbuts[5].x = (cols - l2) / 2; + fbuts[6].x = fbuts[5].x + fbuts[5].len + 1; + fbuts[7].x = fbuts[6].x + fbuts[6].len + 1; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_adjust_header (WDialog * h) +{ + char title[BUF_MEDIUM]; + int title_len; + + if (content_pattern != NULL) + g_snprintf (title, sizeof (title), _("Find File: \"%s\". Content: \"%s\""), find_pattern, + content_pattern); + else + g_snprintf (title, sizeof (title), _("Find File: \"%s\""), find_pattern); + + title_len = str_term_width1 (title); + if (title_len > WIDGET (h)->rect.cols - 6) + { + /* title is too wide, truncate it */ + title_len = WIDGET (h)->rect.cols - 6; + title_len = str_column_to_pos (title, title_len); + title_len -= 3; /* reserve space for three dots */ + title_len = str_offset_to_pos (title, title_len); + /* mark that title is truncated */ + memmove (title + title_len, "...", 4); + } + + frame_set_title (FRAME (h->bg), title); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_relocate_buttons (const WDialog * h, gboolean all_buttons) +{ + size_t i; + + find_calc_button_locations (h, all_buttons); + + for (i = 0; i < fbuts_num; i++) + fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +find_resize (WDialog * h) +{ + Widget *w = WIDGET (h); + WRect r = w->rect; + + r.lines = LINES - 4; + r.cols = COLS - 16; + dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r); + find_adjust_header (h); + find_relocate_buttons (h, TRUE); + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +find_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_INIT: + group_default_callback (w, NULL, MSG_INIT, 0, NULL); + find_adjust_header (h); + return MSG_HANDLED; + + case MSG_KEY: + if (parm == KEY_F (3) || parm == KEY_F (13)) + { + gboolean unparsed_view = (parm == KEY_F (13)); + + return view_edit_currently_selected_file (unparsed_view, FALSE); + } + if (parm == KEY_F (4)) + return view_edit_currently_selected_file (FALSE, TRUE); + return MSG_NOT_HANDLED; + + case MSG_RESIZE: + return find_resize (h); + + case MSG_IDLE: + do_search (h); + return MSG_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Handles the Stop/Start button in the find window */ + +static int +start_stop (WButton * button, int action) +{ + Widget *w = WIDGET (button); + + (void) action; + + running = is_start; + widget_idle (WIDGET (find_dlg), running); + is_start = !is_start; + + status_update (is_start ? _("Stopped") : _("Searching")); + button_set_text (button, fbuts[is_start ? 3 : 2].text); + + find_relocate_buttons (DIALOG (w->owner), FALSE); + widget_draw (WIDGET (w->owner)); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Handle view command, when invoked as a button */ + +static int +find_do_view_file (WButton * button, int action) +{ + (void) button; + (void) action; + + view_edit_currently_selected_file (FALSE, FALSE); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Handle edit command, when invoked as a button */ + +static int +find_do_edit_file (WButton * button, int action) +{ + (void) button; + (void) action; + + view_edit_currently_selected_file (FALSE, TRUE); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +setup_gui (void) +{ + WGroup *g; + size_t i; + int lines, cols; + int y; + + static gboolean i18n_flag = FALSE; + + if (!i18n_flag) + { + for (i = 0; i < fbuts_num; i++) + { +#ifdef ENABLE_NLS + fbuts[i].text = _(fbuts[i].text); +#endif /* ENABLE_NLS */ + fbuts[i].len = str_term_width1 (fbuts[i].text) + 3; + if (fbuts[i].flags == DEFPUSH_BUTTON) + fbuts[i].len += 2; + } + + i18n_flag = TRUE; + } + + lines = LINES - 4; + cols = COLS - 16; + + find_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_callback, NULL, + "[Find File]", NULL); + g = GROUP (find_dlg); + + find_calc_button_locations (find_dlg, TRUE); + + y = 2; + find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL); + group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL); + y += WIDGET (find_list)->rect.lines; + + group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL); + + found_num_label = label_new (y++, 4, ""); + group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL); + + status_label = label_new (y++, 4, _("Searching")); + group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL); + + group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL); + + for (i = 0; i < fbuts_num; i++) + { + if (i == 3) + fbuts[3].button = fbuts[2].button; + else + { + fbuts[i].button = + WIDGET (button_new + (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags, fbuts[i].text, + fbuts[i].callback)); + group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL); + } + + if (i == quit_button) + y++; + } + + widget_select (WIDGET (find_list)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +run_process (void) +{ + int ret; + + search_content_handle = mc_search_new (content_pattern, NULL); + if (search_content_handle) + { + search_content_handle->search_type = + options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL; + search_content_handle->is_case_sensitive = options.content_case_sens; + search_content_handle->whole_words = options.content_whole_words; +#ifdef HAVE_CHARSET + search_content_handle->is_all_charsets = options.content_all_charsets; +#endif + } + search_file_handle = mc_search_new (find_pattern, NULL); + search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX; + search_file_handle->is_case_sensitive = options.file_case_sens; +#ifdef HAVE_CHARSET + search_file_handle->is_all_charsets = options.file_all_charsets; +#endif + search_file_handle->is_entire_line = options.file_pattern; + + resuming = FALSE; + + widget_idle (WIDGET (find_dlg), TRUE); + ret = dlg_run (find_dlg); + + mc_search_free (search_file_handle); + search_file_handle = NULL; + mc_search_free (search_content_handle); + search_content_handle = NULL; + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +kill_gui (void) +{ + Widget *w = WIDGET (find_dlg); + + widget_idle (w, FALSE); + widget_destroy (w); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +do_find (WPanel * panel, const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs, + char **dirname, char **filename) +{ + int return_value = 0; + char *dir_tmp = NULL, *file_tmp = NULL; + + setup_gui (); + + init_find_vars (); + parse_ignore_dirs (ignore_dirs); + push_directory (vfs_path_from_str (start_dir)); + + return_value = run_process (); + + /* Clear variables */ + init_find_vars (); + + get_list_info (&file_tmp, &dir_tmp, NULL, NULL); + + if (dir_tmp) + *dirname = g_strdup (dir_tmp); + if (file_tmp) + *filename = g_strdup (file_tmp); + + if (return_value == B_PANELIZE && *filename) + { + int i; + struct stat st; + GList *entry; + dir_list *list = &panel->dir; + char *name = NULL; + + panel_clean_dir (panel); + dir_list_init (list); + + for (i = 0, entry = listbox_get_first_link (find_list); entry != NULL; + i++, entry = g_list_next (entry)) + { + const char *lc_filename = NULL; + WLEntry *le = LENTRY (entry->data); + find_match_location_t *location = le->data; + char *p; + gboolean link_to_dir, stale_link; + + if ((le->text == NULL) || (location == NULL) || (location->dir == NULL)) + continue; + + if (!content_is_empty) + lc_filename = strchr (le->text + 4, ':') + 1; + else + lc_filename = le->text + 4; + + name = mc_build_filename (location->dir, lc_filename, (char *) NULL); + /* skip initial start dir */ + if (start_dir_len < 0) + p = name; + else + { + p = name + (size_t) start_dir_len; + if (IS_PATH_SEP (*p)) + p++; + } + + if (!handle_path (p, &st, &link_to_dir, &stale_link)) + { + g_free (name); + continue; + } + /* Need to grow the *list? */ + if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP)) + { + g_free (name); + break; + } + + /* don't add files more than once to the panel */ + if (!content_is_empty && list->len != 0 + && strcmp (list->list[list->len - 1].fname->str, p) == 0) + { + g_free (name); + continue; + } + + list->list[list->len].fname = g_string_new (p); + list->list[list->len].f.marked = 0; + list->list[list->len].f.link_to_dir = link_to_dir ? 1 : 0; + list->list[list->len].f.stale_link = stale_link ? 1 : 0; + list->list[list->len].f.dir_size_computed = 0; + list->list[list->len].st = st; + list->list[list->len].sort_key = NULL; + list->list[list->len].second_sort_key = NULL; + list->len++; + g_free (name); + if ((list->len & 15) == 0) + rotate_dash (TRUE); + } + + panel->is_panelized = TRUE; + panelize_absolutize_if_needed (panel); + panelize_save_panel (panel); + } + + kill_gui (); + do_search (NULL); /* force do_search to release resources */ + MC_PTR_FREE (old_dir); + rotate_dash (FALSE); + + return return_value; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +find_cmd (WPanel * panel) +{ + char *start_dir = NULL, *ignore_dirs = NULL; + ssize_t start_dir_len; + + find_pattern = NULL; + content_pattern = NULL; + + while (find_parameters (panel, &start_dir, &start_dir_len, + &ignore_dirs, &find_pattern, &content_pattern)) + { + char *filename = NULL, *dirname = NULL; + int v = B_CANCEL; + + content_is_empty = content_pattern == NULL; + + if (find_pattern[0] != '\0') + { + last_refresh = 0; + + is_start = FALSE; + + if (!content_is_empty && !str_is_valid_string (content_pattern)) + MC_PTR_FREE (content_pattern); + + v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename); + } + + g_free (start_dir); + g_free (ignore_dirs); + MC_PTR_FREE (find_pattern); + + if (v == B_ENTER) + { + if (dirname != NULL) + { + vfs_path_t *dirname_vpath; + + dirname_vpath = vfs_path_from_str (dirname); + panel_cd (panel, dirname_vpath, cd_exact); + vfs_path_free (dirname_vpath, TRUE); + if (filename != NULL) + try_to_select (panel, + filename + (content_pattern != NULL + ? strchr (filename + 4, ':') - filename + 1 : 4)); + } + else if (filename != NULL) + { + vfs_path_t *filename_vpath; + + filename_vpath = vfs_path_from_str (filename); + panel_cd (panel, filename_vpath, cd_exact); + vfs_path_free (filename_vpath, TRUE); + } + } + + MC_PTR_FREE (content_pattern); + g_free (dirname); + g_free (filename); + + if (v == B_ENTER || v == B_CANCEL) + break; + + if (v == B_PANELIZE) + { + panel_re_sort (panel); + try_to_select (panel, NULL); + break; + } + } +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/hotlist.c b/src/filemanager/hotlist.c new file mode 100644 index 0000000..9f38661 --- /dev/null +++ b/src/filemanager/hotlist.c @@ -0,0 +1,1731 @@ +/* + Directory hotlist -- for the Midnight Commander + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Radek Doulik, 1994 + Janne Kukonlehto, 1995 + Andrej Borsenkow, 1996 + Norbert Warmuth, 1997 + Andrew Borodin <aborodin@vmail.ru>, 2012-2022 + + Janne did the original Hotlist code, Andrej made the groupable + hotlist; the move hotlist and revamped the file format and made + it stronger. + + 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 hotlist.c + * \brief Source: directory hotlist + */ + +#include <config.h> + +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" /* COLS */ +#include "lib/tty/key.h" /* KEY_M_CTRL */ +#include "lib/skin.h" /* colors */ +#include "lib/mcconfig.h" /* Load/save directories hotlist */ +#include "lib/fileloc.h" +#include "lib/strutil.h" +#include "lib/vfs/vfs.h" +#include "lib/util.h" +#include "lib/widget.h" + +#include "src/setup.h" /* For profile_bname */ +#include "src/history.h" + +#include "command.h" /* cmdline */ + +#include "hotlist.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define UX 3 +#define UY 2 + +#define B_ADD_CURRENT B_USER +#define B_REMOVE (B_USER + 1) +#define B_NEW_GROUP (B_USER + 2) +#define B_NEW_ENTRY (B_USER + 3) +#define B_ENTER_GROUP (B_USER + 4) +#define B_UP_GROUP (B_USER + 5) +#define B_INSERT (B_USER + 6) +#define B_APPEND (B_USER + 7) +#define B_MOVE (B_USER + 8) +#ifdef ENABLE_VFS +#define B_FREE_ALL_VFS (B_USER + 9) +#define B_REFRESH_VFS (B_USER + 10) +#endif + +#define TKN_GROUP 0 +#define TKN_ENTRY 1 +#define TKN_STRING 2 +#define TKN_URL 3 +#define TKN_ENDGROUP 4 +#define TKN_COMMENT 5 +#define TKN_EOL 125 +#define TKN_EOF 126 +#define TKN_UNKNOWN 127 + +#define SKIP_TO_EOL \ +{ \ + int _tkn; \ + while ((_tkn = hot_next_token ()) != TKN_EOF && _tkn != TKN_EOL) ; \ +} + +#define CHECK_TOKEN(_TKN_) \ +tkn = hot_next_token (); \ +if (tkn != _TKN_) \ +{ \ + hotlist_state.readonly = TRUE; \ + hotlist_state.file_error = TRUE; \ + while (tkn != TKN_EOL && tkn != TKN_EOF) \ + tkn = hot_next_token (); \ + break; \ +} + +/*** file scope type declarations ****************************************************************/ + +enum HotListType +{ + HL_TYPE_GROUP, + HL_TYPE_ENTRY, + HL_TYPE_COMMENT, + HL_TYPE_DOTDOT +}; + +static struct +{ + /* + * these reflect run time state + */ + + gboolean loaded; /* hotlist is loaded */ + gboolean readonly; /* hotlist readonly */ + gboolean file_error; /* parse error while reading file */ + gboolean running; /* we are running dlg (and have to + update listbox */ + gboolean moving; /* we are in moving hotlist currently */ + gboolean modified; /* hotlist was modified */ + hotlist_t type; /* LIST_HOTLIST || LIST_VFSLIST */ +} hotlist_state; + +/* Directory hotlist */ +struct hotlist +{ + enum HotListType type; + char *directory; + char *label; + struct hotlist *head; + struct hotlist *up; + struct hotlist *next; +}; + +/*** file scope variables ************************************************************************/ + +static WPanel *our_panel; + +static gboolean hotlist_has_dot_dot = TRUE; + +static WDialog *hotlist_dlg, *movelist_dlg; +static WGroupbox *hotlist_group, *movelist_group; +static WListbox *l_hotlist, *l_movelist; +static WLabel *pname; + +static struct +{ + int ret_cmd, flags, y, x, len; + const char *text; + int type; + widget_pos_flags_t pos_flags; +} hotlist_but[] = +{ + /* *INDENT-OFF* */ + { B_ENTER, DEFPUSH_BUTTON, 0, 0, 0, N_("Change &to"), + LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, +#ifdef ENABLE_VFS + { B_FREE_ALL_VFS, NORMAL_BUTTON, 0, 20, 0, N_("&Free VFSs now"), + LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_REFRESH_VFS, NORMAL_BUTTON, 0, 43, 0, N_("&Refresh"), + LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, +#endif + { B_ADD_CURRENT, NORMAL_BUTTON, 0, 20, 0, N_("&Add current"), + LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_UP_GROUP, NORMAL_BUTTON, 0, 42, 0, N_("&Up"), + LIST_HOTLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_CANCEL, NORMAL_BUTTON, 0, 53, 0, N_("&Cancel"), + LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_RIGHT | WPOS_KEEP_BOTTOM }, + { B_NEW_GROUP, NORMAL_BUTTON, 1, 0, 0, N_("New &group"), + LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_NEW_ENTRY, NORMAL_BUTTON, 1, 15, 0, N_("New &entry"), + LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_INSERT, NORMAL_BUTTON, 1, 0, 0, N_("&Insert"), + LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_APPEND, NORMAL_BUTTON, 1, 15, 0, N_("A&ppend"), + LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_REMOVE, NORMAL_BUTTON, 1, 30, 0, N_("&Remove"), + LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }, + { B_MOVE, NORMAL_BUTTON, 1, 42, 0, N_("&Move"), + LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM } + /* *INDENT-ON* */ +}; + +static const size_t hotlist_but_num = G_N_ELEMENTS (hotlist_but); + +static struct hotlist *hotlist = NULL; + +static struct hotlist *current_group; + +static GString *tkn_buf = NULL; + +static char *hotlist_file_name; +static FILE *hotlist_file; +static time_t hotlist_file_mtime; + +static int list_level = 0; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void init_movelist (struct hotlist *item); +static void add_new_group_cmd (void); +static void add_new_entry_cmd (WPanel * panel); +static void remove_from_hotlist (struct hotlist *entry); +static void load_hotlist (void); +static void add_dotdot_to_list (void); + +/* --------------------------------------------------------------------------------------------- */ +/** If current->data is 0, then we are dealing with a VFS pathname */ + +static void +update_path_name (void) +{ + const char *text = ""; + char *p; + WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist; + Widget *w = WIDGET (list); + + if (!listbox_is_empty (list)) + { + char *ctext = NULL; + void *cdata = NULL; + + listbox_get_current (list, &ctext, &cdata); + if (cdata == NULL) + text = ctext; + else + { + struct hotlist *hlp = (struct hotlist *) cdata; + + if (hlp->type == HL_TYPE_ENTRY || hlp->type == HL_TYPE_DOTDOT) + text = hlp->directory; + else if (hlp->type == HL_TYPE_GROUP) + text = _("Subgroup - press ENTER to see list"); + } + } + + p = g_strconcat (" ", current_group->label, " ", (char *) NULL); + if (hotlist_state.moving) + groupbox_set_title (movelist_group, str_trunc (p, w->rect.cols - 2)); + else + { + groupbox_set_title (hotlist_group, str_trunc (p, w->rect.cols - 2)); + label_set_text (pname, str_trunc (text, w->rect.cols)); + } + g_free (p); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +fill_listbox (WListbox * list) +{ + struct hotlist *current; + GString *buff; + + buff = g_string_new (""); + + for (current = current_group->head; current != NULL; current = current->next) + switch (current->type) + { + case HL_TYPE_GROUP: + { + /* buff clean up */ + g_string_truncate (buff, 0); + g_string_append (buff, "->"); + g_string_append (buff, current->label); + listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, buff->str, current, FALSE); + } + break; + case HL_TYPE_DOTDOT: + case HL_TYPE_ENTRY: + listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, current->label, current, FALSE); + break; + default: + break; + } + + g_string_free (buff, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +unlink_entry (struct hotlist *entry) +{ + struct hotlist *current = current_group->head; + + if (current == entry) + current_group->head = entry->next; + else + { + while (current != NULL && current->next != entry) + current = current->next; + if (current != NULL) + current->next = entry->next; + } + entry->next = entry->up = NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_VFS +static void +add_name_to_list (const char *path) +{ + listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, path, NULL, FALSE); +} +#endif /* !ENABLE_VFS */ + +/* --------------------------------------------------------------------------------------------- */ + +static int +hotlist_run_cmd (int action) +{ + switch (action) + { + case B_MOVE: + { + struct hotlist *saved = current_group; + struct hotlist *item = NULL; + struct hotlist *moveto_item = NULL; + struct hotlist *moveto_group = NULL; + int ret; + + if (listbox_is_empty (l_hotlist)) + return 0; /* empty group - nothing to do */ + + listbox_get_current (l_hotlist, NULL, (void **) &item); + init_movelist (item); + hotlist_state.moving = TRUE; + ret = dlg_run (movelist_dlg); + hotlist_state.moving = FALSE; + listbox_get_current (l_movelist, NULL, (void **) &moveto_item); + moveto_group = current_group; + widget_destroy (WIDGET (movelist_dlg)); + current_group = saved; + if (ret == B_CANCEL) + return 0; + if (moveto_item == item) + return 0; /* If we insert/append a before/after a + it hardly changes anything ;) */ + unlink_entry (item); + listbox_remove_current (l_hotlist); + item->up = moveto_group; + if (moveto_group->head == NULL) + moveto_group->head = item; + else if (moveto_item == NULL) + { /* we have group with just comments */ + struct hotlist *p = moveto_group->head; + + /* skip comments */ + while (p->next != NULL) + p = p->next; + p->next = item; + } + else if (ret == B_ENTER || ret == B_APPEND) + { + if (moveto_item->next == NULL) + moveto_item->next = item; + else + { + item->next = moveto_item->next; + moveto_item->next = item; + } + } + else if (moveto_group->head == moveto_item) + { + moveto_group->head = item; + item->next = moveto_item; + } + else + { + struct hotlist *p = moveto_group->head; + + while (p->next != moveto_item) + p = p->next; + item->next = p->next; + p->next = item; + } + listbox_remove_list (l_hotlist); + fill_listbox (l_hotlist); + repaint_screen (); + hotlist_state.modified = TRUE; + return 0; + } + case B_REMOVE: + { + struct hotlist *entry = NULL; + + listbox_get_current (l_hotlist, NULL, (void **) &entry); + remove_from_hotlist (entry); + } + return 0; + + case B_NEW_GROUP: + add_new_group_cmd (); + return 0; + + case B_ADD_CURRENT: + add2hotlist_cmd (our_panel); + return 0; + + case B_NEW_ENTRY: + add_new_entry_cmd (our_panel); + return 0; + + case B_ENTER: + case B_ENTER_GROUP: + { + WListbox *list; + void *data; + struct hotlist *hlp; + + list = hotlist_state.moving ? l_movelist : l_hotlist; + listbox_get_current (list, NULL, &data); + + if (data == NULL) + return 1; + + hlp = (struct hotlist *) data; + + if (hlp->type == HL_TYPE_ENTRY) + return (action == B_ENTER ? 1 : 0); + if (hlp->type != HL_TYPE_DOTDOT) + { + listbox_remove_list (list); + current_group = hlp; + fill_listbox (list); + return 0; + } + } + MC_FALLTHROUGH; /* if list empty - just go up */ + + case B_UP_GROUP: + { + WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist; + + listbox_remove_list (list); + current_group = current_group->up; + fill_listbox (list); + return 0; + } + +#ifdef ENABLE_VFS + case B_FREE_ALL_VFS: + vfs_expire (TRUE); + MC_FALLTHROUGH; + + case B_REFRESH_VFS: + listbox_remove_list (l_hotlist); + listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL, + FALSE); + vfs_fill_names (add_name_to_list); + return 0; +#endif /* ENABLE_VFS */ + + default: + return 1; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +hotlist_button_callback (WButton * button, int action) +{ + int ret; + + (void) button; + ret = hotlist_run_cmd (action); + update_path_name (); + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline cb_ret_t +hotlist_handle_key (WDialog * h, int key) +{ + switch (key) + { + case KEY_M_CTRL | '\n': + goto l1; + + case '\n': + case KEY_ENTER: + if (hotlist_button_callback (NULL, B_ENTER) != 0) + { + h->ret_value = B_ENTER; + dlg_stop (h); + } + return MSG_HANDLED; + + case KEY_RIGHT: + /* enter to the group */ + if (hotlist_state.type == LIST_VFSLIST) + return MSG_NOT_HANDLED; + return hotlist_button_callback (NULL, B_ENTER_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED; + + case KEY_LEFT: + /* leave the group */ + if (hotlist_state.type == LIST_VFSLIST) + return MSG_NOT_HANDLED; + return hotlist_button_callback (NULL, B_UP_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED; + + case KEY_DC: + if (hotlist_state.moving) + return MSG_NOT_HANDLED; + hotlist_button_callback (NULL, B_REMOVE); + return MSG_HANDLED; + + l1: + case ALT ('\n'): + case ALT ('\r'): + if (!hotlist_state.moving) + { + void *ldata = NULL; + + listbox_get_current (l_hotlist, NULL, &ldata); + + if (ldata != NULL) + { + struct hotlist *hlp = (struct hotlist *) ldata; + + if (hlp->type == HL_TYPE_ENTRY) + { + char *tmp; + + tmp = g_strconcat ("cd ", hlp->directory, (char *) NULL); + input_insert (cmdline, tmp, FALSE); + g_free (tmp); + h->ret_value = B_CANCEL; + dlg_stop (h); + } + } + } + return MSG_HANDLED; /* ignore key */ + + default: + return MSG_NOT_HANDLED; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +hotlist_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_INIT: + case MSG_NOTIFY: /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */ + update_path_name (); + return MSG_HANDLED; + + case MSG_UNHANDLED_KEY: + return hotlist_handle_key (h, parm); + + case MSG_POST_KEY: + /* + * The code here has two purposes: + * + * (1) Always stay on the hotlist. + * + * Activating a button using its hotkey (and even pressing ENTER, as + * there's a "default button") moves the focus to the button. But we + * want to stay on the hotlist, to be able to use the usual keys (up, + * down, etc.). So we do `widget_select (lst)`. + * + * (2) Refresh the hotlist. + * + * We may have run a command that changed the contents of the list. + * We therefore need to refresh it. So we do `widget_draw (lst)`. + */ + { + Widget *lst; + + lst = WIDGET (h == hotlist_dlg ? l_hotlist : l_movelist); + + /* widget_select() already redraws the widget, but since it's a + * no-op if the widget is already selected ("focused"), we have + * to call widget_draw() separately. */ + if (!widget_get_state (lst, WST_FOCUSED)) + widget_select (lst); + else + widget_draw (lst); + } + return MSG_HANDLED; + + case MSG_RESIZE: + { + WRect r = w->rect; + + r.lines = LINES - (h == hotlist_dlg ? 2 : 6); + r.cols = COLS - 6; + + return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r); + } + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static lcback_ret_t +hotlist_listbox_callback (WListbox * list) +{ + WDialog *dlg = DIALOG (WIDGET (list)->owner); + + if (!listbox_is_empty (list)) + { + void *data = NULL; + + listbox_get_current (list, NULL, &data); + + if (data != NULL) + { + struct hotlist *hlp = (struct hotlist *) data; + + if (hlp->type == HL_TYPE_ENTRY) + { + dlg->ret_value = B_ENTER; + dlg_stop (dlg); + return LISTBOX_DONE; + } + else + { + hotlist_button_callback (NULL, B_ENTER); + send_message (dlg, NULL, MSG_POST_KEY, '\n', NULL); + return LISTBOX_CONT; + } + } + else + { + dlg->ret_value = B_ENTER; + dlg_stop (dlg); + return LISTBOX_DONE; + } + } + + hotlist_button_callback (NULL, B_UP_GROUP); + send_message (dlg, NULL, MSG_POST_KEY, 'u', NULL); + return LISTBOX_CONT; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Expands all button names (once) and recalculates button positions. + * returns number of columns in the dialog box, which is 10 chars longer + * then buttonbar. + * + * If common width of the window (i.e. in xterm) is less than returned + * width - sorry :) (anyway this did not handled in previous version too) + */ + +static int +init_i18n_stuff (int list_type, int cols) +{ + size_t i; + + static gboolean i18n_flag = FALSE; + + if (!i18n_flag) + { + for (i = 0; i < hotlist_but_num; i++) + { +#ifdef ENABLE_NLS + hotlist_but[i].text = _(hotlist_but[i].text); +#endif /* ENABLE_NLS */ + hotlist_but[i].len = str_term_width1 (hotlist_but[i].text) + 3; + if (hotlist_but[i].flags == DEFPUSH_BUTTON) + hotlist_but[i].len += 2; + } + + i18n_flag = TRUE; + } + + /* Dynamic resizing of buttonbars */ + { + int len[2], count[2]; /* at most two lines of buttons */ + int cur_x[2]; + + len[0] = len[1] = 0; + count[0] = count[1] = 0; + cur_x[0] = cur_x[1] = 0; + + /* Count len of buttonbars, assuming 1 extra space between buttons */ + for (i = 0; i < hotlist_but_num; i++) + if ((hotlist_but[i].type & list_type) != 0) + { + int row; + + row = hotlist_but[i].y; + ++count[row]; + len[row] += hotlist_but[i].len + 1; + } + + (len[0])--; + (len[1])--; + + cols = MAX (cols, MAX (len[0], len[1])); + + /* arrange buttons */ + for (i = 0; i < hotlist_but_num; i++) + if ((hotlist_but[i].type & list_type) != 0) + { + int row; + + row = hotlist_but[i].y; + + if (hotlist_but[i].x != 0) + { + /* not first int the row */ + if (hotlist_but[i].ret_cmd == B_CANCEL) + hotlist_but[i].x = cols - hotlist_but[i].len - 6; + else + hotlist_but[i].x = cur_x[row]; + } + + cur_x[row] += hotlist_but[i].len + 1; + } + } + + return cols; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +init_hotlist (hotlist_t list_type) +{ + size_t i; + const char *title, *help_node; + int lines, cols; + int y; + int dh = 0; + WGroup *g; + WGroupbox *path_box; + Widget *hotlist_widget; + + do_refresh (); + + lines = LINES - 2; + cols = init_i18n_stuff (list_type, COLS - 6); + +#ifdef ENABLE_VFS + if (list_type == LIST_VFSLIST) + { + title = _("Active VFS directories"); + help_node = "[vfshot]"; /* FIXME - no such node */ + dh = 1; + } + else +#endif /* !ENABLE_VFS */ + { + title = _("Directory hotlist"); + help_node = "[Hotlist]"; + } + + hotlist_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback, + NULL, help_node, title); + g = GROUP (hotlist_dlg); + + y = UY; + hotlist_group = groupbox_new (y, UX, lines - 10 + dh, cols - 2 * UX, _("Top level group")); + hotlist_widget = WIDGET (hotlist_group); + group_add_widget_autopos (g, hotlist_widget, WPOS_KEEP_ALL, NULL); + + l_hotlist = + listbox_new (y + 1, UX + 1, hotlist_widget->rect.lines - 2, hotlist_widget->rect.cols - 2, + FALSE, hotlist_listbox_callback); + + /* Fill the hotlist with the active VFS or the hotlist */ +#ifdef ENABLE_VFS + if (list_type == LIST_VFSLIST) + { + listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL, + FALSE); + vfs_fill_names (add_name_to_list); + } + else +#endif /* !ENABLE_VFS */ + fill_listbox (l_hotlist); + + /* insert before groupbox to view scrollbar */ + group_add_widget_autopos (g, l_hotlist, WPOS_KEEP_ALL, NULL); + + y += hotlist_widget->rect.lines; + + path_box = groupbox_new (y, UX, 3, hotlist_widget->rect.cols, _("Directory path")); + group_add_widget_autopos (g, path_box, WPOS_KEEP_BOTTOM | WPOS_KEEP_HORZ, NULL); + + pname = label_new (y + 1, UX + 2, ""); + group_add_widget_autopos (g, pname, WPOS_KEEP_BOTTOM | WPOS_KEEP_LEFT, NULL); + y += WIDGET (path_box)->rect.lines; + + group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL); + + for (i = 0; i < hotlist_but_num; i++) + if ((hotlist_but[i].type & list_type) != 0) + group_add_widget_autopos (g, + button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x, + hotlist_but[i].ret_cmd, hotlist_but[i].flags, + hotlist_but[i].text, hotlist_button_callback), + hotlist_but[i].pos_flags, NULL); + + widget_select (WIDGET (l_hotlist)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +init_movelist (struct hotlist *item) +{ + size_t i; + char *hdr; + int lines, cols; + int y; + WGroup *g; + Widget *movelist_widget; + + do_refresh (); + + lines = LINES - 6; + cols = init_i18n_stuff (LIST_MOVELIST, COLS - 6); + + hdr = g_strdup_printf (_("Moving %s"), item->label); + + movelist_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback, + NULL, "[Hotlist]", hdr); + g = GROUP (movelist_dlg); + + g_free (hdr); + + y = UY; + movelist_group = groupbox_new (y, UX, lines - 7, cols - 2 * UX, _("Directory label")); + movelist_widget = WIDGET (movelist_group); + group_add_widget_autopos (g, movelist_widget, WPOS_KEEP_ALL, NULL); + + l_movelist = + listbox_new (y + 1, UX + 1, movelist_widget->rect.lines - 2, movelist_widget->rect.cols - 2, + FALSE, hotlist_listbox_callback); + fill_listbox (l_movelist); + /* insert before groupbox to view scrollbar */ + group_add_widget_autopos (g, l_movelist, WPOS_KEEP_ALL, NULL); + + y += movelist_widget->rect.lines; + + group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL); + + for (i = 0; i < hotlist_but_num; i++) + if ((hotlist_but[i].type & LIST_MOVELIST) != 0) + group_add_widget_autopos (g, + button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x, + hotlist_but[i].ret_cmd, hotlist_but[i].flags, + hotlist_but[i].text, hotlist_button_callback), + hotlist_but[i].pos_flags, NULL); + + widget_select (WIDGET (l_movelist)); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Destroy the list dialog. + * Don't confuse with done_hotlist() for the list in memory. + */ + +static void +hotlist_done (void) +{ + widget_destroy (WIDGET (hotlist_dlg)); + l_hotlist = NULL; +#if 0 + update_panels (UP_OPTIMIZE, UP_KEEPSEL); +#endif + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline char * +find_group_section (struct hotlist *grp) +{ + return g_strconcat (grp->directory, ".Group", (char *) NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +static struct hotlist * +add2hotlist (char *label, char *directory, enum HotListType type, listbox_append_t pos) +{ + struct hotlist *new; + struct hotlist *current = NULL; + + /* + * Hotlist is neither loaded nor loading. + * Must be called by "Ctrl-x a" before using hotlist. + */ + if (current_group == NULL) + load_hotlist (); + + listbox_get_current (l_hotlist, NULL, (void **) ¤t); + + /* Make sure '..' stays at the top of the list. */ + if ((current != NULL) && (current->type == HL_TYPE_DOTDOT)) + pos = LISTBOX_APPEND_AFTER; + + new = g_new0 (struct hotlist, 1); + + new->type = type; + new->label = label; + new->directory = directory; + new->up = current_group; + + if (type == HL_TYPE_GROUP) + { + current_group = new; + add_dotdot_to_list (); + current_group = new->up; + } + + if (current_group->head == NULL) + { + /* first element in group */ + current_group->head = new; + } + else if (pos == LISTBOX_APPEND_AFTER) + { + new->next = current->next; + current->next = new; + } + else if (pos == LISTBOX_APPEND_BEFORE && current == current_group->head) + { + /* should be inserted before first item */ + new->next = current; + current_group->head = new; + } + else if (pos == LISTBOX_APPEND_BEFORE) + { + struct hotlist *p = current_group->head; + + while (p->next != current) + p = p->next; + + new->next = current; + p->next = new; + } + else + { /* append at the end */ + struct hotlist *p = current_group->head; + + while (p->next != NULL) + p = p->next; + + p->next = new; + } + + if (hotlist_state.running && type != HL_TYPE_COMMENT && type != HL_TYPE_DOTDOT) + { + if (type == HL_TYPE_GROUP) + { + char *lbl; + + lbl = g_strconcat ("->", new->label, (char *) NULL); + listbox_add_item (l_hotlist, pos, 0, lbl, new, FALSE); + g_free (lbl); + } + else + listbox_add_item (l_hotlist, pos, 0, new->label, new, FALSE); + listbox_select_entry (l_hotlist, l_hotlist->pos); + } + + return new; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +add_new_entry_input (const char *header, const char *text1, const char *text2, + const char *help, char **r1, char **r2) +{ + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_LABELED_INPUT (text1, input_label_above, *r1, "input-lbl", r1, NULL, + FALSE, FALSE, INPUT_COMPLETE_NONE), + QUICK_SEPARATOR (FALSE), + QUICK_LABELED_INPUT (text2, input_label_above, *r2, "input-lbl", r2, NULL, + FALSE, FALSE, INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD), + QUICK_START_BUTTONS (TRUE, TRUE), + QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL), + QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL), + QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL), + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 64 }; + + quick_dialog_t qdlg = { + r, header, help, + quick_widgets, NULL, NULL + }; + + int ret; + + ret = quick_dialog (&qdlg); + + return (ret != B_CANCEL) ? ret : 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +add_new_entry_cmd (WPanel * panel) +{ + char *title, *url, *to_free; + int ret; + + /* Take current directory as default value for input fields */ + to_free = title = url = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD); + + ret = add_new_entry_input (_("New hotlist entry"), _("Directory label:"), + _("Directory path:"), "[Hotlist]", &title, &url); + g_free (to_free); + + if (ret == 0) + return; + if (title == NULL || *title == '\0' || url == NULL || *url == '\0') + { + g_free (title); + g_free (url); + return; + } + + if (ret == B_ENTER || ret == B_APPEND) + add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AFTER); + else + add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_BEFORE); + + hotlist_state.modified = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +add_new_group_input (const char *header, const char *label, char **result) +{ + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_LABELED_INPUT (label, input_label_above, "", "input", result, NULL, + FALSE, FALSE, INPUT_COMPLETE_NONE), + QUICK_START_BUTTONS (TRUE, TRUE), + QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL), + QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL), + QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL), + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 64 }; + + quick_dialog_t qdlg = { + r, header, "[Hotlist]", + quick_widgets, NULL, NULL + }; + + int ret; + + ret = quick_dialog (&qdlg); + + return (ret != B_CANCEL) ? ret : 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +add_new_group_cmd (void) +{ + char *label; + int ret; + + ret = add_new_group_input (_("New hotlist group"), _("Name of new group:"), &label); + if (ret == 0 || label == NULL || *label == '\0') + return; + + if (ret == B_ENTER || ret == B_APPEND) + add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_AFTER); + else + add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_BEFORE); + + hotlist_state.modified = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +remove_group (struct hotlist *grp) +{ + struct hotlist *current = grp->head; + + while (current != NULL) + { + struct hotlist *next = current->next; + + if (current->type == HL_TYPE_GROUP) + remove_group (current); + + g_free (current->label); + g_free (current->directory); + g_free (current); + + current = next; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +remove_from_hotlist (struct hotlist *entry) +{ + if (entry == NULL) + return; + + if (entry->type == HL_TYPE_DOTDOT) + return; + + if (confirm_directory_hotlist_delete) + { + char text[BUF_MEDIUM]; + int result; + + if (safe_delete) + query_set_sel (1); + + g_snprintf (text, sizeof (text), _("Are you sure you want to remove entry \"%s\"?"), + str_trunc (entry->label, 30)); + result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2, + _("&Yes"), _("&No")); + if (result != 0) + return; + } + + if (entry->type == HL_TYPE_GROUP) + { + struct hotlist *head = entry->head; + + if (head != NULL && (head->type != HL_TYPE_DOTDOT || head->next != NULL)) + { + char text[BUF_MEDIUM]; + int result; + + g_snprintf (text, sizeof (text), _("Group \"%s\" is not empty.\nRemove it?"), + str_trunc (entry->label, 30)); + result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2, + _("&Yes"), _("&No")); + if (result != 0) + return; + } + + remove_group (entry); + } + + unlink_entry (entry); + + g_free (entry->label); + g_free (entry->directory); + g_free (entry); + /* now remove list entry from screen */ + listbox_remove_current (l_hotlist); + hotlist_state.modified = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +load_group (struct hotlist *grp) +{ + gchar **profile_keys, **keys; + char *group_section; + struct hotlist *current = 0; + + group_section = find_group_section (grp); + + keys = mc_config_get_keys (mc_global.main_config, group_section, NULL); + + current_group = grp; + + for (profile_keys = keys; *profile_keys != NULL; profile_keys++) + add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""), + g_strdup (*profile_keys), HL_TYPE_GROUP, LISTBOX_APPEND_AT_END); + + g_strfreev (keys); + + keys = mc_config_get_keys (mc_global.main_config, grp->directory, NULL); + + for (profile_keys = keys; *profile_keys != NULL; profile_keys++) + add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""), + g_strdup (*profile_keys), HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END); + + g_free (group_section); + g_strfreev (keys); + + for (current = grp->head; current; current = current->next) + load_group (current); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +hot_skip_blanks (void) +{ + int c; + + while ((c = getc (hotlist_file)) != EOF && c != '\n' && g_ascii_isspace (c)) + ; + return c; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +hot_next_token (void) +{ + int c, ret = 0; + size_t l; + + if (tkn_buf == NULL) + tkn_buf = g_string_new (""); + g_string_set_size (tkn_buf, 0); + + again: + c = hot_skip_blanks (); + switch (c) + { + case EOF: + ret = TKN_EOF; + break; + case '\n': + ret = TKN_EOL; + break; + case '#': + while ((c = getc (hotlist_file)) != EOF && c != '\n') + g_string_append_c (tkn_buf, c); + ret = TKN_COMMENT; + break; + case '"': + while ((c = getc (hotlist_file)) != EOF && c != '"') + { + if (c == '\\') + { + c = getc (hotlist_file); + if (c == EOF) + { + g_string_free (tkn_buf, TRUE); + return TKN_EOF; + } + } + g_string_append_c (tkn_buf, c == '\n' ? ' ' : c); + } + ret = (c == EOF) ? TKN_EOF : TKN_STRING; + break; + case '\\': + c = getc (hotlist_file); + if (c == EOF) + { + g_string_free (tkn_buf, TRUE); + return TKN_EOF; + } + if (c == '\n') + goto again; + + MC_FALLTHROUGH; /* it is taken as normal character */ + + default: + do + { + g_string_append_c (tkn_buf, g_ascii_toupper (c)); + } + while ((c = fgetc (hotlist_file)) != EOF && (g_ascii_isalnum (c) || !isascii (c))); + if (c != EOF) + ungetc (c, hotlist_file); + l = tkn_buf->len; + if (strncmp (tkn_buf->str, "GROUP", l) == 0) + ret = TKN_GROUP; + else if (strncmp (tkn_buf->str, "ENTRY", l) == 0) + ret = TKN_ENTRY; + else if (strncmp (tkn_buf->str, "ENDGROUP", l) == 0) + ret = TKN_ENDGROUP; + else if (strncmp (tkn_buf->str, "URL", l) == 0) + ret = TKN_URL; + else + ret = TKN_UNKNOWN; + break; + } + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +hot_load_group (struct hotlist *grp) +{ + int tkn; + struct hotlist *new_grp; + char *label, *url; + + current_group = grp; + + while ((tkn = hot_next_token ()) != TKN_ENDGROUP) + switch (tkn) + { + case TKN_GROUP: + CHECK_TOKEN (TKN_STRING); + new_grp = + add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP, + LISTBOX_APPEND_AT_END); + SKIP_TO_EOL; + hot_load_group (new_grp); + current_group = grp; + break; + case TKN_ENTRY: + { + CHECK_TOKEN (TKN_STRING); + label = g_strndup (tkn_buf->str, tkn_buf->len); + CHECK_TOKEN (TKN_URL); + CHECK_TOKEN (TKN_STRING); + url = tilde_expand (tkn_buf->str); + add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END); + SKIP_TO_EOL; + } + break; + case TKN_COMMENT: + label = g_strndup (tkn_buf->str, tkn_buf->len); + add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END); + break; + case TKN_EOF: + hotlist_state.readonly = TRUE; + hotlist_state.file_error = TRUE; + return; + case TKN_EOL: + /* skip empty lines */ + break; + default: + hotlist_state.readonly = TRUE; + hotlist_state.file_error = TRUE; + SKIP_TO_EOL; + break; + } + SKIP_TO_EOL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +hot_load_file (struct hotlist *grp) +{ + int tkn; + struct hotlist *new_grp; + char *label, *url; + + current_group = grp; + + while ((tkn = hot_next_token ()) != TKN_EOF) + switch (tkn) + { + case TKN_GROUP: + CHECK_TOKEN (TKN_STRING); + new_grp = + add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP, + LISTBOX_APPEND_AT_END); + SKIP_TO_EOL; + hot_load_group (new_grp); + current_group = grp; + break; + case TKN_ENTRY: + { + CHECK_TOKEN (TKN_STRING); + label = g_strndup (tkn_buf->str, tkn_buf->len); + CHECK_TOKEN (TKN_URL); + CHECK_TOKEN (TKN_STRING); + url = tilde_expand (tkn_buf->str); + add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END); + SKIP_TO_EOL; + } + break; + case TKN_COMMENT: + label = g_strndup (tkn_buf->str, tkn_buf->len); + add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END); + break; + case TKN_EOL: + /* skip empty lines */ + break; + default: + hotlist_state.readonly = TRUE; + hotlist_state.file_error = TRUE; + SKIP_TO_EOL; + break; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +clean_up_hotlist_groups (const char *section) +{ + char *grp_section; + + grp_section = g_strconcat (section, ".Group", (char *) NULL); + if (mc_config_has_group (mc_global.main_config, section)) + mc_config_del_group (mc_global.main_config, section); + + if (mc_config_has_group (mc_global.main_config, grp_section)) + { + char **profile_keys, **keys; + + keys = mc_config_get_keys (mc_global.main_config, grp_section, NULL); + + for (profile_keys = keys; *profile_keys != NULL; profile_keys++) + clean_up_hotlist_groups (*profile_keys); + + g_strfreev (keys); + mc_config_del_group (mc_global.main_config, grp_section); + } + g_free (grp_section); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +load_hotlist (void) +{ + gboolean remove_old_list = FALSE; + struct stat stat_buf; + + if (hotlist_state.loaded) + { + stat (hotlist_file_name, &stat_buf); + if (hotlist_file_mtime < stat_buf.st_mtime) + done_hotlist (); + else + return; + } + + if (hotlist_file_name == NULL) + hotlist_file_name = mc_config_get_full_path (MC_HOTLIST_FILE); + + hotlist = g_new0 (struct hotlist, 1); + hotlist->type = HL_TYPE_GROUP; + hotlist->label = g_strdup (_("Top level group")); + hotlist->up = hotlist; + /* + * compatibility :-( + */ + hotlist->directory = g_strdup ("Hotlist"); + + hotlist_file = fopen (hotlist_file_name, "r"); + if (hotlist_file == NULL) + { + int result; + + load_group (hotlist); + hotlist_state.loaded = TRUE; + /* + * just to be sure we got copy + */ + hotlist_state.modified = TRUE; + result = save_hotlist (); + hotlist_state.modified = FALSE; + if (result != 0) + remove_old_list = TRUE; + else + message (D_ERROR, _("Hotlist Load"), + _ + ("MC was unable to write %s file,\nyour old hotlist entries were not deleted"), + MC_USERCONF_DIR PATH_SEP_STR MC_HOTLIST_FILE); + } + else + { + hot_load_file (hotlist); + fclose (hotlist_file); + hotlist_state.loaded = TRUE; + } + + if (remove_old_list) + { + GError *mcerror = NULL; + + clean_up_hotlist_groups ("Hotlist"); + if (!mc_config_save_file (mc_global.main_config, &mcerror)) + setup_save_config_show_error (mc_global.main_config->ini_path, &mcerror); + + mc_error_message (&mcerror, NULL); + } + + stat (hotlist_file_name, &stat_buf); + hotlist_file_mtime = stat_buf.st_mtime; + current_group = hotlist; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +hot_save_group (struct hotlist *grp) +{ + struct hotlist *current; + int i; + char *s; + +#define INDENT(n) \ +do { \ + for (i = 0; i < n; i++) \ + putc (' ', hotlist_file); \ +} while (0) + + for (current = grp->head; current != NULL; current = current->next) + switch (current->type) + { + case HL_TYPE_GROUP: + INDENT (list_level); + fputs ("GROUP \"", hotlist_file); + for (s = current->label; *s != '\0'; s++) + { + if (*s == '"' || *s == '\\') + putc ('\\', hotlist_file); + putc (*s, hotlist_file); + } + fputs ("\"\n", hotlist_file); + list_level += 2; + hot_save_group (current); + list_level -= 2; + INDENT (list_level); + fputs ("ENDGROUP\n", hotlist_file); + break; + case HL_TYPE_ENTRY: + INDENT (list_level); + fputs ("ENTRY \"", hotlist_file); + for (s = current->label; *s != '\0'; s++) + { + if (*s == '"' || *s == '\\') + putc ('\\', hotlist_file); + putc (*s, hotlist_file); + } + fputs ("\" URL \"", hotlist_file); + for (s = current->directory; *s != '\0'; s++) + { + if (*s == '"' || *s == '\\') + putc ('\\', hotlist_file); + putc (*s, hotlist_file); + } + fputs ("\"\n", hotlist_file); + break; + case HL_TYPE_COMMENT: + fprintf (hotlist_file, "#%s\n", current->label); + break; + case HL_TYPE_DOTDOT: + /* do nothing */ + break; + default: + break; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +add_dotdot_to_list (void) +{ + if (current_group != hotlist && hotlist_has_dot_dot) + add2hotlist (g_strdup (".."), g_strdup (".."), HL_TYPE_DOTDOT, LISTBOX_APPEND_AT_END); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +add2hotlist_cmd (WPanel * panel) +{ + char *lc_prompt; + const char *cp = N_("Label for \"%s\":"); + int l; + char *label_string, *label; + +#ifdef ENABLE_NLS + cp = _(cp); +#endif + + /* extra variable to use it in the button callback */ + our_panel = panel; + + l = str_term_width1 (cp); + label_string = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD); + lc_prompt = g_strdup_printf (cp, str_trunc (label_string, COLS - 2 * UX - (l + 8))); + label = + input_dialog (_("Add to hotlist"), lc_prompt, MC_HISTORY_HOTLIST_ADD, label_string, + INPUT_COMPLETE_NONE); + g_free (lc_prompt); + + if (label == NULL || *label == '\0') + { + g_free (label_string); + g_free (label); + } + else + { + add2hotlist (label, label_string, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END); + hotlist_state.modified = TRUE; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +char * +hotlist_show (hotlist_t list_type, WPanel * panel) +{ + char *target = NULL; + int res; + + /* extra variable to use it in the button callback */ + our_panel = panel; + + hotlist_state.type = list_type; + load_hotlist (); + + init_hotlist (list_type); + + /* display file info */ + tty_setcolor (SELECTED_COLOR); + + hotlist_state.running = TRUE; + res = dlg_run (hotlist_dlg); + hotlist_state.running = FALSE; + save_hotlist (); + + if (res == B_ENTER) + { + char *text = NULL; + struct hotlist *hlp = NULL; + + listbox_get_current (l_hotlist, &text, (void **) &hlp); + target = g_strdup (hlp != NULL ? hlp->directory : text); + } + + hotlist_done (); + return target; +} + +/* --------------------------------------------------------------------------------------------- */ + +gboolean +save_hotlist (void) +{ + gboolean saved = FALSE; + struct stat stat_buf; + + if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name != NULL) + { + mc_util_make_backup_if_possible (hotlist_file_name, ".bak"); + + hotlist_file = fopen (hotlist_file_name, "w"); + if (hotlist_file == NULL) + mc_util_restore_from_backup_if_possible (hotlist_file_name, ".bak"); + else + { + hot_save_group (hotlist); + fclose (hotlist_file); + stat (hotlist_file_name, &stat_buf); + hotlist_file_mtime = stat_buf.st_mtime; + hotlist_state.modified = FALSE; + saved = TRUE; + } + } + + return saved; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Unload list from memory. + * Don't confuse with hotlist_done() for GUI. + */ + +void +done_hotlist (void) +{ + if (hotlist != NULL) + { + remove_group (hotlist); + g_free (hotlist->label); + g_free (hotlist->directory); + MC_PTR_FREE (hotlist); + } + + hotlist_state.loaded = FALSE; + + MC_PTR_FREE (hotlist_file_name); + l_hotlist = NULL; + current_group = NULL; + + if (tkn_buf != NULL) + { + g_string_free (tkn_buf, TRUE); + tkn_buf = NULL; + } +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/hotlist.h b/src/filemanager/hotlist.h new file mode 100644 index 0000000..94bc305 --- /dev/null +++ b/src/filemanager/hotlist.h @@ -0,0 +1,33 @@ +/** \file hotlist.h + * \brief Header: directory hotlist + */ + +#ifndef MC__HOTLIST_H +#define MC__HOTLIST_H + +/*** typedefs(not structures) and defined constants **********************************************/ + +#include "panel.h" + +/*** enums ***************************************************************************************/ + +typedef enum +{ + LIST_VFSLIST = 0x01, + LIST_HOTLIST = 0x02, + LIST_MOVELIST = 0x04 +} hotlist_t; + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +void add2hotlist_cmd (WPanel * panel); +char *hotlist_show (hotlist_t list_type, WPanel * panel); +gboolean save_hotlist (void); +void done_hotlist (void); + +/*** inline functions ****************************************************************************/ +#endif /* MC__HOTLIST_H */ diff --git a/src/filemanager/info.c b/src/filemanager/info.c new file mode 100644 index 0000000..cb117ac --- /dev/null +++ b/src/filemanager/info.c @@ -0,0 +1,376 @@ +/* + Panel managing. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + 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 info.c + * \brief Source: panel managing + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <inttypes.h> /* PRIuMAX */ +#ifdef ENABLE_EXT2FS_ATTR +#include <e2p/e2p.h> /* fgetflags() */ +#endif + +#include "lib/global.h" +#include "lib/unixcompat.h" +#include "lib/tty/tty.h" +#include "lib/tty/key.h" /* is_idle() */ +#include "lib/skin.h" +#include "lib/strutil.h" +#include "lib/timefmt.h" /* file_date() */ +#include "lib/util.h" +#include "lib/widget.h" + +#include "src/setup.h" /* panels_options */ + +#include "filemanager.h" /* the_menubar */ +#include "layout.h" +#include "mountlist.h" +#ifdef ENABLE_EXT2FS_ATTR +#include "cmd.h" /* chattr_get_as_str() */ +#endif + +#include "info.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +struct WInfo +{ + Widget widget; + gboolean ready; +}; + +/*** file scope variables ************************************************************************/ + +static struct my_statfs myfs_stats; + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +info_box (WInfo * info) +{ + Widget *w = WIDGET (info); + + const char *title = _("Information"); + const int len = str_term_width1 (title); + + tty_set_normal_attrs (); + tty_setcolor (NORMAL_COLOR); + widget_erase (w); + tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE); + + widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2); + tty_printf (" %s ", title); + + widget_gotoyx (w, 2, 0); + tty_print_alt_char (ACS_LTEE, FALSE); + widget_gotoyx (w, 2, w->rect.cols - 1); + tty_print_alt_char (ACS_RTEE, FALSE); + tty_draw_hline (w->rect.y + 2, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +info_show_info (WInfo * info) +{ + const WRect *w = &CONST_WIDGET (info)->rect; + static int i18n_adjust = 0; + static const char *file_label; + GString *buff; + struct stat st; + char rp_cwd[PATH_MAX]; + const char *p_rp_cwd; + + if (!is_idle ()) + return; + + info_box (info); + + tty_setcolor (MARKED_COLOR); + widget_gotoyx (w, 1, 3); + tty_printf (_("Midnight Commander %s"), mc_global.mc_version); + + if (!info->ready) + return; + + if (get_current_type () != view_listing) + return; + + /* don't rely on vpath CWD when cd_symlinks enabled */ + p_rp_cwd = mc_realpath (vfs_path_as_str (current_panel->cwd_vpath), rp_cwd); + if (p_rp_cwd == NULL) + p_rp_cwd = vfs_path_as_str (current_panel->cwd_vpath); + + my_statfs (&myfs_stats, p_rp_cwd); + + st = current_panel->dir.list[current_panel->selected].st; + + /* Print only lines which fit */ + + if (i18n_adjust == 0) + { + /* This printf pattern string is used as a reference for size */ + file_label = _("File: %s"); + i18n_adjust = str_term_width1 (file_label) + 2; + } + + tty_setcolor (NORMAL_COLOR); + + buff = g_string_new (""); + + switch (w->lines - 2) + { + /* Note: all cases are fall-throughs */ + + default: + MC_FALLTHROUGH; + case 17: + widget_gotoyx (w, 17, 3); + if ((myfs_stats.nfree == 0 && myfs_stats.nodes == 0) || + (myfs_stats.nfree == (uintmax_t) (-1) && myfs_stats.nodes == (uintmax_t) (-1))) + tty_print_string (_("No node information")); + else if (myfs_stats.nfree == (uintmax_t) (-1)) + tty_printf ("%s - / %" PRIuMAX, _("Free nodes:"), myfs_stats.nodes); + else if (myfs_stats.nodes == (uintmax_t) (-1)) + tty_printf ("%s %" PRIuMAX " / -", _("Free nodes:"), myfs_stats.nfree); + else + tty_printf ("%s %" PRIuMAX " / %" PRIuMAX " (%d%%)", + _("Free nodes:"), + myfs_stats.nfree, myfs_stats.nodes, + myfs_stats.nodes == 0 ? 0 : + (int) (100 * (long double) myfs_stats.nfree / myfs_stats.nodes)); + MC_FALLTHROUGH; + case 16: + widget_gotoyx (w, 16, 3); + if (myfs_stats.avail == 0 && myfs_stats.total == 0) + tty_print_string (_("No space information")); + else + { + char buffer1[6], buffer2[6]; + + size_trunc_len (buffer1, 5, myfs_stats.avail, 1, panels_options.kilobyte_si); + size_trunc_len (buffer2, 5, myfs_stats.total, 1, panels_options.kilobyte_si); + tty_printf (_("Free space: %s / %s (%d%%)"), buffer1, buffer2, + myfs_stats.total == 0 ? 0 : + (int) (100 * (long double) myfs_stats.avail / myfs_stats.total)); + } + MC_FALLTHROUGH; + case 15: + widget_gotoyx (w, 15, 3); + tty_printf (_("Type: %s"), + myfs_stats.typename ? myfs_stats.typename : _("non-local vfs")); + if (myfs_stats.type != 0xffff && myfs_stats.type != -1) + tty_printf (" (%Xh)", (unsigned int) myfs_stats.type); + MC_FALLTHROUGH; + case 14: + widget_gotoyx (w, 14, 3); + str_printf (buff, _("Device: %s"), + str_trunc (myfs_stats.device, w->cols - i18n_adjust)); + tty_print_string (buff->str); + g_string_set_size (buff, 0); + MC_FALLTHROUGH; + case 13: + widget_gotoyx (w, 13, 3); + str_printf (buff, _("Filesystem: %s"), + str_trunc (myfs_stats.mpoint, w->cols - i18n_adjust)); + tty_print_string (buff->str); + g_string_set_size (buff, 0); + MC_FALLTHROUGH; + case 12: + widget_gotoyx (w, 12, 3); + str_printf (buff, _("Accessed: %s"), file_date (st.st_atime)); + tty_print_string (buff->str); + g_string_set_size (buff, 0); + MC_FALLTHROUGH; + case 11: + widget_gotoyx (w, 11, 3); + str_printf (buff, _("Modified: %s"), file_date (st.st_mtime)); + tty_print_string (buff->str); + g_string_set_size (buff, 0); + MC_FALLTHROUGH; + case 10: + widget_gotoyx (w, 10, 3); + /* The field st_ctime is changed by writing or by setting inode + information (i.e., owner, group, link count, mode, etc.). */ + /* TRANSLATORS: Time of last status change as in stat(2) man. */ + str_printf (buff, _("Changed: %s"), file_date (st.st_ctime)); + tty_print_string (buff->str); + g_string_set_size (buff, 0); + MC_FALLTHROUGH; + case 9: + widget_gotoyx (w, 9, 3); +#ifdef HAVE_STRUCT_STAT_ST_RDEV + if (S_ISCHR (st.st_mode) || S_ISBLK (st.st_mode)) + tty_printf (_("Dev. type: major %lu, minor %lu"), + (unsigned long) major (st.st_rdev), (unsigned long) minor (st.st_rdev)); + else +#endif + { + char buffer[10]; + size_trunc_len (buffer, 9, st.st_size, 0, panels_options.kilobyte_si); + tty_printf (_("Size: %s"), buffer); +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + tty_printf (ngettext (" (%lu block)", " (%lu blocks)", + (unsigned long) st.st_blocks), (unsigned long) st.st_blocks); +#endif + } + MC_FALLTHROUGH; + case 8: + widget_gotoyx (w, 8, 3); + tty_printf (_("Owner: %s/%s"), get_owner (st.st_uid), get_group (st.st_gid)); + MC_FALLTHROUGH; + case 7: + widget_gotoyx (w, 7, 3); + tty_printf (_("Links: %d"), (int) st.st_nlink); + MC_FALLTHROUGH; + case 6: + widget_gotoyx (w, 6, 3); +#ifdef ENABLE_EXT2FS_ATTR + if (!vfs_current_is_local ()) + tty_print_string (_("Attributes: not supported")); + else + { + vfs_path_t *vpath; + unsigned long attr; + + vpath = vfs_path_from_str (current_panel->dir.list[current_panel->selected].fname->str); + + if (fgetflags (vfs_path_as_str (vpath), &attr) == 0) + tty_printf (_("Attributes: %s"), chattr_get_as_str (attr)); + else + tty_print_string (_("Attributes: unavailable")); + + vfs_path_free (vpath, TRUE); + } +#else + tty_print_string (_("Attributes: not supported")); +#endif + MC_FALLTHROUGH; + case 5: + widget_gotoyx (w, 5, 3); + tty_printf (_("Mode: %s (%04o)"), + string_perm (st.st_mode), (unsigned) st.st_mode & 07777); + MC_FALLTHROUGH; + case 4: + widget_gotoyx (w, 4, 3); + tty_printf (_("Location: %Xh:%Xh"), (unsigned int) st.st_dev, (unsigned int) st.st_ino); + MC_FALLTHROUGH; + case 3: + { + const char *fname; + + widget_gotoyx (w, 3, 2); + fname = current_panel->dir.list[current_panel->selected].fname->str; + str_printf (buff, file_label, str_trunc (fname, w->cols - i18n_adjust)); + tty_print_string (buff->str); + } + MC_FALLTHROUGH; + case 2: + MC_FALLTHROUGH; + case 1: + MC_FALLTHROUGH; + case 0: + ; + } /* switch */ + g_string_free (buff, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +info_hook (void *data) +{ + WInfo *info = (WInfo *) data; + Widget *other_widget; + + other_widget = get_panel_widget (get_current_index ()); + if (!other_widget) + return; + if (widget_overlapped (WIDGET (info), other_widget)) + return; + + info->ready = TRUE; + info_show_info (info); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +info_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WInfo *info = (WInfo *) w; + + switch (msg) + { + case MSG_INIT: + init_my_statfs (); + add_hook (&select_file_hook, info_hook, info); + info->ready = FALSE; + return MSG_HANDLED; + + case MSG_DRAW: + info_hook (info); + return MSG_HANDLED; + + case MSG_DESTROY: + delete_hook (&select_file_hook, info_hook); + free_my_statfs (); + return MSG_HANDLED; + + default: + return widget_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +WInfo * +info_new (int y, int x, int lines, int cols) +{ + WRect r = { y, x, lines, cols }; + WInfo *info; + Widget *w; + + info = g_new (struct WInfo, 1); + w = WIDGET (info); + widget_init (w, &r, info_callback, NULL); + + return info; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/info.h b/src/filemanager/info.h new file mode 100644 index 0000000..cba2592 --- /dev/null +++ b/src/filemanager/info.h @@ -0,0 +1,24 @@ +/** \file info.h + * \brief Header: panel managing + */ + +#ifndef MC__INFO_H +#define MC__INFO_H + +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +struct WInfo; +typedef struct WInfo WInfo; + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +WInfo *info_new (int y, int x, int lines, int cols); + +/*** inline functions ****************************************************************************/ +#endif /* MC__INFO_H */ diff --git a/src/filemanager/ioblksize.h b/src/filemanager/ioblksize.h new file mode 100644 index 0000000..3a8b501 --- /dev/null +++ b/src/filemanager/ioblksize.h @@ -0,0 +1,86 @@ +/* I/O block size definitions for coreutils + Copyright (C) 1989-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/>. */ + +/* Include this file _after_ system headers if possible. */ + +/* sys/stat.h will already have been included by system.h. */ +#include "lib/stat-size.h" + +/* *INDENT-OFF* */ + +/* As of May 2014, 128KiB is determined to be the minimium + blksize to best minimize system call overhead. + This can be tested with this script: + + for i in $(seq 0 10); do + bs=$((1024*2**$i)) + printf "%7s=" $bs + timeout --foreground -sINT 2 \ + dd bs=$bs if=/dev/zero of=/dev/null 2>&1 \ + | sed -n 's/.* \([0-9.]* [GM]B\/s\)/\1/p' + done + + With the results shown for these systems: + system #1: 1.7GHz pentium-m with 400MHz DDR2 RAM, arch=i686 + system #2: 2.1GHz i3-2310M with 1333MHz DDR3 RAM, arch=x86_64 + system #3: 3.2GHz i7-970 with 1333MHz DDR3, arch=x86_64 + system #4: 2.20GHz Xeon E5-2660 with 1333MHz DDR3, arch=x86_64 + system #5: 2.30GHz i7-3615QM with 1600MHz DDR3, arch=x86_64 + system #6: 1.30GHz i5-4250U with 1-channel 1600MHz DDR3, arch=x86_64 + system #7: 3.55GHz IBM,8231-E2B with 1066MHz DDR3, POWER7 revision 2.1 + + per-system transfer rate (GB/s) + blksize #1 #2 #3 #4 #5 #6 #7 + ------------------------------------------------------------------------ + 1024 .73 1.7 2.6 .64 1.0 2.5 1.3 + 2048 1.3 3.0 4.4 1.2 2.0 4.4 2.5 + 4096 2.4 5.1 6.5 2.3 3.7 7.4 4.8 + 8192 3.5 7.3 8.5 4.0 6.0 10.4 9.2 + 16384 3.9 9.4 10.1 6.3 8.3 13.3 16.8 + 32768 5.2 9.9 11.1 8.1 10.7 13.2 28.0 + 65536 5.3 11.2 12.0 10.6 12.8 16.1 41.4 + 131072 5.5 11.8 12.3 12.1 14.0 16.7 54.8 + 262144 5.7 11.6 12.5 12.3 14.7 16.4 40.0 + 524288 5.7 11.4 12.5 12.1 14.7 15.5 34.5 + 1048576 5.8 11.4 12.6 12.2 14.9 15.7 36.5 + + + Note that this is to minimize system call overhead. + Other values may be appropriate to minimize file system + or disk overhead. For example on my current GNU/Linux system + the readahead setting is 128KiB which was read using: + + file="." + device=$(df --output=source --local "$file" | tail -n1) + echo $(( $(blockdev --getra $device) * 512 )) + + However there isn't a portable way to get the above. + In the future we could use the above method if available + and default to io_blksize() if not. + */ + + +enum { IO_BUFSIZE = 128 * 1024 }; + +/* *INDENT-ON* */ + +static inline size_t +io_blksize (struct stat sb) +{ + size_t blksize = ST_BLKSIZE (sb); + + return MAX (IO_BUFSIZE, blksize); +} diff --git a/src/filemanager/layout.c b/src/filemanager/layout.c new file mode 100644 index 0000000..d8072df --- /dev/null +++ b/src/filemanager/layout.c @@ -0,0 +1,1577 @@ +/* + Panel layout module for the Midnight Commander + + Copyright (C) 1995-2022 + Free Software Foundation, Inc. + + Written by: + Janne Kukonlehto, 1995 + Miguel de Icaza, 1995 + Andrew Borodin <aborodin@vmail.ru>, 2011-2022 + Slava Zanko <slavazanko@gmail.com>, 2013 + Avi Kelman <patcherton.fixesthings@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 layout.c + * \brief Source: panel layout module + */ + +#include <config.h> + +#include <pwd.h> /* for username in xterm title */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include "lib/global.h" +#include "lib/tty/tty.h" +#include "lib/skin.h" +#include "lib/tty/key.h" +#include "lib/tty/mouse.h" +#include "lib/mcconfig.h" +#include "lib/vfs/vfs.h" /* For _vfs_get_cwd () */ +#include "lib/strutil.h" +#include "lib/widget.h" +#include "lib/event.h" +#include "lib/util.h" /* mc_time_elapsed() */ + +#include "src/consaver/cons.saver.h" +#include "src/viewer/mcviewer.h" /* The view widget */ +#include "src/setup.h" +#ifdef ENABLE_SUBSHELL +#include "src/subshell/subshell.h" +#endif + +#include "command.h" +#include "filemanager.h" +#include "tree.h" +/* Needed for the extern declarations of integer parameters */ +#include "dir.h" +#include "layout.h" +#include "info.h" /* The Info widget */ + +/*** global variables ****************************************************************************/ + +panels_layout_t panels_layout = { + /* Set if the panels are split horizontally */ + .horizontal_split = FALSE, + + /* vertical split */ + .vertical_equal = TRUE, + .left_panel_size = 0, + + /* horizontal split */ + .horizontal_equal = TRUE, + .top_panel_size = 0 +}; + +/* Controls the display of the rotating dash on the verbose mode */ +gboolean nice_rotating_dash = TRUE; + +/* The number of output lines shown (if available) */ +int output_lines = 0; + +/* Set if the command prompt is to be displayed */ +gboolean command_prompt = TRUE; + +/* Set if the main menu is visible */ +gboolean menubar_visible = TRUE; + +/* Set to show current working dir in xterm window title */ +gboolean xterm_title = TRUE; + +/* Set to show free space on device assigned to current directory */ +gboolean free_space = TRUE; + +/* The starting line for the output of the subprogram */ +int output_start_y = 0; + +int ok_to_refresh = 1; + +/*** file scope macro definitions ****************************************************************/ + +/* The maximum number of views managed by the create_panel routine */ +/* Must be at least two (for current and other). Please note that until */ +/* Janne gets around this, we will only manage two of them :-) */ +#define MAX_VIEWS 2 + +/* Width 12 for a wee Quick (Hex) View */ +#define MINWIDTH 12 +#define MINHEIGHT 5 + +#define B_2LEFT B_USER +#define B_2RIGHT (B_USER + 1) +#define B_PLUS (B_USER + 2) +#define B_MINUS (B_USER + 3) + +#define LAYOUT_OPTIONS_COUNT G_N_ELEMENTS (check_options) + +/*** file scope type declarations ****************************************************************/ + +typedef struct +{ + gboolean menubar_visible; + gboolean command_prompt; + gboolean keybar_visible; + gboolean message_visible; + gboolean xterm_title; + gboolean free_space; + int output_lines; +} layout_t; + +/*** file scope variables ************************************************************************/ + +static struct +{ + panel_view_mode_t type; + Widget *widget; + char *last_saved_dir; /* last view_list working directory */ +} panels[MAX_VIEWS] = +{ + /* *INDENT-OFF* */ + /* init MAX_VIEWS items */ + { view_listing, NULL, NULL}, + { view_listing, NULL, NULL} + /* *INDENT-ON* */ +}; + +static layout_t old_layout; +static panels_layout_t old_panels_layout; + +static gboolean equal_split; +static int _output_lines; + +static int height; + +static WRadio *radio_widget; + +static struct +{ + const char *text; + gboolean *variable; + WCheck *widget; +} check_options[] = +{ + /* *INDENT-OFF* */ + { N_("&Equal split"), &equal_split, NULL }, + { N_("&Menubar visible"), &menubar_visible, NULL }, + { N_("Command &prompt"), &command_prompt, NULL }, + { N_("&Keybar visible"), &mc_global.keybar_visible, NULL }, + { N_("H&intbar visible"), &mc_global.message_visible, NULL }, + { N_("&XTerm window title"), &xterm_title, NULL }, + { N_("&Show free space"), &free_space, NULL } + /* *INDENT-ON* */ +}; + +static const char *output_lines_label = NULL; +static int output_lines_label_len; + +static WButton *bleft_widget, *bright_widget; + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* don't use max() macro to avoid double call of str_term_width1() in widget width calculation */ +#undef max + +static int +max (int a, int b) +{ + return a > b ? a : b; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +check_split (panels_layout_t * layout) +{ + if (layout->horizontal_split) + { + if (layout->horizontal_equal) + layout->top_panel_size = height / 2; + else if (layout->top_panel_size < MINHEIGHT) + layout->top_panel_size = MINHEIGHT; + else if (layout->top_panel_size > height - MINHEIGHT) + layout->top_panel_size = height - MINHEIGHT; + } + else + { + int md_cols = CONST_WIDGET (filemanager)->rect.cols; + + if (layout->vertical_equal) + layout->left_panel_size = md_cols / 2; + else if (layout->left_panel_size < MINWIDTH) + layout->left_panel_size = MINWIDTH; + else if (layout->left_panel_size > md_cols - MINWIDTH) + layout->left_panel_size = md_cols - MINWIDTH; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +update_split (const WDialog * h) +{ + /* Check split has to be done before testing if it changed, since + it can change due to calling check_split() as well */ + check_split (&panels_layout); + + if (panels_layout.horizontal_split) + check_options[0].widget->state = panels_layout.horizontal_equal; + else + check_options[0].widget->state = panels_layout.vertical_equal; + widget_draw (WIDGET (check_options[0].widget)); + + tty_setcolor (check_options[0].widget->state ? DISABLED_COLOR : COLOR_NORMAL); + + widget_gotoyx (h, 6, 5); + if (panels_layout.horizontal_split) + tty_printf ("%03d", panels_layout.top_panel_size); + else + tty_printf ("%03d", panels_layout.left_panel_size); + + widget_gotoyx (h, 6, 17); + if (panels_layout.horizontal_split) + tty_printf ("%03d", height - panels_layout.top_panel_size); + else + tty_printf ("%03d", CONST_WIDGET (filemanager)->rect.cols - panels_layout.left_panel_size); + + widget_gotoyx (h, 6, 12); + tty_print_char ('='); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +b_left_right_cback (WButton * button, int action) +{ + (void) action; + + if (button == bright_widget) + { + if (panels_layout.horizontal_split) + panels_layout.top_panel_size++; + else + panels_layout.left_panel_size++; + } + else + { + if (panels_layout.horizontal_split) + panels_layout.top_panel_size--; + else + panels_layout.left_panel_size--; + } + + update_split (DIALOG (WIDGET (button)->owner)); + layout_change (); + do_refresh (); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +bplus_cback (WButton * button, int action) +{ + (void) button; + (void) action; + + if (_output_lines < 99) + _output_lines++; + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +bminus_cback (WButton * button, int action) +{ + (void) button; + (void) action; + + if (_output_lines > 0) + _output_lines--; + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +layout_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_DRAW: + frame_callback (w, NULL, MSG_DRAW, 0, NULL); + + old_layout.output_lines = -1; + + update_split (DIALOG (w->owner)); + + if (old_layout.output_lines != _output_lines) + { + old_layout.output_lines = _output_lines; + tty_setcolor (mc_global.tty.console_flag != '\0' ? COLOR_NORMAL : DISABLED_COLOR); + widget_gotoyx (w, 9, 5); + tty_print_string (output_lines_label); + widget_gotoyx (w, 9, 5 + 3 + output_lines_label_len); + tty_printf ("%02d", _output_lines); + } + return MSG_HANDLED; + + default: + return frame_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +layout_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_POST_KEY: + { + const Widget *mw = CONST_WIDGET (filemanager); + gboolean _menubar_visible, _command_prompt, _keybar_visible, _message_visible; + + _menubar_visible = check_options[1].widget->state; + _command_prompt = check_options[2].widget->state; + _keybar_visible = check_options[3].widget->state; + _message_visible = check_options[4].widget->state; + + if (mc_global.tty.console_flag == '\0') + height = + mw->rect.lines - (_keybar_visible ? 1 : 0) - (_command_prompt ? 1 : 0) - + (_menubar_visible ? 1 : 0) - _output_lines - (_message_visible ? 1 : 0); + else + { + int minimum; + + if (_output_lines < 0) + _output_lines = 0; + height = + mw->rect.lines - (_keybar_visible ? 1 : 0) - (_command_prompt ? 1 : 0) - + (_menubar_visible ? 1 : 0) - _output_lines - (_message_visible ? 1 : 0); + minimum = MINHEIGHT * (1 + (panels_layout.horizontal_split ? 1 : 0)); + if (height < minimum) + { + _output_lines -= minimum - height; + height = minimum; + } + } + + if (old_layout.output_lines != _output_lines) + { + old_layout.output_lines = _output_lines; + tty_setcolor (mc_global.tty.console_flag != '\0' ? COLOR_NORMAL : DISABLED_COLOR); + widget_gotoyx (h, 9, 5 + 3 + output_lines_label_len); + tty_printf ("%02d", _output_lines); + } + } + return MSG_HANDLED; + + case MSG_NOTIFY: + if (sender == WIDGET (radio_widget)) + { + if ((panels_layout.horizontal_split ? 1 : 0) == radio_widget->sel) + update_split (h); + else + { + int eq; + + panels_layout.horizontal_split = radio_widget->sel != 0; + + if (panels_layout.horizontal_split) + { + eq = panels_layout.horizontal_equal; + if (eq) + panels_layout.top_panel_size = height / 2; + } + else + { + eq = panels_layout.vertical_equal; + if (eq) + panels_layout.left_panel_size = CONST_WIDGET (filemanager)->rect.cols / 2; + } + + widget_disable (WIDGET (bleft_widget), eq); + widget_disable (WIDGET (bright_widget), eq); + + update_split (h); + layout_change (); + do_refresh (); + } + + return MSG_HANDLED; + } + + if (sender == WIDGET (check_options[0].widget)) + { + gboolean eq; + + if (panels_layout.horizontal_split) + { + panels_layout.horizontal_equal = check_options[0].widget->state; + eq = panels_layout.horizontal_equal; + } + else + { + panels_layout.vertical_equal = check_options[0].widget->state; + eq = panels_layout.vertical_equal; + } + + widget_disable (WIDGET (bleft_widget), eq); + widget_disable (WIDGET (bright_widget), eq); + + update_split (h); + layout_change (); + do_refresh (); + + return MSG_HANDLED; + } + + { + gboolean ok = TRUE; + + if (sender == WIDGET (check_options[1].widget)) + menubar_visible = check_options[1].widget->state; + else if (sender == WIDGET (check_options[2].widget)) + command_prompt = check_options[2].widget->state; + else if (sender == WIDGET (check_options[3].widget)) + mc_global.keybar_visible = check_options[3].widget->state; + else if (sender == WIDGET (check_options[4].widget)) + mc_global.message_visible = check_options[4].widget->state; + else if (sender == WIDGET (check_options[5].widget)) + xterm_title = check_options[5].widget->state; + else if (sender == WIDGET (check_options[6].widget)) + free_space = check_options[6].widget->state; + else + ok = FALSE; + + if (ok) + { + update_split (h); + layout_change (); + do_refresh (); + return MSG_HANDLED; + } + } + + return MSG_NOT_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static WDialog * +layout_dlg_create (void) +{ + WDialog *layout_dlg; + WGroup *g; + int l1 = 0, width; + int b1, b2, b; + size_t i; + + const char *title1 = N_("Panel split"); + const char *title2 = N_("Console output"); + const char *title3 = N_("Other options"); + + const char *s_split_direction[2] = { + N_("&Vertical"), + N_("&Horizontal") + }; + + const char *ok_button = N_("&OK"); + const char *cancel_button = N_("&Cancel"); + + output_lines_label = _("Output lines:"); + +#ifdef ENABLE_NLS + { + static gboolean i18n = FALSE; + + title1 = _(title1); + title2 = _(title2); + title3 = _(title3); + + i = G_N_ELEMENTS (s_split_direction); + while (i-- != 0) + s_split_direction[i] = _(s_split_direction[i]); + + if (!i18n) + { + for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++) + check_options[i].text = _(check_options[i].text); + i18n = TRUE; + } + + ok_button = _(ok_button); + cancel_button = _(cancel_button); + } +#endif + + /* radiobuttons */ + i = G_N_ELEMENTS (s_split_direction); + while (i-- != 0) + l1 = max (l1, str_term_width1 (s_split_direction[i]) + 7); + /* checkboxes */ + for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++) + l1 = max (l1, str_term_width1 (check_options[i].text) + 7); + /* groupboxes */ + l1 = max (l1, str_term_width1 (title1) + 4); + l1 = max (l1, str_term_width1 (title2) + 4); + l1 = max (l1, str_term_width1 (title3) + 4); + /* label + "+"/"-" buttons */ + output_lines_label_len = str_term_width1 (output_lines_label); + l1 = max (l1, output_lines_label_len + 12); + /* buttons */ + b1 = str_term_width1 (ok_button) + 5; /* default button */ + b2 = str_term_width1 (cancel_button) + 3; + b = b1 + b2 + 1; + /* dialog width */ + width = max (l1 * 2 + 7, b); + + layout_dlg = + dlg_create (TRUE, 0, 0, 15, width, WPOS_CENTER, FALSE, dialog_colors, layout_callback, NULL, + "[Layout]", _("Layout")); + g = GROUP (layout_dlg); + + /* draw background */ + layout_dlg->bg->callback = layout_bg_callback; + +#define XTRACT(i) (*check_options[i].variable != 0), check_options[i].text + + /* "Panel split" groupbox */ + group_add_widget (g, groupbox_new (2, 3, 6, l1, title1)); + + radio_widget = radio_new (3, 5, 2, s_split_direction); + radio_widget->sel = panels_layout.horizontal_split ? 1 : 0; + group_add_widget (g, radio_widget); + + check_options[0].widget = check_new (5, 5, XTRACT (0)); + group_add_widget (g, check_options[0].widget); + + equal_split = panels_layout.horizontal_split ? + panels_layout.horizontal_equal : panels_layout.vertical_equal; + + bleft_widget = button_new (6, 8, B_2LEFT, NARROW_BUTTON, "&<", b_left_right_cback); + widget_disable (WIDGET (bleft_widget), equal_split); + group_add_widget (g, bleft_widget); + + bright_widget = button_new (6, 14, B_2RIGHT, NARROW_BUTTON, "&>", b_left_right_cback); + widget_disable (WIDGET (bright_widget), equal_split); + group_add_widget (g, bright_widget); + + /* "Console output" groupbox */ + { + widget_state_t disabled; + Widget *w; + + disabled = mc_global.tty.console_flag != '\0' ? 0 : WST_DISABLED; + + w = WIDGET (groupbox_new (8, 3, 3, l1, title2)); + w->state |= disabled; + group_add_widget (g, w); + + w = WIDGET (button_new (9, output_lines_label_len + 5, B_PLUS, + NARROW_BUTTON, "&+", bplus_cback)); + w->state |= disabled; + group_add_widget (g, w); + + w = WIDGET (button_new (9, output_lines_label_len + 5 + 5, B_MINUS, + NARROW_BUTTON, "&-", bminus_cback)); + w->state |= disabled; + group_add_widget (g, w); + } + + /* "Other options" groupbox */ + group_add_widget (g, groupbox_new (2, 4 + l1, 9, l1, title3)); + + for (i = 1; i < (size_t) LAYOUT_OPTIONS_COUNT; i++) + { + check_options[i].widget = check_new (i + 2, 6 + l1, XTRACT (i)); + group_add_widget (g, check_options[i].widget); + } + +#undef XTRACT + + group_add_widget (g, hline_new (11, -1, -1)); + /* buttons */ + group_add_widget (g, button_new (12, (width - b) / 2, B_ENTER, DEFPUSH_BUTTON, ok_button, 0)); + group_add_widget (g, + button_new (12, (width - b) / 2 + b1 + 1, B_CANCEL, NORMAL_BUTTON, + cancel_button, 0)); + + widget_select (WIDGET (radio_widget)); + + return layout_dlg; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_do_cols (int idx) +{ + if (get_panel_type (idx) == view_listing) + set_panel_formats (PANEL (panels[idx].widget)); + else + panel_update_cols (panels[idx].widget, frame_half); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Save current list_view widget directory into panel */ + +static Widget * +restore_into_right_dir_panel (int idx, gboolean last_was_panel, int y, int x, int lines, int cols) +{ + WPanel *new_widget; + const char *p_name; + + p_name = get_nth_panel_name (idx); + + if (last_was_panel) + { + vfs_path_t *saved_dir_vpath; + + saved_dir_vpath = vfs_path_from_str (panels[idx].last_saved_dir); + new_widget = panel_sized_with_dir_new (p_name, y, x, lines, cols, saved_dir_vpath); + vfs_path_free (saved_dir_vpath, TRUE); + } + else + new_widget = panel_sized_new (p_name, y, x, lines, cols); + + return WIDGET (new_widget); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +layout_save (void) +{ + old_layout.menubar_visible = menubar_visible; + old_layout.command_prompt = command_prompt; + old_layout.keybar_visible = mc_global.keybar_visible; + old_layout.message_visible = mc_global.message_visible; + old_layout.xterm_title = xterm_title; + old_layout.free_space = free_space; + old_layout.output_lines = -1; + + _output_lines = output_lines; + + old_panels_layout = panels_layout; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +layout_restore (void) +{ + menubar_visible = old_layout.menubar_visible; + command_prompt = old_layout.command_prompt; + mc_global.keybar_visible = old_layout.keybar_visible; + mc_global.message_visible = old_layout.message_visible; + xterm_title = old_layout.xterm_title; + free_space = old_layout.free_space; + output_lines = old_layout.output_lines; + + panels_layout = old_panels_layout; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +layout_change (void) +{ + setup_panels (); + /* update the main menu, because perhaps there was a change in the way + how the panel are split (horizontal/vertical), + and a change of menu visibility. */ + update_menu (); + load_hint (TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +layout_box (void) +{ + WDialog *layout_dlg; + + layout_save (); + + layout_dlg = layout_dlg_create (); + + if (dlg_run (layout_dlg) == B_ENTER) + { + size_t i; + + for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++) + if (check_options[i].widget != NULL) + *check_options[i].variable = check_options[i].widget->state; + + output_lines = _output_lines; + } + else + layout_restore (); + + widget_destroy (WIDGET (layout_dlg)); + layout_change (); + do_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_update_cols (Widget * widget, panel_display_t frame_size) +{ + const Widget *mw = CONST_WIDGET (filemanager); + int cols, x; + + /* don't touch panel if it is not in dialog yet */ + /* if panel is not in dialog it is not in widgets list + and cannot be compared with get_panel_widget() result */ + if (widget->owner == NULL) + return; + + if (panels_layout.horizontal_split) + { + widget->rect.cols = mw->rect.cols; + return; + } + + if (frame_size == frame_full) + { + cols = mw->rect.cols; + x = mw->rect.x; + } + else if (widget == get_panel_widget (0)) + { + cols = panels_layout.left_panel_size; + x = mw->rect.x; + } + else + { + cols = mw->rect.cols - panels_layout.left_panel_size; + x = mw->rect.x + panels_layout.left_panel_size; + } + + widget->rect.cols = cols; + widget->rect.x = x; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +setup_panels (void) +{ + /* File manager screen layout: + * + * +---------------------------------------------------------------+ + * | Menu bar | + * +-------------------------------+-------------------------------+ + * | | | + * | | | + * | | | + * | | | + * | Left panel | Right panel | + * | | | + * | | | + * | | | + * | | | + * +-------------------------------+-------------------------------+ + * | Hint (message) bar | + * +---------------------------------------------------------------+ + * | | + * | Console content | + * | | + * +--------+------------------------------------------------------+ + * | Prompt | Command line | + * | Key (button) bar | + * +--------+------------------------------------------------------+ + */ + + Widget *mw = WIDGET (filemanager); + const WRect *r = &CONST_WIDGET (mw)->rect; + int start_y; + gboolean active; + WRect rb; + + active = widget_get_state (mw, WST_ACTIVE); + + /* lock the group to avoid many redraws */ + if (active) + widget_set_state (mw, WST_SUSPENDED, TRUE); + + /* iniitial height of panels */ + height = + r->lines - (menubar_visible ? 1 : 0) - (mc_global.message_visible ? 1 : 0) - + (command_prompt ? 1 : 0) - (mc_global.keybar_visible ? 1 : 0); + + if (mc_global.tty.console_flag != '\0') + { + int minimum; + + if (output_lines < 0) + output_lines = 0; + else + height -= output_lines; + minimum = MINHEIGHT * (1 + (panels_layout.horizontal_split ? 1 : 0)); + if (height < minimum) + { + output_lines -= minimum - height; + height = minimum; + } + } + + rb = *r; + rb.lines = 1; + widget_set_size_rect (WIDGET (the_menubar), &rb); + widget_set_visibility (WIDGET (the_menubar), menubar_visible); + + check_split (&panels_layout); + start_y = r->y + (menubar_visible ? 1 : 0); + + /* update columns first... */ + panel_do_cols (0); + panel_do_cols (1); + + /* ...then rows and origin */ + if (panels_layout.horizontal_split) + { + widget_set_size (panels[0].widget, start_y, r->x, panels_layout.top_panel_size, + panels[0].widget->rect.cols); + widget_set_size (panels[1].widget, start_y + panels_layout.top_panel_size, r->x, + height - panels_layout.top_panel_size, panels[1].widget->rect.cols); + } + else + { + widget_set_size (panels[0].widget, start_y, r->x, height, panels[0].widget->rect.cols); + widget_set_size (panels[1].widget, start_y, panels[1].widget->rect.x, height, + panels[1].widget->rect.cols); + } + + widget_set_size (WIDGET (the_hint), height + start_y, r->x, 1, r->cols); + widget_set_visibility (WIDGET (the_hint), mc_global.message_visible); + + /* Output window */ + if (mc_global.tty.console_flag != '\0' && output_lines != 0) + { + unsigned char end_line; + + end_line = r->lines - (mc_global.keybar_visible ? 1 : 0) - 1; + output_start_y = end_line - (command_prompt ? 1 : 0) - output_lines + 1; + show_console_contents (output_start_y, end_line - output_lines, end_line); + } + + if (command_prompt) + { +#ifdef ENABLE_SUBSHELL + if (!mc_global.tty.use_subshell || !do_load_prompt ()) +#endif + setup_cmdline (); + } + else + { + /* make invisible */ + widget_hide (WIDGET (cmdline)); + widget_hide (WIDGET (the_prompt)); + } + + rb = *r; + rb.y = r->lines - 1; + rb.lines = 1; + widget_set_size_rect (WIDGET (the_bar), &rb); + widget_set_visibility (WIDGET (the_bar), mc_global.keybar_visible); + + update_xterm_title_path (); + + /* unlock */ + if (active) + { + widget_set_state (mw, WST_ACTIVE, TRUE); + widget_draw (mw); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panels_split_equal (void) +{ + if (panels_layout.horizontal_split) + panels_layout.horizontal_equal = TRUE; + else + panels_layout.vertical_equal = TRUE; + + layout_change (); + do_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panels_split_more (void) +{ + if (panels_layout.horizontal_split) + { + panels_layout.horizontal_equal = FALSE; + panels_layout.top_panel_size++; + } + else + { + panels_layout.vertical_equal = FALSE; + panels_layout.left_panel_size++; + } + + layout_change (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panels_split_less (void) +{ + if (panels_layout.horizontal_split) + { + panels_layout.horizontal_equal = FALSE; + panels_layout.top_panel_size--; + } + else + { + panels_layout.vertical_equal = FALSE; + panels_layout.left_panel_size--; + } + + layout_change (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +setup_cmdline (void) +{ + const Widget *mw = CONST_WIDGET (filemanager); + const WRect *r = &mw->rect; + int prompt_width; + int y; + char *tmp_prompt = (char *) mc_prompt; + + if (!command_prompt) + return; + +#ifdef ENABLE_SUBSHELL + if (mc_global.tty.use_subshell) + { + /* Workaround: avoid crash on FreeBSD (see ticket #4213 for details) */ + if (subshell_prompt != NULL) + tmp_prompt = g_string_free (subshell_prompt, FALSE); + else + tmp_prompt = g_strdup (mc_prompt); + (void) strip_ctrl_codes (tmp_prompt); + } +#endif + + prompt_width = str_term_width1 (tmp_prompt); + + /* Check for prompts too big */ + if (r->cols > 8 && prompt_width > r->cols - 8) + { + int prompt_len; + + prompt_width = r->cols - 8; + prompt_len = str_offset_to_pos (tmp_prompt, prompt_width); + tmp_prompt[prompt_len] = '\0'; + } + +#ifdef ENABLE_SUBSHELL + if (mc_global.tty.use_subshell) + { + subshell_prompt = g_string_new (tmp_prompt); + g_free (tmp_prompt); + mc_prompt = subshell_prompt->str; + } +#endif + + y = r->lines - 1 - (mc_global.keybar_visible ? 1 : 0); + + widget_set_size (WIDGET (the_prompt), y, r->x, 1, prompt_width); + label_set_text (the_prompt, mc_prompt); + widget_set_size (WIDGET (cmdline), y, r->x + prompt_width, 1, r->cols - prompt_width); + + widget_show (WIDGET (the_prompt)); + widget_show (WIDGET (cmdline)); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +use_dash (gboolean flag) +{ + if (flag) + ok_to_refresh++; + else + ok_to_refresh--; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +set_hintbar (const char *str) +{ + label_set_text (the_hint, str); + if (ok_to_refresh > 0) + mc_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +rotate_dash (gboolean show) +{ + static gint64 timestamp = 0; + /* update with 10 FPS rate */ + static const gint64 delay = G_USEC_PER_SEC / 10; + + const Widget *w = CONST_WIDGET (filemanager); + + if (!nice_rotating_dash || (ok_to_refresh <= 0)) + return; + + if (show && !mc_time_elapsed (×tamp, delay)) + return; + + widget_gotoyx (w, menubar_visible ? 1 : 0, w->rect.cols - 1); + tty_setcolor (NORMAL_COLOR); + + if (!show) + tty_print_alt_char (ACS_URCORNER, FALSE); + else + { + static const char rotating_dash[4] = "|/-\\"; + static size_t pos = 0; + + tty_print_char (rotating_dash[pos]); + pos = (pos + 1) % sizeof (rotating_dash); + } + + mc_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +const char * +get_nth_panel_name (int num) +{ + if (num == 0) + return "New Left Panel"; + + if (num == 1) + return "New Right Panel"; + + { + static char buffer[BUF_SMALL]; + + g_snprintf (buffer, sizeof (buffer), "%ith Panel", num); + return buffer; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/* I wonder if I should start to use the folding mode than Dugan uses */ +/* */ +/* This is the centralized managing of the panel display types */ +/* This routine takes care of destroying and creating new widgets */ +/* Please note that it could manage MAX_VIEWS, not just left and right */ +/* Currently nothing in the code takes advantage of this and has hard- */ +/* coded values for two panels only */ + +/* Set the num-th panel to the view type: type */ +/* This routine also keeps at least one WPanel object in the screen */ +/* since a lot of routines depend on the current_panel variable */ + +void +create_panel (int num, panel_view_mode_t type) +{ + WRect r = { 0, 0, 0, 0 }; + unsigned int the_other = 0; /* Index to the other panel */ + const char *file_name = NULL; /* For Quick view */ + Widget *new_widget = NULL, *old_widget = NULL; + panel_view_mode_t old_type = view_listing; + WPanel *the_other_panel = NULL; + + if (num >= MAX_VIEWS) + { + fprintf (stderr, "Cannot allocate more that %d views\n", MAX_VIEWS); + abort (); + } + /* Check that we will have a WPanel * at least */ + if (type != view_listing) + { + the_other = num == 0 ? 1 : 0; + + if (panels[the_other].type != view_listing) + return; + } + + /* Get rid of it */ + if (panels[num].widget != NULL) + { + Widget *w = panels[num].widget; + WPanel *panel = PANEL (w); + + r = w->rect; + old_widget = w; + old_type = panels[num].type; + + if (old_type == view_listing && panel->frame_size == frame_full && type != view_listing) + { + int md_cols = CONST_WIDGET (filemanager)->rect.cols; + + if (panels_layout.horizontal_split) + { + r.cols = md_cols; + r.x = 0; + } + else + { + r.cols = md_cols - panels_layout.left_panel_size; + if (num == 1) + r.x = panels_layout.left_panel_size; + } + } + } + + /* Restoring saved path from panels.ini for nonlist panel */ + /* when it's first creation (for example view_info) */ + if (old_widget == NULL && type != view_listing) + panels[num].last_saved_dir = _vfs_get_cwd (); + + switch (type) + { + case view_nothing: + case view_listing: + { + gboolean last_was_panel; + + last_was_panel = old_widget != NULL && get_panel_type (num) != view_listing; + new_widget = + restore_into_right_dir_panel (num, last_was_panel, r.y, r.x, r.lines, r.cols); + break; + } + + case view_info: + new_widget = WIDGET (info_new (r.y, r.x, r.lines, r.cols)); + break; + + case view_tree: + new_widget = WIDGET (tree_new (r.y, r.x, r.lines, r.cols, TRUE)); + break; + + case view_quick: + new_widget = WIDGET (mcview_new (r.y, r.x, r.lines, r.cols, TRUE)); + the_other_panel = PANEL (panels[the_other].widget); + if (the_other_panel != NULL) + file_name = the_other_panel->dir.list[the_other_panel->selected].fname->str; + else + file_name = ""; + + mcview_load ((WView *) new_widget, 0, file_name, 0, 0, 0); + break; + + default: + break; + } + + if (type != view_listing) + /* Must save dir, for restoring after change type to */ + /* view_listing */ + save_panel_dir (num); + + panels[num].type = type; + panels[num].widget = new_widget; + + /* We use replace to keep the circular list of the dialog in the */ + /* same state. Maybe we could just kill it and then replace it */ + if (old_widget != NULL) + { + if (old_type == view_listing) + { + /* save and write directory history of panel + * ... and other histories of filemanager */ + dlg_save_history (filemanager); + } + + widget_replace (old_widget, new_widget); + } + + if (type == view_listing) + { + WPanel *panel = PANEL (new_widget); + + /* if existing panel changed type to view_listing, then load history */ + if (old_widget != NULL) + { + ev_history_load_save_t event_data = { NULL, new_widget }; + + mc_event_raise (filemanager->event_group, MCEVENT_HISTORY_LOAD, &event_data); + } + + if (num == 0) + left_panel = panel; + else + right_panel = panel; + + /* forced update format after set new sizes */ + set_panel_formats (panel); + } + + if (type == view_tree) + the_tree = (WTree *) new_widget; + + /* Prevent current_panel's value from becoming invalid. + * It's just a quick hack to prevent segfaults. Comment out and + * try following: + * - select left panel + * - invoke menue left/tree + * - as long as you stay in the left panel almost everything that uses + * current_panel causes segfault, e.g. C-Enter, C-x c, ... + */ + if ((type != view_listing) && (current_panel == PANEL (old_widget))) + current_panel = num == 0 ? right_panel : left_panel; + + g_free (old_widget); +} + +/* --------------------------------------------------------------------------------------------- */ +/** This routine is deeply sticked to the two panels idea. + What should it do in more panels. ANSWER - don't use it + in any multiple panels environment. */ + +void +swap_panels (void) +{ + WPanel *panel1, *panel2; + Widget *tmp_widget; + + panel1 = PANEL (panels[0].widget); + panel2 = PANEL (panels[1].widget); + + if (panels[0].type == view_listing && panels[1].type == view_listing && + !mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION, "simple_swap", FALSE)) + { + WPanel panel; + +#define panelswap(x) panel.x = panel1->x; panel1->x = panel2->x; panel2->x = panel.x; + /* Change content and related stuff */ + panelswap (dir); + panelswap (active); + panelswap (cwd_vpath); + panelswap (lwd_vpath); + panelswap (marked); + panelswap (dirs_marked); + panelswap (total); + panelswap (top_file); + panelswap (selected); + panelswap (is_panelized); + panelswap (dir_stat); + panelswap (sort_field); +#undef panelswap + + panel1->quick_search.active = FALSE; + panel2->quick_search.active = FALSE; + + if (current_panel == panel1) + current_panel = panel2; + else + current_panel = panel1; + + /* if sort options are different -> resort panels */ + if (memcmp (&panel1->sort_info, &panel2->sort_info, sizeof (dir_sort_options_t)) != 0) + { + panel_re_sort (other_panel); + panel_re_sort (current_panel); + } + + if (widget_is_active (panels[0].widget)) + widget_select (panels[1].widget); + else if (widget_is_active (panels[1].widget)) + widget_select (panels[0].widget); + } + else + { + WPanel *tmp_panel; + WRect r; + int tmp_type; + + tmp_panel = right_panel; + right_panel = left_panel; + left_panel = tmp_panel; + + if (panels[0].type == view_listing) + { + if (strcmp (panel1->name, get_nth_panel_name (0)) == 0) + { + g_free (panel1->name); + panel1->name = g_strdup (get_nth_panel_name (1)); + } + } + if (panels[1].type == view_listing) + { + if (strcmp (panel2->name, get_nth_panel_name (1)) == 0) + { + g_free (panel2->name); + panel2->name = g_strdup (get_nth_panel_name (0)); + } + } + + r = panels[0].widget->rect; + panels[0].widget->rect = panels[1].widget->rect; + panels[1].widget->rect = r; + + tmp_widget = panels[0].widget; + panels[0].widget = panels[1].widget; + panels[1].widget = tmp_widget; + tmp_type = panels[0].type; + panels[0].type = panels[1].type; + panels[1].type = tmp_type; + + /* force update formats because of possible changed sizes */ + if (panels[0].type == view_listing) + set_panel_formats (PANEL (panels[0].widget)); + if (panels[1].type == view_listing) + set_panel_formats (PANEL (panels[1].widget)); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +panel_view_mode_t +get_panel_type (int idx) +{ + return panels[idx].type; +} + +/* --------------------------------------------------------------------------------------------- */ + +Widget * +get_panel_widget (int idx) +{ + return panels[idx].widget; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +get_current_index (void) +{ + return (panels[0].widget == WIDGET (current_panel) ? 0 : 1); +} + +/* --------------------------------------------------------------------------------------------- */ + +int +get_other_index (void) +{ + return (get_current_index () == 0 ? 1 : 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +WPanel * +get_other_panel (void) +{ + return PANEL (get_panel_widget (get_other_index ())); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Returns the view type for the current panel/view */ + +panel_view_mode_t +get_current_type (void) +{ + return (panels[0].widget == WIDGET (current_panel) ? panels[0].type : panels[1].type); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Returns the view type of the unselected panel */ + +panel_view_mode_t +get_other_type (void) +{ + return (panels[0].widget == WIDGET (current_panel) ? panels[1].type : panels[0].type); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Save current list_view widget directory into panel */ + +void +save_panel_dir (int idx) +{ + panel_view_mode_t type; + + type = get_panel_type (idx); + if (type == view_listing) + { + WPanel *p; + + p = PANEL (get_panel_widget (idx)); + if (p != NULL) + { + g_free (panels[idx].last_saved_dir); /* last path no needed */ + /* Because path can be nonlocal */ + panels[idx].last_saved_dir = g_strdup (vfs_path_as_str (p->cwd_vpath)); + } + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Return working dir, if it's view_listing - cwd, + but for other types - last_saved_dir */ + +char * +get_panel_dir_for (const WPanel * widget) +{ + int i; + + for (i = 0; i < MAX_VIEWS; i++) + if (PANEL (get_panel_widget (i)) == widget) + break; + + if (i >= MAX_VIEWS) + return g_strdup ("."); + + if (get_panel_type (i) == view_listing) + { + vfs_path_t *cwd_vpath; + + cwd_vpath = PANEL (get_panel_widget (i))->cwd_vpath; + return g_strdup (vfs_path_as_str (cwd_vpath)); + } + + return g_strdup (panels[i].last_saved_dir); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_SUBSHELL +gboolean +do_load_prompt (void) +{ + gboolean ret = FALSE; + + if (!read_subshell_prompt ()) + return ret; + + /* Don't actually change the prompt if it's invisible */ + if (top_dlg != NULL && DIALOG (top_dlg->data) == filemanager && command_prompt) + { + setup_cmdline (); + + /* since the prompt has changed, and we are called from one of the + * tty_get_event channels, the prompt updating does not take place + * automatically: force a cursor update and a screen refresh + */ + widget_update_cursor (WIDGET (filemanager)); + mc_refresh (); + ret = TRUE; + } + update_subshell_prompt = TRUE; + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +int +load_prompt (int fd, void *unused) +{ + (void) fd; + (void) unused; + + if (should_read_new_subshell_prompt) + do_load_prompt (); + else + flush_subshell (0, QUIETLY); + + return 0; +} +#endif /* ENABLE_SUBSHELL */ + +/* --------------------------------------------------------------------------------------------- */ + +void +title_path_prepare (char **path, char **login) +{ + char host[BUF_TINY]; + struct passwd *pw = NULL; + int res = 0; + + *path = + vfs_path_to_str_flags (current_panel->cwd_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD); + + res = gethostname (host, sizeof (host)); + if (res != 0) + host[0] = '\0'; + else + host[sizeof (host) - 1] = '\0'; + + pw = getpwuid (getuid ()); + if (pw != NULL) + *login = g_strdup_printf ("%s@%s", pw->pw_name, host); + else + *login = g_strdup (host); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Show current directory in the xterm title */ +void +update_xterm_title_path (void) +{ + if (mc_global.tty.xterm_flag && xterm_title) + { + char *p; + char *path; + char *login; + + title_path_prepare (&path, &login); + + p = g_strdup_printf ("mc [%s]:%s", login, path); + g_free (login); + g_free (path); + + fprintf (stdout, "\33]0;%s\7", str_term_form (p)); + g_free (p); + + if (!mc_global.tty.alternate_plus_minus) + numeric_keypad_mode (); + (void) fflush (stdout); + } +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/layout.h b/src/filemanager/layout.h new file mode 100644 index 0000000..2566cfa --- /dev/null +++ b/src/filemanager/layout.h @@ -0,0 +1,98 @@ +/** \file layout.h + * \brief Header: panel layout module + */ + +#ifndef MC__LAYOUT_H +#define MC__LAYOUT_H + +#include "lib/global.h" +#include "lib/widget.h" + +#include "panel.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +typedef enum +{ + view_listing = 0, /* Directory listing */ + view_info = 1, /* Information panel */ + view_tree = 2, /* Tree view */ + view_quick = 3, /* Quick view */ + view_nothing = 4, /* Undefined */ +} panel_view_mode_t; + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +typedef struct +{ + gboolean horizontal_split; + + /* vertical split */ + gboolean vertical_equal; + int left_panel_size; + + /* horizontal split */ + gboolean horizontal_equal; + int top_panel_size; +} panels_layout_t; + +/*** global variables defined in .c file *********************************************************/ + +extern int output_lines; +extern gboolean command_prompt; +extern gboolean menubar_visible; +extern int output_start_y; +extern gboolean xterm_title; +extern gboolean free_space; +extern gboolean nice_rotating_dash; + +extern int ok_to_refresh; + +extern panels_layout_t panels_layout; + +/*** declarations of public functions ************************************************************/ +void layout_change (void); +void layout_box (void); +void panel_update_cols (Widget * widget, panel_display_t frame_size); +void setup_panels (void); +void panels_split_equal (void); +void panels_split_more (void); +void panels_split_less (void); +void destroy_panels (void); +void setup_cmdline (void); +void create_panel (int num, panel_view_mode_t type); +void swap_panels (void); +panel_view_mode_t get_panel_type (int idx); +panel_view_mode_t get_current_type (void); +panel_view_mode_t get_other_type (void); +int get_current_index (void); +int get_other_index (void); +const char *get_nth_panel_name (int num); + +Widget *get_panel_widget (int idx); + +WPanel *get_other_panel (void); + +void save_panel_dir (int idx); +char *get_panel_dir_for (const WPanel * widget); + +void set_hintbar (const char *str); + +/* Rotating dash routines */ +void use_dash (gboolean flag); /* Disable/Enable rotate_dash routines */ +void rotate_dash (gboolean show); + +#ifdef ENABLE_SUBSHELL +gboolean do_load_prompt (void); +int load_prompt (int fd, void *unused); +#endif + +void update_xterm_title_path (void); + +void title_path_prepare (char **path, char **login); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__LAYOUT_H */ diff --git a/src/filemanager/mountlist.c b/src/filemanager/mountlist.c new file mode 100644 index 0000000..daa7b80 --- /dev/null +++ b/src/filemanager/mountlist.c @@ -0,0 +1,1572 @@ +/* + Return a list of mounted file systems + + Copyright (C) 1991-2022 + 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 mountlist.c + * \brief Source: list of mounted filesystems + */ + +#include <config.h> + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> /* SIZE_MAX */ +#include <sys/types.h> + +#include <errno.h> + +/* This header needs to be included before sys/mount.h on *BSD */ +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#if defined STAT_STATVFS || defined STAT_STATVFS64 /* POSIX 1003.1-2001 (and later) with XSI */ +#include <sys/statvfs.h> +#else +/* Don't include backward-compatibility files unless they're needed. + Eventually we'd like to remove all this cruft. */ +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> + +#ifdef MOUNTED_GETFSSTAT /* OSF_1, also (obsolete) Apple Darwin 1.3 */ +#ifdef HAVE_SYS_UCRED_H +#include <grp.h> /* needed on OSF V4.0 for definition of NGROUPS, + NGROUPS is used as an array dimension in ucred.h */ +#include <sys/ucred.h> /* needed by powerpc-apple-darwin1.3.7 */ +#endif +#ifdef HAVE_SYS_MOUNT_H +#include <sys/mount.h> +#endif +#ifdef HAVE_SYS_FS_TYPES_H +#include <sys/fs_types.h> /* needed by powerpc-apple-darwin1.3.7 */ +#endif +#ifdef HAVE_STRUCT_FSSTAT_F_FSTYPENAME +#define FS_TYPE(Ent) ((Ent).f_fstypename) +#else +#define FS_TYPE(Ent) mnt_names[(Ent).f_type] +#endif +#endif /* MOUNTED_GETFSSTAT */ +#endif /* STAT_STATVFS || STAT_STATVFS64 */ + +#ifdef HAVE_SYS_VFS_H +#include <sys/vfs.h> +#endif +#ifdef HAVE_SYS_FS_S5PARAM_H /* Fujitsu UXP/V */ +#include <sys/fs/s5param.h> +#endif +#ifdef HAVE_SYS_STATFS_H +#include <sys/statfs.h> +#endif + +#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android, + also (obsolete) 4.3BSD, SunOS */ +#include <mntent.h> +#include <sys/types.h> +#if defined __ANDROID__ /* Android */ + /* Bionic versions from between 2014-01-09 and 2015-01-08 define MOUNTED to + an incorrect value; older Bionic versions don't define it at all. */ +#undef MOUNTED +#define MOUNTED "/proc/mounts" +#elif !defined MOUNTED +#ifdef _PATH_MOUNTED /* GNU libc */ +#define MOUNTED _PATH_MOUNTED +#endif +#ifdef MNT_MNTTAB /* HP-UX. */ +#define MOUNTED MNT_MNTTAB +#endif +#endif +#endif + +#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */ +#include <sys/mount.h> +#endif + +#ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */ +#include <sys/statvfs.h> +#endif + +#ifdef MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */ +#include <fs_info.h> +#include <dirent.h> +#endif + +#ifdef MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */ +#include <mnttab.h> +#include <sys/fstyp.h> +#include <sys/statfs.h> +#endif + +#ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */ +#include <sys/mnttab.h> +#endif + +#ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */ +#include <sys/mnttab.h> +#endif + +#ifdef MOUNTED_VMOUNT /* AIX */ +#include <fshelp.h> +#include <sys/vfs.h> +#endif + +#ifdef MOUNTED_INTERIX_STATVFS /* Interix */ +#include <sys/statvfs.h> +#include <dirent.h> +#endif + +#ifdef HAVE_SYS_MNTENT_H +/* This is to get MNTOPT_IGNORE on e.g. SVR4. */ +#include <sys/mntent.h> +#endif + +#ifdef MOUNTED_GETMNTENT1 +#if !HAVE_SETMNTENT /* Android <= 4.4 */ +#define setmntent(fp,mode) fopen (fp, mode) +#endif +#if !HAVE_ENDMNTENT /* Android <= 4.4 */ +#define endmntent(fp) fclose (fp) +#endif +#endif + +#ifndef HAVE_HASMNTOPT +#define hasmntopt(mnt, opt) ((char *) 0) +#endif + +#undef MNT_IGNORE +#ifdef MNTOPT_IGNORE +#if defined __sun && defined __SVR4 +/* Solaris defines hasmntopt(struct mnttab *, char *) + while it is otherwise hasmntopt(struct mnttab *, const char *). */ +#define MNT_IGNORE(M) hasmntopt (M, (char *) MNTOPT_IGNORE) +#else +#define MNT_IGNORE(M) hasmntopt (M, MNTOPT_IGNORE) +#endif +#else +#define MNT_IGNORE(M) 0 +#endif + +#ifdef HAVE_INFOMOUNT_QNX +#include <sys/disk.h> +#include <sys/fsys.h> +#endif + +#ifdef HAVE_SYS_STATVFS_H /* SVR4. */ +#include <sys/statvfs.h> +#endif + +#include "lib/global.h" +#include "lib/strutil.h" /* str_verscmp() */ +#include "lib/unixcompat.h" /* makedev */ +#include "mountlist.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#if defined (__QNX__) && !defined(__QNXNTO__) && !defined (HAVE_INFOMOUNT_LIST) +#define HAVE_INFOMOUNT_QNX +#endif + +#if defined(HAVE_INFOMOUNT_LIST) || defined(HAVE_INFOMOUNT_QNX) +#define HAVE_INFOMOUNT +#endif + +/* The results of opendir() in this file are not used with dirfd and fchdir, + therefore save some unnecessary work in fchdir.c. */ +#undef opendir +#undef closedir + +#define ME_DUMMY_0(Fs_name, Fs_type) \ + (strcmp (Fs_type, "autofs") == 0 \ + || strcmp (Fs_type, "proc") == 0 \ + || strcmp (Fs_type, "subfs") == 0 \ + /* for Linux 2.6/3.x */ \ + || strcmp (Fs_type, "debugfs") == 0 \ + || strcmp (Fs_type, "devpts") == 0 \ + || strcmp (Fs_type, "fusectl") == 0 \ + || strcmp (Fs_type, "fuse.portal") == 0 \ + || strcmp (Fs_type, "mqueue") == 0 \ + || strcmp (Fs_type, "rpc_pipefs") == 0 \ + || strcmp (Fs_type, "sysfs") == 0 \ + /* FreeBSD, Linux 2.4 */ \ + || strcmp (Fs_type, "devfs") == 0 \ + /* for NetBSD 3.0 */ \ + || strcmp (Fs_type, "kernfs") == 0 \ + /* for Irix 6.5 */ \ + || strcmp (Fs_type, "ignore") == 0) + +/* Historically, we have marked as "dummy" any file system of type "none", + but now that programs like du need to know about bind-mounted directories, + we grant an exception to any with "bind" in its list of mount options. + I.e., those are *not* dummy entries. */ +#ifdef MOUNTED_GETMNTENT1 +#define ME_DUMMY(Fs_name, Fs_type, Bind) \ + (ME_DUMMY_0 (Fs_name, Fs_type) \ + || (strcmp (Fs_type, "none") == 0 && !Bind)) +#else +#define ME_DUMMY(Fs_name, Fs_type) \ + (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0) +#endif + +#ifdef __CYGWIN__ +#include <windows.h> +#define ME_REMOTE me_remote +/* All cygwin mount points include ':' or start with '//'; so it + requires a native Windows call to determine remote disks. */ +static int +me_remote (char const *fs_name, char const *fs_type) +{ + (void) fs_type; + + if (fs_name[0] && fs_name[1] == ':') + { + char drive[4]; + sprintf (drive, "%c:\\", fs_name[0]); + switch (GetDriveType (drive)) + { + case DRIVE_REMOVABLE: + case DRIVE_FIXED: + case DRIVE_CDROM: + case DRIVE_RAMDISK: + return 0; + } + } + return 1; +} +#endif +#ifndef ME_REMOTE +/* A file system is 'remote' if its Fs_name contains a ':' + or if (it is of type (smbfs or smb3 or cifs) and its Fs_name starts with '//') + or if it is of any other of the listed types + or Fs_name is equal to "-hosts" (used by autofs to mount remote fs). + "VM" file systems like prl_fs or vboxsf are not considered remote here. */ +#define ME_REMOTE(Fs_name, Fs_type) \ + (strchr (Fs_name, ':') != NULL \ + || ((Fs_name)[0] == '/' \ + && (Fs_name)[1] == '/' \ + && (strcmp (Fs_type, "smbfs") == 0 \ + || strcmp (Fs_type, "smb3") == 0 \ + || strcmp (Fs_type, "cifs") == 0)) \ + || strcmp (Fs_type, "acfs") == 0 \ + || strcmp (Fs_type, "afs") == 0 \ + || strcmp (Fs_type, "coda") == 0 \ + || strcmp (Fs_type, "auristorfs") == 0 \ + || strcmp (Fs_type, "fhgfs") == 0 \ + || strcmp (Fs_type, "gpfs") == 0 \ + || strcmp (Fs_type, "ibrix") == 0 \ + || strcmp (Fs_type, "ocfs2") == 0 \ + || strcmp (Fs_type, "vxfs") == 0 \ + || strcmp ("-hosts", Fs_name) == 0) +#endif + +/* Many space usage primitives use all 1 bits to denote a value that is + not applicable or unknown. Propagate this information by returning + a uintmax_t value that is all 1 bits if X is all 1 bits, even if X + is unsigned and narrower than uintmax_t. */ +#define PROPAGATE_ALL_ONES(x) \ + ((sizeof (x) < sizeof (uintmax_t) \ + && (~ (x) == (sizeof (x) < sizeof (int) \ + ? - (1 << (sizeof (x) * CHAR_BIT)) \ + : 0))) \ + ? UINTMAX_MAX : (uintmax_t) (x)) + +/* Extract the top bit of X as an uintmax_t value. */ +#define EXTRACT_TOP_BIT(x) ((x) & ((uintmax_t) 1 << (sizeof (x) * CHAR_BIT - 1))) + +/* If a value is negative, many space usage primitives store it into an + integer variable by assignment, even if the variable's type is unsigned. + So, if a space usage variable X's top bit is set, convert X to the + uintmax_t value V such that (- (uintmax_t) V) is the negative of + the original value. If X's top bit is clear, just yield X. + Use PROPAGATE_TOP_BIT if the original value might be negative; + otherwise, use PROPAGATE_ALL_ONES. */ +#define PROPAGATE_TOP_BIT(x) ((x) | ~ (EXTRACT_TOP_BIT (x) - 1)) + +#ifdef STAT_STATVFS +#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)) +/* The FRSIZE fallback is not required in this case. */ +#undef STAT_STATFS2_FRSIZE +#else +#include <sys/utsname.h> +#include <sys/statfs.h> +#define STAT_STATFS2_BSIZE 1 +#endif +#endif + +/*** file scope type declarations ****************************************************************/ + +/* A mount table entry. */ +struct mount_entry +{ + char *me_devname; /* Device node name, including "/dev/". */ + char *me_mountdir; /* Mount point directory name. */ + char *me_mntroot; /* Directory on filesystem of device used + as root for the (bind) mount. */ + char *me_type; /* "nfs", "4.2", etc. */ + dev_t me_dev; /* Device number of me_mountdir. */ + unsigned int me_dummy:1; /* Nonzero for dummy file systems. */ + unsigned int me_remote:1; /* Nonzero for remote fileystems. */ + unsigned int me_type_malloced:1; /* Nonzero if me_type was malloced. */ +}; + +struct fs_usage +{ + uintmax_t fsu_blocksize; /* Size of a block. */ + uintmax_t fsu_blocks; /* Total blocks. */ + uintmax_t fsu_bfree; /* Free blocks available to superuser. */ + uintmax_t fsu_bavail; /* Free blocks available to non-superuser. */ + int fsu_bavail_top_bit_set; /* 1 if fsu_bavail represents a value < 0. */ + uintmax_t fsu_files; /* Total file nodes. */ + uintmax_t fsu_ffree; /* Free file nodes. */ +}; + +/*** file scope variables ************************************************************************/ + +#ifdef HAVE_INFOMOUNT_LIST +static GSList *mc_mount_list = NULL; +#endif /* HAVE_INFOMOUNT_LIST */ + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +#ifdef STAT_STATVFS +/* Return true if statvfs works. This is false for statvfs on systems + with GNU libc on Linux kernels before 2.6.36, which stats all + preceding entries in /proc/mounts; that makes df hang if even one + of the corresponding file systems is hard-mounted but not available. */ +static int +statvfs_works (void) +{ +#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)) + return 1; +#else + static int statvfs_works_cache = -1; + struct utsname name; + + if (statvfs_works_cache < 0) + statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36")); + return statvfs_works_cache; +#endif +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef HAVE_INFOMOUNT_LIST +static void +free_mount_entry (struct mount_entry *me) +{ + if (me == NULL) + return; + g_free (me->me_devname); + g_free (me->me_mountdir); + g_free (me->me_mntroot); + if (me->me_type_malloced) + g_free (me->me_type); + g_free (me); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */ + +#ifndef HAVE_STRUCT_STATFS_F_FSTYPENAME +static char * +fstype_to_string (short int t) +{ + switch (t) + { +#ifdef MOUNT_PC + /* cppcheck-suppress syntaxError */ + case MOUNT_PC: + return "pc"; +#endif +#ifdef MOUNT_MFS + /* cppcheck-suppress syntaxError */ + case MOUNT_MFS: + return "mfs"; +#endif +#ifdef MOUNT_LO + /* cppcheck-suppress syntaxError */ + case MOUNT_LO: + return "lo"; +#endif +#ifdef MOUNT_TFS + /* cppcheck-suppress syntaxError */ + case MOUNT_TFS: + return "tfs"; +#endif +#ifdef MOUNT_TMP + /* cppcheck-suppress syntaxError */ + case MOUNT_TMP: + return "tmp"; +#endif +#ifdef MOUNT_UFS + /* cppcheck-suppress syntaxError */ + case MOUNT_UFS: + return "ufs"; +#endif +#ifdef MOUNT_NFS + /* cppcheck-suppress syntaxError */ + case MOUNT_NFS: + return "nfs"; +#endif +#ifdef MOUNT_MSDOS + /* cppcheck-suppress syntaxError */ + case MOUNT_MSDOS: + return "msdos"; +#endif +#ifdef MOUNT_LFS + /* cppcheck-suppress syntaxError */ + case MOUNT_LFS: + return "lfs"; +#endif +#ifdef MOUNT_LOFS + /* cppcheck-suppress syntaxError */ + case MOUNT_LOFS: + return "lofs"; +#endif +#ifdef MOUNT_FDESC + /* cppcheck-suppress syntaxError */ + case MOUNT_FDESC: + return "fdesc"; +#endif +#ifdef MOUNT_PORTAL + /* cppcheck-suppress syntaxError */ + case MOUNT_PORTAL: + return "portal"; +#endif +#ifdef MOUNT_NULL + /* cppcheck-suppress syntaxError */ + case MOUNT_NULL: + return "null"; +#endif +#ifdef MOUNT_UMAP + /* cppcheck-suppress syntaxError */ + case MOUNT_UMAP: + return "umap"; +#endif +#ifdef MOUNT_KERNFS + /* cppcheck-suppress syntaxError */ + case MOUNT_KERNFS: + return "kernfs"; +#endif +#ifdef MOUNT_PROCFS + /* cppcheck-suppress syntaxError */ + case MOUNT_PROCFS: + return "procfs"; +#endif +#ifdef MOUNT_AFS + /* cppcheck-suppress syntaxError */ + case MOUNT_AFS: + return "afs"; +#endif +#ifdef MOUNT_CD9660 + /* cppcheck-suppress syntaxError */ + case MOUNT_CD9660: + return "cd9660"; +#endif +#ifdef MOUNT_UNION + /* cppcheck-suppress syntaxError */ + case MOUNT_UNION: + return "union"; +#endif +#ifdef MOUNT_DEVFS + /* cppcheck-suppress syntaxError */ + case MOUNT_DEVFS: + return "devfs"; +#endif +#ifdef MOUNT_EXT2FS + /* cppcheck-suppress syntaxError */ + case MOUNT_EXT2FS: + return "ext2fs"; +#endif + default: + return "?"; + } +} +#endif /* ! HAVE_STRUCT_STATFS_F_FSTYPENAME */ + +/* --------------------------------------------------------------------------------------------- */ + +static char * +fsp_to_string (const struct statfs *fsp) +{ +#ifdef HAVE_STRUCT_STATFS_F_FSTYPENAME + return (char *) (fsp->f_fstypename); +#else + return fstype_to_string (fsp->f_type); +#endif +} +#endif /* MOUNTED_GETMNTINFO */ + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef MOUNTED_VMOUNT /* AIX */ +static char * +fstype_to_string (int t) +{ + struct vfs_ent *e; + + e = getvfsbytype (t); + if (!e || !e->vfsent_name) + return "none"; + else + return e->vfsent_name; +} +#endif /* MOUNTED_VMOUNT */ + +/* --------------------------------------------------------------------------------------------- */ + +#if defined MOUNTED_GETMNTENT1 || defined MOUNTED_GETMNTENT2 + +/* Return the device number from MOUNT_OPTIONS, if possible. + Otherwise return (dev_t) -1. */ + +/* --------------------------------------------------------------------------------------------- */ + +static dev_t +dev_from_mount_options (char const *mount_options) +{ + /* GNU/Linux allows file system implementations to define their own + meaning for "dev=" mount options, so don't trust the meaning + here. */ +#ifndef __linux__ + static char const dev_pattern[] = ",dev="; + char const *devopt = strstr (mount_options, dev_pattern); + + if (devopt) + { + char const *optval = devopt + sizeof (dev_pattern) - 1; + char *optvalend; + unsigned long int dev; + errno = 0; + dev = strtoul (optval, &optvalend, 16); + if (optval != optvalend + && (*optvalend == '\0' || *optvalend == ',') + && !(dev == ULONG_MAX && errno == ERANGE) && dev == (dev_t) dev) + return dev; + } +#endif + + (void) mount_options; + return -1; +} + +#endif + +/* --------------------------------------------------------------------------------------------- */ + +#if defined MOUNTED_GETMNTENT1 && (defined __linux__ || defined __ANDROID__) /* GNU/Linux, Android */ + +/* Unescape the paths in mount tables. + STR is updated in place. */ +static void +unescape_tab (char *str) +{ + size_t i, j = 0; + size_t len; + + len = strlen (str) + 1; + + for (i = 0; i < len; i++) + { + if (str[i] == '\\' && (i + 4 < len) + && str[i + 1] >= '0' && str[i + 1] <= '3' + && str[i + 2] >= '0' && str[i + 2] <= '7' && str[i + 3] >= '0' && str[i + 3] <= '7') + { + str[j++] = (str[i + 1] - '0') * 64 + (str[i + 2] - '0') * 8 + (str[i + 3] - '0'); + i += 3; + } + else + str[j++] = str[i]; + } +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +/* Return a list of the currently mounted file systems, or NULL on error. + Add each entry to the tail of the list so that they stay in order. */ + +static GSList * +read_file_system_list (void) +{ + GSList *mount_list = NULL; + struct mount_entry *me; + +#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android, + also (obsolete) 4.3BSD, SunOS */ + { + FILE *fp; + +#if defined __linux__ || defined __ANDROID__ + /* Try parsing mountinfo first, as that make device IDs available. + Note we could use libmount routines to simplify this parsing a little + (and that code is in previous versions of this function), however + libmount depends on libselinux which pulls in many dependencies. */ + char const *mountinfo = "/proc/self/mountinfo"; + + fp = fopen (mountinfo, "r"); + if (fp != NULL) + { + char *line = NULL; + size_t buf_size = 0; + + while (getline (&line, &buf_size, fp) != -1) + { + unsigned int devmaj, devmin; + int target_s, target_e, type_s, type_e; + int source_s, source_e, mntroot_s, mntroot_e; + char test; + char *dash; + int rc; + + rc = sscanf (line, "%*u " /* id - discarded */ + "%*u " /* parent - discarded */ + "%u:%u " /* dev major:minor */ + "%n%*s%n " /* mountroot */ + "%n%*s%n" /* target, start and end */ + "%c", /* more data... */ + &devmaj, &devmin, &mntroot_s, &mntroot_e, &target_s, &target_e, &test); + + if (rc != 3 && rc != 7) /* 7 if %n included in count. */ + continue; + + /* skip optional fields, terminated by " - " */ + dash = strstr (line + target_e, " - "); + if (dash == NULL) + continue; + + rc = sscanf (dash, " - " /* */ + "%n%*s%n " /* FS type, start and end */ + "%n%*s%n " /* source, start and end */ + "%c", /* more data... */ + &type_s, &type_e, &source_s, &source_e, &test); + if (rc != 1 && rc != 5) /* 5 if %n included in count. */ + continue; + + /* manipulate the sub-strings in place. */ + line[mntroot_e] = '\0'; + line[target_e] = '\0'; + dash[type_e] = '\0'; + dash[source_e] = '\0'; + unescape_tab (dash + source_s); + unescape_tab (line + target_s); + unescape_tab (line + mntroot_s); + + me = g_malloc (sizeof *me); + + me->me_devname = g_strdup (dash + source_s); + me->me_mountdir = g_strdup (line + target_s); + me->me_mntroot = g_strdup (line + mntroot_s); + me->me_type = g_strdup (dash + type_s); + me->me_type_malloced = 1; + me->me_dev = makedev (devmaj, devmin); + /* we pass "false" for the "Bind" option as that's only + significant when the Fs_type is "none" which will not be + the case when parsing "/proc/self/mountinfo", and only + applies for static /etc/mtab files. */ + me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, FALSE); + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + + mount_list = g_slist_prepend (mount_list, me); + } + + free (line); + + if (ferror (fp) != 0) + { + int saved_errno = errno; + + fclose (fp); + errno = saved_errno; + goto free_then_fail; + } + + if (fclose (fp) == EOF) + goto free_then_fail; + } + else /* fallback to /proc/self/mounts (/etc/mtab). */ +#endif /* __linux __ || __ANDROID__ */ + { + struct mntent *mnt; + const char *table = MOUNTED; + + fp = setmntent (table, "r"); + if (fp == NULL) + return NULL; + + while ((mnt = getmntent (fp)) != NULL) + { + gboolean bind; + + bind = hasmntopt (mnt, "bind") != NULL; + + me = g_malloc (sizeof (*me)); + me->me_devname = g_strdup (mnt->mnt_fsname); + me->me_mountdir = g_strdup (mnt->mnt_dir); + me->me_mntroot = NULL; + me->me_type = g_strdup (mnt->mnt_type); + me->me_type_malloced = 1; + me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, bind); + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + me->me_dev = dev_from_mount_options (mnt->mnt_opts); + + mount_list = g_slist_prepend (mount_list, me); + } + + if (endmntent (fp) == 0) + goto free_then_fail; + } + } +#endif /* MOUNTED_GETMNTENT1. */ + +#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */ + { + struct statfs *fsp; + int entries; + + entries = getmntinfo (&fsp, MNT_NOWAIT); + if (entries < 0) + return NULL; + for (; entries-- > 0; fsp++) + { + char *fs_type = fsp_to_string (fsp); + + me = g_malloc (sizeof (*me)); + me->me_devname = g_strdup (fsp->f_mntfromname); + me->me_mountdir = g_strdup (fsp->f_mntonname); + me->me_mntroot = NULL; + me->me_type = fs_type; + me->me_type_malloced = 0; + me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */ + + mount_list = g_slist_prepend (mount_list, me); + } + } +#endif /* MOUNTED_GETMNTINFO */ + +#ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */ + { + struct statvfs *fsp; + int entries; + + entries = getmntinfo (&fsp, MNT_NOWAIT); + if (entries < 0) + return NULL; + for (; entries-- > 0; fsp++) + { + me = g_malloc (sizeof (*me)); + me->me_devname = g_strdup (fsp->f_mntfromname); + me->me_mountdir = g_strdup (fsp->f_mntonname); + me->me_mntroot = NULL; + me->me_type = g_strdup (fsp->f_fstypename); + me->me_type_malloced = 1; + me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */ + + mount_list = g_slist_prepend (mount_list, me); + } + } +#endif /* MOUNTED_GETMNTINFO2 */ + +#if defined MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */ + { + /* The next_dev() and fs_stat_dev() system calls give the list of + all file systems, including the information returned by statvfs() + (fs type, total blocks, free blocks etc.), but without the mount + point. But on BeOS all file systems except / are mounted in the + rootfs, directly under /. + The directory name of the mount point is often, but not always, + identical to the volume name of the device. + We therefore get the list of subdirectories of /, and the list + of all file systems, and match the two lists. */ + + DIR *dirp; + struct rootdir_entry + { + char *name; + dev_t dev; + ino_t ino; + struct rootdir_entry *next; + }; + struct rootdir_entry *rootdir_list; + struct rootdir_entry **rootdir_tail; + int32 pos; + dev_t dev; + fs_info fi; + + /* All volumes are mounted in the rootfs, directly under /. */ + rootdir_list = NULL; + rootdir_tail = &rootdir_list; + dirp = opendir (PATH_SEP_STR); + if (dirp) + { + struct dirent *d; + + while ((d = readdir (dirp)) != NULL) + { + char *name; + struct stat statbuf; + + if (DIR_IS_DOT (d->d_name)) + continue; + + if (DIR_IS_DOTDOT (d->d_name)) + name = g_strdup (PATH_SEP_STR); + else + name = g_strconcat (PATH_SEP_STR, d->d_name, (char *) NULL); + + if (lstat (name, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode)) + { + struct rootdir_entry *re = g_malloc (sizeof (*re)); + re->name = name; + re->dev = statbuf.st_dev; + re->ino = statbuf.st_ino; + + /* Add to the linked list. */ + *rootdir_tail = re; + rootdir_tail = &re->next; + } + else + g_free (name); + } + closedir (dirp); + } + *rootdir_tail = NULL; + + for (pos = 0; (dev = next_dev (&pos)) >= 0;) + if (fs_stat_dev (dev, &fi) >= 0) + { + /* Note: fi.dev == dev. */ + struct rootdir_entry *re; + + for (re = rootdir_list; re; re = re->next) + if (re->dev == fi.dev && re->ino == fi.root) + break; + + me = g_malloc (sizeof (*me)); + me->me_devname = + g_strdup (fi.device_name[0] != '\0' ? fi.device_name : fi.fsh_name); + me->me_mountdir = g_strdup (re != NULL ? re->name : fi.fsh_name); + me->me_mntroot = NULL; + me->me_type = g_strdup (fi.fsh_name); + me->me_type_malloced = 1; + me->me_dev = fi.dev; + me->me_dummy = 0; + me->me_remote = (fi.flags & B_FS_IS_SHARED) != 0; + + mount_list = g_slist_prepend (mount_list, me); + } + + while (rootdir_list != NULL) + { + struct rootdir_entry *re = rootdir_list; + + rootdir_list = re->next; + g_free (re->name); + g_free (re); + } + } +#endif /* MOUNTED_FS_STAT_DEV */ + +#ifdef MOUNTED_GETFSSTAT /* OSF/1, also (obsolete) Apple Darwin 1.3 */ + { + int numsys, counter; + size_t bufsize; + struct statfs *stats; + + numsys = getfsstat (NULL, 0L, MNT_NOWAIT); + if (numsys < 0) + return NULL; + if (SIZE_MAX / sizeof (*stats) <= numsys) + { + fprintf (stderr, "%s\n", _("Memory exhausted!")); + exit (EXIT_FAILURE); + } + + bufsize = (1 + numsys) * sizeof (*stats); + stats = g_malloc (bufsize); + numsys = getfsstat (stats, bufsize, MNT_NOWAIT); + + if (numsys < 0) + { + g_free (stats); + return NULL; + } + + for (counter = 0; counter < numsys; counter++) + { + me = g_malloc (sizeof (*me)); + me->me_devname = g_strdup (stats[counter].f_mntfromname); + me->me_mountdir = g_strdup (stats[counter].f_mntonname); + me->me_mntroot = NULL; + me->me_type = g_strdup (FS_TYPE (stats[counter])); + me->me_type_malloced = 1; + me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */ + + mount_list = g_slist_prepend (mount_list, me); + } + + g_free (stats); + } +#endif /* MOUNTED_GETFSSTAT */ + +#if defined MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */ + { + struct mnttab mnt; + char *table = "/etc/mnttab"; + FILE *fp; + + fp = fopen (table, "r"); + if (fp == NULL) + return NULL; + + while (fread (&mnt, sizeof (mnt), 1, fp) > 0) + { + me = g_malloc (sizeof (*me)); + me->me_devname = g_strdup (mnt.mt_dev); + me->me_mountdir = g_strdup (mnt.mt_filsys); + me->me_mntroot = NULL; + me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */ + me->me_type = ""; + me->me_type_malloced = 0; + { + struct statfs fsd; + char typebuf[FSTYPSZ]; + + if (statfs (me->me_mountdir, &fsd, sizeof (fsd), 0) != -1 + && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1) + { + me->me_type = g_strdup (typebuf); + me->me_type_malloced = 1; + } + } + me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + + mount_list = g_slist_prepend (mount_list, me); + } + + if (ferror (fp)) + { + /* The last fread() call must have failed. */ + int saved_errno = errno; + + fclose (fp); + errno = saved_errno; + goto free_then_fail; + } + + if (fclose (fp) == EOF) + goto free_then_fail; + } +#endif /* MOUNTED_FREAD_FSTYP */ + +#ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */ + { + struct extmnttab mnt; + const char *table = MNTTAB; + FILE *fp; + int ret; + + /* No locking is needed, because the contents of /etc/mnttab is generated by the kernel. */ + + errno = 0; + fp = fopen (table, "r"); + if (fp == NULL) + ret = errno; + else + { + while ((ret = getextmntent (fp, &mnt, 1)) == 0) + { + me = g_malloc (sizeof *me); + me->me_devname = g_strdup (mnt.mnt_special); + me->me_mountdir = g_strdup (mnt.mnt_mountp); + me->me_mntroot = NULL; + me->me_type = g_strdup (mnt.mnt_fstype); + me->me_type_malloced = 1; + me->me_dummy = MNT_IGNORE (&mnt) != 0; + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + me->me_dev = makedev (mnt.mnt_major, mnt.mnt_minor); + + mount_list = g_slist_prepend (mount_list, me); + } + + ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1; + /* Here ret = -1 means success, ret >= 0 means failure. */ + } + + if (ret >= 0) + { + errno = ret; + goto free_then_fail; + } + } +#endif /* MOUNTED_GETEXTMNTENT */ + +#ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */ + { + struct mnttab mnt; + const char *table = MNTTAB; + FILE *fp; + int ret; + int lockfd = -1; + +#if defined F_RDLCK && defined F_SETLKW + /* MNTTAB_LOCK is a macro name of our own invention; it's not present in + e.g. Solaris 2.6. If the SVR4 folks ever define a macro + for this file name, we should use their macro name instead. + (Why not just lock MNTTAB directly? We don't know.) */ +#ifndef MNTTAB_LOCK +#define MNTTAB_LOCK "/etc/.mnttab.lock" +#endif + lockfd = open (MNTTAB_LOCK, O_RDONLY); + if (lockfd >= 0) + { + struct flock flock; + + flock.l_type = F_RDLCK; + flock.l_whence = SEEK_SET; + flock.l_start = 0; + flock.l_len = 0; + while (fcntl (lockfd, F_SETLKW, &flock) == -1) + if (errno != EINTR) + { + int saved_errno = errno; + close (lockfd); + errno = saved_errno; + return NULL; + } + } + else if (errno != ENOENT) + return NULL; +#endif + + errno = 0; + fp = fopen (table, "r"); + if (fp == NULL) + ret = errno; + else + { + while ((ret = getmntent (fp, &mnt)) == 0) + { + me = g_malloc (sizeof (*me)); + me->me_devname = g_strdup (mnt.mnt_special); + me->me_mountdir = g_strdup (mnt.mnt_mountp); + me->me_mntroot = NULL; + me->me_type = g_strdup (mnt.mnt_fstype); + me->me_type_malloced = 1; + me->me_dummy = MNT_IGNORE (&mnt) != 0; + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + me->me_dev = dev_from_mount_options (mnt.mnt_mntopts); + + mount_list = g_slist_prepend (mount_list, me); + } + + ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1; + /* Here ret = -1 means success, ret >= 0 means failure. */ + } + + if (lockfd >= 0 && close (lockfd) != 0) + ret = errno; + + if (ret >= 0) + { + errno = ret; + goto free_then_fail; + } + } +#endif /* MOUNTED_GETMNTENT2. */ + +#ifdef MOUNTED_VMOUNT /* AIX */ + { + int bufsize; + void *entries; + char *thisent; + struct vmount *vmp; + int n_entries; + int i; + + /* Ask how many bytes to allocate for the mounted file system info. */ + entries = &bufsize; + if (mntctl (MCTL_QUERY, sizeof (bufsize), entries) != 0) + return NULL; + entries = g_malloc (bufsize); + + /* Get the list of mounted file systems. */ + n_entries = mntctl (MCTL_QUERY, bufsize, entries); + if (n_entries < 0) + { + int saved_errno = errno; + + g_free (entries); + errno = saved_errno; + return NULL; + } + + for (i = 0, thisent = entries; i < n_entries; i++, thisent += vmp->vmt_length) + { + char *options, *ignore; + + vmp = (struct vmount *) thisent; + me = g_malloc (sizeof (*me)); + if (vmp->vmt_flags & MNT_REMOTE) + { + char *host, *dir; + + me->me_remote = 1; + /* Prepend the remote dirname. */ + host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off; + dir = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off; + me->me_devname = g_strconcat (host, ":", dir, (char *) NULL); + } + else + { + me->me_remote = 0; + me->me_devname = g_strdup (thisent + vmp->vmt_data[VMT_OBJECT].vmt_off); + } + me->me_mountdir = g_strdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off); + me->me_mntroot = NULL; + me->me_type = g_strdup (fstype_to_string (vmp->vmt_gfstype)); + me->me_type_malloced = 1; + options = thisent + vmp->vmt_data[VMT_ARGS].vmt_off; + ignore = strstr (options, "ignore"); + me->me_dummy = (ignore + && (ignore == options || ignore[-1] == ',') + && (ignore[sizeof ("ignore") - 1] == ',' + || ignore[sizeof ("ignore") - 1] == '\0')); + me->me_dev = (dev_t) (-1); /* vmt_fsid might be the info we want. */ + + mount_list = g_slist_prepend (mount_list, me); + } + g_free (entries); + } +#endif /* MOUNTED_VMOUNT. */ + +#ifdef MOUNTED_INTERIX_STATVFS /* Interix */ + { + DIR *dirp = opendir ("/dev/fs"); + char node[9 + NAME_MAX]; + + if (!dirp) + goto free_then_fail; + + while (1) + { + struct statvfs dev; + struct dirent entry; + struct dirent *result; + + if (readdir_r (dirp, &entry, &result) || result == NULL) + break; + + strcpy (node, "/dev/fs/"); + strcat (node, entry.d_name); + + if (statvfs (node, &dev) == 0) + { + me = g_malloc (sizeof *me); + me->me_devname = g_strdup (dev.f_mntfromname); + me->me_mountdir = g_strdup (dev.f_mntonname); + me->me_mntroot = NULL; + me->me_type = g_strdup (dev.f_fstypename); + me->me_type_malloced = 1; + me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); + me->me_remote = ME_REMOTE (me->me_devname, me->me_type); + me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */ + + mount_list = g_slist_prepend (mount_list, me); + } + } + closedir (dirp); + } +#endif /* MOUNTED_INTERIX_STATVFS */ + + return g_slist_reverse (mount_list); + + free_then_fail: + { + int saved_errno = errno; + + g_slist_free_full (mount_list, (GDestroyNotify) free_mount_entry); + + errno = saved_errno; + return NULL; + } +} +#endif /* HAVE_INFOMOUNT_LIST */ + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef HAVE_INFOMOUNT_QNX +/** + ** QNX has no [gs]etmnt*(), [gs]etfs*(), or /etc/mnttab, but can do + ** this via the following code. + ** Note that, as this is based on CWD, it only fills one mount_entry + ** structure. See my_statfs() below for the "other side" of this hack. + */ + +static GSList * +read_file_system_list (void) +{ + struct _disk_entry de; + struct statfs fs; + int i, fd; + char *tp, dev[_POSIX_NAME_MAX], dir[_POSIX_PATH_MAX]; + struct mount_entry *me = NULL; + static GSList *list = NULL; + + if (list != NULL) + { + me = (struct mount_entry *) list->data; + + g_free (me->me_devname); + g_free (me->me_mountdir); + g_free (me->me_mntroot); + g_free (me->me_type); + } + else + { + me = (struct mount_entry *) g_malloc (sizeof (struct mount_entry)); + list = g_slist_prepend (list, me); + } + + if (!getcwd (dir, _POSIX_PATH_MAX)) + return (NULL); + + fd = open (dir, O_RDONLY); + if (fd == -1) + return (NULL); + + i = disk_get_entry (fd, &de); + + close (fd); + + if (i == -1) + return (NULL); + + switch (de.disk_type) + { + case _UNMOUNTED: + tp = "unmounted"; + break; + case _FLOPPY: + tp = "Floppy"; + break; + case _HARD: + tp = "Hard"; + break; + case _RAMDISK: + tp = "Ram"; + break; + case _REMOVABLE: + tp = "Removable"; + break; + case _TAPE: + tp = "Tape"; + break; + case _CDROM: + tp = "CDROM"; + break; + default: + tp = "unknown"; + } + + if (fsys_get_mount_dev (dir, &dev) == -1) + return (NULL); + + if (fsys_get_mount_pt (dev, &dir) == -1) + return (NULL); + + me->me_devname = g_strdup (dev); + me->me_mountdir = g_strdup (dir); + me->me_mntroot = NULL; + me->me_type = g_strdup (tp); + me->me_dev = de.disk_type; + +#ifdef DEBUG + fprintf (stderr, + "disk_get_entry():\n\tdisk_type=%d (%s)\n\tdriver_name='%-*.*s'\n\tdisk_drv=%d\n", + de.disk_type, tp, _DRIVER_NAME_LEN, _DRIVER_NAME_LEN, de.driver_name, de.disk_drv); + fprintf (stderr, "fsys_get_mount_dev():\n\tdevice='%s'\n", dev); + fprintf (stderr, "fsys_get_mount_pt():\n\tmount point='%s'\n", dir); +#endif /* DEBUG */ + + return (list); +} +#endif /* HAVE_INFOMOUNT_QNX */ + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef HAVE_INFOMOUNT +/* Fill in the fields of FSP with information about space usage for + the file system on which FILE resides. + DISK is the device on which FILE is mounted, for space-getting + methods that need to know it. + Return 0 if successful, -1 if not. When returning -1, ensure that + ERRNO is either a system error value, or zero if DISK is NULL + on a system that requires a non-NULL value. */ +static int +get_fs_usage (char const *file, char const *disk, struct fs_usage *fsp) +{ +#ifdef STAT_STATVFS /* POSIX, except pre-2.6.36 glibc/Linux */ + + if (statvfs_works ()) + { + struct statvfs vfsd; + + if (statvfs (file, &vfsd) < 0) + return -1; + + /* f_frsize isn't guaranteed to be supported. */ + fsp->fsu_blocksize = (vfsd.f_frsize + ? PROPAGATE_ALL_ONES (vfsd.f_frsize) + : PROPAGATE_ALL_ONES (vfsd.f_bsize)); + + fsp->fsu_blocks = PROPAGATE_ALL_ONES (vfsd.f_blocks); + fsp->fsu_bfree = PROPAGATE_ALL_ONES (vfsd.f_bfree); + fsp->fsu_bavail = PROPAGATE_TOP_BIT (vfsd.f_bavail); + fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (vfsd.f_bavail) != 0; + fsp->fsu_files = PROPAGATE_ALL_ONES (vfsd.f_files); + fsp->fsu_ffree = PROPAGATE_ALL_ONES (vfsd.f_ffree); + } + else +#endif + + { +#if defined STAT_STATVFS64 /* AIX */ + + struct statvfs64 fsd; + + if (statvfs64 (file, &fsd) < 0) + return -1; + + /* f_frsize isn't guaranteed to be supported. */ + /* *INDENT-OFF* */ + fsp->fsu_blocksize = fsd.f_frsize + ? PROPAGATE_ALL_ONES (fsd.f_frsize) + : PROPAGATE_ALL_ONES (fsd.f_bsize); + /* *INDENT-ON* */ + +#elif defined STAT_STATFS3_OSF1 /* OSF/1 */ + + struct statfs fsd; + + if (statfs (file, &fsd, sizeof (struct statfs)) != 0) + return -1; + + fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize); + +#elif defined STAT_STATFS2_FRSIZE /* 2.6 < glibc/Linux < 2.6.36 */ + + struct statfs fsd; + + if (statfs (file, &fsd) < 0) + return -1; + + fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_frsize); + +#elif defined STAT_STATFS2_BSIZE /* glibc/Linux < 2.6, 4.3BSD, SunOS 4, \ + Mac OS X < 10.4, FreeBSD < 5.0, \ + NetBSD < 3.0, OpenBSD < 4.4 */ + + struct statfs fsd; + + if (statfs (file, &fsd) < 0) + return -1; + + fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize); + +#ifdef STATFS_TRUNCATES_BLOCK_COUNTS + + /* In SunOS 4.1.2, 4.1.3, and 4.1.3_U1, the block counts in the + struct statfs are truncated to 2GB. These conditions detect that + truncation, presumably without botching the 4.1.1 case, in which + the values are not truncated. The correct counts are stored in + undocumented spare fields. */ + if (fsd.f_blocks == 0x7fffffff / fsd.f_bsize && fsd.f_spare[0] > 0) + { + fsd.f_blocks = fsd.f_spare[0]; + fsd.f_bfree = fsd.f_spare[1]; + fsd.f_bavail = fsd.f_spare[2]; + } +#endif /* STATFS_TRUNCATES_BLOCK_COUNTS */ + +#elif defined STAT_STATFS2_FSIZE /* 4.4BSD and older NetBSD */ + + struct statfs fsd; + + if (statfs (file, &fsd) < 0) + return -1; + + fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize); + +#elif defined STAT_STATFS4 /* SVR3, old Irix */ + + struct statfs fsd; + + if (statfs (file, &fsd, sizeof (fsd), 0) < 0) + return -1; + + /* Empirically, the block counts on most SVR3 and SVR3-derived + systems seem to always be in terms of 512-byte blocks, + no matter what value f_bsize has. */ + fsp->fsu_blocksize = 512; +#endif + +#if (defined STAT_STATVFS64 || defined STAT_STATFS3_OSF1 \ + || defined STAT_STATFS2_FRSIZE || defined STAT_STATFS2_BSIZE \ + || defined STAT_STATFS2_FSIZE || defined STAT_STATFS4) + + fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.f_blocks); + fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.f_bfree); + fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.f_bavail); + fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.f_bavail) != 0; + fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.f_files); + fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.f_ffree); + +#endif + } + + (void) disk; /* avoid argument-unused warning */ + + return 0; +} +#endif /* HAVE_INFOMOUNT */ + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +free_my_statfs (void) +{ +#ifdef HAVE_INFOMOUNT_LIST + g_clear_slist (&mc_mount_list, (GDestroyNotify) free_mount_entry); +#endif /* HAVE_INFOMOUNT_LIST */ +} + +/* --------------------------------------------------------------------------------------------- */ + +void +init_my_statfs (void) +{ +#ifdef HAVE_INFOMOUNT_LIST + free_my_statfs (); + mc_mount_list = read_file_system_list (); +#endif /* HAVE_INFOMOUNT_LIST */ +} + +/* --------------------------------------------------------------------------------------------- */ + +void +my_statfs (struct my_statfs *myfs_stats, const char *path) +{ +#ifdef HAVE_INFOMOUNT_LIST + size_t len = 0; + struct mount_entry *entry = NULL; + GSList *temp; + struct fs_usage fs_use; + + for (temp = mc_mount_list; temp != NULL; temp = g_slist_next (temp)) + { + struct mount_entry *me; + size_t i; + + me = (struct mount_entry *) temp->data; + i = strlen (me->me_mountdir); + if (i > len && (strncmp (path, me->me_mountdir, i) == 0) && + (entry == NULL || IS_PATH_SEP (path[i]) || path[i] == '\0')) + { + len = i; + entry = me; + } + } + + if (entry != NULL) + { + memset (&fs_use, 0, sizeof (fs_use)); + get_fs_usage (entry->me_mountdir, NULL, &fs_use); + + myfs_stats->type = entry->me_dev; + myfs_stats->typename = entry->me_type; + myfs_stats->mpoint = entry->me_mountdir; + myfs_stats->mroot = entry->me_mntroot; + myfs_stats->device = entry->me_devname; + myfs_stats->avail = + ((uintmax_t) (getuid ()? fs_use.fsu_bavail : fs_use.fsu_bfree) * + fs_use.fsu_blocksize) >> 10; + myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10; + myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree; + myfs_stats->nodes = (uintmax_t) fs_use.fsu_files; + } + else +#endif /* HAVE_INFOMOUNT_LIST */ + +#ifdef HAVE_INFOMOUNT_QNX + /* + ** This is the "other side" of the hack to read_file_system_list() above. + ** It's not the most efficient approach, but consumes less memory. It + ** also accommodates QNX's ability to mount filesystems on the fly. + */ + struct mount_entry *entry; + struct fs_usage fs_use; + + entry = read_file_system_list (); + if (entry != NULL) + { + get_fs_usage (entry->me_mountdir, NULL, &fs_use); + + myfs_stats->type = entry->me_dev; + myfs_stats->typename = entry->me_type; + myfs_stats->mpoint = entry->me_mountdir; + myfs_stats->mroot = entry->me_mntroot; + myfs_stats->device = entry->me_devname; + + myfs_stats->avail = ((uintmax_t) fs_use.fsu_bfree * fs_use.fsu_blocksize) >> 10; + myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10; + myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree; + myfs_stats->nodes = (uintmax_t) fs_use.fsu_files; + } + else +#endif /* HAVE_INFOMOUNT_QNX */ + { + myfs_stats->type = 0; + myfs_stats->mpoint = "unknown"; + myfs_stats->device = "unknown"; + myfs_stats->avail = 0; + myfs_stats->total = 0; + myfs_stats->nfree = 0; + myfs_stats->nodes = 0; + } +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/mountlist.h b/src/filemanager/mountlist.h new file mode 100644 index 0000000..f506488 --- /dev/null +++ b/src/filemanager/mountlist.h @@ -0,0 +1,44 @@ +/* + Declarations for list of mounted filesystems + */ + +/** \file mountlist.h + * \brief Header: list of mounted filesystems + */ + +#ifndef MC__MOUNTLIST_H +#define MC__MOUNTLIST_H + +#include <stdint.h> /* uintmax_t */ + +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/* Filesystem status */ +struct my_statfs +{ + int type; + char *typename; + const char *mpoint; + const char *mroot; + const char *device; + uintmax_t avail; /* in kB */ + uintmax_t total; /* in kB */ + uintmax_t nfree; + uintmax_t nodes; +}; + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +void init_my_statfs (void); +void my_statfs (struct my_statfs *myfs_stats, const char *path); +void free_my_statfs (void); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__MOUNTLIST_H */ diff --git a/src/filemanager/panel.c b/src/filemanager/panel.c new file mode 100644 index 0000000..bb87875 --- /dev/null +++ b/src/filemanager/panel.c @@ -0,0 +1,5204 @@ +/* + Panel managing. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Miguel de Icaza, 1995 + Timur Bakeyev, 1997, 1999 + 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 panel.c + * \brief Source: panel managin module + */ + +#include <config.h> + +#include <errno.h> +#include <stdio.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/strescape.h" +#include "lib/mcconfig.h" +#include "lib/vfs/vfs.h" +#include "lib/unixcompat.h" +#include "lib/search.h" +#include "lib/timefmt.h" /* file_date() */ +#include "lib/util.h" +#include "lib/widget.h" +#ifdef HAVE_CHARSET +#include "lib/charsets.h" /* get_codepage_id () */ +#endif +#include "lib/event.h" + +#include "src/setup.h" /* For loading/saving panel options */ +#include "src/execute.h" +#ifdef HAVE_CHARSET +#include "src/selcodepage.h" /* select_charset (), SELECT_CHARSET_NO_TRANSLATE */ +#endif +#include "src/keymap.h" /* global_keymap_t */ +#include "src/history.h" +#ifdef ENABLE_SUBSHELL +#include "src/subshell/subshell.h" /* do_subshell_chdir() */ +#endif + +#include "src/usermenu.h" + +#include "dir.h" +#include "boxes.h" +#include "tree.h" +#include "ext.h" /* regexp_command */ +#include "layout.h" /* Most layout variables are here */ +#include "cmd.h" +#include "command.h" /* cmdline */ +#include "filemanager.h" +#include "mountlist.h" /* my_statfs */ + +#include "panel.h" + +/*** global variables ****************************************************************************/ + +/* The hook list for the select file function */ +hook_t *select_file_hook = NULL; + +/* *INDENT-OFF* */ +panelized_panel_t panelized_panel = { {NULL, 0, -1, NULL}, NULL }; +/* *INDENT-ON* */ + +static const char *string_file_name (file_entry_t *, int); +static const char *string_file_size (file_entry_t *, int); +static const char *string_file_size_brief (file_entry_t *, int); +static const char *string_file_type (file_entry_t *, int); +static const char *string_file_mtime (file_entry_t *, int); +static const char *string_file_atime (file_entry_t *, int); +static const char *string_file_ctime (file_entry_t *, int); +static const char *string_file_permission (file_entry_t *, int); +static const char *string_file_perm_octal (file_entry_t *, int); +static const char *string_file_nlinks (file_entry_t *, int); +static const char *string_inode (file_entry_t *, int); +static const char *string_file_nuid (file_entry_t *, int); +static const char *string_file_ngid (file_entry_t *, int); +static const char *string_file_owner (file_entry_t *, int); +static const char *string_file_group (file_entry_t *, int); +static const char *string_marked (file_entry_t *, int); +static const char *string_space (file_entry_t *, int); +static const char *string_dot (file_entry_t *, int); + +mc_fhl_t *mc_filehighlight = NULL; + +/*** file scope macro definitions ****************************************************************/ + +#define NORMAL 0 +#define SELECTED 1 +#define MARKED 2 +#define MARKED_SELECTED 3 +#define STATUS 5 + +/* select/unselect dialog results */ +#define SELECT_RESET ((mc_search_t *)(-1)) +#define SELECT_ERROR ((mc_search_t *)(-2)) + +/* mouse position relative to file list */ +#define MOUSE_UPPER_FILE_LIST (-1) +#define MOUSE_BELOW_FILE_LIST (-2) +#define MOUSE_AFTER_LAST_FILE (-3) + +/*** file scope type declarations ****************************************************************/ + +typedef enum +{ + MARK_DONT_MOVE = 0, + MARK_DOWN = 1, + MARK_FORCE_DOWN = 2, + MARK_FORCE_UP = 3 +} mark_act_t; + +/* + * This describes a format item. The parse_display_format routine parses + * the user specified format and creates a linked list of format_item_t structures. + */ +typedef struct format_item_t +{ + int requested_field_len; + int field_len; + align_crt_t just_mode; + gboolean expand; + const char *(*string_fn) (file_entry_t *, int len); + char *title; + const char *id; +} format_item_t; + +/* File name scroll states */ +typedef enum +{ + FILENAME_NOSCROLL = 1, + FILENAME_SCROLL_LEFT = 2, + FILENAME_SCROLL_RIGHT = 4 +} filename_scroll_flag_t; + +/*** file scope variables ************************************************************************/ + +/* *INDENT-OFF* */ +static panel_field_t panel_fields[] = { + { + "unsorted", 12, TRUE, J_LEFT_FIT, + /* TRANSLATORS: one single character to represent 'unsorted' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|u"), + N_("&Unsorted"), TRUE, FALSE, + string_file_name, + (GCompareFunc) unsorted + } + , + { + "name", 12, TRUE, J_LEFT_FIT, + /* TRANSLATORS: one single character to represent 'name' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|n"), + N_("&Name"), TRUE, TRUE, + string_file_name, + (GCompareFunc) sort_name + } + , + { + "version", 12, TRUE, J_LEFT_FIT, + /* TRANSLATORS: one single character to represent 'version' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|v"), + N_("&Version"), TRUE, FALSE, + string_file_name, + (GCompareFunc) sort_vers + } + , + { + "extension", 12, TRUE, J_LEFT_FIT, + /* TRANSLATORS: one single character to represent 'extension' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|e"), + N_("E&xtension"), TRUE, FALSE, + string_file_name, /* TODO: string_file_ext */ + (GCompareFunc) sort_ext + } + , + { + "size", 7, FALSE, J_RIGHT, + /* TRANSLATORS: one single character to represent 'size' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|s"), + N_("&Size"), TRUE, TRUE, + string_file_size, + (GCompareFunc) sort_size + } + , + { + "bsize", 7, FALSE, J_RIGHT, + "", + N_("Block Size"), FALSE, FALSE, + string_file_size_brief, + (GCompareFunc) sort_size + } + , + { + "type", 1, FALSE, J_LEFT, + "", + "", FALSE, TRUE, + string_file_type, + NULL + } + , + { + "mtime", 12, FALSE, J_RIGHT, + /* TRANSLATORS: one single character to represent 'Modify time' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|m"), + N_("&Modify time"), TRUE, TRUE, + string_file_mtime, + (GCompareFunc) sort_time + } + , + { + "atime", 12, FALSE, J_RIGHT, + /* TRANSLATORS: one single character to represent 'Access time' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|a"), + N_("&Access time"), TRUE, TRUE, + string_file_atime, + (GCompareFunc) sort_atime + } + , + { + "ctime", 12, FALSE, J_RIGHT, + /* TRANSLATORS: one single character to represent 'Change time' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|h"), + N_("C&hange time"), TRUE, TRUE, + string_file_ctime, + (GCompareFunc) sort_ctime + } + , + { + "perm", 10, FALSE, J_LEFT, + "", + N_("Permission"), FALSE, TRUE, + string_file_permission, + NULL + } + , + { + "mode", 6, FALSE, J_RIGHT, + "", + N_("Perm"), FALSE, TRUE, + string_file_perm_octal, + NULL + } + , + { + "nlink", 2, FALSE, J_RIGHT, + "", + N_("Nl"), FALSE, TRUE, + string_file_nlinks, NULL + } + , + { + "inode", 5, FALSE, J_RIGHT, + /* TRANSLATORS: one single character to represent 'inode' sort mode */ + /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */ + N_("sort|i"), + N_("&Inode"), TRUE, TRUE, + string_inode, + (GCompareFunc) sort_inode + } + , + { + "nuid", 5, FALSE, J_RIGHT, + "", + N_("UID"), FALSE, FALSE, + string_file_nuid, + NULL + } + , + { + "ngid", 5, FALSE, J_RIGHT, + "", + N_("GID"), FALSE, FALSE, + string_file_ngid, + NULL + } + , + { + "owner", 8, FALSE, J_LEFT_FIT, + "", + N_("Owner"), FALSE, TRUE, + string_file_owner, + NULL + } + , + { + "group", 8, FALSE, J_LEFT_FIT, + "", + N_("Group"), FALSE, TRUE, + string_file_group, + NULL + } + , + { + "mark", 1, FALSE, J_RIGHT, + "", + " ", FALSE, TRUE, + string_marked, + NULL + } + , + { + "|", 1, FALSE, J_RIGHT, + "", + " ", FALSE, TRUE, + NULL, + NULL + } + , + { + "space", 1, FALSE, J_RIGHT, + "", + " ", FALSE, TRUE, + string_space, + NULL + } + , + { + "dot", 1, FALSE, J_RIGHT, + "", + " ", FALSE, FALSE, + string_dot, + NULL + } + , + { + NULL, 0, FALSE, J_RIGHT, NULL, NULL, FALSE, FALSE, NULL, NULL + } +}; +/* *INDENT-ON* */ + +static char *panel_sort_up_char = NULL; +static char *panel_sort_down_char = NULL; + +static char *panel_hiddenfiles_show_char = NULL; +static char *panel_hiddenfiles_hide_char = NULL; +static char *panel_history_prev_item_char = NULL; +static char *panel_history_next_item_char = NULL; +static char *panel_history_show_list_char = NULL; +static char *panel_filename_scroll_left_char = NULL; +static char *panel_filename_scroll_right_char = NULL; + +/* Panel that selection started */ +static WPanel *mouse_mark_panel = NULL; + +static gboolean mouse_marking = FALSE; +static int state_mark = 0; + +static GString *string_file_name_buffer; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +set_colors (const WPanel * panel) +{ + (void) panel; + + tty_set_normal_attrs (); + tty_setcolor (NORMAL_COLOR); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Delete format_item_t object */ + +static void +format_item_free (format_item_t * format) +{ + g_free (format->title); + g_free (format); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Extract the number of available lines in a panel */ + +static int +panel_lines (const WPanel * p) +{ + /* 3 lines are: top frame, column header, botton frame */ + return (CONST_WIDGET (p)->rect.lines - 3 - (panels_options.show_mini_info ? 2 : 0)); +} + +/* --------------------------------------------------------------------------------------------- */ +/** This code relies on the default justification!!! */ + +static void +add_permission_string (const char *dest, int width, file_entry_t * fe, int attr, int color, + gboolean is_octal) +{ + int i, r, l; + + l = get_user_permissions (&fe->st); + + if (is_octal) + { + /* Place of the access bit in octal mode */ + l = width + l - 3; + r = l + 1; + } + else + { + /* The same to the triplet in string mode */ + l = l * 3 + 1; + r = l + 3; + } + + for (i = 0; i < width; i++) + { + if (i >= l && i < r) + { + if (attr == SELECTED || attr == MARKED_SELECTED) + tty_setcolor (MARKED_SELECTED_COLOR); + else + tty_setcolor (MARKED_COLOR); + } + else if (color >= 0) + tty_setcolor (color); + else + tty_lowlevel_setcolor (-color); + + tty_print_char (dest[i]); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** String representations of various file attributes name */ + +static const char * +string_file_name (file_entry_t * fe, int len) +{ + (void) len; + + mc_g_string_copy (string_file_name_buffer, fe->fname); + + return string_file_name_buffer->str; +} + +/* --------------------------------------------------------------------------------------------- */ + +static unsigned int +ilog10 (dev_t n) +{ + unsigned int digits = 0; + + do + { + digits++; + n /= 10; + } + while (n != 0); + + return digits; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +format_device_number (char *buf, size_t bufsize, dev_t dev) +{ + dev_t major_dev, minor_dev; + unsigned int major_digits, minor_digits; + + major_dev = major (dev); + major_digits = ilog10 (major_dev); + + minor_dev = minor (dev); + minor_digits = ilog10 (minor_dev); + + g_assert (bufsize >= 1); + + if (major_digits + 1 + minor_digits + 1 <= bufsize) + g_snprintf (buf, bufsize, "%lu,%lu", (unsigned long) major_dev, (unsigned long) minor_dev); + else + g_strlcpy (buf, _("[dev]"), bufsize); +} + +/* --------------------------------------------------------------------------------------------- */ +/** size */ + +static const char * +string_file_size (file_entry_t * fe, int len) +{ + static char buffer[BUF_TINY]; + + /* Don't ever show size of ".." since we don't calculate it */ + if (DIR_IS_DOTDOT (fe->fname->str)) + return _("UP--DIR"); + +#ifdef HAVE_STRUCT_STAT_ST_RDEV + if (S_ISBLK (fe->st.st_mode) || S_ISCHR (fe->st.st_mode)) + format_device_number (buffer, len + 1, fe->st.st_rdev); + else +#endif + size_trunc_len (buffer, (unsigned int) len, fe->st.st_size, 0, panels_options.kilobyte_si); + + return buffer; +} + +/* --------------------------------------------------------------------------------------------- */ +/** bsize */ + +static const char * +string_file_size_brief (file_entry_t * fe, int len) +{ + if (S_ISLNK (fe->st.st_mode) && !link_isdir (fe)) + return _("SYMLINK"); + + if ((S_ISDIR (fe->st.st_mode) || link_isdir (fe)) && !DIR_IS_DOTDOT (fe->fname->str)) + return _("SUB-DIR"); + + return string_file_size (fe, len); +} + +/* --------------------------------------------------------------------------------------------- */ +/** This functions return a string representation of a file entry type */ + +static const char * +string_file_type (file_entry_t * fe, int len) +{ + static char buffer[2]; + + (void) len; + + if (S_ISDIR (fe->st.st_mode)) + buffer[0] = PATH_SEP; + else if (S_ISLNK (fe->st.st_mode)) + { + if (link_isdir (fe)) + buffer[0] = '~'; + else if (fe->f.stale_link) + buffer[0] = '!'; + else + buffer[0] = '@'; + } + else if (S_ISCHR (fe->st.st_mode)) + buffer[0] = '-'; + else if (S_ISSOCK (fe->st.st_mode)) + buffer[0] = '='; + else if (S_ISDOOR (fe->st.st_mode)) + buffer[0] = '>'; + else if (S_ISBLK (fe->st.st_mode)) + buffer[0] = '+'; + else if (S_ISFIFO (fe->st.st_mode)) + buffer[0] = '|'; + else if (S_ISNAM (fe->st.st_mode)) + buffer[0] = '#'; + else if (!S_ISREG (fe->st.st_mode)) + buffer[0] = '?'; /* non-regular of unknown kind */ + else if (is_exe (fe->st.st_mode)) + buffer[0] = '*'; + else + buffer[0] = ' '; + buffer[1] = '\0'; + return buffer; +} + +/* --------------------------------------------------------------------------------------------- */ +/** mtime */ + +static const char * +string_file_mtime (file_entry_t * fe, int len) +{ + (void) len; + + return file_date (fe->st.st_mtime); +} + +/* --------------------------------------------------------------------------------------------- */ +/** atime */ + +static const char * +string_file_atime (file_entry_t * fe, int len) +{ + (void) len; + + return file_date (fe->st.st_atime); +} + +/* --------------------------------------------------------------------------------------------- */ +/** ctime */ + +static const char * +string_file_ctime (file_entry_t * fe, int len) +{ + (void) len; + + return file_date (fe->st.st_ctime); +} + +/* --------------------------------------------------------------------------------------------- */ +/** perm */ + +static const char * +string_file_permission (file_entry_t * fe, int len) +{ + (void) len; + + return string_perm (fe->st.st_mode); +} + +/* --------------------------------------------------------------------------------------------- */ +/** mode */ + +static const char * +string_file_perm_octal (file_entry_t * fe, int len) +{ + static char buffer[10]; + + (void) len; + + g_snprintf (buffer, sizeof (buffer), "0%06lo", (unsigned long) fe->st.st_mode); + return buffer; +} + +/* --------------------------------------------------------------------------------------------- */ +/** nlink */ + +static const char * +string_file_nlinks (file_entry_t * fe, int len) +{ + static char buffer[BUF_TINY]; + + (void) len; + + g_snprintf (buffer, sizeof (buffer), "%16d", (int) fe->st.st_nlink); + return buffer; +} + +/* --------------------------------------------------------------------------------------------- */ +/** inode */ + +static const char * +string_inode (file_entry_t * fe, int len) +{ + static char buffer[10]; + + (void) len; + + g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_ino); + return buffer; +} + +/* --------------------------------------------------------------------------------------------- */ +/** nuid */ + +static const char * +string_file_nuid (file_entry_t * fe, int len) +{ + static char buffer[10]; + + (void) len; + + g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_uid); + return buffer; +} + +/* --------------------------------------------------------------------------------------------- */ +/** ngid */ + +static const char * +string_file_ngid (file_entry_t * fe, int len) +{ + static char buffer[10]; + + (void) len; + + g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_gid); + return buffer; +} + +/* --------------------------------------------------------------------------------------------- */ +/** owner */ + +static const char * +string_file_owner (file_entry_t * fe, int len) +{ + (void) len; + + return get_owner (fe->st.st_uid); +} + +/* --------------------------------------------------------------------------------------------- */ +/** group */ + +static const char * +string_file_group (file_entry_t * fe, int len) +{ + (void) len; + + return get_group (fe->st.st_gid); +} + +/* --------------------------------------------------------------------------------------------- */ +/** mark */ + +static const char * +string_marked (file_entry_t * fe, int len) +{ + (void) len; + + return fe->f.marked ? "*" : " "; +} + +/* --------------------------------------------------------------------------------------------- */ +/** space */ + +static const char * +string_space (file_entry_t * fe, int len) +{ + (void) fe; + (void) len; + + return " "; +} + +/* --------------------------------------------------------------------------------------------- */ +/** dot */ + +static const char * +string_dot (file_entry_t * fe, int len) +{ + (void) fe; + (void) len; + + return "."; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +file_compute_color (int attr, file_entry_t * fe) +{ + switch (attr) + { + case SELECTED: + return (SELECTED_COLOR); + case MARKED: + return (MARKED_COLOR); + case MARKED_SELECTED: + return (MARKED_SELECTED_COLOR); + case STATUS: + return (NORMAL_COLOR); + case NORMAL: + default: + if (!panels_options.filetype_mode) + return (NORMAL_COLOR); + } + + return mc_fhl_get_color (mc_filehighlight, fe); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Returns the number of items in the given panel */ + +static int +panel_items (const WPanel * p) +{ + return panel_lines (p) * p->list_cols; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Formats the file number file_index of panel in the buffer dest */ + +static filename_scroll_flag_t +format_file (WPanel * panel, int file_index, int width, int attr, gboolean isstatus, + int *field_length) +{ + int color = NORMAL_COLOR; + int length = 0; + GSList *format, *home; + file_entry_t *fe = NULL; + filename_scroll_flag_t res = FILENAME_NOSCROLL; + + *field_length = 0; + + if (file_index < panel->dir.len) + { + fe = &panel->dir.list[file_index]; + color = file_compute_color (attr, fe); + } + + home = isstatus ? panel->status_format : panel->format; + + for (format = home; format != NULL && length != width; format = g_slist_next (format)) + { + format_item_t *fi = (format_item_t *) format->data; + + if (fi->string_fn != NULL) + { + const char *txt = " "; + int len, perm = 0; + const char *prepared_text; + int name_offset = 0; + + if (fe != NULL) + txt = fi->string_fn (fe, fi->field_len); + + len = fi->field_len; + if (len + length > width) + len = width - length; + if (len <= 0) + break; + + if (!isstatus && panel->content_shift > -1 && strcmp (fi->id, "name") == 0) + { + int str_len; + int i; + + *field_length = len + 1; + + str_len = str_length (txt); + i = MAX (0, str_len - len); + panel->max_shift = MAX (panel->max_shift, i); + i = MIN (panel->content_shift, i); + + if (i > -1) + { + name_offset = str_offset_to_pos (txt, i); + if (str_len > len) + { + res = FILENAME_SCROLL_LEFT; + if (str_length (txt + name_offset) > len) + res |= FILENAME_SCROLL_RIGHT; + } + } + } + + if (panels_options.permission_mode) + { + if (strcmp (fi->id, "perm") == 0) + perm = 1; + else if (strcmp (fi->id, "mode") == 0) + perm = 2; + } + + if (color >= 0) + tty_setcolor (color); + else + tty_lowlevel_setcolor (-color); + + if (!isstatus && panel->content_shift > -1) + prepared_text = str_fit_to_term (txt + name_offset, len, HIDE_FIT (fi->just_mode)); + else + prepared_text = str_fit_to_term (txt, len, fi->just_mode); + + if (perm != 0 && fe != NULL) + add_permission_string (prepared_text, fi->field_len, fe, attr, color, perm != 1); + else + tty_print_string (prepared_text); + + length += len; + } + else + { + if (attr == SELECTED || attr == MARKED_SELECTED) + tty_setcolor (SELECTED_COLOR); + else + tty_setcolor (NORMAL_COLOR); + tty_print_one_vline (TRUE); + length++; + } + } + + if (length < width) + { + int y, x; + + tty_getyx (&y, &x); + tty_draw_hline (y, x, ' ', width - length); + } + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +repaint_file (WPanel * panel, int file_index, int attr, gboolean isstatus) +{ + Widget *w = WIDGET (panel); + + int nth_column = 0; + int width; + int offset = 0; + filename_scroll_flag_t ret_frm; + int ypos = 0; + gboolean panel_is_split; + int fln = 0; + + panel_is_split = !isstatus && panel->list_cols > 1; + width = w->rect.cols - 2; + + if (panel_is_split) + { + nth_column = (file_index - panel->top_file) / panel_lines (panel); + width /= panel->list_cols; + + offset = width * nth_column; + + if (nth_column + 1 >= panel->list_cols) + width = w->rect.cols - offset - 2; + } + + /* Nothing to paint */ + if (width <= 0) + return; + + if (!isstatus) + { + ypos = file_index - panel->top_file; + + if (panel_is_split) + ypos %= panel_lines (panel); + + ypos += 2; /* top frame and header */ + widget_gotoyx (w, ypos, offset + 1); + } + + ret_frm = format_file (panel, file_index, width, attr, isstatus, &fln); + + if (panel_is_split && nth_column + 1 < panel->list_cols) + { + tty_setcolor (NORMAL_COLOR); + tty_print_one_vline (TRUE); + } + + if (!isstatus && ret_frm != FILENAME_NOSCROLL) + { + if (!panel_is_split && fln > 0) + { + if (panel->list_format != list_long) + width = fln; + else + { + offset = width - fln + 1; + width = fln - 1; + } + } + + widget_gotoyx (w, ypos, offset); + tty_setcolor (NORMAL_COLOR); + tty_print_string (panel_filename_scroll_left_char); + + if ((ret_frm & FILENAME_SCROLL_RIGHT) != 0) + { + offset += width; + if (nth_column + 1 >= panel->list_cols) + offset++; + + widget_gotoyx (w, ypos, offset); + tty_setcolor (NORMAL_COLOR); + tty_print_string (panel_filename_scroll_right_char); + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +display_mini_info (WPanel * panel) +{ + Widget *w = WIDGET (panel); + + if (!panels_options.show_mini_info || panel->selected < 0) + return; + + widget_gotoyx (w, panel_lines (panel) + 3, 1); + + if (panel->quick_search.active) + { + tty_setcolor (INPUT_COLOR); + tty_print_char ('/'); + tty_print_string (str_fit_to_term + (panel->quick_search.buffer->str, w->rect.cols - 3, J_LEFT)); + return; + } + + /* Status resolves links and show them */ + set_colors (panel); + + if (S_ISLNK (panel->dir.list[panel->selected].st.st_mode)) + { + char link_target[MC_MAXPATHLEN]; + vfs_path_t *lc_link_vpath; + int len; + + lc_link_vpath = + vfs_path_append_new (panel->cwd_vpath, panel->dir.list[panel->selected].fname->str, + (char *) NULL); + len = mc_readlink (lc_link_vpath, link_target, MC_MAXPATHLEN - 1); + vfs_path_free (lc_link_vpath, TRUE); + if (len > 0) + { + link_target[len] = 0; + tty_print_string ("-> "); + tty_print_string (str_fit_to_term (link_target, w->rect.cols - 5, J_LEFT_FIT)); + } + else + tty_print_string (str_fit_to_term (_("<readlink failed>"), w->rect.cols - 2, J_LEFT)); + } + else if (DIR_IS_DOTDOT (panel->dir.list[panel->selected].fname->str)) + { + /* FIXME: + * while loading directory (dir_list_load() and dir_list_reload()), + * the actual stat info about ".." directory isn't got; + * so just don't display incorrect info about ".." directory */ + tty_print_string (str_fit_to_term (_("UP--DIR"), w->rect.cols - 2, J_LEFT)); + } + else + /* Default behavior */ + repaint_file (panel, panel->selected, STATUS, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +paint_dir (WPanel * panel) +{ + int i; + int items; /* Number of items */ + + items = panel_items (panel); + /* reset max len of filename because we have the new max length for the new file list */ + panel->max_shift = -1; + + for (i = 0; i < items; i++) + { + int color = 0; /* Color value of the line */ + + if (i + panel->top_file < panel->dir.len) + { + color = 2 * (panel->dir.list[i + panel->top_file].f.marked); + color += (panel->selected == i + panel->top_file && panel->active); + } + + repaint_file (panel, i + panel->top_file, color, FALSE); + } + + tty_set_normal_attrs (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +display_total_marked_size (const WPanel * panel, int y, int x, gboolean size_only) +{ + const Widget *w = CONST_WIDGET (panel); + + char buffer[BUF_SMALL], b_bytes[BUF_SMALL]; + const char *buf; + int cols; + + if (panel->marked <= 0) + return; + + buf = size_only ? b_bytes : buffer; + cols = w->rect.cols - 2; + + g_strlcpy (b_bytes, size_trunc_sep (panel->total, panels_options.kilobyte_si), + sizeof (b_bytes)); + + if (!size_only) + g_snprintf (buffer, sizeof (buffer), + ngettext ("%s in %d file", "%s in %d files", panel->marked), + b_bytes, panel->marked); + + /* don't forget spaces around buffer content */ + buf = str_trunc (buf, cols - 4); + + if (x < 0) + /* center in panel */ + x = (w->rect.cols - str_term_width1 (buf)) / 2 - 1; + + /* + * y == panel_lines (panel) + 2 for mini_info_separator + * y == w->lines - 1 for panel bottom frame + */ + widget_gotoyx (w, y, x); + tty_setcolor (MARKED_COLOR); + tty_printf (" %s ", buf); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +mini_info_separator (const WPanel * panel) +{ + if (panels_options.show_mini_info) + { + const Widget *w = CONST_WIDGET (panel); + int y; + + y = panel_lines (panel) + 2; + + tty_setcolor (NORMAL_COLOR); + tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2); + /* Status displays total marked size. + * Centered in panel, full format. */ + display_total_marked_size (panel, y, -1, FALSE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +show_free_space (const WPanel * panel) +{ + /* Used to figure out how many free space we have */ + static struct my_statfs myfs_stats; + /* Old current working directory for displaying free space */ + static char *old_cwd = NULL; + + /* Don't try to stat non-local fs */ + if (!vfs_file_is_local (panel->cwd_vpath) || !free_space) + return; + + if (old_cwd == NULL || strcmp (old_cwd, vfs_path_as_str (panel->cwd_vpath)) != 0) + { + char rpath[PATH_MAX]; + + init_my_statfs (); + g_free (old_cwd); + old_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath)); + + if (mc_realpath (old_cwd, rpath) == NULL) + return; + + my_statfs (&myfs_stats, rpath); + } + + if (myfs_stats.avail != 0 || myfs_stats.total != 0) + { + const Widget *w = CONST_WIDGET (panel); + char buffer1[6], buffer2[6], tmp[BUF_SMALL]; + + size_trunc_len (buffer1, sizeof (buffer1) - 1, myfs_stats.avail, 1, + panels_options.kilobyte_si); + size_trunc_len (buffer2, sizeof (buffer2) - 1, myfs_stats.total, 1, + panels_options.kilobyte_si); + g_snprintf (tmp, sizeof (tmp), " %s / %s (%d%%) ", buffer1, buffer2, + myfs_stats.total == 0 ? 0 : + (int) (100 * (long double) myfs_stats.avail / myfs_stats.total)); + widget_gotoyx (w, w->rect.lines - 1, w->rect.cols - 2 - (int) strlen (tmp)); + tty_setcolor (NORMAL_COLOR); + tty_print_string (tmp); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Prepare path string for showing in panel's header. + * Passwords will removed, also home dir will replaced by ~ + * + * @param panel WPanel object + * + * @return newly allocated string. + */ + +static char * +panel_correct_path_to_show (const WPanel * panel) +{ + vfs_path_t *last_vpath; + const vfs_path_element_t *path_element; + char *return_path; + int elements_count; + + elements_count = vfs_path_elements_count (panel->cwd_vpath); + + /* get last path element */ + path_element = vfs_path_element_clone (vfs_path_get_by_index (panel->cwd_vpath, -1)); + + if (elements_count > 1 && (strcmp (path_element->class->name, "cpiofs") == 0 || + strcmp (path_element->class->name, "extfs") == 0 || + strcmp (path_element->class->name, "tarfs") == 0)) + { + const char *archive_name; + const vfs_path_element_t *prev_path_element; + + /* get previous path element for catching archive name */ + prev_path_element = vfs_path_get_by_index (panel->cwd_vpath, -2); + archive_name = strrchr (prev_path_element->path, PATH_SEP); + if (archive_name != NULL) + last_vpath = vfs_path_from_str_flags (archive_name + 1, VPF_NO_CANON); + else + { + last_vpath = vfs_path_from_str_flags (prev_path_element->path, VPF_NO_CANON); + last_vpath->relative = TRUE; + } + } + else + { + last_vpath = vfs_path_new (); + last_vpath->relative = TRUE; + } + + vfs_path_add_element (last_vpath, path_element); + return_path = + vfs_path_to_str_flags (last_vpath, 0, + VPF_STRIP_HOME | VPF_STRIP_PASSWORD | VPF_HIDE_CHARSET); + vfs_path_free (last_vpath, TRUE); + + return return_path; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Get Current path element encoding + * + * @param panel WPanel object + * + * @return newly allocated string or NULL if path charset is same as system charset + */ + +#ifdef HAVE_CHARSET +static char * +panel_get_encoding_info_str (const WPanel * panel) +{ + char *ret_str = NULL; + const vfs_path_element_t *path_element; + + path_element = vfs_path_get_by_index (panel->cwd_vpath, -1); + if (path_element->encoding != NULL) + ret_str = g_strdup_printf ("[%s]", path_element->encoding); + + return ret_str; +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +static void +show_dir (const WPanel * panel) +{ + const Widget *w = CONST_WIDGET (panel); + gchar *tmp; + + set_colors (panel); + tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE); + + if (panels_options.show_mini_info) + { + int y; + + y = panel_lines (panel) + 2; + + widget_gotoyx (w, y, 0); + tty_print_alt_char (ACS_LTEE, FALSE); + widget_gotoyx (w, y, w->rect.cols - 1); + tty_print_alt_char (ACS_RTEE, FALSE); + } + + widget_gotoyx (w, 0, 1); + tty_print_string (panel_history_prev_item_char); + + tmp = panels_options.show_dot_files ? panel_hiddenfiles_show_char : panel_hiddenfiles_hide_char; + tmp = g_strdup_printf ("%s[%s]%s", tmp, panel_history_show_list_char, + panel_history_next_item_char); + + widget_gotoyx (w, 0, w->rect.cols - 6); + tty_print_string (tmp); + + g_free (tmp); + + widget_gotoyx (w, 0, 3); + + if (panel->is_panelized) + tty_printf (" %s ", _("Panelize")); +#ifdef HAVE_CHARSET + else + { + tmp = panel_get_encoding_info_str (panel); + if (tmp != NULL) + { + tty_printf ("%s", tmp); + widget_gotoyx (w, 0, 3 + strlen (tmp)); + g_free (tmp); + } + } +#endif + + if (panel->active) + tty_setcolor (REVERSE_COLOR); + + tmp = panel_correct_path_to_show (panel); + tty_printf (" %s ", str_term_trim (tmp, MIN (MAX (w->rect.cols - 12, 0), w->rect.cols))); + g_free (tmp); + + if (!panels_options.show_mini_info) + { + if (panel->marked == 0) + { + /* Show size of curret file in the bottom of panel */ + if (S_ISREG (panel->dir.list[panel->selected].st.st_mode)) + { + char buffer[BUF_SMALL]; + + g_snprintf (buffer, sizeof (buffer), " %s ", + size_trunc_sep (panel->dir.list[panel->selected].st.st_size, + panels_options.kilobyte_si)); + tty_setcolor (NORMAL_COLOR); + widget_gotoyx (w, w->rect.lines - 1, 4); + tty_print_string (buffer); + } + } + else + { + /* Show total size of marked files + * In the bottom of panel, display size only. */ + display_total_marked_size (panel, w->rect.lines - 1, 2, TRUE); + } + } + + show_free_space (panel); + + if (panel->active) + tty_set_normal_attrs (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +adjust_top_file (WPanel * panel) +{ + int items; + + /* Update panel->selected to avoid out of range in panel->dir.list[panel->selected] + * when panel is redrawing when directory is reloading, for example in path: + * dir_list_reload() -> mc_refresh() -> dialog_change_screen_size() -> + * midnight_callback (MSG_RESIZE) -> setup_panels() -> panel_callback(MSG_DRAW) -> + * display_mini_info() + */ + panel->selected = CLAMP (panel->selected, 0, panel->dir.len - 1); + + items = panel_items (panel); + + if (panel->dir.len <= items) + { + /* If all files fit, show them all. */ + panel->top_file = 0; + } + else + { + int i; + + /* top_file has to be in the range [selected-items+1, selected] so that + the selected file is visible. + top_file should be in the range [0, count-items] so that there's + no empty space wasted. + Within these ranges, adjust it by as little as possible. */ + + if (panel->top_file < 0) + panel->top_file = 0; + + i = panel->selected - items + 1; + if (panel->top_file < i) + panel->top_file = i; + + i = panel->dir.len - items; + if (panel->top_file > i) + panel->top_file = i; + + if (panel->top_file > panel->selected) + panel->top_file = panel->selected; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** add "#enc:encodning" to end of path */ +/* if path end width a previous #enc:, only encoding is changed no additional + * #enc: is appended + * retun new string + */ + +static char * +panel_save_name (WPanel * panel) +{ + /* If the program is shuting down */ + if ((mc_global.midnight_shutdown && auto_save_setup) || saving_setup) + return g_strdup (panel->name); + + return g_strconcat ("Temporal:", panel->name, (char *) NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +directory_history_add (WPanel * panel, const vfs_path_t * vpath) +{ + char *tmp; + + tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD); + panel->dir_history.list = list_append_unique (panel->dir_history.list, tmp); + panel->dir_history.current = panel->dir_history.list; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* "history_load" event handler */ +static gboolean +panel_load_history (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + WPanel *p = PANEL (init_data); + ev_history_load_save_t *ev = (ev_history_load_save_t *) data; + + (void) event_group_name; + (void) event_name; + + if (ev->receiver == NULL || ev->receiver == WIDGET (p)) + { + if (ev->cfg != NULL) + p->dir_history.list = mc_config_history_load (ev->cfg, p->dir_history.name); + else + p->dir_history.list = mc_config_history_get (p->dir_history.name); + + directory_history_add (p, p->cwd_vpath); + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* "history_save" event handler */ +static gboolean +panel_save_history (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + WPanel *p = PANEL (init_data); + + (void) event_group_name; + (void) event_name; + + if (p->dir_history.list != NULL) + { + ev_history_load_save_t *ev = (ev_history_load_save_t *) data; + + mc_config_history_save (ev->cfg, p->dir_history.name, p->dir_history.list); + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_destroy (WPanel * p) +{ + size_t i; + + if (panels_options.auto_save_setup) + { + char *name; + + name = panel_save_name (p); + panel_save_setup (p, name); + g_free (name); + } + + panel_clean_dir (p); + + /* clean history */ + if (p->dir_history.list != NULL) + { + /* directory history is already saved before this moment */ + p->dir_history.list = g_list_first (p->dir_history.list); + g_list_free_full (p->dir_history.list, g_free); + } + g_free (p->dir_history.name); + + file_filter_clear (&p->filter); + + g_slist_free_full (p->format, (GDestroyNotify) format_item_free); + g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free); + + g_free (p->user_format); + for (i = 0; i < LIST_FORMATS; i++) + g_free (p->user_status_format[i]); + + g_free (p->dir.list); + g_free (p->name); + + g_string_free (p->quick_search.buffer, TRUE); + g_string_free (p->quick_search.prev_buffer, TRUE); + + vfs_path_free (p->lwd_vpath, TRUE); + vfs_path_free (p->cwd_vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_paint_sort_info (const WPanel * panel) +{ + if (*panel->sort_field->hotkey != '\0') + { + const char *sort_sign = + panel->sort_info.reverse ? panel_sort_up_char : panel_sort_down_char; + char *str; + + str = g_strdup_printf ("%s%s", sort_sign, Q_ (panel->sort_field->hotkey)); + widget_gotoyx (panel, 1, 1); + tty_print_string (str); + g_free (str); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static const char * +panel_get_title_without_hotkey (const char *title) +{ + static char translated_title[BUF_TINY]; + + if (title == NULL || title[0] == '\0') + translated_title[0] = '\0'; + else + { + char *hkey; + + g_snprintf (translated_title, sizeof (translated_title), "%s", _(title)); + + hkey = strchr (translated_title, '&'); + if (hkey != NULL && hkey[1] != '\0') + memmove (hkey, hkey + 1, strlen (hkey)); + } + + return translated_title; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_print_header (const WPanel * panel) +{ + const Widget *w = CONST_WIDGET (panel); + + int y, x; + int i; + GString *format_txt; + + widget_gotoyx (w, 1, 1); + tty_getyx (&y, &x); + tty_setcolor (NORMAL_COLOR); + tty_draw_hline (y, x, ' ', w->rect.cols - 2); + + format_txt = g_string_new (""); + + for (i = 0; i < panel->list_cols; i++) + { + GSList *format; + + for (format = panel->format; format != NULL; format = g_slist_next (format)) + { + format_item_t *fi = (format_item_t *) format->data; + + if (fi->string_fn != NULL) + { + g_string_set_size (format_txt, 0); + + if (panel->list_format == list_long && strcmp (fi->id, panel->sort_field->id) == 0) + g_string_append (format_txt, + panel->sort_info.reverse + ? panel_sort_up_char : panel_sort_down_char); + + g_string_append (format_txt, fi->title); + + if (panel->filter.handler != NULL && strcmp (fi->id, "name") == 0) + { + g_string_append (format_txt, " ["); + g_string_append (format_txt, panel->filter.value); + g_string_append (format_txt, "]"); + } + + tty_setcolor (HEADER_COLOR); + tty_print_string (str_fit_to_term (format_txt->str, fi->field_len, J_CENTER_LEFT)); + } + else + { + tty_setcolor (NORMAL_COLOR); + tty_print_one_vline (TRUE); + } + } + + if (i < panel->list_cols - 1) + { + tty_setcolor (NORMAL_COLOR); + tty_print_one_vline (TRUE); + } + } + + g_string_free (format_txt, TRUE); + + if (panel->list_format != list_long) + panel_paint_sort_info (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static const char * +parse_panel_size (WPanel * panel, const char *format, gboolean isstatus) +{ + panel_display_t frame = frame_half; + + format = skip_separators (format); + + if (strncmp (format, "full", 4) == 0) + { + frame = frame_full; + format += 4; + } + else if (strncmp (format, "half", 4) == 0) + { + frame = frame_half; + format += 4; + } + + if (!isstatus) + { + panel->frame_size = frame; + panel->list_cols = 1; + } + + /* Now, the optional column specifier */ + format = skip_separators (format); + + if (g_ascii_isdigit (*format)) + { + if (!isstatus) + { + panel->list_cols = g_ascii_digit_value (*format); + if (panel->list_cols < 1) + panel->list_cols = 1; + } + + format++; + } + + if (!isstatus) + panel_update_cols (WIDGET (panel), panel->frame_size); + + return skip_separators (format); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* *INDENT-OFF* */ +/* Format is: + + all := panel_format? format + panel_format := [full|half] [1-9] + format := one_format_item_t + | format , one_format_item_t + + one_format_item_t := just format.id [opt_size] + just := [<=>] + opt_size := : size [opt_expand] + size := [0-9]+ + opt_expand := + + + */ +/* *INDENT-ON* */ + +static GSList * +parse_display_format (WPanel * panel, const char *format, char **error, gboolean isstatus, + int *res_total_cols) +{ + GSList *home = NULL; /* The formats we return */ + int total_cols = 0; /* Used columns by the format */ + size_t i; + + static size_t i18n_timelength = 0; /* flag: check ?Time length at startup */ + + *error = NULL; + + if (i18n_timelength == 0) + { + i18n_timelength = i18n_checktimelength (); /* Musn't be 0 */ + + for (i = 0; panel_fields[i].id != NULL; i++) + if (strcmp ("time", panel_fields[i].id + 1) == 0) + panel_fields[i].min_size = i18n_timelength; + } + + /* + * This makes sure that the panel and mini status full/half mode + * setting is equal + */ + format = parse_panel_size (panel, format, isstatus); + + while (*format != '\0') + { /* format can be an empty string */ + format_item_t *darr; + align_crt_t justify; /* Which mode. */ + gboolean set_justify = TRUE; /* flag: set justification mode? */ + gboolean found = FALSE; + size_t klen = 0; + + darr = g_new0 (format_item_t, 1); + home = g_slist_append (home, darr); + + format = skip_separators (format); + + switch (*format) + { + case '<': + justify = J_LEFT; + format = skip_separators (format + 1); + break; + case '=': + justify = J_CENTER; + format = skip_separators (format + 1); + break; + case '>': + justify = J_RIGHT; + format = skip_separators (format + 1); + break; + default: + justify = J_LEFT; + set_justify = FALSE; + break; + } + + for (i = 0; !found && panel_fields[i].id != NULL; i++) + { + klen = strlen (panel_fields[i].id); + found = strncmp (format, panel_fields[i].id, klen) == 0; + } + + if (found) + { + i--; + format += klen; + + darr->requested_field_len = panel_fields[i].min_size; + darr->string_fn = panel_fields[i].string_fn; + darr->title = g_strdup (panel_get_title_without_hotkey (panel_fields[i].title_hotkey)); + darr->id = panel_fields[i].id; + darr->expand = panel_fields[i].expands; + darr->just_mode = panel_fields[i].default_just; + + if (set_justify) + { + if (IS_FIT (darr->just_mode)) + darr->just_mode = MAKE_FIT (justify); + else + darr->just_mode = justify; + } + + format = skip_separators (format); + + /* If we have a size specifier */ + if (*format == ':') + { + int req_length; + + /* If the size was specified, we don't want + * auto-expansion by default + */ + darr->expand = FALSE; + format++; + req_length = atoi (format); + darr->requested_field_len = req_length; + + format = skip_numbers (format); + + /* Now, if they insist on expansion */ + if (*format == '+') + { + darr->expand = TRUE; + format++; + } + } + } + else + { + size_t pos; + char *tmp_format; + + pos = strlen (format); + if (pos > 8) + pos = 8; + + tmp_format = g_strndup (format, pos); + g_slist_free_full (home, (GDestroyNotify) format_item_free); + *error = + g_strconcat (_("Unknown tag on display format:"), " ", tmp_format, (char *) NULL); + g_free (tmp_format); + + return NULL; + } + + total_cols += darr->requested_field_len; + } + + *res_total_cols = total_cols; + return home; +} + +/* --------------------------------------------------------------------------------------------- */ + +static GSList * +use_display_format (WPanel * panel, const char *format, char **error, gboolean isstatus) +{ +#define MAX_EXPAND 4 + int expand_top = 0; /* Max used element in expand */ + int usable_columns; /* Usable columns in the panel */ + int total_cols = 0; + GSList *darr, *home; + + if (format == NULL) + format = DEFAULT_USER_FORMAT; + + home = parse_display_format (panel, format, error, isstatus, &total_cols); + + if (*error != NULL) + return NULL; + + panel->dirty = TRUE; + + usable_columns = WIDGET (panel)->rect.cols - 2; + /* Status needn't to be split */ + if (!isstatus) + { + usable_columns /= panel->list_cols; + if (panel->list_cols > 1) + usable_columns--; + } + + /* Look for the expandable fields and set field_len based on the requested field len */ + for (darr = home; darr != NULL && expand_top < MAX_EXPAND; darr = g_slist_next (darr)) + { + format_item_t *fi = (format_item_t *) darr->data; + + fi->field_len = fi->requested_field_len; + if (fi->expand) + expand_top++; + } + + /* If we used more columns than the available columns, adjust that */ + if (total_cols > usable_columns) + { + int dif; + int pdif = 0; + + dif = total_cols - usable_columns; + + while (dif != 0 && pdif != dif) + { + pdif = dif; + + for (darr = home; darr != NULL; darr = g_slist_next (darr)) + { + format_item_t *fi = (format_item_t *) darr->data; + + if (dif != 0 && fi->field_len != 1) + { + fi->field_len--; + dif--; + } + } + } + + total_cols = usable_columns; /* give up, the rest should be truncated */ + } + + /* Expand the available space */ + if (usable_columns > total_cols && expand_top != 0) + { + int i; + int spaces; + + spaces = (usable_columns - total_cols) / expand_top; + + for (i = 0, darr = home; darr != NULL && i < expand_top; darr = g_slist_next (darr)) + { + format_item_t *fi = (format_item_t *) darr->data; + + if (fi->expand) + { + fi->field_len += spaces; + if (i == 0) + fi->field_len += (usable_columns - total_cols) % expand_top; + i++; + } + } + } + + return home; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Given the panel->view_type returns the format string to be parsed */ + +static const char * +panel_format (WPanel * panel) +{ + switch (panel->list_format) + { + case list_long: + return "full perm space nlink space owner space group space size space mtime space name"; + + case list_brief: + { + static char format[BUF_TINY]; + int brief_cols = panel->brief_cols; + + if (brief_cols < 1) + brief_cols = 2; + + if (brief_cols > 9) + brief_cols = 9; + + g_snprintf (format, sizeof (format), "half %d type name", brief_cols); + return format; + } + + case list_user: + return panel->user_format; + + default: + case list_full: + return "half type name | size | mtime"; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static const char * +mini_status_format (WPanel * panel) +{ + if (panel->user_mini_status) + return panel->user_status_format[panel->list_format]; + + switch (panel->list_format) + { + case list_long: + return "full perm space nlink space owner space group space size space mtime space name"; + + case list_brief: + return "half type name space bsize space perm space"; + + case list_full: + return "half type name"; + + default: + case list_user: + return panel->user_format; + } +} + +/* */ +/* Panel operation commands */ +/* */ + +/* --------------------------------------------------------------------------------------------- */ + +static void +cd_up_dir (WPanel * panel) +{ + vfs_path_t *up_dir; + + up_dir = vfs_path_from_str (".."); + panel_cd (panel, up_dir, cd_exact); + vfs_path_free (up_dir, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Used to emulate Lynx's entering leaving a directory with the arrow keys */ + +static cb_ret_t +maybe_cd (WPanel * panel, gboolean move_up_dir) +{ + if (panels_options.navigate_with_arrows && input_is_empty (cmdline)) + { + if (move_up_dir) + { + cd_up_dir (panel); + return MSG_HANDLED; + } + + if (S_ISDIR (selection (panel)->st.st_mode) || link_isdir (selection (panel))) + { + vfs_path_t *vpath; + + vpath = vfs_path_from_str (selection (panel)->fname->str); + panel_cd (panel, vpath, cd_exact); + vfs_path_free (vpath, TRUE); + return MSG_HANDLED; + } + } + + return MSG_NOT_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* if command line is empty then do 'cd ..' */ +static cb_ret_t +force_maybe_cd (WPanel * panel) +{ + if (input_is_empty (cmdline)) + { + cd_up_dir (panel); + return MSG_HANDLED; + } + + return MSG_NOT_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +unselect_item (WPanel * panel) +{ + repaint_file (panel, panel->selected, 2 * selection (panel)->f.marked, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Select/unselect all the files like a current file by extension */ + +static void +panel_select_ext_cmd (WPanel * panel) +{ + GString *filename; + gboolean do_select; + char *reg_exp, *cur_file_ext; + mc_search_t *search; + int i; + + filename = selection (panel)->fname; + if (filename == NULL) + return; + + do_select = !selection (panel)->f.marked; + + cur_file_ext = strutils_regex_escape (extension (filename->str)); + if (cur_file_ext[0] != '\0') + reg_exp = g_strconcat ("^.*\\.", cur_file_ext, "$", (char *) NULL); + else + reg_exp = g_strdup ("^[^\\.]+$"); + + g_free (cur_file_ext); + + search = mc_search_new (reg_exp, NULL); + search->search_type = MC_SEARCH_T_REGEX; + search->is_case_sensitive = FALSE; + + for (i = 0; i < panel->dir.len; i++) + { + file_entry_t *file_entry = &panel->dir.list[i]; + + if (DIR_IS_DOTDOT (file_entry->fname->str) || S_ISDIR (file_entry->st.st_mode)) + continue; + + if (!mc_search_run (search, file_entry->fname->str, 0, file_entry->fname->len, NULL)) + continue; + + do_file_mark (panel, i, do_select ? 1 : 0); + } + + mc_search_free (search); + g_free (reg_exp); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +panel_selected_at_half (const WPanel * panel) +{ + int lines, top; + + lines = panel_lines (panel); + + /* define top file of column */ + top = panel->top_file; + if (panel->list_cols > 1) + top += lines * ((panel->selected - top) / lines); + + return (panel->selected - top - lines / 2); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +move_down (WPanel * panel) +{ + int items; + + if (panel->selected + 1 == panel->dir.len) + return; + + unselect_item (panel); + panel->selected++; + + items = panel_items (panel); + + if (panels_options.scroll_pages && panel->selected - panel->top_file == items) + { + /* Scroll window half screen */ + panel->top_file += items / 2; + if (panel->top_file > panel->dir.len - items) + panel->top_file = panel->dir.len - items; + paint_dir (panel); + } + else if (panels_options.scroll_center && panel_selected_at_half (panel) > 0) + { + /* Scroll window when cursor is halfway down */ + panel->top_file++; + if (panel->top_file > panel->dir.len - items) + panel->top_file = panel->dir.len - items; + } + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +move_up (WPanel * panel) +{ + if (panel->selected == 0) + return; + + unselect_item (panel); + panel->selected--; + + if (panels_options.scroll_pages && panel->selected < panel->top_file) + { + /* Scroll window half screen */ + panel->top_file -= panel_items (panel) / 2; + if (panel->top_file < 0) + panel->top_file = 0; + paint_dir (panel); + } + else if (panels_options.scroll_center && panel_selected_at_half (panel) < 0) + { + /* Scroll window when cursor is halfway up */ + panel->top_file--; + if (panel->top_file < 0) + panel->top_file = 0; + } + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Changes the selection by lines (may be negative) */ + +static void +move_selection (WPanel * panel, int lines) +{ + int new_pos; + gboolean adjust = FALSE; + + new_pos = panel->selected + lines; + if (new_pos >= panel->dir.len) + new_pos = panel->dir.len - 1; + + if (new_pos < 0) + new_pos = 0; + + unselect_item (panel); + panel->selected = new_pos; + + if (panel->selected - panel->top_file >= panel_items (panel)) + { + panel->top_file += lines; + adjust = TRUE; + } + + if (panel->selected - panel->top_file < 0) + { + panel->top_file += lines; + adjust = TRUE; + } + + if (adjust) + { + if (panel->top_file > panel->selected) + panel->top_file = panel->selected; + if (panel->top_file < 0) + panel->top_file = 0; + paint_dir (panel); + } + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +move_left (WPanel * panel) +{ + if (panel->list_cols > 1) + { + move_selection (panel, -panel_lines (panel)); + return MSG_HANDLED; + } + + return maybe_cd (panel, TRUE); /* cd .. */ +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +move_right (WPanel * panel) +{ + if (panel->list_cols > 1) + { + move_selection (panel, panel_lines (panel)); + return MSG_HANDLED; + } + + return maybe_cd (panel, FALSE); /* cd (selection) */ +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +prev_page (WPanel * panel) +{ + int items; + + if (panel->selected == 0 && panel->top_file == 0) + return; + + unselect_item (panel); + items = panel_items (panel); + if (panel->top_file < items) + items = panel->top_file; + if (items == 0) + panel->selected = 0; + else + panel->selected -= items; + panel->top_file -= items; + + select_item (panel); + paint_dir (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +goto_parent_dir (WPanel * panel) +{ + if (!panel->is_panelized) + cd_up_dir (panel); + else + { + GString *fname = panel->dir.list[panel->selected].fname; + const char *bname; + vfs_path_t *dname_vpath; + + if (g_path_is_absolute (fname->str)) + fname = mc_g_string_dup (fname); + else + { + char *fname2; + + fname2 = + mc_build_filename (vfs_path_as_str (panelized_panel.root_vpath), fname->str, + (char *) NULL); + + fname = g_string_new (fname2); + g_free (fname2); + } + + bname = x_basename (fname->str); + + if (bname == fname->str) + dname_vpath = vfs_path_from_str ("."); + else + { + g_string_truncate (fname, bname - fname->str); + dname_vpath = vfs_path_from_str (fname->str); + } + + panel_cd (panel, dname_vpath, cd_exact); + try_to_select (panel, bname); + + vfs_path_free (dname_vpath, TRUE); + g_string_free (fname, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +next_page (WPanel * panel) +{ + int items; + + if (panel->selected == panel->dir.len - 1) + return; + + unselect_item (panel); + items = panel_items (panel); + if (panel->top_file > panel->dir.len - 2 * items) + items = panel->dir.len - items - panel->top_file; + if (panel->top_file + items < 0) + items = -panel->top_file; + if (items == 0) + panel->selected = panel->dir.len - 1; + else + panel->selected += items; + panel->top_file += items; + + select_item (panel); + paint_dir (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +goto_child_dir (WPanel * panel) +{ + if ((S_ISDIR (selection (panel)->st.st_mode) || link_isdir (selection (panel)))) + { + vfs_path_t *vpath; + + vpath = vfs_path_from_str (selection (panel)->fname->str); + panel_cd (panel, vpath, cd_exact); + vfs_path_free (vpath, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +goto_top_file (WPanel * panel) +{ + unselect_item (panel); + panel->selected = panel->top_file; + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +goto_middle_file (WPanel * panel) +{ + unselect_item (panel); + panel->selected = panel->top_file + panel_items (panel) / 2; + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +goto_bottom_file (WPanel * panel) +{ + unselect_item (panel); + panel->selected = panel->top_file + panel_items (panel) - 1; + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +move_home (WPanel * panel) +{ + if (panel->selected == 0) + return; + + unselect_item (panel); + + if (panels_options.torben_fj_mode) + { + int middle_pos; + + middle_pos = panel->top_file + panel_items (panel) / 2; + + if (panel->selected > middle_pos) + { + goto_middle_file (panel); + return; + } + if (panel->selected != panel->top_file) + { + goto_top_file (panel); + return; + } + } + + panel->top_file = 0; + panel->selected = 0; + + paint_dir (panel); + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +move_end (WPanel * panel) +{ + if (panel->selected == panel->dir.len - 1) + return; + + unselect_item (panel); + + if (panels_options.torben_fj_mode) + { + int items, middle_pos; + + items = panel_items (panel); + middle_pos = panel->top_file + items / 2; + + if (panel->selected < middle_pos) + { + goto_middle_file (panel); + return; + } + if (panel->selected != panel->top_file + items - 1) + { + goto_bottom_file (panel); + return; + } + } + + panel->selected = panel->dir.len - 1; + paint_dir (panel); + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +do_mark_file (WPanel * panel, mark_act_t do_move) +{ + do_file_mark (panel, panel->selected, selection (panel)->f.marked ? 0 : 1); + + if ((panels_options.mark_moves_down && do_move == MARK_DOWN) || do_move == MARK_FORCE_DOWN) + move_down (panel); + else if (do_move == MARK_FORCE_UP) + move_up (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +mark_file (WPanel * panel) +{ + do_mark_file (panel, MARK_DOWN); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +mark_file_up (WPanel * panel) +{ + do_mark_file (panel, MARK_FORCE_UP); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +mark_file_down (WPanel * panel) +{ + do_mark_file (panel, MARK_FORCE_DOWN); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +mark_file_right (WPanel * panel) +{ + int lines; + + if (state_mark < 0) + state_mark = selection (panel)->f.marked ? 0 : 1; + + lines = panel_lines (panel); + lines = MIN (lines, panel->dir.len - panel->selected - 1); + for (; lines != 0; lines--) + { + do_file_mark (panel, panel->selected, state_mark); + move_down (panel); + } + do_file_mark (panel, panel->selected, state_mark); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +mark_file_left (WPanel * panel) +{ + int lines; + + if (state_mark < 0) + state_mark = selection (panel)->f.marked ? 0 : 1; + + lines = panel_lines (panel); + lines = MIN (lines, panel->selected + 1); + for (; lines != 0; lines--) + { + do_file_mark (panel, panel->selected, state_mark); + move_up (panel); + } + do_file_mark (panel, panel->selected, state_mark); +} + +/* --------------------------------------------------------------------------------------------- */ + +static mc_search_t * +panel_select_unselect_files_dialog (select_flags_t * flags, const char *title, + const char *history_name, const char *help_section, char **str) +{ + gboolean files_only = (*flags & SELECT_FILES_ONLY) != 0; + gboolean case_sens = (*flags & SELECT_MATCH_CASE) != 0; + gboolean shell_patterns = (*flags & SELECT_SHELL_PATTERNS) != 0; + + char *reg_exp; + mc_search_t *search; + + quick_widget_t quick_widgets[] = { + /* *INDENT-OFF* */ + QUICK_INPUT (INPUT_LAST_TEXT, history_name, ®_exp, NULL, + FALSE, FALSE, INPUT_COMPLETE_FILENAMES), + QUICK_START_COLUMNS, + QUICK_CHECKBOX (N_("&Files only"), &files_only, NULL), + QUICK_CHECKBOX (N_("&Using shell patterns"), &shell_patterns, NULL), + QUICK_NEXT_COLUMN, + QUICK_CHECKBOX (N_("&Case sensitive"), &case_sens, NULL), + QUICK_STOP_COLUMNS, + QUICK_END + /* *INDENT-ON* */ + }; + + WRect r = { -1, -1, 0, 50 }; + + quick_dialog_t qdlg = { + r, title, help_section, + quick_widgets, NULL, NULL + }; + + if (quick_dialog (&qdlg) == B_CANCEL) + return NULL; + + if (*reg_exp == '\0') + { + g_free (reg_exp); + if (str != NULL) + *str = NULL; + return SELECT_RESET; + } + + search = mc_search_new (reg_exp, NULL); + search->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX; + search->is_entire_line = TRUE; + search->is_case_sensitive = case_sens; + + if (str != NULL) + *str = reg_exp; + else + g_free (reg_exp); + + if (!mc_search_prepare (search)) + { + message (D_ERROR, MSG_ERROR, _("Malformed regular expression")); + mc_search_free (search); + return SELECT_ERROR; + } + + /* result flags */ + *flags = 0; + if (case_sens) + *flags |= SELECT_MATCH_CASE; + if (files_only) + *flags |= SELECT_FILES_ONLY; + if (shell_patterns) + *flags |= SELECT_SHELL_PATTERNS; + + return search; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_select_unselect_files (WPanel * panel, const char *title, const char *history_name, + const char *help_section, gboolean do_select) +{ + mc_search_t *search; + gboolean files_only; + int i; + + search = panel_select_unselect_files_dialog (&panels_options.select_flags, title, history_name, + help_section, NULL); + if (search == NULL || search == SELECT_RESET || search == SELECT_ERROR) + return; + + files_only = (panels_options.select_flags & SELECT_FILES_ONLY) != 0; + + for (i = 0; i < panel->dir.len; i++) + { + if (DIR_IS_DOTDOT (panel->dir.list[i].fname->str)) + continue; + if (S_ISDIR (panel->dir.list[i].st.st_mode) && files_only) + continue; + + if (mc_search_run + (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL)) + do_file_mark (panel, i, do_select ? 1 : 0); + } + + mc_search_free (search); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_select_files (WPanel * panel) +{ + panel_select_unselect_files (panel, _("Select"), MC_HISTORY_FM_PANEL_SELECT, + "[Select/Unselect Files]", TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_unselect_files (WPanel * panel) +{ + panel_select_unselect_files (panel, _("Unselect"), MC_HISTORY_FM_PANEL_UNSELECT, + "[Select/Unselect Files]", FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_select_invert_files (WPanel * panel) +{ + int i; + + for (i = 0; i < panel->dir.len; i++) + { + file_entry_t *file = &panel->dir.list[i]; + + if (!panels_options.reverse_files_only || !S_ISDIR (file->st.st_mode)) + do_file_mark (panel, i, file->f.marked ? 0 : 1); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_do_set_filter (WPanel * panel) +{ + file_filter_t ff = {.value = NULL,.handler = NULL,.flags = panel->filter.flags }; + + ff.handler = + panel_select_unselect_files_dialog (&ff.flags, _("Filter"), MC_HISTORY_FM_PANEL_FILTER, + "[Filter...]", &ff.value); + + if (ff.handler == NULL || ff.handler == SELECT_ERROR) + return; + + if (ff.handler == SELECT_RESET) + ff.handler = NULL; + + panel_set_filter (panel, &ff); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Incremental search of a file name in the panel. + * @param panel instance of WPanel structure + * @param c_code key code + */ + +static void +do_search (WPanel * panel, int c_code) +{ + int i, sel; + gboolean wrapped = FALSE; + char *act; + mc_search_t *search; + char *reg_exp, *esc_str; + gboolean is_found = FALSE; + + if (c_code == KEY_BACKSPACE) + { + if (panel->quick_search.buffer->len != 0) + { + act = panel->quick_search.buffer->str + panel->quick_search.buffer->len; + str_prev_noncomb_char (&act, panel->quick_search.buffer->str); + g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str); + } + panel->quick_search.chpoint = 0; + } + else + { + if (c_code != 0 && (gsize) panel->quick_search.chpoint < sizeof (panel->quick_search.ch)) + { + panel->quick_search.ch[panel->quick_search.chpoint] = c_code; + panel->quick_search.chpoint++; + } + + if (panel->quick_search.chpoint > 0) + { + switch (str_is_valid_char (panel->quick_search.ch, panel->quick_search.chpoint)) + { + case -2: + return; + case -1: + panel->quick_search.chpoint = 0; + return; + default: + g_string_append_len (panel->quick_search.buffer, panel->quick_search.ch, + panel->quick_search.chpoint); + panel->quick_search.chpoint = 0; + } + } + } + + reg_exp = g_strdup_printf ("%s*", panel->quick_search.buffer->str); + esc_str = strutils_escape (reg_exp, -1, ",|\\{}[]", TRUE); + search = mc_search_new (esc_str, NULL); + search->search_type = MC_SEARCH_T_GLOB; + search->is_entire_line = TRUE; + + switch (panels_options.qsearch_mode) + { + case QSEARCH_CASE_SENSITIVE: + search->is_case_sensitive = TRUE; + break; + case QSEARCH_CASE_INSENSITIVE: + search->is_case_sensitive = FALSE; + break; + default: + search->is_case_sensitive = panel->sort_info.case_sensitive; + break; + } + + sel = panel->selected; + + for (i = panel->selected; !wrapped || i != panel->selected; i++) + { + if (i >= panel->dir.len) + { + i = 0; + if (wrapped) + break; + wrapped = TRUE; + } + if (mc_search_run + (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL)) + { + sel = i; + is_found = TRUE; + break; + } + } + if (is_found) + { + unselect_item (panel); + panel->selected = sel; + select_item (panel); + widget_draw (WIDGET (panel)); + } + else if (c_code != KEY_BACKSPACE) + { + act = panel->quick_search.buffer->str + panel->quick_search.buffer->len; + str_prev_noncomb_char (&act, panel->quick_search.buffer->str); + g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str); + } + mc_search_free (search); + g_free (reg_exp); + g_free (esc_str); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Start new search. + * @param panel instance of WPanel structure + */ + +static void +start_search (WPanel * panel) +{ + if (panel->quick_search.active) + { + if (panel->selected == panel->dir.len - 1) + panel->selected = 0; + else + move_down (panel); + + /* in case if there was no search string we need to recall + previous string, with which we ended previous searching */ + if (panel->quick_search.buffer->len == 0) + mc_g_string_copy (panel->quick_search.buffer, panel->quick_search.prev_buffer); + + do_search (panel, 0); + } + else + { + panel->quick_search.active = TRUE; + g_string_set_size (panel->quick_search.buffer, 0); + panel->quick_search.ch[0] = '\0'; + panel->quick_search.chpoint = 0; + display_mini_info (panel); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +stop_search (WPanel * panel) +{ + if (!panel->quick_search.active) + return; + + panel->quick_search.active = FALSE; + + /* if user overrdied search string, we need to store it + to the quick_search.prev_buffer */ + if (panel->quick_search.buffer->len != 0) + mc_g_string_copy (panel->quick_search.prev_buffer, panel->quick_search.buffer); + + display_mini_info (panel); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Return TRUE if the Enter key has been processed, FALSE otherwise */ + +static gboolean +do_enter_on_file_entry (WPanel * panel, file_entry_t * fe) +{ + const char *fname = fe->fname->str; + vfs_path_t *full_name_vpath; + gboolean ok; + + /* + * Directory or link to directory - change directory. + * Try the same for the entries on which mc_lstat() has failed. + */ + if (S_ISDIR (fe->st.st_mode) || link_isdir (fe) || (fe->st.st_mode == 0)) + { + vfs_path_t *fname_vpath; + + fname_vpath = vfs_path_from_str (fname); + if (!panel_cd (panel, fname_vpath, cd_exact)) + message (D_ERROR, MSG_ERROR, _("Cannot change directory")); + vfs_path_free (fname_vpath, TRUE); + return TRUE; + } + + full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL); + + /* Try associated command */ + ok = regex_command (full_name_vpath, "Open") != 0; + vfs_path_free (full_name_vpath, TRUE); + if (ok) + return TRUE; + + /* Check if the file is executable */ + full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL); + ok = (is_exe (fe->st.st_mode) && if_link_is_exe (full_name_vpath, fe)); + vfs_path_free (full_name_vpath, TRUE); + if (!ok) + return FALSE; + + if (confirm_execute + && query_dialog (_("The Midnight Commander"), _("Do you really want to execute?"), D_NORMAL, + 2, _("&Yes"), _("&No")) != 0) + return TRUE; + + if (!vfs_current_is_local ()) + { + int ret; + vfs_path_t *tmp_vpath; + + tmp_vpath = vfs_path_append_new (vfs_get_raw_current_dir (), fname, (char *) NULL); + ret = mc_setctl (tmp_vpath, VFS_SETCTL_RUN, NULL); + vfs_path_free (tmp_vpath, TRUE); + /* We took action only if the dialog was shown or the execution was successful */ + return confirm_execute || (ret == 0); + } + + { + char *tmp, *cmd; + + tmp = name_quote (fname, FALSE); + cmd = g_strconcat (".", PATH_SEP_STR, tmp, (char *) NULL); + g_free (tmp); + shell_execute (cmd, 0); + g_free (cmd); + } + +#ifdef HAVE_CHARSET + mc_global.source_codepage = default_source_codepage; +#endif + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline gboolean +do_enter (WPanel * panel) +{ + return do_enter_on_file_entry (panel, selection (panel)); +} + + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_cycle_listing_format (WPanel * panel) +{ + panel->list_format = (panel->list_format + 1) % LIST_FORMATS; + + if (set_panel_formats (panel) == 0) + do_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chdir_other_panel (WPanel * panel) +{ + const file_entry_t *entry = &panel->dir.list[panel->selected]; + vfs_path_t *new_dir_vpath; + char *sel_entry = NULL; + WPanel *p; + + if (get_other_type () != view_listing) + create_panel (get_other_index (), view_listing); + + if (S_ISDIR (entry->st.st_mode) || link_isdir (entry)) + new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, entry->fname->str, (char *) NULL); + else + { + new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, "..", (char *) NULL); + sel_entry = strrchr (vfs_path_get_last_path_str (panel->cwd_vpath), PATH_SEP); + } + + p = change_panel (); + panel_cd (p, new_dir_vpath, cd_exact); + vfs_path_free (new_dir_vpath, TRUE); + + if (sel_entry) + try_to_select (p, sel_entry); + (void) change_panel (); + + move_down (panel); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Make the current directory of the current panel also the current + * directory of the other panel. Put the other panel to the listing + * mode if needed. If the current panel is panelized, the other panel + * doesn't become panelized. + */ + +static void +panel_sync_other (const WPanel * panel) +{ + if (get_other_type () != view_listing) + create_panel (get_other_index (), view_listing); + + panel_do_cd (other_panel, panel->cwd_vpath, cd_exact); + + /* try to select current filename on the other panel */ + if (!panel->is_panelized) + try_to_select (other_panel, selection (panel)->fname->str); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +chdir_to_readlink (WPanel * panel) +{ + vfs_path_t *new_dir_vpath; + char buffer[MC_MAXPATHLEN]; + int i; + struct stat st; + vfs_path_t *panel_fname_vpath; + gboolean ok; + WPanel *cpanel; + + if (get_other_type () != view_listing) + return; + + if (!S_ISLNK (panel->dir.list[panel->selected].st.st_mode)) + return; + + i = readlink (selection (panel)->fname->str, buffer, MC_MAXPATHLEN - 1); + if (i < 0) + return; + + panel_fname_vpath = vfs_path_from_str (selection (panel)->fname->str); + ok = (mc_stat (panel_fname_vpath, &st) >= 0); + vfs_path_free (panel_fname_vpath, TRUE); + if (!ok) + return; + + buffer[i] = '\0'; + if (!S_ISDIR (st.st_mode)) + { + char *p; + + p = strrchr (buffer, PATH_SEP); + if (p != NULL && p[1] == '\0') + { + *p = '\0'; + p = strrchr (buffer, PATH_SEP); + } + if (p == NULL) + return; + + p[1] = '\0'; + } + if (IS_PATH_SEP (*buffer)) + new_dir_vpath = vfs_path_from_str (buffer); + else + new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, buffer, (char *) NULL); + + cpanel = change_panel (); + panel_cd (cpanel, new_dir_vpath, cd_exact); + vfs_path_free (new_dir_vpath, TRUE); + (void) change_panel (); + + move_down (panel); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + function return 0 if not found and REAL_INDEX+1 if found + */ + +static gsize +panel_get_format_field_index_by_name (const WPanel * panel, const char *name) +{ + GSList *format; + gsize lc_index; + + for (lc_index = 1, format = panel->format; + format != NULL && strcmp (((format_item_t *) format->data)->title, name) != 0; + format = g_slist_next (format), lc_index++) + ; + + if (format == NULL) + lc_index = 0; + + return lc_index; +} + +/* --------------------------------------------------------------------------------------------- */ + +static const panel_field_t * +panel_get_sortable_field_by_format (const WPanel * panel, gsize lc_index) +{ + const panel_field_t *pfield; + const format_item_t *format; + + format = (const format_item_t *) g_slist_nth_data (panel->format, lc_index); + if (format == NULL) + return NULL; + + pfield = panel_get_field_by_title (format->title); + if (pfield == NULL) + return NULL; + if (pfield->sort_routine == NULL) + return NULL; + return pfield; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_toggle_sort_order_prev (WPanel * panel) +{ + gsize lc_index, i; + const char *title; + const panel_field_t *pfield = NULL; + + title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey); + lc_index = panel_get_format_field_index_by_name (panel, title); + + if (lc_index > 1) + { + /* search for prev sortable column in panel format */ + for (i = lc_index - 1; + i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--) + ; + } + + if (pfield == NULL) + { + /* Sortable field not found. Try to search in each array */ + for (i = g_slist_length (panel->format); + i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--) + ; + } + + if (pfield != NULL) + { + panel->sort_field = pfield; + panel_set_sort_order (panel, pfield); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_toggle_sort_order_next (WPanel * panel) +{ + gsize lc_index, i; + const panel_field_t *pfield = NULL; + gsize format_field_count; + const char *title; + + format_field_count = g_slist_length (panel->format); + title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey); + lc_index = panel_get_format_field_index_by_name (panel, title); + + if (lc_index != 0 && lc_index != format_field_count) + { + /* search for prev sortable column in panel format */ + for (i = lc_index; + i != format_field_count + && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++) + ; + } + + if (pfield == NULL) + { + /* Sortable field not found. Try to search in each array */ + for (i = 0; + i != format_field_count + && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++) + ; + } + + if (pfield != NULL) + { + panel->sort_field = pfield; + panel_set_sort_order (panel, pfield); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_select_sort_order (WPanel * panel) +{ + const panel_field_t *sort_order; + + sort_order = sort_box (&panel->sort_info, panel->sort_field); + if (sort_order != NULL) + { + panel->sort_field = sort_order; + panel_set_sort_order (panel, sort_order); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * panel_content_scroll_left: + * @param panel the pointer to the panel on which we operate + * + * scroll long filename to the left (decrement scroll pointer) + * + */ + +static void +panel_content_scroll_left (WPanel * panel) +{ + if (panel->content_shift > -1) + { + if (panel->content_shift > panel->max_shift) + panel->content_shift = panel->max_shift; + + panel->content_shift--; + show_dir (panel); + paint_dir (panel); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * panel_content_scroll_right: + * @param panel the pointer to the panel on which we operate + * + * scroll long filename to the right (increment scroll pointer) + * + */ + +static void +panel_content_scroll_right (WPanel * panel) +{ + if (panel->content_shift < 0 || panel->content_shift < panel->max_shift) + { + panel->content_shift++; + show_dir (panel); + paint_dir (panel); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_set_sort_type_by_id (WPanel * panel, const char *name) +{ + if (strcmp (panel->sort_field->id, name) == 0) + panel->sort_info.reverse = !panel->sort_info.reverse; + else + { + const panel_field_t *sort_order; + + sort_order = panel_get_field_by_id (name); + if (sort_order == NULL) + return; + + panel->sort_field = sort_order; + } + + panel_set_sort_order (panel, panel->sort_field); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * If we moved to the parent directory move the selection pointer to + * the old directory name; If we leave VFS dir, remove FS specificator. + * + * You do _NOT_ want to add any vfs aware code here. <pavel@ucw.cz> + */ + +static const char * +get_parent_dir_name (const vfs_path_t * cwd_vpath, const vfs_path_t * lwd_vpath) +{ + size_t llen, clen; + const char *p, *lwd; + + llen = vfs_path_len (lwd_vpath); + clen = vfs_path_len (cwd_vpath); + + if (llen <= clen) + return NULL; + + lwd = vfs_path_as_str (lwd_vpath); + + p = g_strrstr (lwd, VFS_PATH_URL_DELIMITER); + + if (p == NULL) + { + const char *cwd; + + cwd = vfs_path_as_str (cwd_vpath); + + p = strrchr (lwd, PATH_SEP); + + if (p != NULL && strncmp (cwd, lwd, (size_t) (p - lwd)) == 0 + && (clen == (size_t) (p - lwd) || (p == lwd && IS_PATH_SEP (cwd[0]) && cwd[1] == '\0'))) + return (p + 1); + + return NULL; + } + + /* skip VFS prefix */ + while (--p > lwd && !IS_PATH_SEP (*p)) + ; + /* get last component */ + while (--p > lwd && !IS_PATH_SEP (*p)) + ; + + /* return last component */ + return (p != lwd || IS_PATH_SEP (*p)) ? p + 1 : p; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Wrapper for do_subshell_chdir, check for availability of subshell */ + +static void +subshell_chdir (const vfs_path_t * vpath) +{ +#ifdef ENABLE_SUBSHELL + if (mc_global.tty.use_subshell && vfs_current_is_local ()) + do_subshell_chdir (vpath, FALSE); +#else /* ENABLE_SUBSHELL */ + (void) vpath; +#endif /* ENABLE_SUBSHELL */ +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Changes the current directory of the panel. + * Don't record change in the directory history. + */ + +static gboolean +panel_do_cd_int (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type) +{ + vfs_path_t *olddir_vpath; + + /* Convert *new_path to a suitable pathname, handle ~user */ + if (cd_type == cd_parse_command) + { + const vfs_path_element_t *element; + + element = vfs_path_get_by_index (new_dir_vpath, 0); + if (strcmp (element->path, "-") == 0) + new_dir_vpath = panel->lwd_vpath; + } + + if (mc_chdir (new_dir_vpath) == -1) + return FALSE; + + /* Success: save previous directory, shutdown status of previous dir */ + olddir_vpath = vfs_path_clone (panel->cwd_vpath); + panel_set_lwd (panel, panel->cwd_vpath); + input_complete_free (cmdline); + + vfs_path_free (panel->cwd_vpath, TRUE); + vfs_setup_cwd (); + panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ()); + + vfs_release_path (olddir_vpath); + + subshell_chdir (panel->cwd_vpath); + + /* Reload current panel */ + panel_clean_dir (panel); + + if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine, + &panel->sort_info, &panel->filter)) + message (D_ERROR, MSG_ERROR, _("Cannot read directory contents")); + + try_to_select (panel, get_parent_dir_name (panel->cwd_vpath, olddir_vpath)); + + load_hint (FALSE); + panel->dirty = TRUE; + update_xterm_title_path (); + + vfs_path_free (olddir_vpath, TRUE); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +directory_history_next (WPanel * panel) +{ + gboolean ok; + + do + { + GList *next; + + ok = TRUE; + next = g_list_next (panel->dir_history.current); + if (next != NULL) + { + vfs_path_t *data_vpath; + + data_vpath = vfs_path_from_str ((char *) next->data); + ok = panel_do_cd_int (panel, data_vpath, cd_exact); + vfs_path_free (data_vpath, TRUE); + panel->dir_history.current = next; + } + /* skip directories that present in history but absent in file system */ + } + while (!ok); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +directory_history_prev (WPanel * panel) +{ + gboolean ok; + + do + { + GList *prev; + + ok = TRUE; + prev = g_list_previous (panel->dir_history.current); + if (prev != NULL) + { + vfs_path_t *data_vpath; + + data_vpath = vfs_path_from_str ((char *) prev->data); + ok = panel_do_cd_int (panel, data_vpath, cd_exact); + vfs_path_free (data_vpath, TRUE); + panel->dir_history.current = prev; + } + /* skip directories that present in history but absent in file system */ + } + while (!ok); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +directory_history_list (WPanel * panel) +{ + history_descriptor_t hd; + gboolean ok = FALSE; + size_t pos; + + pos = g_list_position (panel->dir_history.current, panel->dir_history.list); + + history_descriptor_init (&hd, WIDGET (panel)->rect.y, WIDGET (panel)->rect.x, + panel->dir_history.list, (int) pos); + history_show (&hd); + + panel->dir_history.list = hd.list; + if (hd.text != NULL) + { + vfs_path_t *s_vpath; + + s_vpath = vfs_path_from_str (hd.text); + ok = panel_do_cd_int (panel, s_vpath, cd_exact); + if (ok) + directory_history_add (panel, panel->cwd_vpath); + else + message (D_ERROR, MSG_ERROR, _("Cannot change directory")); + vfs_path_free (s_vpath, TRUE); + g_free (hd.text); + } + + if (!ok) + { + /* Since history is fully modified in history_show(), panel->dir_history actually + * points to the invalid place. Try restore current postition here. */ + + size_t i; + + panel->dir_history.current = panel->dir_history.list; + + for (i = 0; i <= pos; i++) + { + GList *prev; + + prev = g_list_previous (panel->dir_history.current); + if (prev == NULL) + break; + + panel->dir_history.current = prev; + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +panel_execute_cmd (WPanel * panel, long command) +{ + int res = MSG_HANDLED; + + if (command != CK_Search) + stop_search (panel); + + switch (command) + { + case CK_Up: + case CK_Down: + case CK_Left: + case CK_Right: + case CK_Bottom: + case CK_Top: + case CK_PageDown: + case CK_PageUp: + /* reset state of marks flag */ + state_mark = -1; + break; + default: + break; + } + + switch (command) + { + case CK_CycleListingFormat: + panel_cycle_listing_format (panel); + break; + case CK_PanelOtherCd: + chdir_other_panel (panel); + break; + case CK_PanelOtherCdLink: + chdir_to_readlink (panel); + break; + case CK_CopySingle: + copy_cmd_local (panel); + break; + case CK_DeleteSingle: + delete_cmd_local (panel); + break; + case CK_Enter: + do_enter (panel); + break; + case CK_ViewRaw: + view_raw_cmd (panel); + break; + case CK_EditNew: + edit_cmd_new (); + break; + case CK_MoveSingle: + rename_cmd_local (panel); + break; + case CK_SelectInvert: + panel_select_invert_files (panel); + break; + case CK_Select: + panel_select_files (panel); + break; + case CK_SelectExt: + panel_select_ext_cmd (panel); + break; + case CK_Unselect: + panel_unselect_files (panel); + break; + case CK_Filter: + panel_do_set_filter (panel); + break; + case CK_PageDown: + next_page (panel); + break; + case CK_PageUp: + prev_page (panel); + break; + case CK_CdChild: + goto_child_dir (panel); + break; + case CK_CdParent: + goto_parent_dir (panel); + break; + case CK_History: + directory_history_list (panel); + break; + case CK_HistoryNext: + directory_history_next (panel); + break; + case CK_HistoryPrev: + directory_history_prev (panel); + break; + case CK_BottomOnScreen: + goto_bottom_file (panel); + break; + case CK_MiddleOnScreen: + goto_middle_file (panel); + break; + case CK_TopOnScreen: + goto_top_file (panel); + break; + case CK_Mark: + mark_file (panel); + break; + case CK_MarkUp: + mark_file_up (panel); + break; + case CK_MarkDown: + mark_file_down (panel); + break; + case CK_MarkLeft: + mark_file_left (panel); + break; + case CK_MarkRight: + mark_file_right (panel); + break; + case CK_CdParentSmart: + res = force_maybe_cd (panel); + break; + case CK_Up: + move_up (panel); + break; + case CK_Down: + move_down (panel); + break; + case CK_Left: + res = move_left (panel); + break; + case CK_Right: + res = move_right (panel); + break; + case CK_Bottom: + move_end (panel); + break; + case CK_Top: + move_home (panel); + break; +#ifdef HAVE_CHARSET + case CK_SelectCodepage: + panel_change_encoding (panel); + break; +#endif + case CK_ScrollLeft: + panel_content_scroll_left (panel); + break; + case CK_ScrollRight: + panel_content_scroll_right (panel); + break; + case CK_Search: + start_search (panel); + break; + case CK_SearchStop: + break; + case CK_PanelOtherSync: + panel_sync_other (panel); + break; + case CK_Sort: + panel_select_sort_order (panel); + break; + case CK_SortPrev: + panel_toggle_sort_order_prev (panel); + break; + case CK_SortNext: + panel_toggle_sort_order_next (panel); + break; + case CK_SortReverse: + panel->sort_info.reverse = !panel->sort_info.reverse; + panel_set_sort_order (panel, panel->sort_field); + break; + case CK_SortByName: + panel_set_sort_type_by_id (panel, "name"); + break; + case CK_SortByExt: + panel_set_sort_type_by_id (panel, "extension"); + break; + case CK_SortBySize: + panel_set_sort_type_by_id (panel, "size"); + break; + case CK_SortByMTime: + panel_set_sort_type_by_id (panel, "mtime"); + break; + default: + res = MSG_NOT_HANDLED; + break; + } + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +panel_key (WPanel * panel, int key) +{ + long command; + + if (is_abort_char (key)) + { + stop_search (panel); + return MSG_HANDLED; + } + + if (panel->quick_search.active && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE)) + { + do_search (panel, key); + return MSG_HANDLED; + } + + command = widget_lookup_key (WIDGET (panel), key); + if (command != CK_IgnoreKey) + return panel_execute_cmd (panel, command); + + if (panels_options.torben_fj_mode && key == ALT ('h')) + { + goto_middle_file (panel); + return MSG_HANDLED; + } + + if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE)) + { + start_search (panel); + do_search (panel, key); + return MSG_HANDLED; + } + + return MSG_NOT_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +panel_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WPanel *panel = PANEL (w); + WDialog *h = DIALOG (w->owner); + WButtonBar *bb; + + switch (msg) + { + case MSG_INIT: + /* subscribe to "history_load" event */ + mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w, NULL); + /* subscribe to "history_save" event */ + mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w, NULL); + return MSG_HANDLED; + + case MSG_DRAW: + /* Repaint everything, including frame and separator */ + widget_erase (w); + show_dir (panel); + panel_print_header (panel); + adjust_top_file (panel); + paint_dir (panel); + mini_info_separator (panel); + display_mini_info (panel); + panel->dirty = FALSE; + return MSG_HANDLED; + + case MSG_FOCUS: + state_mark = -1; + current_panel = panel; + panel->active = TRUE; + + if (mc_chdir (panel->cwd_vpath) != 0) + { + char *cwd; + + cwd = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD); + message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), + cwd, unix_error_string (errno)); + g_free (cwd); + } + else + subshell_chdir (panel->cwd_vpath); + + update_xterm_title_path (); + select_item (panel); + + bb = find_buttonbar (h); + midnight_set_buttonbar (bb); + widget_draw (WIDGET (bb)); + return MSG_HANDLED; + + case MSG_UNFOCUS: + /* Janne: look at this for the multiple panel options */ + stop_search (panel); + panel->active = FALSE; + unselect_item (panel); + return MSG_HANDLED; + + case MSG_KEY: + return panel_key (panel, parm); + + case MSG_ACTION: + return panel_execute_cmd (panel, parm); + + case MSG_DESTROY: + vfs_stamp_path (panel->cwd_vpath); + /* unsubscribe from "history_load" event */ + mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w); + /* unsubscribe from "history_save" event */ + mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w); + panel_destroy (panel); + free_my_statfs (); + return MSG_HANDLED; + + default: + return widget_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/* */ +/* Panel mouse events support routines */ +/* */ + +static void +mouse_toggle_mark (WPanel * panel) +{ + do_mark_file (panel, MARK_DONT_MOVE); + mouse_marking = selection (panel)->f.marked; + mouse_mark_panel = current_panel; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +mouse_set_mark (WPanel * panel) +{ + if (mouse_mark_panel == panel) + { + if (mouse_marking && !selection (panel)->f.marked) + do_mark_file (panel, MARK_DONT_MOVE); + else if (!mouse_marking && selection (panel)->f.marked) + do_mark_file (panel, MARK_DONT_MOVE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +mark_if_marking (WPanel * panel, const mouse_event_t * event, int previous_selected) +{ + if ((event->buttons & GPM_B_RIGHT) == 0) + return; + + if (event->msg == MSG_MOUSE_DOWN) + mouse_toggle_mark (panel); + else + { + int psel, sel1, sel2; + + psel = panel->selected; + sel1 = MIN (previous_selected, panel->selected); + sel2 = MAX (previous_selected, panel->selected); + + for (; sel1 <= sel2; sel1++) + { + panel->selected = sel1; + mouse_set_mark (panel); + } + + panel->selected = psel; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Determine which column was clicked, and sort the panel on + * that column, or reverse sort on that column if already + * sorted on that column. + */ + +static void +mouse_sort_col (WPanel * panel, int x) +{ + int i = 0; + GSList *format; + const char *lc_sort_name = NULL; + panel_field_t *col_sort_format = NULL; + + for (format = panel->format; format != NULL; format = g_slist_next (format)) + { + format_item_t *fi = (format_item_t *) format->data; + + i += fi->field_len; + if (x < i + 1) + { + /* found column */ + lc_sort_name = fi->title; + break; + } + } + + if (lc_sort_name == NULL) + return; + + for (i = 0; panel_fields[i].id != NULL; i++) + { + const char *title; + + title = panel_get_title_without_hotkey (panel_fields[i].title_hotkey); + if (panel_fields[i].sort_routine != NULL && strcmp (title, lc_sort_name) == 0) + { + col_sort_format = &panel_fields[i]; + break; + } + } + + if (col_sort_format != NULL) + { + if (panel->sort_field == col_sort_format) + /* reverse the sort if clicked column is already the sorted column */ + panel->sort_info.reverse = !panel->sort_info.reverse; + else + /* new sort is forced to be ascending */ + panel->sort_info.reverse = FALSE; + + panel_set_sort_order (panel, col_sort_format); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +panel_mouse_is_on_item (const WPanel * panel, int y, int x) +{ + int lines, col_width, col; + + if (y < 0) + return MOUSE_UPPER_FILE_LIST; + + lines = panel_lines (panel); + if (y >= lines) + return MOUSE_BELOW_FILE_LIST; + + col_width = (CONST_WIDGET (panel)->rect.cols - 2) / panel->list_cols; + /* column where mouse is */ + col = x / col_width; + + y += panel->top_file + lines * col; + + /* are we below or in the next column of last file? */ + if (y > panel->dir.len) + return MOUSE_AFTER_LAST_FILE; + + /* we are on item of the file file; return an index to select a file */ + return y; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) +{ + WPanel *panel = PANEL (w); + gboolean is_active; + + is_active = widget_is_active (w); + + switch (msg) + { + case MSG_MOUSE_DOWN: + if (event->y == 0) + { + /* top frame */ + if (event->x == 1) + /* "<" button */ + directory_history_prev (panel); + else if (event->x == w->rect.cols - 2) + /* ">" button */ + directory_history_next (panel); + else if (event->x >= w->rect.cols - 5 && event->x <= w->rect.cols - 3) + /* "^" button */ + directory_history_list (panel); + else if (event->x == w->rect.cols - 6) + /* "." button show/hide hidden files */ + send_message (filemanager, NULL, MSG_ACTION, CK_ShowHidden, NULL); + else + { + /* no other events on 1st line, return MOU_UNHANDLED */ + event->result.abort = TRUE; + /* avoid extra panel redraw */ + panel->dirty = FALSE; + } + break; + } + + if (event->y == 1) + { + /* sort on clicked column */ + mouse_sort_col (panel, event->x + 1); + break; + } + + if (!is_active) + (void) change_panel (); + MC_FALLTHROUGH; + + case MSG_MOUSE_DRAG: + { + int my_index; + int previous_selected; + + my_index = panel_mouse_is_on_item (panel, event->y - 2, event->x); + previous_selected = panel->selected; + + switch (my_index) + { + case MOUSE_UPPER_FILE_LIST: + move_up (panel); + mark_if_marking (panel, event, previous_selected); + break; + + case MOUSE_BELOW_FILE_LIST: + move_down (panel); + mark_if_marking (panel, event, previous_selected); + break; + + case MOUSE_AFTER_LAST_FILE: + break; /* do nothing */ + + default: + if (my_index != panel->selected) + { + unselect_item (panel); + panel->selected = my_index; + select_item (panel); + } + + mark_if_marking (panel, event, previous_selected); + break; + } + } + break; + + case MSG_MOUSE_UP: + break; + + case MSG_MOUSE_CLICK: + if ((event->count & GPM_DOUBLE) != 0 && (event->buttons & GPM_B_LEFT) != 0 && + panel_mouse_is_on_item (panel, event->y - 2, event->x) >= 0) + do_enter (panel); + break; + + case MSG_MOUSE_MOVE: + break; + + case MSG_MOUSE_SCROLL_UP: + if (is_active) + { + if (panels_options.mouse_move_pages && panel->top_file > 0) + prev_page (panel); + else /* We are in first page */ + move_up (panel); + } + break; + + case MSG_MOUSE_SCROLL_DOWN: + if (is_active) + { + if (panels_options.mouse_move_pages + && panel->top_file + panel_items (panel) < panel->dir.len) + next_page (panel); + else /* We are in last page */ + move_down (panel); + } + break; + + default: + break; + } + + if (panel->dirty) + widget_draw (w); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +reload_panelized (WPanel * panel) +{ + int i, j; + dir_list *list = &panel->dir; + + /* refresh current VFS directory required for vfs_path_from_str() */ + (void) mc_chdir (panel->cwd_vpath); + + for (i = 0, j = 0; i < list->len; i++) + { + vfs_path_t *vpath; + + vpath = vfs_path_from_str (list->list[i].fname->str); + if (mc_lstat (vpath, &list->list[i].st) != 0) + g_string_free (list->list[i].fname, TRUE); + else + { + if (j != i) + list->list[j] = list->list[i]; + j++; + } + vfs_path_free (vpath, TRUE); + } + if (j == 0) + dir_list_init (list); + else + list->len = j; + + recalculate_panel_summary (panel); + + if (panel != current_panel) + (void) mc_chdir (current_panel->cwd_vpath); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +update_one_panel_widget (WPanel * panel, panel_update_flags_t flags, const char *current_file) +{ + gboolean free_pointer; + char *my_current_file = NULL; + + if ((flags & UP_RELOAD) != 0) + { + panel->is_panelized = FALSE; + mc_setctl (panel->cwd_vpath, VFS_SETCTL_FLUSH, NULL); + memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat)); + } + + /* If current_file == -1 (an invalid pointer) then preserve selection */ + free_pointer = current_file == UP_KEEPSEL; + + if (free_pointer) + { + my_current_file = g_strndup (panel->dir.list[panel->selected].fname->str, + panel->dir.list[panel->selected].fname->len); + current_file = my_current_file; + } + + if (panel->is_panelized) + reload_panelized (panel); + else + panel_reload (panel); + + try_to_select (panel, current_file); + panel->dirty = TRUE; + + if (free_pointer) + g_free (my_current_file); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +update_one_panel (int which, panel_update_flags_t flags, const char *current_file) +{ + if (get_panel_type (which) == view_listing) + { + WPanel *panel; + + panel = PANEL (get_panel_widget (which)); + if (panel->is_panelized) + flags &= ~UP_RELOAD; + update_one_panel_widget (panel, flags, current_file); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +do_select (WPanel * panel, int i) +{ + if (i != panel->selected) + { + panel->dirty = TRUE; + panel->selected = i; + panel->top_file = panel->selected - (WIDGET (panel)->rect.lines - 2) / 2; + if (panel->top_file < 0) + panel->top_file = 0; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +do_try_to_select (WPanel * panel, const char *name) +{ + int i; + char *subdir; + + if (name == NULL) + { + do_select (panel, 0); + return; + } + + /* We only want the last component of the directory, + * and from this only the name without suffix. + * Cut prefix if the panel is not panelized */ + + if (panel->is_panelized) + subdir = vfs_strip_suffix_from_filename (name); + else + subdir = vfs_strip_suffix_from_filename (x_basename (name)); + + /* Search that subdir or filename without prefix (if not panelized panel), select it if found */ + for (i = 0; i < panel->dir.len; i++) + { + if (strcmp (subdir, panel->dir.list[i].fname->str) == 0) + { + do_select (panel, i); + g_free (subdir); + return; + } + } + + /* Try to select a file near the file that is missing */ + if (panel->selected >= panel->dir.len) + do_select (panel, panel->dir.len - 1); + g_free (subdir); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* event callback */ +static gboolean +event_update_panels (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + (void) event_group_name; + (void) event_name; + (void) init_data; + (void) data; + + update_panels (UP_RELOAD, UP_KEEPSEL); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* event callback */ +static gboolean +panel_save_current_file_to_clip_file (const gchar * event_group_name, const gchar * event_name, + gpointer init_data, gpointer data) +{ + (void) event_group_name; + (void) event_name; + (void) init_data; + (void) data; + + if (current_panel->marked == 0) + mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", + (gpointer) selection (current_panel)->fname->str); + else + { + int i; + gboolean first = TRUE; + char *flist = NULL; + + for (i = 0; i < current_panel->dir.len; i++) + if (current_panel->dir.list[i].f.marked != 0) + { /* Skip the unmarked ones */ + if (first) + { + flist = g_strndup (current_panel->dir.list[i].fname->str, + current_panel->dir.list[i].fname->len); + first = FALSE; + } + else + { + /* Add empty lines after the file */ + char *tmp; + + tmp = + g_strconcat (flist, "\n", current_panel->dir.list[i].fname->str, + (char *) NULL); + g_free (flist); + flist = tmp; + } + } + + mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", (gpointer) flist); + g_free (flist); + } + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static vfs_path_t * +panel_recursive_cd_to_parent (const vfs_path_t * vpath) +{ + vfs_path_t *cwd_vpath; + + cwd_vpath = vfs_path_clone (vpath); + + while (mc_chdir (cwd_vpath) < 0) + { + const char *panel_cwd_path; + vfs_path_t *tmp_vpath; + + /* check if path contains only '/' */ + panel_cwd_path = vfs_path_as_str (cwd_vpath); + if (panel_cwd_path != NULL && IS_PATH_SEP (panel_cwd_path[0]) && panel_cwd_path[1] == '\0') + { + vfs_path_free (cwd_vpath, TRUE); + return NULL; + } + + tmp_vpath = vfs_path_vtokens_get (cwd_vpath, 0, -1); + vfs_path_free (cwd_vpath, TRUE); + cwd_vpath = + vfs_path_build_filename (PATH_SEP_STR, vfs_path_as_str (tmp_vpath), (char *) NULL); + vfs_path_free (tmp_vpath, TRUE); + } + + return cwd_vpath; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panel_dir_list_callback (dir_list_cb_state_t state, void *data) +{ + static int count = 0; + + (void) data; + + switch (state) + { + case DIR_OPEN: + count = 0; + break; + + case DIR_READ: + count++; + if ((count & 15) == 0) + rotate_dash (TRUE); + break; + + case DIR_CLOSE: + rotate_dash (FALSE); + break; + + default: + g_assert_not_reached (); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +try_to_select (WPanel * panel, const char *name) +{ + do_try_to_select (panel, name); + select_item (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_clean_dir (WPanel * panel) +{ + panel->top_file = 0; + panel->selected = 0; + panel->marked = 0; + panel->dirs_marked = 0; + panel->total = 0; + panel->quick_search.active = FALSE; + panel->is_panelized = FALSE; + panel->dirty = TRUE; + panel->content_shift = -1; + panel->max_shift = -1; + + dir_list_free_list (&panel->dir); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Set Up panel's current dir object + * + * @param panel panel object + * @param path_str string contain path + */ + +void +panel_set_cwd (WPanel * panel, const vfs_path_t * vpath) +{ + if (vpath != panel->cwd_vpath) /* check if new vpath is not the panel->cwd_vpath object */ + { + vfs_path_free (panel->cwd_vpath, TRUE); + panel->cwd_vpath = vfs_path_clone (vpath); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Set Up panel's last working dir object + * + * @param panel panel object + * @param path_str string contain path + */ + +void +panel_set_lwd (WPanel * panel, const vfs_path_t * vpath) +{ + if (vpath != panel->lwd_vpath) /* check if new vpath is not the panel->lwd_vpath object */ + { + vfs_path_free (panel->lwd_vpath, TRUE); + panel->lwd_vpath = vfs_path_clone (vpath); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Creatie an empty panel with specified size. + * + * @param panel_name name of panel for setup retieving + * + * @return new instance of WPanel + */ + +WPanel * +panel_sized_empty_new (const char *panel_name, int y, int x, int lines, int cols) +{ + WRect r = { y, x, lines, cols }; + WPanel *panel; + Widget *w; + char *section; + int i, err; + + panel = g_new0 (WPanel, 1); + w = WIDGET (panel); + widget_init (w, &r, panel_callback, panel_mouse_callback); + w->options |= WOP_SELECTABLE | WOP_TOP_SELECT; + w->keymap = panel_map; + + panel->dir.size = DIR_LIST_MIN_SIZE; + panel->dir.list = g_new (file_entry_t, panel->dir.size); + panel->dir.len = 0; + panel->dir.callback = panel_dir_list_callback; + + panel->list_cols = 1; + panel->brief_cols = 2; + panel->dirty = TRUE; + panel->content_shift = -1; + panel->max_shift = -1; + + panel->list_format = list_full; + panel->user_format = g_strdup (DEFAULT_USER_FORMAT); + + panel->filter.flags = FILE_FILTER_DEFAULT_FLAGS; + + for (i = 0; i < LIST_FORMATS; i++) + panel->user_status_format[i] = g_strdup (DEFAULT_USER_FORMAT); + +#ifdef HAVE_CHARSET + panel->codepage = SELECT_CHARSET_NO_TRANSLATE; +#endif + + panel->frame_size = frame_half; + + panel->quick_search.buffer = g_string_sized_new (MC_MAXFILENAMELEN); + panel->quick_search.prev_buffer = g_string_sized_new (MC_MAXFILENAMELEN); + + panel->name = g_strdup (panel_name); + panel->dir_history.name = g_strconcat ("Dir Hist ", panel->name, (char *) NULL); + /* directories history will be get later */ + + section = g_strconcat ("Temporal:", panel->name, (char *) NULL); + if (!mc_config_has_group (mc_global.main_config, section)) + { + g_free (section); + section = g_strdup (panel->name); + } + panel_load_setup (panel, section); + g_free (section); + + if (panel->filter.value != NULL) + { + gboolean case_sens = (panel->filter.flags & SELECT_MATCH_CASE) != 0; + gboolean shell_patterns = (panel->filter.flags & SELECT_SHELL_PATTERNS) != 0; + + panel->filter.handler = mc_search_new (panel->filter.value, NULL); + panel->filter.handler->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX; + panel->filter.handler->is_entire_line = TRUE; + panel->filter.handler->is_case_sensitive = case_sens; + + /* FIXME: silent check -- do not display an error message */ + if (!mc_search_prepare (panel->filter.handler)) + file_filter_clear (&panel->filter); + } + + /* Load format strings */ + err = set_panel_formats (panel); + if (err != 0) + set_panel_formats (panel); + + return panel; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Panel creation for specified size and directory. + * + * @param panel_name name of panel for setup retieving + * @param y y coordinate of top-left corner + * @param x x coordinate of top-left corner + * @param lines vertical size + * @param cols horizontal size + * @param vpath working panel directory. If NULL then current directory is used + * + * @return new instance of WPanel + */ + +WPanel * +panel_sized_with_dir_new (const char *panel_name, int y, int x, int lines, int cols, + const vfs_path_t * vpath) +{ + WPanel *panel; + char *curdir = NULL; +#ifdef HAVE_CHARSET + const vfs_path_element_t *path_element; +#endif + + panel = panel_sized_empty_new (panel_name, y, x, lines, cols); + + if (vpath != NULL) + { + curdir = _vfs_get_cwd (); + panel_set_cwd (panel, vpath); + } + else + { + vfs_setup_cwd (); + panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ()); + } + + panel_set_lwd (panel, vfs_get_raw_current_dir ()); + +#ifdef HAVE_CHARSET + path_element = vfs_path_get_by_index (panel->cwd_vpath, -1); + if (path_element->encoding != NULL) + panel->codepage = get_codepage_index (path_element->encoding); +#endif + + if (mc_chdir (panel->cwd_vpath) != 0) + { +#ifdef HAVE_CHARSET + panel->codepage = SELECT_CHARSET_NO_TRANSLATE; +#endif + vfs_setup_cwd (); + vfs_path_free (panel->cwd_vpath, TRUE); + panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ()); + } + + /* Load the default format */ + if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine, + &panel->sort_info, &panel->filter)) + message (D_ERROR, MSG_ERROR, _("Cannot read directory contents")); + + /* Restore old right path */ + if (curdir != NULL) + { + vfs_path_t *tmp_vpath; + int err; + + tmp_vpath = vfs_path_from_str (curdir); + mc_chdir (tmp_vpath); + vfs_path_free (tmp_vpath, TRUE); + (void) err; + } + g_free (curdir); + + return panel; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_reload (WPanel * panel) +{ + struct stat current_stat; + vfs_path_t *cwd_vpath; + + if (panels_options.fast_reload && stat (vfs_path_as_str (panel->cwd_vpath), ¤t_stat) == 0 + && current_stat.st_ctime == panel->dir_stat.st_ctime + && current_stat.st_mtime == panel->dir_stat.st_mtime) + return; + + cwd_vpath = panel_recursive_cd_to_parent (panel->cwd_vpath); + vfs_path_free (panel->cwd_vpath, TRUE); + + if (cwd_vpath == NULL) + { + panel->cwd_vpath = vfs_path_from_str (PATH_SEP_STR); + panel_clean_dir (panel); + dir_list_init (&panel->dir); + return; + } + + panel->cwd_vpath = cwd_vpath; + memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat)); + show_dir (panel); + + if (!dir_list_reload (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine, + &panel->sort_info, &panel->filter)) + message (D_ERROR, MSG_ERROR, _("Cannot read directory contents")); + + panel->dirty = TRUE; + if (panel->selected >= panel->dir.len) + do_select (panel, panel->dir.len - 1); + + recalculate_panel_summary (panel); +} + +/* --------------------------------------------------------------------------------------------- */ +/* Switches the panel to the mode specified in the format */ +/* Seting up both format and status string. Return: 0 - on success; */ +/* 1 - format error; 2 - status error; 3 - errors in both formats. */ + +int +set_panel_formats (WPanel * p) +{ + GSList *form; + char *err = NULL; + int retcode = 0; + + form = use_display_format (p, panel_format (p), &err, FALSE); + + if (err != NULL) + { + g_free (err); + retcode = 1; + } + else + { + g_slist_free_full (p->format, (GDestroyNotify) format_item_free); + p->format = form; + } + + if (panels_options.show_mini_info) + { + form = use_display_format (p, mini_status_format (p), &err, TRUE); + + if (err != NULL) + { + g_free (err); + retcode += 2; + } + else + { + g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free); + p->status_format = form; + } + } + + panel_update_cols (WIDGET (p), p->frame_size); + + if (retcode) + message (D_ERROR, _("Warning"), + _("User supplied format looks invalid, reverting to default.")); + if (retcode & 0x01) + { + g_free (p->user_format); + p->user_format = g_strdup (DEFAULT_USER_FORMAT); + } + if (retcode & 0x02) + { + g_free (p->user_status_format[p->list_format]); + p->user_status_format[p->list_format] = g_strdup (DEFAULT_USER_FORMAT); + } + + return retcode; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_set_filter (WPanel * panel, const file_filter_t * filter) +{ + MC_PTR_FREE (panel->filter.value); + mc_search_free (panel->filter.handler); + panel->filter.handler = NULL; + + /* NULL to clear filter */ + if (filter != NULL) + panel->filter = *filter; + + reread_cmd (); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* Select current item and readjust the panel */ +void +select_item (WPanel * panel) +{ + adjust_top_file (panel); + + panel->dirty = TRUE; + + execute_hooks (select_file_hook); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Clears all files in the panel, used only when one file was marked */ +void +unmark_files (WPanel * panel) +{ + if (panel->marked != 0) + { + int i; + + for (i = 0; i < panel->dir.len; i++) + file_mark (panel, i, 0); + + panel->dirs_marked = 0; + panel->marked = 0; + panel->total = 0; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Recalculate the panels summary information, used e.g. when marked + files might have been removed by an external command */ + +void +recalculate_panel_summary (WPanel * panel) +{ + int i; + + panel->marked = 0; + panel->dirs_marked = 0; + panel->total = 0; + + for (i = 0; i < panel->dir.len; i++) + if (panel->dir.list[i].f.marked) + { + /* do_file_mark will return immediately if newmark == oldmark. + So we have to first unmark it to get panel's summary information + updated. (Norbert) */ + panel->dir.list[i].f.marked = 0; + do_file_mark (panel, i, 1); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** This routine marks a file or a directory */ + +void +do_file_mark (WPanel * panel, int idx, int mark) +{ + if (panel->dir.list[idx].f.marked == mark) + return; + + /* Only '..' can't be marked, '.' isn't visible */ + if (DIR_IS_DOTDOT (panel->dir.list[idx].fname->str)) + return; + + file_mark (panel, idx, mark); + if (panel->dir.list[idx].f.marked) + { + panel->marked++; + + if (S_ISDIR (panel->dir.list[idx].st.st_mode)) + { + if (panel->dir.list[idx].f.dir_size_computed) + panel->total += (uintmax_t) panel->dir.list[idx].st.st_size; + panel->dirs_marked++; + } + else + panel->total += (uintmax_t) panel->dir.list[idx].st.st_size; + + set_colors (panel); + } + else + { + if (S_ISDIR (panel->dir.list[idx].st.st_mode)) + { + if (panel->dir.list[idx].f.dir_size_computed) + panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size; + panel->dirs_marked--; + } + else + panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size; + + panel->marked--; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Changes the current directory of the panel. + * Record change in the directory history. + */ +gboolean +panel_do_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type) +{ + gboolean r; + + r = panel_do_cd_int (panel, new_dir_vpath, cd_type); + if (r) + directory_history_add (panel, panel->cwd_vpath); + return r; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +file_mark (WPanel * panel, int lc_index, int val) +{ + if (panel->dir.list[lc_index].f.marked != val) + { + panel->dir.list[lc_index].f.marked = val; + panel->dirty = TRUE; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_re_sort (WPanel * panel) +{ + char *filename; + file_entry_t *fe; + int i; + + if (panel == NULL) + return; + + fe = selection (panel); + filename = g_strndup (fe->fname->str, fe->fname->len); + unselect_item (panel); + dir_list_sort (&panel->dir, panel->sort_field->sort_routine, &panel->sort_info); + panel->selected = -1; + + for (i = panel->dir.len; i != 0; i--) + if (strcmp (panel->dir.list[i - 1].fname->str, filename) == 0) + { + panel->selected = i - 1; + break; + } + + g_free (filename); + panel->top_file = panel->selected - panel_items (panel) / 2; + select_item (panel); + panel->dirty = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_set_sort_order (WPanel * panel, const panel_field_t * sort_order) +{ + if (sort_order == NULL) + return; + + panel->sort_field = sort_order; + + /* The directory is already sorted, we have to load the unsorted stuff */ + if (sort_order->sort_routine == (GCompareFunc) unsorted) + { + char *current_file; + + current_file = g_strndup (panel->dir.list[panel->selected].fname->str, + panel->dir.list[panel->selected].fname->len); + panel_reload (panel); + try_to_select (panel, current_file); + g_free (current_file); + } + panel_re_sort (panel); +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef HAVE_CHARSET + +/** + * Change panel encoding. + * @param panel WPanel object + */ + +void +panel_change_encoding (WPanel * panel) +{ + const char *encoding = NULL; + char *errmsg; + int r; + + r = select_charset (-1, -1, panel->codepage, FALSE); + + if (r == SELECT_CHARSET_CANCEL) + return; /* Cancel */ + + panel->codepage = r; + + if (panel->codepage == SELECT_CHARSET_NO_TRANSLATE) + { + /* No translation */ + vfs_path_t *cd_path_vpath; + + g_free (init_translation_table (mc_global.display_codepage, mc_global.display_codepage)); + cd_path_vpath = remove_encoding_from_path (panel->cwd_vpath); + panel_do_cd (panel, cd_path_vpath, cd_parse_command); + show_dir (panel); + vfs_path_free (cd_path_vpath, TRUE); + return; + } + + errmsg = init_translation_table (panel->codepage, mc_global.display_codepage); + if (errmsg != NULL) + { + message (D_ERROR, MSG_ERROR, "%s", errmsg); + g_free (errmsg); + return; + } + + encoding = get_codepage_id (panel->codepage); + if (encoding != NULL) + { + vfs_path_change_encoding (panel->cwd_vpath, encoding); + + if (!panel_do_cd (panel, panel->cwd_vpath, cd_parse_command)) + message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\""), + vfs_path_as_str (panel->cwd_vpath)); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Remove encode info from last path element. + * + */ +vfs_path_t * +remove_encoding_from_path (const vfs_path_t * vpath) +{ + vfs_path_t *ret_vpath; + GString *tmp_conv; + int indx; + + ret_vpath = vfs_path_new (); + + tmp_conv = g_string_new (""); + + for (indx = 0; indx < vfs_path_elements_count (vpath); indx++) + { + GIConv converter; + vfs_path_element_t *path_element; + + path_element = vfs_path_element_clone (vfs_path_get_by_index (vpath, indx)); + + if (path_element->encoding == NULL) + { + vfs_path_add_element (ret_vpath, path_element); + continue; + } + + converter = str_crt_conv_to (path_element->encoding); + if (converter == INVALID_CONV) + { + vfs_path_add_element (ret_vpath, path_element); + continue; + } + + MC_PTR_FREE (path_element->encoding); + + str_vfs_convert_from (converter, path_element->path, tmp_conv); + + g_free (path_element->path); + path_element->path = g_strndup (tmp_conv->str, tmp_conv->len); + + g_string_set_size (tmp_conv, 0); + + str_close_conv (converter); + str_close_conv (path_element->dir.converter); + path_element->dir.converter = INVALID_CONV; + vfs_path_add_element (ret_vpath, path_element); + } + g_string_free (tmp_conv, TRUE); + return ret_vpath; +} +#endif /* HAVE_CHARSET */ + +/* --------------------------------------------------------------------------------------------- */ + +/** + * This routine reloads the directory in both panels. It tries to + * select current_file in current_panel and other_file in other_panel. + * If current_file == -1 then it automatically sets current_file and + * other_file to the currently selected files in the panels. + * + * If flags has the UP_ONLY_CURRENT bit toggled on, then it + * will not reload the other panel. + * + * @param flags for reload panel + * @param current_file name of the current file + */ + +void +update_panels (panel_update_flags_t flags, const char *current_file) +{ + WPanel *panel; + + /* first, update other panel... */ + if ((flags & UP_ONLY_CURRENT) == 0) + update_one_panel (get_other_index (), flags, UP_KEEPSEL); + /* ...then current one */ + update_one_panel (get_current_index (), flags, current_file); + + if (get_current_type () == view_listing) + panel = PANEL (get_panel_widget (get_current_index ())); + else + panel = PANEL (get_panel_widget (get_other_index ())); + + if (!panel->is_panelized) + (void) mc_chdir (panel->cwd_vpath); +} + +/* --------------------------------------------------------------------------------------------- */ + +gsize +panel_get_num_of_sortable_fields (void) +{ + gsize ret = 0, lc_index; + + for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++) + if (panel_fields[lc_index].is_user_choice) + ret++; + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +char ** +panel_get_sortable_fields (gsize * array_size) +{ + char **ret; + gsize lc_index, i; + + lc_index = panel_get_num_of_sortable_fields (); + + ret = g_try_new0 (char *, lc_index + 1); + if (ret == NULL) + return NULL; + + if (array_size != NULL) + *array_size = lc_index; + + lc_index = 0; + + for (i = 0; panel_fields[i].id != NULL; i++) + if (panel_fields[i].is_user_choice) + ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey)); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +const panel_field_t * +panel_get_field_by_id (const char *name) +{ + gsize lc_index; + + for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++) + if (panel_fields[lc_index].id != NULL && strcmp (name, panel_fields[lc_index].id) == 0) + return &panel_fields[lc_index]; + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +const panel_field_t * +panel_get_field_by_title_hotkey (const char *name) +{ + gsize lc_index; + + for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++) + if (panel_fields[lc_index].title_hotkey != NULL && + strcmp (name, _(panel_fields[lc_index].title_hotkey)) == 0) + return &panel_fields[lc_index]; + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +const panel_field_t * +panel_get_field_by_title (const char *name) +{ + gsize lc_index; + + for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++) + { + const char *title; + + title = panel_get_title_without_hotkey (panel_fields[lc_index].title_hotkey); + if (strcmp (title, name) == 0) + return &panel_fields[lc_index]; + } + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +gsize +panel_get_num_of_user_possible_fields (void) +{ + gsize ret = 0, lc_index; + + for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++) + if (panel_fields[lc_index].use_in_user_format) + ret++; + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +char ** +panel_get_user_possible_fields (gsize * array_size) +{ + char **ret; + gsize lc_index, i; + + lc_index = panel_get_num_of_user_possible_fields (); + + ret = g_try_new0 (char *, lc_index + 1); + if (ret == NULL) + return NULL; + + if (array_size != NULL) + *array_size = lc_index; + + lc_index = 0; + + for (i = 0; panel_fields[i].id != NULL; i++) + if (panel_fields[i].use_in_user_format) + ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey)); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_init (void) +{ + panel_sort_up_char = mc_skin_get ("widget-panel", "sort-up-char", "'"); + panel_sort_down_char = mc_skin_get ("widget-panel", "sort-down-char", "."); + panel_hiddenfiles_show_char = mc_skin_get ("widget-panel", "hiddenfiles-show-char", "."); + panel_hiddenfiles_hide_char = mc_skin_get ("widget-panel", "hiddenfiles-hide-char", "."); + panel_history_prev_item_char = mc_skin_get ("widget-panel", "history-prev-item-char", "<"); + panel_history_next_item_char = mc_skin_get ("widget-panel", "history-next-item-char", ">"); + panel_history_show_list_char = mc_skin_get ("widget-panel", "history-show-list-char", "^"); + panel_filename_scroll_left_char = + mc_skin_get ("widget-panel", "filename-scroll-left-char", "{"); + panel_filename_scroll_right_char = + mc_skin_get ("widget-panel", "filename-scroll-right-char", "}"); + + string_file_name_buffer = g_string_sized_new (MC_MAXFILENAMELEN); + + mc_event_add (MCEVENT_GROUP_FILEMANAGER, "update_panels", event_update_panels, NULL, NULL); + mc_event_add (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file", + panel_save_current_file_to_clip_file, NULL, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panel_deinit (void) +{ + g_free (panel_sort_up_char); + g_free (panel_sort_down_char); + g_free (panel_hiddenfiles_show_char); + g_free (panel_hiddenfiles_hide_char); + g_free (panel_history_prev_item_char); + g_free (panel_history_next_item_char); + g_free (panel_history_show_list_char); + g_free (panel_filename_scroll_left_char); + g_free (panel_filename_scroll_right_char); + g_string_free (string_file_name_buffer, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +gboolean +panel_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum exact) +{ + gboolean res; + const vfs_path_t *_new_dir_vpath = new_dir_vpath; + + if (panel->is_panelized) + { + size_t new_vpath_len; + + new_vpath_len = vfs_path_len (new_dir_vpath); + if (vfs_path_equal_len (new_dir_vpath, panelized_panel.root_vpath, new_vpath_len)) + _new_dir_vpath = panelized_panel.root_vpath; + } + + res = panel_do_cd (panel, _new_dir_vpath, exact); + +#ifdef HAVE_CHARSET + if (res) + { + const vfs_path_element_t *path_element; + + path_element = vfs_path_get_by_index (panel->cwd_vpath, -1); + if (path_element->encoding != NULL) + panel->codepage = get_codepage_index (path_element->encoding); + else + panel->codepage = SELECT_CHARSET_NO_TRANSLATE; + } +#endif /* HAVE_CHARSET */ + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/panel.h b/src/filemanager/panel.h new file mode 100644 index 0000000..f7cbfa4 --- /dev/null +++ b/src/filemanager/panel.h @@ -0,0 +1,272 @@ +/** \file panel.h + * \brief Header: defines WPanel structure + */ + +#ifndef MC__PANEL_H +#define MC__PANEL_H + +#include <inttypes.h> /* uintmax_t */ +#include <limits.h> /* MB_LEN_MAX */ + +#include "lib/global.h" /* gboolean */ +#include "lib/fs.h" /* MC_MAXPATHLEN */ +#include "lib/strutil.h" +#include "lib/widget.h" /* Widget */ +#include "lib/filehighlight.h" + +#include "dir.h" /* dir_list */ + +/*** typedefs(not structures) and defined constants **********************************************/ + +#define PANEL(x) ((WPanel *)(x)) +#define selection(p) (&(p->dir.list[p->selected])) +#define DEFAULT_USER_FORMAT "half type name | size | perm" + +#define LIST_FORMATS 4 + +#define UP_KEEPSEL ((char *) -1) + +/*** enums ***************************************************************************************/ + +typedef enum +{ + list_full, /* Name, size, perm/date */ + list_brief, /* Name */ + list_long, /* Like ls -l */ + list_user /* User defined */ +} list_format_t; + +typedef enum +{ + frame_full, /* full screen frame */ + frame_half /* half screen frame */ +} panel_display_t; + +typedef enum +{ + UP_OPTIMIZE = 0, + UP_RELOAD = 1, + UP_ONLY_CURRENT = 2 +} panel_update_flags_t; + +/* run mode and params */ +enum cd_enum +{ + cd_parse_command, + cd_exact +}; + +/*** structures declarations (and typedefs of structures)*****************************************/ + +typedef struct panel_field_struct +{ + const char *id; + int min_size; + gboolean expands; + align_crt_t default_just; + const char *hotkey; + const char *title_hotkey; + gboolean is_user_choice; + gboolean use_in_user_format; + const char *(*string_fn) (file_entry_t *, int); + GCompareFunc sort_routine; /* used by mouse_sort_col() */ +} panel_field_t; + +typedef struct +{ + dir_list list; + vfs_path_t *root_vpath; +} panelized_panel_t; + +typedef struct +{ + Widget widget; + + char *name; /* The panel name */ + + panel_display_t frame_size; /* half or full frame */ + + gboolean active; /* If panel is currently selected */ + gboolean dirty; /* Should we redisplay the panel? */ + gboolean is_panelized; /* Flag: special filelisting, can't reload */ + +#ifdef HAVE_CHARSET + int codepage; /* Panel codepage */ +#endif + + dir_list dir; /* Directory contents */ + struct stat dir_stat; /* Stat of current dir: used by execute () */ + + vfs_path_t *cwd_vpath; /* Current Working Directory */ + vfs_path_t *lwd_vpath; /* Last Working Directory */ + + list_format_t list_format; /* Listing type */ + GSList *format; /* Display format */ + char *user_format; /* User format */ + int list_cols; /* Number of file list columns */ + int brief_cols; /* Number of columns in case of list_brief format */ + /* sort */ + dir_sort_options_t sort_info; + const panel_field_t *sort_field; + + int marked; /* Count of marked files */ + int dirs_marked; /* Count of marked directories */ + uintmax_t total; /* Bytes in marked files */ + + int top_file; /* The file showed on the top of the panel */ + int selected; /* Index to the selected file */ + + GSList *status_format; /* Mini status format */ + gboolean user_mini_status; /* Is user_status_format used */ + char *user_status_format[LIST_FORMATS]; /* User format for status line */ + + file_filter_t filter; /* File name filter */ + + struct + { + char *name; /* Directory history name for history file */ + GList *list; /* Directory history */ + GList *current; /* Pointer to the current history item */ + } dir_history; + + struct + { + gboolean active; + GString *buffer; + GString *prev_buffer; + char ch[MB_LEN_MAX]; /* Buffer for multi-byte character */ + int chpoint; /* Point after last characters in @ch */ + } quick_search; + + int content_shift; /* Number of characters of filename need to skip from left side. */ + int max_shift; /* Max shift for visible part of current panel */ +} WPanel; + +/*** global variables defined in .c file *********************************************************/ + +extern panelized_panel_t panelized_panel; + +extern hook_t *select_file_hook; + +extern mc_fhl_t *mc_filehighlight; + +/*** declarations of public functions ************************************************************/ + +WPanel *panel_sized_empty_new (const char *panel_name, int y, int x, int lines, int cols); +WPanel *panel_sized_with_dir_new (const char *panel_name, int y, int x, int lines, int cols, + const vfs_path_t * vpath); + +void panel_clean_dir (WPanel * panel); + +void panel_reload (WPanel * panel); +void panel_set_sort_order (WPanel * panel, const panel_field_t * sort_order); +void panel_re_sort (WPanel * panel); + +#ifdef HAVE_CHARSET +void panel_change_encoding (WPanel * panel); +vfs_path_t *remove_encoding_from_path (const vfs_path_t * vpath); +#endif + +void update_panels (panel_update_flags_t flags, const char *current_file); +int set_panel_formats (WPanel * p); + +void panel_set_filter (WPanel * panel, const file_filter_t * filter); + +void try_to_select (WPanel * panel, const char *name); + +void unmark_files (WPanel * panel); +void select_item (WPanel * panel); + +void recalculate_panel_summary (WPanel * panel); +void file_mark (WPanel * panel, int idx, int val); +void do_file_mark (WPanel * panel, int idx, int val); + +gboolean panel_do_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type); +gboolean panel_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type); + +gsize panel_get_num_of_sortable_fields (void); +char **panel_get_sortable_fields (gsize * array_size); +const panel_field_t *panel_get_field_by_id (const char *name); +const panel_field_t *panel_get_field_by_title (const char *name); +const panel_field_t *panel_get_field_by_title_hotkey (const char *name); +gsize panel_get_num_of_user_possible_fields (void); +char **panel_get_user_possible_fields (gsize * array_size); +void panel_set_cwd (WPanel * panel, const vfs_path_t * vpath); +void panel_set_lwd (WPanel * panel, const vfs_path_t * vpath); + +void panel_init (void); +void panel_deinit (void); + +/* --------------------------------------------------------------------------------------------- */ +/*** inline functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Empty panel creation. + * + * @param panel_name name of panel for setup retieving + * + * @return new instance of WPanel + */ + +static inline WPanel * +panel_empty_new (const char *panel_name) +{ + /* Unknown sizes of the panel at startup */ + return panel_sized_empty_new (panel_name, 0, 0, 1, 1); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Panel creation for specified directory. + * + * @param panel_name name of panel for setup retieving + * @param vpath working panel directory. If NULL then current directory is used + * + * @return new instance of WPanel + */ + +static inline WPanel * +panel_with_dir_new (const char *panel_name, const vfs_path_t * vpath) +{ + /* Unknown sizes of the panel at startup */ + return panel_sized_with_dir_new (panel_name, 0, 0, 1, 1, vpath); +} + + +/* --------------------------------------------------------------------------------------------- */ +/** + * Panel creation. + * + * @param panel_name name of panel for setup retieving + * + * @return new instance of WPanel + */ + +static inline WPanel * +panel_new (const char *panel_name) +{ + return panel_with_dir_new (panel_name, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Panel creation with specified size. + * + * @param panel_name name of panel for setup retieving + * @param y y coordinate of top-left corner + * @param x x coordinate of top-left corner + * @param lines vertical size + * @param cols horizontal size + * + * @return new instance of WPanel + */ + +static inline WPanel * +panel_sized_new (const char *panel_name, int y, int x, int lines, int cols) +{ + return panel_sized_with_dir_new (panel_name, y, x, lines, cols, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +#endif /* MC__PANEL_H */ diff --git a/src/filemanager/panelize.c b/src/filemanager/panelize.c new file mode 100644 index 0000000..ab0dbb6 --- /dev/null +++ b/src/filemanager/panelize.c @@ -0,0 +1,727 @@ +/* + External panelize + + Copyright (C) 1995-2022 + Free Software Foundation, Inc. + + Written by: + Janne Kukonlehto, 1995 + Jakub Jelinek, 1995 + 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 panelize.c + * \brief Source: External panelization module + */ + +#include <config.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "lib/global.h" + +#include "lib/skin.h" +#include "lib/vfs/vfs.h" +#include "lib/mcconfig.h" /* Load/save directories panelize */ +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/widget.h" + +#include "src/setup.h" /* For profile_bname */ +#include "src/history.h" + +#include "dir.h" +#include "filemanager.h" /* current_panel */ +#include "layout.h" /* rotate_dash() */ +#include "panel.h" /* WPanel */ + +#include "panelize.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define UX 3 +#define UY 2 + +#define B_ADD B_USER +#define B_REMOVE (B_USER + 1) + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static WListbox *l_panelize; +static WDialog *panelize_dlg; +static int last_listitem; +static WInput *pname; + +static const char *panelize_section = "Panelize"; + +/* Directory panelize */ +static struct panelize +{ + char *command; + char *label; + struct panelize *next; +} *panelize = NULL; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +update_command (void) +{ + if (l_panelize->pos != last_listitem) + { + struct panelize *data = NULL; + + last_listitem = l_panelize->pos; + listbox_get_current (l_panelize, NULL, (void **) &data); + input_assign_text (pname, data->command); + pname->point = 0; + input_update (pname, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +panelize_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + switch (msg) + { + case MSG_INIT: + group_default_callback (w, NULL, MSG_INIT, 0, NULL); + MC_FALLTHROUGH; + + case MSG_NOTIFY: /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */ + update_command (); + return MSG_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +init_panelize (void) +{ + struct + { + int ret_cmd; + button_flags_t flags; + const char *text; + } panelize_but[] = + { + /* *INDENT-OFF* */ + { B_ENTER, DEFPUSH_BUTTON, N_("Pane&lize") }, + { B_REMOVE, NORMAL_BUTTON, N_("&Remove") }, + { B_ADD, NORMAL_BUTTON, N_("&Add new") }, + { B_CANCEL, NORMAL_BUTTON, N_("&Cancel") } + /* *INDENT-ON* */ + }; + + WGroup *g; + + size_t i; + int blen; + int panelize_cols; + struct panelize *current; + int x, y; + + last_listitem = 0; + + do_refresh (); + + i = G_N_ELEMENTS (panelize_but); + blen = i - 1; /* gaps between buttons */ + while (i-- != 0) + { +#ifdef ENABLE_NLS + panelize_but[i].text = _(panelize_but[i].text); +#endif + blen += str_term_width1 (panelize_but[i].text) + 3 + 1; + if (panelize_but[i].flags == DEFPUSH_BUTTON) + blen += 2; + } + + panelize_cols = COLS - 6; + panelize_cols = MAX (panelize_cols, blen + 4); + + panelize_dlg = + dlg_create (TRUE, 0, 0, 20, panelize_cols, WPOS_CENTER, FALSE, dialog_colors, + panelize_callback, NULL, "[External panelize]", _("External panelize")); + g = GROUP (panelize_dlg); + + /* add listbox to the dialogs */ + y = UY; + group_add_widget (g, groupbox_new (y++, UX, 12, panelize_cols - UX * 2, "")); + + l_panelize = listbox_new (y, UX + 1, 10, panelize_cols - UX * 2 - 2, FALSE, NULL); + for (current = panelize; current != NULL; current = current->next) + listbox_add_item (l_panelize, LISTBOX_APPEND_AT_END, 0, current->label, current, FALSE); + listbox_select_entry (l_panelize, listbox_search_text (l_panelize, _("Other command"))); + group_add_widget (g, l_panelize); + + y += WIDGET (l_panelize)->rect.lines + 1; + group_add_widget (g, label_new (y++, UX, _("Command"))); + pname = + input_new (y++, UX, input_colors, panelize_cols - UX * 2, "", "in", + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_COMMANDS | + INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES | INPUT_COMPLETE_CD | + INPUT_COMPLETE_SHELL_ESC); + group_add_widget (g, pname); + + group_add_widget (g, hline_new (y++, -1, -1)); + + x = (panelize_cols - blen) / 2; + for (i = 0; i < G_N_ELEMENTS (panelize_but); i++) + { + WButton *b; + + b = button_new (y, x, + panelize_but[i].ret_cmd, panelize_but[i].flags, panelize_but[i].text, NULL); + group_add_widget (g, b); + + x += button_get_len (b) + 1; + } + + widget_select (WIDGET (l_panelize)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +panelize_done (void) +{ + widget_destroy (WIDGET (panelize_dlg)); + repaint_screen (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +add2panelize (char *label, char *command) +{ + struct panelize *current; + struct panelize *old = NULL; + + current = panelize; + while (current != NULL && strcmp (current->label, label) <= 0) + { + old = current; + current = current->next; + } + + if (old == NULL) + { + panelize = g_new (struct panelize, 1); + panelize->label = label; + panelize->command = command; + panelize->next = current; + } + else + { + struct panelize *new; + + new = g_new (struct panelize, 1); + new->label = label; + new->command = command; + old->next = new; + new->next = current; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +add2panelize_cmd (void) +{ + if (!input_is_empty (pname)) + { + char *label; + + label = input_dialog (_("Add to external panelize"), + _("Enter command label:"), MC_HISTORY_FM_PANELIZE_ADD, "", + INPUT_COMPLETE_NONE); + if (label == NULL || *label == '\0') + g_free (label); + else + add2panelize (label, input_get_text (pname)); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +remove_from_panelize (struct panelize *entry) +{ + if (strcmp (entry->label, _("Other command")) != 0) + { + if (entry == panelize) + panelize = panelize->next; + else + { + struct panelize *current = panelize; + + while (current != NULL && current->next != entry) + current = current->next; + + if (current != NULL) + current->next = entry->next; + } + + g_free (entry->label); + g_free (entry->command); + g_free (entry); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +do_external_panelize (char *command) +{ + dir_list *list = ¤t_panel->dir; + mc_pipe_t *external; + GError *error = NULL; + GString *remain_file_name = NULL; + + external = mc_popen (command, TRUE, TRUE, &error); + if (external == NULL) + { + message (D_ERROR, _("External panelize"), "%s", error->message); + g_error_free (error); + return; + } + + /* Clear the counters and the directory list */ + panel_clean_dir (current_panel); + + panelize_change_root (current_panel->cwd_vpath); + + dir_list_init (list); + + while (TRUE) + { + GString *line; + gboolean ok; + + /* init buffers before call of mc_pread() */ + external->out.len = MC_PIPE_BUFSIZE; + external->err.len = MC_PIPE_BUFSIZE; + external->err.null_term = TRUE; + + mc_pread (external, &error); + + if (error != NULL) + { + message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), error->message); + g_error_free (error); + break; + } + + if (external->err.len > 0) + message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), external->err.buf); + + if (external->out.len == MC_PIPE_STREAM_EOF) + break; + + if (external->out.len == 0) + continue; + + if (external->out.len == MC_PIPE_ERROR_READ) + { + message (D_ERROR, MSG_ERROR, + _("External panelize:\nfailed to read data from child stdout:\n%s"), + unix_error_string (external->out.error)); + break; + } + + ok = TRUE; + + while (ok && (line = mc_pstream_get_string (&external->out)) != NULL) + { + char *name; + gboolean link_to_dir, stale_link; + struct stat st; + + /* handle a \n-separated file list */ + + if (line->str[line->len - 1] == '\n') + { + /* entire file name or last chunk */ + + g_string_truncate (line, line->len - 1); + + /* join filename chunks */ + if (remain_file_name != NULL) + { + g_string_append_len (remain_file_name, line->str, line->len); + g_string_free (line, TRUE); + line = remain_file_name; + remain_file_name = NULL; + } + } + else + { + /* first or middle chunk of file name */ + + if (remain_file_name == NULL) + remain_file_name = line; + else + { + g_string_append_len (remain_file_name, line->str, line->len); + g_string_free (line, TRUE); + } + + continue; + } + + name = line->str; + + if (name[0] == '.' && IS_PATH_SEP (name[1])) + name += 2; + + if (handle_path (name, &st, &link_to_dir, &stale_link)) + { + ok = dir_list_append (list, name, &st, link_to_dir, stale_link); + + if (ok) + { + file_mark (current_panel, list->len - 1, 0); + + if ((list->len & 31) == 0) + rotate_dash (TRUE); + } + } + + g_string_free (line, TRUE); + } + } + + if (remain_file_name != NULL) + g_string_free (remain_file_name, TRUE); + + mc_pclose (external, NULL); + + current_panel->is_panelized = TRUE; + panelize_absolutize_if_needed (current_panel); + + try_to_select (current_panel, NULL); + panel_re_sort (current_panel); + rotate_dash (FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +do_panelize_cd (WPanel * panel) +{ + int i; + dir_list *list; + gboolean panelized_same; + + dir_list_clean (&panel->dir); + if (panelized_panel.root_vpath == NULL) + panelize_change_root (current_panel->cwd_vpath); + + if (panelized_panel.list.len < 1) + dir_list_init (&panelized_panel.list); + else if (panelized_panel.list.len > panel->dir.size) + dir_list_grow (&panel->dir, panelized_panel.list.len - panel->dir.size); + + list = &panel->dir; + list->len = panelized_panel.list.len; + + panelized_same = vfs_path_equal (panelized_panel.root_vpath, panel->cwd_vpath); + + for (i = 0; i < panelized_panel.list.len; i++) + { + if (panelized_same || DIR_IS_DOTDOT (panelized_panel.list.list[i].fname->str)) + list->list[i].fname = mc_g_string_dup (panelized_panel.list.list[i].fname); + else + { + vfs_path_t *tmp_vpath; + + tmp_vpath = + vfs_path_append_new (panelized_panel.root_vpath, + panelized_panel.list.list[i].fname->str, (char *) NULL); + list->list[i].fname = g_string_new (vfs_path_as_str (tmp_vpath)); + vfs_path_free (tmp_vpath, TRUE); + } + list->list[i].f.link_to_dir = panelized_panel.list.list[i].f.link_to_dir; + list->list[i].f.stale_link = panelized_panel.list.list[i].f.stale_link; + list->list[i].f.dir_size_computed = panelized_panel.list.list[i].f.dir_size_computed; + list->list[i].f.marked = panelized_panel.list.list[i].f.marked; + list->list[i].st = panelized_panel.list.list[i].st; + list->list[i].sort_key = panelized_panel.list.list[i].sort_key; + list->list[i].second_sort_key = panelized_panel.list.list[i].second_sort_key; + } + + panel->is_panelized = TRUE; + panelize_absolutize_if_needed (panel); + + try_to_select (panel, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** + * Change root directory of panelized content. + * @param new_root - object with new path. + */ +void +panelize_change_root (const vfs_path_t * new_root) +{ + vfs_path_free (panelized_panel.root_vpath, TRUE); + panelized_panel.root_vpath = vfs_path_clone (new_root); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +panelize_save_panel (WPanel * panel) +{ + int i; + dir_list *list = &panel->dir; + + panelize_change_root (current_panel->cwd_vpath); + + if (panelized_panel.list.len > 0) + dir_list_clean (&panelized_panel.list); + if (panel->dir.len == 0) + return; + + if (panel->dir.len > panelized_panel.list.size) + dir_list_grow (&panelized_panel.list, panel->dir.len - panelized_panel.list.size); + panelized_panel.list.len = panel->dir.len; + + for (i = 0; i < panel->dir.len; i++) + { + panelized_panel.list.list[i].fname = mc_g_string_dup (list->list[i].fname); + panelized_panel.list.list[i].f.link_to_dir = list->list[i].f.link_to_dir; + panelized_panel.list.list[i].f.stale_link = list->list[i].f.stale_link; + panelized_panel.list.list[i].f.dir_size_computed = list->list[i].f.dir_size_computed; + panelized_panel.list.list[i].f.marked = list->list[i].f.marked; + panelized_panel.list.list[i].st = list->list[i].st; + panelized_panel.list.list[i].sort_key = list->list[i].sort_key; + panelized_panel.list.list[i].second_sort_key = list->list[i].second_sort_key; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Conditionally switches a panel's directory to "/" (root). + * + * If a panelized panel's listing contain absolute paths, this function + * sets the panel's directory to "/". Otherwise it does nothing. + * + * Rationale: + * + * This makes tokenized strings like "%d/%p" work. This also makes other + * places work where such naive concatenation is done in code (e.g., when + * pressing ctrl+shift+enter, for CK_PutCurrentFullSelected). + * + * When to call: + * + * You should always call this function after you populate the listing + * of a panelized panel. + */ +void +panelize_absolutize_if_needed (WPanel * panel) +{ + const dir_list *const list = &panel->dir; + + /* Note: We don't support mixing of absolute and relative paths, which is + * why it's ok for us to check only the 1st entry. */ + if (list->len > 1 && g_path_is_absolute (list->list[1].fname->str)) + { + vfs_path_t *root; + + root = vfs_path_from_str (PATH_SEP_STR); + panel_set_cwd (panel, root); + if (panel == current_panel) + mc_chdir (root); + vfs_path_free (root, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +cd_panelize_cmd (void) +{ + if (!SELECTED_IS_PANEL) + create_panel (MENU_PANEL_IDX, view_listing); + + do_panelize_cd (PANEL (get_panel_widget (MENU_PANEL_IDX))); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +external_panelize (void) +{ + if (!vfs_current_is_local ()) + { + message (D_ERROR, MSG_ERROR, _("Cannot run external panelize in a non-local directory")); + return; + } + + init_panelize (); + + /* display file info */ + tty_setcolor (SELECTED_COLOR); + + switch (dlg_run (panelize_dlg)) + { + case B_CANCEL: + break; + + case B_ADD: + add2panelize_cmd (); + break; + + case B_REMOVE: + { + struct panelize *entry; + + listbox_get_current (l_panelize, NULL, (void **) &entry); + remove_from_panelize (entry); + break; + } + + case B_ENTER: + if (!input_is_empty (pname)) + { + char *cmd; + + cmd = input_get_text (pname); + widget_destroy (WIDGET (panelize_dlg)); + do_external_panelize (cmd); + g_free (cmd); + repaint_screen (); + return; + } + break; + + default: + break; + } + + panelize_done (); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +load_panelize (void) +{ + char **keys; + + keys = mc_config_get_keys (mc_global.main_config, panelize_section, NULL); + + add2panelize (g_strdup (_("Other command")), g_strdup ("")); + + if (*keys == NULL) + { + add2panelize (g_strdup (_("Modified git files")), g_strdup ("git ls-files --modified")); + add2panelize (g_strdup (_("Find rejects after patching")), + g_strdup ("find . -name \\*.rej -print")); + add2panelize (g_strdup (_("Find *.orig after patching")), + g_strdup ("find . -name \\*.orig -print")); + add2panelize (g_strdup (_("Find SUID and SGID programs")), + g_strdup + ("find . \\( \\( -perm -04000 -a -perm /011 \\) -o \\( -perm -02000 -a -perm /01 \\) \\) -print")); + } + else + { + GIConv conv; + char **profile_keys; + + conv = str_crt_conv_from ("UTF-8"); + + for (profile_keys = keys; *profile_keys != NULL; profile_keys++) + { + GString *buffer; + + if (mc_global.utf8_display || conv == INVALID_CONV) + buffer = g_string_new (*profile_keys); + else + { + buffer = g_string_new (""); + if (str_convert (conv, *profile_keys, buffer) == ESTR_FAILURE) + g_string_assign (buffer, *profile_keys); + } + + add2panelize (g_string_free (buffer, FALSE), + mc_config_get_string (mc_global.main_config, panelize_section, + *profile_keys, "")); + } + + str_close_conv (conv); + } + + g_strfreev (keys); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +save_panelize (void) +{ + struct panelize *current; + + mc_config_del_group (mc_global.main_config, panelize_section); + + for (current = panelize; current != NULL; current = current->next) + if (strcmp (current->label, _("Other command")) != 0) + mc_config_set_string (mc_global.main_config, + panelize_section, current->label, current->command); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +done_panelize (void) +{ + struct panelize *current, *next; + + for (current = panelize; current != NULL; current = next) + { + next = current->next; + g_free (current->label); + g_free (current->command); + g_free (current); + } +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/panelize.h b/src/filemanager/panelize.h new file mode 100644 index 0000000..f230b29 --- /dev/null +++ b/src/filemanager/panelize.h @@ -0,0 +1,28 @@ +/** \file panelize.h + * \brief Header: External panelization module + */ + +#ifndef MC__PANELIZE_H +#define MC__PANELIZE_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 external_panelize (void); +void load_panelize (void); +void save_panelize (void); +void done_panelize (void); +void cd_panelize_cmd (void); +void panelize_save_panel (WPanel * panel); +void panelize_change_root (const vfs_path_t * new_root); +void panelize_absolutize_if_needed (WPanel * panel); + +/*** inline functions ****************************************************************************/ +#endif /* MC__PANELIZE_H */ diff --git a/src/filemanager/tree.c b/src/filemanager/tree.c new file mode 100644 index 0000000..8955774 --- /dev/null +++ b/src/filemanager/tree.c @@ -0,0 +1,1348 @@ +/* + Directory tree browser for the Midnight Commander + This module has been converted to be a widget. + + The program load and saves the tree each time the tree widget is + created and destroyed. This is required for the future vfs layer, + it will be possible to have tree views over virtual file systems. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Janne Kukonlehto, 1994, 1996 + Norbert Warmuth, 1997 + Miguel de Icaza, 1996, 1999 + 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 tree.c + * \brief Source: directory tree browser + */ + +#include <config.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" +#include "lib/tty/key.h" +#include "lib/skin.h" +#include "lib/vfs/vfs.h" +#include "lib/fileloc.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/widget.h" +#include "lib/event.h" /* mc_event_raise() */ + +#include "src/setup.h" /* confirm_delete, panels_options */ +#include "src/keymap.h" +#include "src/history.h" + +#include "dir.h" +#include "filemanager.h" /* the_menubar */ +#include "file.h" /* copy_dir_dir(), move_dir_dir(), erase_dir() */ +#include "layout.h" /* command_prompt */ +#include "treestore.h" +#include "cmd.h" +#include "filegui.h" + +#include "tree.h" + +/*** global variables ****************************************************************************/ + +/* The pointer to the tree */ +WTree *the_tree = NULL; + +/* If this is true, then when browsing the tree the other window will + * automatically reload it's directory with the contents of the currently + * selected directory. + */ +gboolean xtree_mode = FALSE; + +/*** file scope macro definitions ****************************************************************/ + +#define tlines(t) (t->is_panel ? WIDGET (t)->rect.lines - 2 - \ + (panels_options.show_mini_info ? 2 : 0) : WIDGET (t)->rect.lines) + +/*** file scope type declarations ****************************************************************/ + +struct WTree +{ + Widget widget; + struct TreeStore *store; + tree_entry *selected_ptr; /* The selected directory */ + GString *search_buffer; /* Current search string */ + tree_entry **tree_shown; /* Entries currently on screen */ + gboolean is_panel; /* panel or plain widget flag */ + gboolean searching; /* Are we on searching mode? */ + int topdiff; /* The difference between the topmost + shown and the selected */ +}; + +/*** file scope variables ************************************************************************/ + +/* Specifies the display mode: 1d or 2d */ +static gboolean tree_navigation_flag = FALSE; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void tree_rescan (void *data); + +/* --------------------------------------------------------------------------------------------- */ + +static tree_entry * +back_ptr (tree_entry * ptr, int *count) +{ + int i; + + for (i = 0; ptr != NULL && ptr->prev != NULL && i < *count; ptr = ptr->prev, i++) + ; + + *count = i; + return ptr; +} + +/* --------------------------------------------------------------------------------------------- */ + +static tree_entry * +forw_ptr (tree_entry * ptr, int *count) +{ + int i; + + for (i = 0; ptr != NULL && ptr->next != NULL && i < *count; ptr = ptr->next, i++) + ; + + *count = i; + return ptr; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +remove_callback (tree_entry * entry, void *data) +{ + WTree *tree = data; + + if (tree->selected_ptr == entry) + { + if (tree->selected_ptr->next != NULL) + tree->selected_ptr = tree->selected_ptr->next; + else + tree->selected_ptr = tree->selected_ptr->prev; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Save the ${XDG_CACHE_HOME}/mc/Tree file */ + +static void +save_tree (WTree * tree) +{ + int error; + + (void) tree; + + error = tree_store_save (); + if (error != 0) + { + char *tree_name; + + tree_name = mc_config_get_full_path (MC_TREESTORE_FILE); + fprintf (stderr, _("Cannot open the %s file for writing:\n%s\n"), tree_name, + unix_error_string (error)); + g_free (tree_name); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_remove_entry (WTree * tree, const vfs_path_t * name_vpath) +{ + (void) tree; + tree_store_remove_entry (name_vpath); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_destroy (WTree * tree) +{ + tree_store_remove_entry_remove_hook (remove_callback); + save_tree (tree); + + MC_PTR_FREE (tree->tree_shown); + g_string_free (tree->search_buffer, TRUE); + tree->selected_ptr = NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Loads the .mc.tree file */ + +static void +load_tree (WTree * tree) +{ + vfs_path_t *vpath; + + tree_store_load (); + + tree->selected_ptr = tree->store->tree_first; + vpath = vfs_path_from_str (mc_config_get_home_dir ()); + tree_chdir (tree, vpath); + vfs_path_free (vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_show_mini_info (WTree * tree, int tree_lines, int tree_cols) +{ + Widget *w = WIDGET (tree); + int line; + + /* Show mini info */ + if (tree->is_panel) + { + if (!panels_options.show_mini_info) + return; + line = tree_lines + 2; + } + else + line = tree_lines + 1; + + if (tree->searching) + { + /* Show search string */ + tty_setcolor (INPUT_COLOR); + tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols); + widget_gotoyx (w, line, 1); + tty_print_char (PATH_SEP); + tty_print_string (str_fit_to_term (tree->search_buffer->str, tree_cols - 2, J_LEFT_FIT)); + tty_print_char (' '); + } + else + { + /* Show full name of selected directory */ + + const int *colors; + + colors = widget_get_colors (w); + tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]); + tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols); + widget_gotoyx (w, line, 1); + tty_print_string (str_fit_to_term + (vfs_path_as_str (tree->selected_ptr->name), tree_cols, J_LEFT_FIT)); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +show_tree (WTree * tree) +{ + Widget *w = WIDGET (tree); + tree_entry *current; + int i, j; + int topsublevel = 0; + int x = 0, y = 0; + int tree_lines, tree_cols; + + /* Initialize */ + tree_lines = tlines (tree); + tree_cols = w->rect.cols; + + widget_gotoyx (w, y, x); + if (tree->is_panel) + { + tree_cols -= 2; + x = y = 1; + } + + g_free (tree->tree_shown); + tree->tree_shown = g_new0 (tree_entry *, tree_lines); + + if (tree->store->tree_first != NULL) + topsublevel = tree->store->tree_first->sublevel; + + if (tree->selected_ptr == NULL) + { + tree->selected_ptr = tree->store->tree_first; + tree->topdiff = 0; + } + current = tree->selected_ptr; + + /* Calculate the directory which is to be shown on the topmost line */ + if (!tree_navigation_flag) + current = back_ptr (current, &tree->topdiff); + else + { + i = 0; + + while (current->prev != NULL && i < tree->topdiff) + { + current = current->prev; + + if (current->sublevel < tree->selected_ptr->sublevel) + { + if (vfs_path_equal (current->name, tree->selected_ptr->name)) + i++; + } + else if (current->sublevel == tree->selected_ptr->sublevel) + { + const char *cname; + + cname = vfs_path_as_str (current->name); + for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--) + ; + if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j)) + i++; + } + else if (current->sublevel == tree->selected_ptr->sublevel + 1) + { + j = vfs_path_len (tree->selected_ptr->name); + if (j > 1 && vfs_path_equal_len (current->name, tree->selected_ptr->name, j)) + i++; + } + } + tree->topdiff = i; + } + + /* Loop for every line */ + for (i = 0; i < tree_lines; i++) + { + const int *colors; + + colors = widget_get_colors (w); + tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]); + + /* Move to the beginning of the line */ + tty_draw_hline (w->rect.y + y + i, w->rect.x + x, ' ', tree_cols); + + if (current == NULL) + continue; + + if (tree->is_panel) + { + gboolean selected; + + selected = widget_get_state (w, WST_FOCUSED) && current == tree->selected_ptr; + tty_setcolor (selected ? SELECTED_COLOR : NORMAL_COLOR); + } + else + { + int idx = current == tree->selected_ptr ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL; + + tty_setcolor (colors[idx]); + } + + tree->tree_shown[i] = current; + if (current->sublevel == topsublevel) + /* Show full name */ + tty_print_string (str_fit_to_term + (vfs_path_as_str (current->name), + tree_cols + (tree->is_panel ? 0 : 1), J_LEFT_FIT)); + else + { + /* Sub level directory */ + tty_set_alt_charset (TRUE); + + /* Output branch parts */ + for (j = 0; j < current->sublevel - topsublevel - 1; j++) + { + if (tree_cols - 8 - 3 * j < 9) + break; + tty_print_char (' '); + if ((current->submask & (1 << (j + topsublevel + 1))) != 0) + tty_print_char (ACS_VLINE); + else + tty_print_char (' '); + tty_print_char (' '); + } + tty_print_char (' '); + j++; + if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0) + tty_print_char (ACS_LLCORNER); + else + tty_print_char (ACS_LTEE); + tty_print_char (ACS_HLINE); + tty_set_alt_charset (FALSE); + + /* Show sub-name */ + tty_print_char (' '); + tty_print_string (str_fit_to_term + (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT)); + } + + /* Calculate the next value for current */ + current = current->next; + if (tree_navigation_flag) + for (; current != NULL; current = current->next) + { + if (current->sublevel < tree->selected_ptr->sublevel) + { + if (vfs_path_equal_len (current->name, tree->selected_ptr->name, + vfs_path_len (current->name))) + break; + } + else if (current->sublevel == tree->selected_ptr->sublevel) + { + const char *cname; + + cname = vfs_path_as_str (current->name); + for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--) + ; + if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j)) + break; + } + else if (current->sublevel == tree->selected_ptr->sublevel + 1 + && vfs_path_len (tree->selected_ptr->name) > 1) + { + if (vfs_path_equal_len (current->name, tree->selected_ptr->name, + vfs_path_len (tree->selected_ptr->name))) + break; + } + } + } + + tree_show_mini_info (tree, tree_lines, tree_cols); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_check_focus (WTree * tree) +{ + if (tree->topdiff < 3) + tree->topdiff = 3; + else if (tree->topdiff >= tlines (tree) - 3) + tree->topdiff = tlines (tree) - 3 - 1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move_backward (WTree * tree, int i) +{ + if (!tree_navigation_flag) + tree->selected_ptr = back_ptr (tree->selected_ptr, &i); + else + { + tree_entry *current; + int j = 0; + + current = tree->selected_ptr; + while (j < i && current->prev != NULL + && current->prev->sublevel >= tree->selected_ptr->sublevel) + { + current = current->prev; + if (current->sublevel == tree->selected_ptr->sublevel) + { + tree->selected_ptr = current; + j++; + } + } + i = j; + } + + tree->topdiff -= i; + tree_check_focus (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move_forward (WTree * tree, int i) +{ + if (!tree_navigation_flag) + tree->selected_ptr = forw_ptr (tree->selected_ptr, &i); + else + { + tree_entry *current; + int j = 0; + + current = tree->selected_ptr; + while (j < i && current->next != NULL + && current->next->sublevel >= tree->selected_ptr->sublevel) + { + current = current->next; + if (current->sublevel == tree->selected_ptr->sublevel) + { + tree->selected_ptr = current; + j++; + } + } + i = j; + } + + tree->topdiff += i; + tree_check_focus (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move_to_child (WTree * tree) +{ + tree_entry *current; + + /* Do we have a starting point? */ + if (tree->selected_ptr == NULL) + return; + + /* Take the next entry */ + current = tree->selected_ptr->next; + /* Is it the child of the selected entry */ + if (current != NULL && current->sublevel > tree->selected_ptr->sublevel) + { + /* Yes -> select this entry */ + tree->selected_ptr = current; + tree->topdiff++; + tree_check_focus (tree); + } + else + { + /* No -> rescan and try again */ + tree_rescan (tree); + current = tree->selected_ptr->next; + if (current != NULL && current->sublevel > tree->selected_ptr->sublevel) + { + tree->selected_ptr = current; + tree->topdiff++; + tree_check_focus (tree); + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +tree_move_to_parent (WTree * tree) +{ + tree_entry *current; + tree_entry *old; + + if (tree->selected_ptr == NULL) + return FALSE; + + old = tree->selected_ptr; + + for (current = tree->selected_ptr->prev; + current != NULL && current->sublevel >= tree->selected_ptr->sublevel; + current = current->prev) + tree->topdiff--; + + if (current == NULL) + current = tree->store->tree_first; + tree->selected_ptr = current; + tree_check_focus (tree); + return tree->selected_ptr != old; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move_to_top (WTree * tree) +{ + tree->selected_ptr = tree->store->tree_first; + tree->topdiff = 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move_to_bottom (WTree * tree) +{ + tree->selected_ptr = tree->store->tree_last; + tree->topdiff = tlines (tree) - 3 - 1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_chdir_sel (WTree * tree) +{ + if (tree->is_panel) + { + WPanel *p; + + p = change_panel (); + + if (panel_cd (p, tree->selected_ptr->name, cd_exact)) + select_item (p); + else + message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"), + vfs_path_as_str (tree->selected_ptr->name), unix_error_string (errno)); + + widget_draw (WIDGET (p)); + (void) change_panel (); + show_tree (tree); + } + else + { + WDialog *h = DIALOG (WIDGET (tree)->owner); + + h->ret_value = B_ENTER; + dlg_stop (h); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +maybe_chdir (WTree * tree) +{ + if (xtree_mode && tree->is_panel && is_idle ()) + tree_chdir_sel (tree); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Search tree for text */ + +static gboolean +search_tree (WTree * tree, const GString * text) +{ + tree_entry *current = tree->selected_ptr; + gboolean wrapped = FALSE; + gboolean found = FALSE; + + while (!found && (!wrapped || current != tree->selected_ptr)) + if (strncmp (current->subname, text->str, text->len) == 0) + { + tree->selected_ptr = current; + found = TRUE; + } + else + { + current = current->next; + if (current == NULL) + { + current = tree->store->tree_first; + wrapped = TRUE; + } + + tree->topdiff++; + } + + tree_check_focus (tree); + return found; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_do_search (WTree * tree, int key) +{ + /* TODO: support multi-byte characters, see do_search() in panel.c */ + + if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE) + g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1); + else if (key != 0) + g_string_append_c (tree->search_buffer, (gchar) key); + + if (!search_tree (tree, tree->search_buffer)) + g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1); + + show_tree (tree); + maybe_chdir (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_rescan (void *data) +{ + WTree *tree = data; + vfs_path_t *old_vpath; + + old_vpath = vfs_path_clone (vfs_get_raw_current_dir ()); + if (old_vpath == NULL) + return; + + if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0) + { + int ret; + + tree_store_rescan (tree->selected_ptr->name); + ret = mc_chdir (old_vpath); + (void) ret; + } + vfs_path_free (old_vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_forget (void *data) +{ + WTree *tree = data; + + if (tree->selected_ptr != NULL) + tree_remove_entry (tree, tree->selected_ptr->name); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_copy (WTree * tree, const char *default_dest) +{ + char msg[BUF_MEDIUM]; + char *dest; + + if (tree->selected_ptr == NULL) + return; + + g_snprintf (msg, sizeof (msg), _("Copy \"%s\" directory to:"), + str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50)); + dest = input_expand_dialog (Q_ ("DialogTitle|Copy"), + msg, MC_HISTORY_FM_TREE_COPY, default_dest, + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD); + + if (dest != NULL && *dest != '\0') + { + file_op_context_t *ctx; + file_op_total_context_t *tctx; + + ctx = file_op_context_new (OP_COPY); + tctx = file_op_total_context_new (); + file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM); + tctx->ask_overwrite = FALSE; + copy_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE, + FALSE, NULL); + file_op_total_context_destroy (tctx); + file_op_context_destroy (ctx); + } + + g_free (dest); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move (WTree * tree, const char *default_dest) +{ + char msg[BUF_MEDIUM]; + char *dest; + struct stat buf; + file_op_context_t *ctx; + file_op_total_context_t *tctx; + vfs_path_t *dest_vpath = NULL; + + if (tree->selected_ptr == NULL) + return; + + g_snprintf (msg, sizeof (msg), _("Move \"%s\" directory to:"), + str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50)); + dest = + input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest, + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD); + + if (dest == NULL || *dest == '\0') + goto ret; + + dest_vpath = vfs_path_from_str (dest); + + if (mc_stat (dest_vpath, &buf)) + { + message (D_ERROR, MSG_ERROR, _("Cannot stat the destination\n%s"), + unix_error_string (errno)); + goto ret; + } + + if (!S_ISDIR (buf.st_mode)) + { + file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), dest); + goto ret; + } + + ctx = file_op_context_new (OP_MOVE); + tctx = file_op_total_context_new (); + file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM); + move_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest); + file_op_total_context_destroy (tctx); + file_op_context_destroy (ctx); + + ret: + vfs_path_free (dest_vpath, TRUE); + g_free (dest); +} + +/* --------------------------------------------------------------------------------------------- */ + +#if 0 +static void +tree_mkdir (WTree * tree) +{ + char old_dir[MC_MAXPATHLEN]; + + if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0) + return; + /* FIXME + mkdir_cmd (tree); + */ + tree_rescan (tree); + chdir (old_dir); +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_rmdir (void *data) +{ + WTree *tree = data; + file_op_context_t *ctx; + file_op_total_context_t *tctx; + + if (tree->selected_ptr == NULL) + return; + + if (confirm_delete) + { + char *buf; + int result; + + buf = g_strdup_printf (_("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name)); + + result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _("&Yes"), _("&No")); + g_free (buf); + if (result != 0) + return; + } + + ctx = file_op_context_new (OP_DELETE); + tctx = file_op_total_context_new (); + + file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM); + if (erase_dir (tctx, ctx, tree->selected_ptr->name) == FILE_CONT) + tree_forget (tree); + file_op_total_context_destroy (tctx); + file_op_context_destroy (ctx); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +tree_move_up (WTree * tree) +{ + tree_move_backward (tree, 1); + show_tree (tree); + maybe_chdir (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +tree_move_down (WTree * tree) +{ + tree_move_forward (tree, 1); + show_tree (tree); + maybe_chdir (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +tree_move_home (WTree * tree) +{ + tree_move_to_top (tree); + show_tree (tree); + maybe_chdir (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +tree_move_end (WTree * tree) +{ + tree_move_to_bottom (tree); + show_tree (tree); + maybe_chdir (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move_pgup (WTree * tree) +{ + tree_move_backward (tree, tlines (tree) - 1); + show_tree (tree); + maybe_chdir (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_move_pgdn (WTree * tree) +{ + tree_move_forward (tree, tlines (tree) - 1); + show_tree (tree); + maybe_chdir (tree); +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +tree_move_left (WTree * tree) +{ + gboolean v = FALSE; + + if (tree_navigation_flag) + { + v = tree_move_to_parent (tree); + show_tree (tree); + maybe_chdir (tree); + } + + return v; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +tree_move_right (WTree * tree) +{ + gboolean v = FALSE; + + if (tree_navigation_flag) + { + tree_move_to_child (tree); + show_tree (tree); + maybe_chdir (tree); + v = TRUE; + } + + return v; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_start_search (WTree * tree) +{ + if (tree->searching) + { + if (tree->selected_ptr == tree->store->tree_last) + tree_move_to_top (tree); + else + { + gboolean i; + + /* set navigation mode temporarily to 'Static' because in + * dynamic navigation mode tree_move_forward will not move + * to a lower sublevel if necessary (sequent searches must + * start with the directory followed the last found directory) + */ + i = tree_navigation_flag; + tree_navigation_flag = FALSE; + tree_move_forward (tree, 1); + tree_navigation_flag = i; + } + tree_do_search (tree, 0); + } + else + { + tree->searching = TRUE; + g_string_set_size (tree->search_buffer, 0); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_toggle_navig (WTree * tree) +{ + Widget *w = WIDGET (tree); + WButtonBar *b; + + tree_navigation_flag = !tree_navigation_flag; + + b = find_buttonbar (DIALOG (w->owner)); + buttonbar_set_label (b, 4, + tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"), + w->keymap, w); + widget_draw (WIDGET (b)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +tree_execute_cmd (WTree * tree, long command) +{ + cb_ret_t res = MSG_HANDLED; + + if (command != CK_Search) + tree->searching = FALSE; + + switch (command) + { + case CK_Help: + { + ev_help_t event_data = { NULL, "[Directory Tree]" }; + mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data); + } + break; + case CK_Forget: + tree_forget (tree); + break; + case CK_ToggleNavigation: + tree_toggle_navig (tree); + break; + case CK_Copy: + tree_copy (tree, ""); + break; + case CK_Move: + tree_move (tree, ""); + break; + case CK_Up: + tree_move_up (tree); + break; + case CK_Down: + tree_move_down (tree); + break; + case CK_Top: + tree_move_home (tree); + break; + case CK_Bottom: + tree_move_end (tree); + break; + case CK_PageUp: + tree_move_pgup (tree); + break; + case CK_PageDown: + tree_move_pgdn (tree); + break; + case CK_Enter: + tree_chdir_sel (tree); + break; + case CK_Reread: + tree_rescan (tree); + break; + case CK_Search: + tree_start_search (tree); + break; + case CK_Delete: + tree_rmdir (tree); + break; + case CK_Quit: + if (!tree->is_panel) + dlg_stop (DIALOG (WIDGET (tree)->owner)); + return res; + default: + res = MSG_NOT_HANDLED; + } + + show_tree (tree); + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +tree_key (WTree * tree, int key) +{ + long command; + + if (is_abort_char (key)) + { + if (tree->is_panel) + { + tree->searching = FALSE; + show_tree (tree); + return MSG_HANDLED; /* eat abort char */ + } + /* modal tree dialog: let upper layer see the + abort character and close the dialog */ + return MSG_NOT_HANDLED; + } + + if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE)) + { + tree_do_search (tree, key); + show_tree (tree); + return MSG_HANDLED; + } + + command = widget_lookup_key (WIDGET (tree), key); + switch (command) + { + case CK_IgnoreKey: + break; + case CK_Left: + return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED; + case CK_Right: + return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED; + default: + tree_execute_cmd (tree, command); + return MSG_HANDLED; + } + + /* Do not eat characters not meant for the tree below ' ' (e.g. C-l). */ + if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE)) + { + tree_start_search (tree); + tree_do_search (tree, key); + return MSG_HANDLED; + } + + return MSG_NOT_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_frame (WDialog * h, WTree * tree) +{ + Widget *w = WIDGET (tree); + + (void) h; + + tty_setcolor (NORMAL_COLOR); + widget_erase (w); + if (tree->is_panel) + { + const char *title = _("Directory tree"); + const int len = str_term_width1 (title); + + tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE); + + widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2); + tty_printf (" %s ", title); + + if (panels_options.show_mini_info) + { + int y; + + y = w->rect.lines - 3; + widget_gotoyx (w, y, 0); + tty_print_alt_char (ACS_LTEE, FALSE); + widget_gotoyx (w, y, w->rect.cols - 1); + tty_print_alt_char (ACS_RTEE, FALSE); + tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2); + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WTree *tree = (WTree *) w; + WDialog *h = DIALOG (w->owner); + WButtonBar *b; + + switch (msg) + { + case MSG_DRAW: + tree_frame (h, tree); + show_tree (tree); + if (widget_get_state (w, WST_FOCUSED)) + { + b = find_buttonbar (h); + widget_draw (WIDGET (b)); + } + return MSG_HANDLED; + + case MSG_FOCUS: + b = find_buttonbar (h); + buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, w); + buttonbar_set_label (b, 2, Q_ ("ButtonBar|Rescan"), w->keymap, w); + buttonbar_set_label (b, 3, Q_ ("ButtonBar|Forget"), w->keymap, w); + buttonbar_set_label (b, 4, tree_navigation_flag ? Q_ ("ButtonBar|Static") + : Q_ ("ButtonBar|Dynamc"), w->keymap, w); + buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, w); + buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, w); +#if 0 + /* FIXME: mkdir is currently defunct */ + buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, w); +#else + buttonbar_clear_label (b, 7, w); +#endif + buttonbar_set_label (b, 8, Q_ ("ButtonBar|Rmdir"), w->keymap, w); + + return MSG_HANDLED; + + case MSG_UNFOCUS: + tree->searching = FALSE; + return MSG_HANDLED; + + case MSG_KEY: + return tree_key (tree, parm); + + case MSG_ACTION: + /* command from buttonbar */ + return tree_execute_cmd (tree, parm); + + case MSG_DESTROY: + tree_destroy (tree); + return MSG_HANDLED; + + default: + return widget_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Mouse callback + */ +static void +tree_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) +{ + WTree *tree = (WTree *) w; + int y; + + y = event->y; + if (tree->is_panel) + y--; + + switch (msg) + { + case MSG_MOUSE_DOWN: + /* rest of the upper frame - call menu */ + if (tree->is_panel && event->y == WIDGET (w->owner)->rect.y) + { + /* return MOU_UNHANDLED */ + event->result.abort = TRUE; + } + else if (!widget_get_state (w, WST_FOCUSED)) + (void) change_panel (); + break; + + case MSG_MOUSE_CLICK: + { + int lines; + + lines = tlines (tree); + + if (y < 0) + { + tree_move_backward (tree, lines - 1); + show_tree (tree); + } + else if (y >= lines) + { + tree_move_forward (tree, lines - 1); + show_tree (tree); + } + else if ((event->count & GPM_DOUBLE) != 0) + { + if (tree->tree_shown[y] != NULL) + { + tree->selected_ptr = tree->tree_shown[y]; + tree->topdiff = y; + } + + tree_chdir_sel (tree); + } + } + break; + + case MSG_MOUSE_SCROLL_UP: + case MSG_MOUSE_SCROLL_DOWN: + /* TODO: Ticket #2218 */ + break; + + default: + break; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +WTree * +tree_new (int y, int x, int lines, int cols, gboolean is_panel) +{ + WRect r = { y, x, lines, cols }; + WTree *tree; + Widget *w; + + tree = g_new (WTree, 1); + + w = WIDGET (tree); + widget_init (w, &r, tree_callback, tree_mouse_callback); + w->options |= WOP_SELECTABLE | WOP_TOP_SELECT; + w->keymap = tree_map; + + tree->is_panel = is_panel; + tree->selected_ptr = NULL; + + tree->store = tree_store_get (); + tree_store_add_entry_remove_hook (remove_callback, tree); + tree->tree_shown = NULL; + tree->search_buffer = g_string_sized_new (MC_MAXPATHLEN); + tree->topdiff = w->rect.lines / 2; + tree->searching = FALSE; + + load_tree (tree); + return tree; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +tree_chdir (WTree * tree, const vfs_path_t * dir) +{ + tree_entry *current; + + current = tree_store_whereis (dir); + if (current != NULL) + { + tree->selected_ptr = current; + tree_check_focus (tree); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Return name of the currently selected entry */ + +const vfs_path_t * +tree_selected_name (const WTree * tree) +{ + return tree->selected_ptr->name; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +sync_tree (const vfs_path_t * vpath) +{ + tree_chdir (the_tree, vpath); +} + +/* --------------------------------------------------------------------------------------------- */ + +WTree * +find_tree (const WDialog * h) +{ + return (WTree *) widget_find_by_type (CONST_WIDGET (h), tree_callback); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/tree.h b/src/filemanager/tree.h new file mode 100644 index 0000000..f1dbba6 --- /dev/null +++ b/src/filemanager/tree.h @@ -0,0 +1,35 @@ +/** \file tree.h + * \brief Header: directory tree browser + */ + +#ifndef MC__TREE_H +#define MC__TREE_H + +#include "lib/global.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +typedef struct WTree WTree; + +/*** global variables defined in .c file *********************************************************/ + +extern WTree *the_tree; +extern gboolean xtree_mode; + +/*** declarations of public functions ************************************************************/ + +WTree *tree_new (int y, int x, int lines, int cols, gboolean is_panel); + +void tree_chdir (WTree * tree, const vfs_path_t * dir); +const vfs_path_t *tree_selected_name (const WTree * tree); + +void sync_tree (const vfs_path_t * vpath); + +WTree *find_tree (const WDialog * h); + +/*** inline functions ****************************************************************************/ +#endif /* MC__TREE_H */ diff --git a/src/filemanager/treestore.c b/src/filemanager/treestore.c new file mode 100644 index 0000000..722a1e5 --- /dev/null +++ b/src/filemanager/treestore.c @@ -0,0 +1,941 @@ +/* + Tree Store + Contains a storage of the file system tree representation + + This module has been converted to be a widget. + + The program load and saves the tree each time the tree widget is + created and destroyed. This is required for the future vfs layer, + it will be possible to have tree views over virtual file systems. + + Copyright (C) 1999-2022 + Free Software Foundation, Inc. + + Written by: + Janne Kukonlehto, 1994, 1996 + Norbert Warmuth, 1997 + Miguel de Icaza, 1996, 1999 + Slava Zanko <slavazanko@gmail.com>, 2013 + 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/>. + */ + +/** \file treestore.c + * \brief Source: tree store + * + * Contains a storage of the file system tree representation. + */ + +#include <config.h> + +#include <errno.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/vfs/vfs.h" +#include "lib/fileloc.h" +#include "lib/strescape.h" +#include "lib/hook.h" +#include "lib/util.h" + +#include "src/setup.h" /* setup_init() */ + +#include "treestore.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0" + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static struct TreeStore ts; + +static hook_t *remove_entry_hooks; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static tree_entry *tree_store_add_entry (const vfs_path_t * name); + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +tree_store_dirty (gboolean dirty) +{ + ts.dirty = dirty; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * + * @return the number of common bytes in the strings. + */ + +static size_t +str_common (const vfs_path_t * s1_vpath, const vfs_path_t * s2_vpath) +{ + size_t result = 0; + const char *s1, *s2; + + s1 = vfs_path_as_str (s1_vpath); + s2 = vfs_path_as_str (s2_vpath); + + while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++) + result++; + + return result; +} + +/* --------------------------------------------------------------------------------------------- */ +/** The directory names are arranged in a single linked list in the same + * order as they are displayed. When the tree is displayed the expected + * order is like this: + * / + * /bin + * /etc + * /etc/X11 + * /etc/rc.d + * /etc.old/X11 + * /etc.old/rc.d + * /usr + * + * i.e. the required collating sequence when comparing two directory names is + * '\0' < PATH_SEP < all-other-characters-in-encoding-order + * + * Since strcmp doesn't fulfil this requirement we use pathcmp when + * inserting directory names into the list. The meaning of the return value + * of pathcmp and strcmp are the same (an integer less than, equal to, or + * greater than zero if p1 is found to be less than, to match, or be greater + * than p2. + */ + +static int +pathcmp (const vfs_path_t * p1_vpath, const vfs_path_t * p2_vpath) +{ + int ret_val; + const char *p1, *p2; + + p1 = vfs_path_as_str (p1_vpath); + p2 = vfs_path_as_str (p2_vpath); + + for (; *p1 == *p2; p1++, p2++) + if (*p1 == '\0') + return 0; + + if (*p1 == '\0') + ret_val = -1; + else if (*p2 == '\0') + ret_val = 1; + else if (IS_PATH_SEP (*p1)) + ret_val = -1; + else if (IS_PATH_SEP (*p2)) + ret_val = 1; + else + ret_val = (*p1 - *p2); + + return ret_val; +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +decode (char *buffer) +{ + char *res, *p, *q; + + res = g_strdup (buffer); + + for (p = q = res; *p != '\0'; p++, q++) + { + if (*p == '\n') + { + *q = '\0'; + return res; + } + + if (*p != '\\') + { + *q = *p; + continue; + } + + p++; + + switch (*p) + { + case 'n': + *q = '\n'; + break; + case '\\': + *q = '\\'; + break; + default: + break; + } + } + + *q = *p; + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Loads the tree store from the specified filename */ + +static int +tree_store_load_from (const char *name) +{ + FILE *file; + char buffer[MC_MAXPATHLEN + 20]; + + g_return_val_if_fail (name != NULL, 0); + + if (ts.loaded) + return 1; + + file = fopen (name, "r"); + + if (file != NULL + && (fgets (buffer, sizeof (buffer), file) == NULL + || strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0)) + { + fclose (file); + file = NULL; + } + + if (file != NULL) + { + char oldname[MC_MAXPATHLEN] = "\0"; + + ts.loaded = TRUE; + + /* File open -> read contents */ + while (fgets (buffer, MC_MAXPATHLEN, file)) + { + tree_entry *e; + gboolean scanned; + char *lc_name; + + /* Skip invalid records */ + if (buffer[0] != '0' && buffer[0] != '1') + continue; + + if (buffer[1] != ':') + continue; + + scanned = buffer[0] == '1'; + + lc_name = decode (buffer + 2); + if (!IS_PATH_SEP (lc_name[0])) + { + /* Clear-text decompression */ + char *s; + + s = strtok (lc_name, " "); + if (s != NULL) + { + char *different; + int common; + + common = atoi (s); + different = strtok (NULL, ""); + if (different != NULL) + { + vfs_path_t *vpath; + + vpath = vfs_path_from_str (oldname); + g_strlcpy (oldname + common, different, sizeof (oldname) - (size_t) common); + if (vfs_file_is_local (vpath)) + { + vfs_path_t *tmp_vpath; + + tmp_vpath = vfs_path_from_str (oldname); + e = tree_store_add_entry (tmp_vpath); + vfs_path_free (tmp_vpath, TRUE); + e->scanned = scanned; + } + vfs_path_free (vpath, TRUE); + } + } + } + else + { + vfs_path_t *vpath; + + vpath = vfs_path_from_str (lc_name); + if (vfs_file_is_local (vpath)) + { + e = tree_store_add_entry (vpath); + e->scanned = scanned; + } + vfs_path_free (vpath, TRUE); + g_strlcpy (oldname, lc_name, sizeof (oldname)); + } + g_free (lc_name); + } + + fclose (file); + } + + /* Nothing loaded, we add some standard directories */ + if (!ts.tree_first) + { + vfs_path_t *tmp_vpath; + + tmp_vpath = vfs_path_from_str (PATH_SEP_STR); + tree_store_add_entry (tmp_vpath); + tree_store_rescan (tmp_vpath); + vfs_path_free (tmp_vpath, TRUE); + ts.loaded = TRUE; + } + + return 1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +encode (const vfs_path_t * vpath, size_t offset) +{ + return strutils_escape (vfs_path_as_str (vpath) + offset, -1, "\n\\", FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Saves the tree to the specified filename */ + +static int +tree_store_save_to (char *name) +{ + tree_entry *current; + FILE *file; + + file = fopen (name, "w"); + if (file == NULL) + return errno; + + fprintf (file, "%s\n", TREE_SIGNATURE); + + for (current = ts.tree_first; current != NULL; current = current->next) + if (vfs_file_is_local (current->name)) + { + int i, common; + + /* Clear-text compression */ + if (current->prev != NULL + && (common = str_common (current->prev->name, current->name)) > 2) + { + char *encoded; + + encoded = encode (current->name, common); + i = fprintf (file, "%d:%d %s\n", current->scanned ? 1 : 0, common, encoded); + g_free (encoded); + } + else + { + char *encoded; + + encoded = encode (current->name, 0); + i = fprintf (file, "%d:%s\n", current->scanned ? 1 : 0, encoded); + g_free (encoded); + } + + if (i == EOF) + { + fprintf (stderr, _("Cannot write to the %s file:\n%s\n"), + name, unix_error_string (errno)); + break; + } + } + + tree_store_dirty (FALSE); + fclose (file); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static tree_entry * +tree_store_add_entry (const vfs_path_t * name) +{ + int flag = -1; + tree_entry *current; + tree_entry *old = NULL; + tree_entry *new; + int submask = 0; + + if (ts.tree_last != NULL && ts.tree_last->next != NULL) + abort (); + + /* Search for the correct place */ + for (current = ts.tree_first; + current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next) + old = current; + + if (flag == 0) + return current; /* Already in the list */ + + /* Not in the list -> add it */ + new = g_new0 (tree_entry, 1); + if (current == NULL) + { + /* Append to the end of the list */ + if (ts.tree_first == NULL) + { + /* Empty list */ + ts.tree_first = new; + new->prev = NULL; + } + else + { + if (old != NULL) + old->next = new; + new->prev = old; + } + new->next = NULL; + ts.tree_last = new; + } + else + { + /* Insert in to the middle of the list */ + new->prev = old; + if (old != NULL) + { + /* Yes, in the middle */ + new->next = old->next; + old->next = new; + } + else + { + /* Nope, in the beginning of the list */ + new->next = ts.tree_first; + ts.tree_first = new; + } + new->next->prev = new; + } + + /* Calculate attributes */ + new->name = vfs_path_clone (name); + new->sublevel = vfs_path_tokens_count (new->name); + + { + const char *new_name; + + new_name = vfs_path_get_last_path_str (new->name); + new->subname = strrchr (new_name, PATH_SEP); + if (new->subname == NULL) + new->subname = new_name; + else + new->subname++; + } + + if (new->next != NULL) + submask = new->next->submask; + + submask |= 1 << new->sublevel; + submask &= (2 << new->sublevel) - 1; + new->submask = submask; + new->mark = FALSE; + + /* Correct the submasks of the previous entries */ + for (current = new->prev; + current != NULL && current->sublevel > new->sublevel; current = current->prev) + current->submask |= 1 << new->sublevel; + + tree_store_dirty (TRUE); + return new; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +tree_store_notify_remove (tree_entry * entry) +{ + hook_t *p; + + for (p = remove_entry_hooks; p != NULL; p = p->next) + { + tree_store_remove_fn r = (tree_store_remove_fn) p->hook_fn; + + r (entry, p->hook_data); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static tree_entry * +remove_entry (tree_entry * entry) +{ + tree_entry *current = entry->prev; + long submask = 0; + tree_entry *ret = NULL; + + tree_store_notify_remove (entry); + + /* Correct the submasks of the previous entries */ + if (entry->next != NULL) + submask = entry->next->submask; + + for (; current != NULL && current->sublevel > entry->sublevel; current = current->prev) + { + submask |= 1 << current->sublevel; + submask &= (2 << current->sublevel) - 1; + current->submask = submask; + } + + /* Unlink the entry from the list */ + if (entry->prev != NULL) + entry->prev->next = entry->next; + else + ts.tree_first = entry->next; + + if (entry->next != NULL) + entry->next->prev = entry->prev; + else + ts.tree_last = entry->prev; + + /* Free the memory used by the entry */ + vfs_path_free (entry->name, TRUE); + g_free (entry); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +process_special_dirs (GList ** special_dirs, const char *file) +{ + gchar **start_buff; + mc_config_t *cfg; + + cfg = mc_config_init (file, TRUE); + if (cfg == NULL) + return; + + start_buff = mc_config_get_string_list (cfg, "Special dirs", "list", NULL); + if (start_buff != NULL) + { + gchar **buffers; + + for (buffers = start_buff; *buffers != NULL; buffers++) + { + *special_dirs = g_list_prepend (*special_dirs, *buffers); + *buffers = NULL; + } + + g_strfreev (start_buff); + } + mc_config_deinit (cfg); +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +should_skip_directory (const vfs_path_t * vpath) +{ + static GList *special_dirs = NULL; + GList *l; + static gboolean loaded = FALSE; + gboolean ret = FALSE; + + if (!loaded) + { + const char *profile_name; + + profile_name = setup_init (); + process_special_dirs (&special_dirs, profile_name); + process_special_dirs (&special_dirs, global_profile_name); + + loaded = TRUE; + } + + for (l = special_dirs; l != NULL; l = g_list_next (l)) + if (strncmp (vfs_path_as_str (vpath), l->data, strlen (l->data)) == 0) + { + ret = TRUE; + break; + } + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +queue_vpath_free (gpointer data) +{ + vfs_path_free ((vfs_path_t *) data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* Searches for specified directory */ +tree_entry * +tree_store_whereis (const vfs_path_t * name) +{ + tree_entry *current; + int flag = -1; + + for (current = ts.tree_first; + current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next) + ; + + return flag == 0 ? current : NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +struct TreeStore * +tree_store_get (void) +{ + return &ts; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * \fn int tree_store_load(void) + * \brief Loads the tree from the default location + * \return 1 if success (true), 0 otherwise (false) + */ + +int +tree_store_load (void) +{ + char *name; + int retval; + + name = mc_config_get_full_path (MC_TREESTORE_FILE); + retval = tree_store_load_from (name); + g_free (name); + + return retval; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * \fn int tree_store_save(void) + * \brief Saves the tree to the default file in an atomic fashion + * \return 0 if success, errno on error + */ + +int +tree_store_save (void) +{ + char *name; + int retval; + + name = mc_config_get_full_path (MC_TREESTORE_FILE); + mc_util_make_backup_if_possible (name, ".tmp"); + + retval = tree_store_save_to (name); + if (retval != 0) + mc_util_restore_from_backup_if_possible (name, ".tmp"); + else + mc_util_unlink_backup_if_possible (name, ".tmp"); + + g_free (name); + return retval; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data) +{ + add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +tree_store_remove_entry_remove_hook (tree_store_remove_fn callback) +{ + delete_hook (&remove_entry_hooks, (void (*)(void *)) callback); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +tree_store_remove_entry (const vfs_path_t * name_vpath) +{ + tree_entry *current, *base; + size_t len; + + g_return_if_fail (name_vpath != NULL); + + /* Miguel Ugly hack */ + { + gboolean is_root; + const char *name_vpath_str; + + name_vpath_str = vfs_path_as_str (name_vpath); + is_root = (IS_PATH_SEP (name_vpath_str[0]) && name_vpath_str[1] == '\0'); + if (is_root) + return; + } + /* Miguel Ugly hack end */ + + base = tree_store_whereis (name_vpath); + if (base == NULL) + return; /* Doesn't exist */ + + len = vfs_path_len (base->name); + current = base->next; + while (current != NULL && vfs_path_equal_len (current->name, base->name, len)) + { + gboolean ok; + tree_entry *old; + const char *cname; + + cname = vfs_path_as_str (current->name); + ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len])); + if (!ok) + break; + + old = current; + current = current->next; + remove_entry (old); + } + remove_entry (base); + tree_store_dirty (TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** This subdirectory exists -> clear deletion mark */ + +void +tree_store_mark_checked (const char *subname) +{ + vfs_path_t *name; + tree_entry *current, *base; + int flag = 1; + const char *cname; + + if (!ts.loaded) + return; + + if (ts.check_name == NULL) + return; + + /* Calculate the full name of the subdirectory */ + if (DIR_IS_DOT (subname) || DIR_IS_DOTDOT (subname)) + return; + + cname = vfs_path_as_str (ts.check_name); + if (IS_PATH_SEP (cname[0]) && cname[1] == '\0') + name = vfs_path_build_filename (PATH_SEP_STR, subname, (char *) NULL); + else + name = vfs_path_append_new (ts.check_name, subname, (char *) NULL); + + /* Search for the subdirectory */ + for (current = ts.check_start; + current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next) + ; + + if (flag != 0) + { + /* Doesn't exist -> add it */ + current = tree_store_add_entry (name); + ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, name); + } + else + vfs_path_free (name, TRUE); + + /* Clear the deletion mark from the subdirectory and its children */ + base = current; + if (base != NULL) + { + size_t len; + + len = vfs_path_len (base->name); + base->mark = FALSE; + for (current = base->next; + current != NULL && vfs_path_equal_len (current->name, base->name, len); + current = current->next) + { + gboolean ok; + + cname = vfs_path_as_str (current->name); + ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1); + if (!ok) + break; + + current->mark = FALSE; + } + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Mark the subdirectories of the current directory for delete */ + +tree_entry * +tree_store_start_check (const vfs_path_t * vpath) +{ + tree_entry *current, *retval; + size_t len; + + if (!ts.loaded) + return NULL; + + g_return_val_if_fail (ts.check_name == NULL, NULL); + ts.check_start = NULL; + + /* Search for the start of subdirectories */ + current = tree_store_whereis (vpath); + if (current == NULL) + { + struct stat s; + + if (mc_stat (vpath, &s) == -1 || !S_ISDIR (s.st_mode)) + return NULL; + + current = tree_store_add_entry (vpath); + ts.check_name = vfs_path_clone (vpath); + + return current; + } + + ts.check_name = vfs_path_clone (vpath); + + retval = current; + + /* Mark old subdirectories for delete */ + ts.check_start = current->next; + len = vfs_path_len (ts.check_name); + + for (current = ts.check_start; + current != NULL && vfs_path_equal_len (current->name, ts.check_name, len); + current = current->next) + { + gboolean ok; + const char *cname; + + cname = vfs_path_as_str (current->name); + ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1); + if (!ok) + break; + + current->mark = TRUE; + } + + return retval; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Delete subdirectories which still have the deletion mark */ + +void +tree_store_end_check (void) +{ + tree_entry *current; + size_t len; + GList *the_queue; + + if (!ts.loaded) + return; + + g_return_if_fail (ts.check_name != NULL); + + /* Check delete marks and delete if found */ + len = vfs_path_len (ts.check_name); + + current = ts.check_start; + while (current != NULL && vfs_path_equal_len (current->name, ts.check_name, len)) + { + gboolean ok; + tree_entry *old; + const char *cname; + + cname = vfs_path_as_str (current->name); + ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1); + if (!ok) + break; + + old = current; + current = current->next; + if (old->mark) + remove_entry (old); + } + + /* get the stuff in the scan order */ + the_queue = g_list_reverse (ts.add_queue_vpath); + ts.add_queue_vpath = NULL; + vfs_path_free (ts.check_name, TRUE); + ts.check_name = NULL; + + g_list_free_full (the_queue, queue_vpath_free); +} + +/* --------------------------------------------------------------------------------------------- */ + +tree_entry * +tree_store_rescan (const vfs_path_t * vpath) +{ + DIR *dirp; + struct stat buf; + tree_entry *entry; + + if (should_skip_directory (vpath)) + { + entry = tree_store_add_entry (vpath); + entry->scanned = TRUE; + return entry; + } + + entry = tree_store_start_check (vpath); + if (entry == NULL) + return NULL; + + dirp = mc_opendir (vpath); + if (dirp != NULL) + { + struct vfs_dirent *dp; + + for (dp = mc_readdir (dirp); dp != NULL; dp = mc_readdir (dirp)) + if (!DIR_IS_DOT (dp->d_name) && !DIR_IS_DOTDOT (dp->d_name)) + { + vfs_path_t *tmp_vpath; + + tmp_vpath = vfs_path_append_new (vpath, dp->d_name, (char *) NULL); + if (mc_lstat (tmp_vpath, &buf) != -1 && S_ISDIR (buf.st_mode)) + tree_store_mark_checked (dp->d_name); + vfs_path_free (tmp_vpath, TRUE); + } + + mc_closedir (dirp); + } + tree_store_end_check (); + entry->scanned = TRUE; + + return entry; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/filemanager/treestore.h b/src/filemanager/treestore.h new file mode 100644 index 0000000..34e15a9 --- /dev/null +++ b/src/filemanager/treestore.h @@ -0,0 +1,63 @@ +/** \file treestore.h + * \brief Header: tree store + * + * Contains a storage of the file system tree representation. + */ + +#ifndef MC__TREE_STORE_H +#define MC__TREE_STORE_H + +/*** typedefs(not structures) and defined constants **********************************************/ + +/* + * Register/unregister notification functions for "entry_remove" + */ +struct tree_entry; +typedef void (*tree_store_remove_fn) (struct tree_entry * tree, void *data); + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +typedef struct tree_entry +{ + vfs_path_t *name; /* The full path of directory */ + int sublevel; /* Number of parent directories (slashes) */ + long submask; /* Bitmask of existing sublevels after this entry */ + const char *subname; /* The last part of name (the actual name) */ + gboolean mark; /* Flag: Is this entry marked (e. g. for delete)? */ + gboolean scanned; /* Flag: childs scanned or not */ + struct tree_entry *next; /* Next item in the list */ + struct tree_entry *prev; /* Previous item in the list */ +} tree_entry; + +struct TreeStore +{ + tree_entry *tree_first; /* First entry in the list */ + tree_entry *tree_last; /* Last entry in the list */ + tree_entry *check_start; /* Start of checked subdirectories */ + vfs_path_t *check_name; + GList *add_queue_vpath; /* List of vfs_path_t objects of added directories */ + gboolean loaded; + gboolean dirty; +}; + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +struct TreeStore *tree_store_get (void); +int tree_store_load (void); +int tree_store_save (void); +void tree_store_remove_entry (const vfs_path_t * name_vpath); +tree_entry *tree_store_start_check (const vfs_path_t * vpath); +void tree_store_mark_checked (const char *subname); +void tree_store_end_check (void); +tree_entry *tree_store_whereis (const vfs_path_t * name); +tree_entry *tree_store_rescan (const vfs_path_t * vpath); + +void tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data); +void tree_store_remove_entry_remove_hook (tree_store_remove_fn callback); + +/*** inline functions ****************************************************************************/ +#endif /* MC__TREE_STORE_H */ |