git: 7863be845544 - main - x11-fm/4pane: new port had been added (+)

From: Alexey Dokuchaev <danfe_at_FreeBSD.org>
Date: Tue, 15 Nov 2022 07:49:48 UTC
The branch main has been updated by danfe:

URL: https://cgit.FreeBSD.org/ports/commit/?id=7863be845544d90c80f6e4d71ef0da9eda30b4dc

commit 7863be845544d90c80f6e4d71ef0da9eda30b4dc
Author:     Alexey Dokuchaev <danfe@FreeBSD.org>
AuthorDate: 2022-11-15 07:47:42 +0000
Commit:     Alexey Dokuchaev <danfe@FreeBSD.org>
CommitDate: 2022-11-15 07:47:42 +0000

    x11-fm/4pane: new port had been added (+)
    
    4Pane is a multi-pane, detailed-list file manager for Unix-like
    systems.  It is designed to be fully-featured without bloat, and
    aims for speed rather than visual effects.
    
    The program relies on some GNU/Linux utilities and API to manage
    disks and mounts.  Non-portable calls to getmntent(3) et al. had
    been replaced with our native getmntinfo(3) ones.  However, full
    storage support is still lacking, owing to limited blkid(8) and
    lsblk(8) functionality and inconsistent naming of different file
    systems between GNU/Linux and FreeBSD.
---
 x11-fm/4pane/Makefile                |  23 +++
 x11-fm/4pane/distinfo                |   3 +
 x11-fm/4pane/files/patch-Devices.cpp | 342 +++++++++++++++++++++++++++++++++++
 x11-fm/4pane/files/patch-Devices.h   |  26 +++
 x11-fm/4pane/files/patch-Misc.cpp    |  33 ++++
 x11-fm/4pane/files/patch-Mounts.cpp  | 102 +++++++++++
 x11-fm/4pane/pkg-descr               |   6 +
 x11-fm/4pane/pkg-plist               | 196 ++++++++++++++++++++
 x11-fm/Makefile                      |   1 +
 9 files changed, 732 insertions(+)

diff --git a/x11-fm/4pane/Makefile b/x11-fm/4pane/Makefile
new file mode 100644
index 000000000000..78a4ef999717
--- /dev/null
+++ b/x11-fm/4pane/Makefile
@@ -0,0 +1,23 @@
+PORTNAME=	4pane
+PORTVERSION=	7.0
+CATEGORIES=	x11-fm
+MASTER_SITES=	SF/fourpane/${PORTVERSION}
+
+MAINTAINER=	danfe@FreeBSD.org
+COMMENT=	Multi-pane, detailed-list graphical file manager
+WWW=		http://www.4pane.co.uk/
+
+LICENSE=	GPLv3
+
+USES=		pkgconfig
+USE_WX=		3.0+
+GNU_CONFIGURE=	yes
+CONFIGURE_ARGS=	--with-wx-config="${WX_CONFIG}"
+
+OPTIONS_DEFINE=	NLS
+OPTIONS_SUB=	yes
+
+NLS_USES=	gettext
+NLS_CONFIGURE_OFF=	--disable-locale
+
+.include <bsd.port.mk>
diff --git a/x11-fm/4pane/distinfo b/x11-fm/4pane/distinfo
new file mode 100644
index 000000000000..9947c9110125
--- /dev/null
+++ b/x11-fm/4pane/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1607257783
+SHA256 (4pane-7.0.tar.gz) = 09716c4000ba193db128d97d04e6bc8c9dfebf11e2755bfc071ce1db339d8b80
+SIZE (4pane-7.0.tar.gz) = 2113199
diff --git a/x11-fm/4pane/files/patch-Devices.cpp b/x11-fm/4pane/files/patch-Devices.cpp
new file mode 100644
index 000000000000..8866393bd213
--- /dev/null
+++ b/x11-fm/4pane/files/patch-Devices.cpp
@@ -0,0 +1,342 @@
+--- Devices.cpp.orig	2020-11-22 11:42:51 UTC
++++ Devices.cpp
+@@ -1357,7 +1357,7 @@ if (!wxFileExists(BLKID))
+     if (wxFileExists(wxT("/usr/bin/blkid"))) BLKID = wxT("/usr/bin/blkid");
+      else
+       { wxArrayString output, errors;long ans;
+-        if (wxGetOsDescription().Contains(wxT("kFreeBSD"))) // The kFreeBSD forkpty hangs
++        if (wxGetOsDescription().Contains(wxT("FreeBSD"))) // FreeBSD's forkpty() hangs
+           ans = wxExecute(wxT("which blkid"), output, errors);
+          else
+           ans = ExecuteInPty(wxT("which blkid"), output, errors);
+@@ -1371,7 +1371,7 @@ if (!wxFileExists(LSBLK))
+     if (wxFileExists(wxT("/sbin/lsblk"))) LSBLK = wxT("/sbin/lsblk");
+      else
+       { wxArrayString output, errors; long ans;
+-        if (wxGetOsDescription().Contains(wxT("kFreeBSD")))
++        if (wxGetOsDescription().Contains(wxT("FreeBSD")))
+           ans = wxExecute(wxT("which lsblk"), output, errors);
+          else
+           ans = ExecuteInPty(wxT("which lsblk"), output, errors);
+@@ -1590,14 +1590,22 @@ bool DeviceAndMountManager::Checkmtab(wxString& mountp
+   return DeviceAndMountManager::GnuCheckmtab(mountpoint, device);
+ #endif
+ 
++#ifdef __linux__
+ struct mntent* mnt = NULL;
+ 
+ FILE* fmp = setmntent (_PATH_MOUNTED, "r");                // Get a file* to (probably) /etc/mtab
+ if (fmp==NULL) return false;
++#else
++struct statfs* fslist;
+ 
++int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++if (numfs < 1) return false;
++#endif
++
+ wxString mountpt(mountpoint); FileData mp(mountpt);
+ if (mp.IsSymlink()) mountpt = mp.GetUltimateDestination(); // Cope with a symlink
+ 
++#ifdef __linux__
+ while (1)                                                  // For every mtab entry
+   { mnt = getmntent(fmp);                                  // Get a struct representing a mounted partition
+     if (mnt == NULL)
+@@ -1605,14 +1613,24 @@ while (1)                                             
+ 
+     wxString mntdir(mnt->mnt_dir, wxConvUTF8);
+     wxString type(mnt->mnt_type, wxConvUTF8); type.MakeLower();
++#else
++for (int i = 0; i < numfs; ++i)
++  { wxString mntdir(fslist[i].f_mntonname, wxConvUTF8);
++    wxString type(fslist[i].f_fstypename, wxConvUTF8); type.MakeLower();
++#endif
+     // Don't try to create a FileData if we're testing a network mount. It's less likely to be symlinked, and e.g. a stale nfs mount hangs lstat!
+     if (!type.StartsWith(wxT("nfs")) && !type.Contains(wxT("sshfs")) && type != wxT("cifs") && type != wxT("smbfs"))
+       { FileData mt(mntdir); if (mt.IsSymlink()) mntdir = mt.GetUltimateDestination(); } // Cope with a symlink
+     if (mntdir == mountpt)                                 // This is the one we're looking for
++#ifdef __linux__
+       { endmntent(fmp);
+         if (device.IsEmpty()) return true;                 // If we don't care which device is mounted there
+ 
+         wxString mntfsname(mnt->mnt_fsname, wxConvUTF8);   // in case we DO care
++#else
++      { if (device.IsEmpty()) return true;
++        wxString mntfsname(fslist[i].f_mntfromname, wxConvUTF8);
++#endif
+         FileData dev(device); FileData mntfs(mntfsname);   // Cope with any symlinks
+         if (dev.IsSymlink()) device = dev.GetUltimateDestination();
+         if (mntfs.IsSymlink()) mntfsname = mntfs.GetUltimateDestination();
+@@ -1649,6 +1667,7 @@ wxString DeviceAndMountManager::WhereIsDeviceMounted(w
+ 
+ wxString DeviceAndMountManager::WhereIsDeviceMounted(wxString device, size_t type)
+ {
++#ifdef __linux__
+ struct mntent* mnt = NULL;
+ 
+ FILE* fmp = setmntent (_PATH_MOUNTED, "r");             // Get a file* to (probably) /etc/mtab
+@@ -1660,6 +1679,17 @@ while (1)                                             
+     wxString mntfsname(mnt->mnt_fsname, wxConvUTF8);
+     if (mntfsname == device)
+       { endmntent(fmp); return wxString(mnt->mnt_dir, wxConvUTF8); }  // If it's the one we're looking for, return the associated mountpt
++#else
++struct statfs* fslist;
++
++int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++if (numfs < 1) return wxEmptyString;
++
++for (int i = 0; i < numfs; ++i)
++  { wxString mntfsname(fslist[i].f_mntfromname, wxConvUTF8);
++    if (mntfsname == device)
++      return wxString(fslist[i].f_mntonname, wxConvUTF8);
++#endif
+   }
+ 
+             // If we're here, the device wasn't found.  See if it's actually a symlink for a different device  eg /dev/dvd -> /dev/hdc.  If so, try that
+@@ -1678,17 +1708,27 @@ return wxEmptyString;                                 
+ }
+ 
+ //static
++#ifdef __linux__
+ struct mntent* DeviceAndMountManager::ReadMtab(const wxString& partition, bool DvdRamFS /*=false*/)    // Is 'partition' currently mounted? Returns struct of data if it is, NULL if it isn't
+ {
+ struct mntent* mnt = NULL;
+ 
+ FILE* fmp = setmntent (_PATH_MOUNTED, "r");           // Get a file* to (probably) /etc/mtab
+ if (fmp==NULL) return mnt;
++#else
++struct statfs* DeviceAndMountManager::ReadMtab(const wxString& partition, bool DvdRamFS /*=false*/)
++{
++struct statfs* fslist;
+ 
++int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++if (numfs < 1) return NULL;
++#endif
++
+ wxString partitionwithsep(partition),  partitionwithout(partition); // Avoid the usual '/' issue
+ if (partition.Right(1) == wxFILE_SEP_PATH) partitionwithout = partition.BeforeLast(wxFILE_SEP_PATH);
+  else partitionwithsep << wxFILE_SEP_PATH;
+ 
++#ifdef __linux__
+ while (1)                                             // For every mtab entry
+   { mnt = getmntent(fmp);                             // Get a struct representing a mounted partition
+     if (mnt == NULL)  break;                          // If it's null, we've run out of candidates.  Return null as flag
+@@ -1706,6 +1746,22 @@ mnt = NULL; return mnt;                               
+ if (stat.IsSymlink())   { wxString target = stat.GetSymlinkDestination(); return ReadMtab(target, DvdRamFS); }
+ 
+ mnt = NULL; return mnt;                               // If we're here, it failed. Return null as flag
++#else
++for (int i = 0; i < numfs; ++i)
++  { wxString mntfsname(fslist[i].f_mntfromname, wxConvUTF8);
++    if (!mntfsname.empty() && ((mntfsname == partitionwithsep) || (mntfsname == partitionwithout)))
++      { if (!DvdRamFS) return &fslist[i];
++        wxString mnttype(fslist[i].f_fstypename, wxConvUTF8);
++        for (size_t n=0; n < RealFilesystemsArray.GetCount(); ++n)
++          if (mnttype.Left(3) == RealFilesystemsArray.Item(n).Left(3)) return &fslist[i];
++      }
++  }
++FileData stat(partition);
++if (stat.IsSymlink())
++  { wxString target = stat.GetSymlinkDestination();
++    return ReadMtab(target, DvdRamFS);
++  } else return NULL;
++#endif
+ }
+ 
+ 
+@@ -1887,14 +1943,25 @@ if (!FillPartitionArrayUsingLsblk(true))              
+           { pstruct->mountpt = wxString(fs->fs_file, wxConvUTF8); // Store the mountpt
+             pstruct->IsMounted = Checkmtab(pstruct->mountpt, pstruct->device);// & whether it's mounted.  Pass the device-name too, so that we only record if the mountpt contains THIS device
+             if (!pstruct->IsMounted)                            // If it isn't mounted where it's supposed to be, look elsewhere in case it was su-mounted in the 'wrong' place
++#ifdef __linux__
+               { struct mntent* mnt = ReadMtab(pstruct->device);
+                 if (mnt != NULL) { pstruct->mountpt = wxString(mnt->mnt_dir, wxConvUTF8); pstruct->IsMounted = true; }
++#else
++              { struct statfs* mnt = ReadMtab(pstruct->device);
++                if (mnt != NULL) { pstruct->mountpt = wxString(mnt->f_mntonname, wxConvUTF8); pstruct->IsMounted = true; }
++#endif
+               }
+           }
+          else                                                   // If we didn't find it in fstab, check mtab anyway, in case someone mounted it the hard way
++#ifdef __linux__
+           { struct mntent* mnt = ReadMtab(pstruct->device);
+             if (mnt != NULL)
+               { pstruct->mountpt = wxString(mnt->mnt_dir, wxConvUTF8);  // Store the mountpt
++#else
++          { struct statfs* mnt = ReadMtab(pstruct->device);
++            if (mnt != NULL)
++              { pstruct->mountpt = wxString(mnt->f_mntonname, wxConvUTF8);
++#endif
+                 pstruct->IsMounted = true;                      // It's mounted by definition:  it's in mtab
+               }
+              else pstruct->IsMounted = false;                   // Otherwise reset this bool, as it's otherwise undefined
+@@ -2027,6 +2094,7 @@ void DeviceAndMountManager::FindMountedImages()  // Ad
+ 
+ void DeviceAndMountManager::FindMountedImages()  // Add mounted iso-images from mtab to array
+ {
++#ifdef __linux__
+ FILE* fmp = setmntent (_PATH_MOUNTED, "r");                 // Get a file* to (probably) /etc/mtab
+ if (fmp==NULL) return;
+ 
+@@ -2036,12 +2104,26 @@ while (1)                                             
+       { endmntent(fmp);  return; }                          // If it's null, we've finished mtab
+     
+     wxString device(mnt->mnt_fsname, wxConvUTF8);           // Make the 'device' into a wxString for convenience
++#else
++struct statfs* fslist;
++
++int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++if (numfs < 1) return;
++
++for (int i = 0; i < numfs; ++i)
++  { wxString device(fslist[i].f_mntfromname, wxConvUTF8);
++#endif
+     // If it starts with a '/dev/loop' or with a '/' but not with /dev/ or //  it's probably an iso-image
+     if ((device.Left(9) == wxT("/dev/loop")) || (device.GetChar(0) == wxT('/') && 
+                             !(device.Left(5) == wxT("/dev/") || device.Left(2) == wxT("//"))))
+       { struct PartitionStruct* newmnt = new struct PartitionStruct;  // store in structarray
++#ifdef __linux__
+         newmnt->device = wxString(mnt->mnt_fsname, wxConvUTF8);
+         newmnt->mountpt = wxString(mnt->mnt_dir, wxConvUTF8);
++#else
++        newmnt->device = wxString(fslist[i].f_mntfromname, wxConvUTF8);
++        newmnt->mountpt = wxString(fslist[i].f_mntonname, wxConvUTF8);
++#endif
+         newmnt->IsMounted = true;                           // By definition
+         PartitionArray->Add(newmnt);                      
+       }
+@@ -2153,6 +2235,7 @@ for (size_t n=0; n < partitions.GetCount(); ++n)
+ for (size_t n=0; n < partitions.GetCount(); ++n)
+   { wxString item = partitions.Item(n);
+     if (item == dev || item == symtarget)
++#ifdef __linux__
+       { struct mntent* mnt = NULL;             // Found an entry.  Look for it in mtab
+         FILE* fmp = setmntent (_PATH_MOUNTED, "r"); if (fmp==NULL) { endfsent(); return answerarray.GetCount() > 0; }
+         while (1)                    
+@@ -2166,6 +2249,16 @@ for (size_t n=0; n < partitions.GetCount(); ++n)
+               if (wxString(mnt->mnt_dir,wxConvUTF8) == mountpts.Item(n))                                          //   but it's already mounted here
+                   { endmntent(fmp);  break; }  // so look in 'partitions' array for another entry
+           }
++#else
++      { struct statfs* fslist;
++        int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++        if (numfs < 1) return answerarray.GetCount() > 0;
++        for (int i = 0; i < numfs; ++i)
++          if (wxString(fslist[i].f_mntfromname, wxConvUTF8) == dev || wxString(fslist[i].f_mntfromname, wxConvUTF8) == symtarget)
++            if (wxString(fslist[i].f_mntonname, wxConvUTF8) == mountpts.Item(n)) goto another;
++        answerarray.Add(mountpts.Item(n));
++another:;
++#endif
+       }
+   }
+ 
+@@ -2921,8 +3014,13 @@ for (size_t n=0; n < allpartitions.GetCount(); ++n)
+   { if (allpartitions[n].Left(len) == dev.Left(len))  // If the 1st 'len' letters of the NAME bit of this partition match dev  ie  /dev/sda1 matches /dev/sda
+       { partitions.Add(allpartitions[n]);             //   store it in the real partition array
+     
++#ifdef __linux__
+         struct mntent* mnt = ReadMtab(allpartitions[n]); // Now see if this partition is currently mounted
+         if (mnt != NULL)  mountpts.Add(wxString(mnt->mnt_dir, wxConvUTF8));  // If so, store the mountpt
++#else
++        struct statfs* mnt = ReadMtab(allpartitions[n]);
++        if (mnt != NULL) mountpts.Add(wxString(mnt->f_mntonname, wxConvUTF8));
++#endif
+           else mountpts.Add(wxEmptyString);           // Otherwise store ""
+       }
+   }
+@@ -2944,7 +3042,11 @@ wxTextFile file;                                    //
+ void DeviceAndMountManager::CheckSupermountTab(wxArrayString& partitions, wxArrayString& mountpts,  wxString dev, bool SearchMtab)    // In Supermount systems, finds data for this device
+ {
+ wxTextFile file;                                    // Create a textfile
++#ifdef __linux__
+ if (SearchMtab)  file.Create(wxT(_PATH_MOUNTED));   //  using mtab
++#else
++if (SearchMtab) return;
++#endif
+  else  file.Create(wxT(_PATH_FSTAB));               //   or fstab
+ if (!file.Exists())    return;
+ file.Open(); if (!file.IsOpened())  return;
+@@ -2971,17 +3073,27 @@ void DeviceAndMountManager::SearchMtab(wxArrayString& 
+ 
+ void DeviceAndMountManager::SearchMtab(wxArrayString& partitions, wxArrayString& mountpts,  wxString dev /*=wxEmptyString*/)  // Loads arrays with data for the named device, or for all devices. NB Only finds mounted partitions
+ {
++#ifdef __linux__
+ struct mntent* mnt = NULL;
++#else
++struct statfs* fslist;
++#endif
+ 
+ partitions.Empty(); mountpts.Empty();               // Empty the arrays of any old data:  we don't want to append!
+ 
++#ifdef __linux__
+ FILE* fmp = setmntent (_PATH_MOUNTED, "r");         // Get a file* to (probably) /etc/mtab
+ if (fmp==NULL) return;
++#else
++int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++if (numfs < 1) return;
++#endif
+ 
+ wxString mask;
+ if (dev.IsEmpty()) mask = AUTOMOUNT_USB_PREFIX;     // We normally search for all AUTOMOUNT_USB_PREFIX devices, probably "/dev/sd"
+   else mask = dev;                                  //   but if dev isn't empty, use this instead
+ 
++#ifdef __linux__
+ while (1)                                           // For every mtab entry
+   { mnt = getmntent(fmp);                           // Get a struct representing a mounted partition
+     if (mnt == NULL) { endmntent(fmp); return; }    // If it's null, we've run out of candidates
+@@ -2990,18 +3102,29 @@ while (1)                                           //
+     if (wxString(mnt->mnt_fsname, wxConvUTF8).Left(mask.Len()) == mask)
+       { partitions.Add(wxString(mnt->mnt_fsname, wxConvUTF8));  // If so, store the devnode and mountpt
+         mountpts.Add(wxString(mnt->mnt_dir, wxConvUTF8));
++#else
++for (int i = 0; i < numfs; ++i)
++  { if (wxString(fslist[i].f_mntfromname, wxConvUTF8).Left(mask.Len()) == mask)
++      { partitions.Add(wxString(fslist[i].f_mntfromname, wxConvUTF8));
++        mountpts.Add(wxString(fslist[i].f_mntonname, wxConvUTF8));
++#endif
+       }
+   }
+ }
+ 
+ void DeviceAndMountManager::SearchMtabForStandardMounts(wxString& device, wxArrayString& mountpts)  // Finds ext2 etc mountpts for the device ie not subfs. Used for DVD-RAM
+ {
++#ifdef __linux__
+ struct mntent* mnt = NULL;
++#else
++struct statfs* fslist;
++#endif
+ mountpts.Empty();                                   // Empty the array of any old data:  we don't want to append!
+ 
+ wxString symtarget; FileData fd(device);            // Beware of the symlink
+ if (fd.IsSymlink()) symtarget = fd.GetUltimateDestination();
+ 
++#ifdef __linux__
+ FILE* fmp = setmntent (_PATH_MOUNTED, "r");         // Get a file* to (probably) /etc/mtab
+ if (fmp==NULL) return;
+ 
+@@ -3012,12 +3135,26 @@ while (1)                                           //
+     bool found = false;
+     if (wxString(mnt->mnt_fsname, wxConvUTF8).Left(device.Len()) == device)  found=true;   // See if this is a mount for the device we're interested in
+      else if (!symtarget.IsEmpty() && wxString(mnt->mnt_fsname, wxConvUTF8).Left(symtarget.Len()) == symtarget)  found=true;   // Try again with any symlink target
++#else
++int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++if (numfs < 1) return;
+ 
++for (int i = 0; i < numfs; ++i)
++  { bool found = false;
++    if (wxString(fslist[i].f_mntfromname, wxConvUTF8).Left(device.Len()) == device) found=true;
++    else if (!symtarget.IsEmpty() && wxString(fslist[i].f_mntfromname, wxConvUTF8).Left(symtarget.Len()) == symtarget) found=true;
++#endif
++
+      if (!found) continue;                          // Wrong device
+      
+     for (size_t n=0; n < RealFilesystemsArray.GetCount(); ++n)  // For DVD-RAM, we're only interested in filesystems like ext2 not eg subfs
++#ifdef __linux__
+       if (wxString(mnt->mnt_type, wxConvUTF8).Left(3) == RealFilesystemsArray.Item(n).Left(3))
+           { mountpts.Add(wxString(mnt->mnt_dir, wxConvUTF8)); break; } // If the type is right,  this mountpt is one we want to store  
++#else
++      if (wxString(fslist[i].f_fstypename, wxConvUTF8).Left(3) == RealFilesystemsArray.Item(n).Left(3))
++          { mountpts.Add(wxString(fslist[i].f_mntonname, wxConvUTF8)); break; }
++#endif
+     }
+ }
+ 
diff --git a/x11-fm/4pane/files/patch-Devices.h b/x11-fm/4pane/files/patch-Devices.h
new file mode 100644
index 000000000000..12ffcf07852a
--- /dev/null
+++ b/x11-fm/4pane/files/patch-Devices.h
@@ -0,0 +1,26 @@
+--- Devices.h.orig	2020-11-22 11:43:42 UTC
++++ Devices.h
+@@ -13,7 +13,11 @@
+ #include "wx/config.h"
+ 
+ #include <fstab.h>
++#ifdef __linux__
+ #include <mntent.h>
++#else
++#include <sys/mount.h>
++#endif
+ 
+ #include "Externs.h"
+ 
+@@ -310,7 +314,11 @@ void OnUnMountNetwork();
+ void OnMountSshfs();
+ void OnMountSamba();
+ void OnUnMountNetwork();
++#ifdef __linux__
+ static struct mntent* ReadMtab(const wxString& partition, bool DvdRamFS=false); // Goes thru mtab, to find if 'partition' currently mounted. If DvdRamFS, ignores eg subfs mounts (used for DVD-RAM)
++#else
++static struct statfs* ReadMtab(const wxString& partition, bool DvdRamFS=false);
++#endif
+ static struct fstab* ReadFstab(const wxString& dev, const wxString& uuid = "", const wxString& label = ""); // Search fstab for a line for this device
+ static struct fstab* ReadFstab(const PartitionStruct* ps) { return ReadFstab(ps->device, ps->uuid, ps->label); }
+ static bool FindUnmountedFstabEntry(wxString& dev, wxArrayString& answerarray); // Ditto but only returning Unmounted entries.  Used for DVD-RAM
diff --git a/x11-fm/4pane/files/patch-Misc.cpp b/x11-fm/4pane/files/patch-Misc.cpp
new file mode 100644
index 000000000000..fc3e96fbcf6f
--- /dev/null
+++ b/x11-fm/4pane/files/patch-Misc.cpp
@@ -0,0 +1,33 @@
+--- Misc.cpp.orig	2020-11-19 18:24:13 UTC
++++ Misc.cpp
+@@ -511,7 +511,7 @@ wxArrayString output, errors;
+ wxCHECK_MSG(!lib.empty(), "", "Empty parameter");
+ 
+ wxArrayString output, errors;
+-long ans = wxExecute("sh -c \"/sbin/ldconfig -p | grep " + lib + '\"', output,errors);
++long ans = wxExecute("sh -c \"/sbin/ldconfig -r | grep " + lib + '\"', output,errors);
+ if (ans != 0 || output.IsEmpty()) return "";
+ 
+ wxString fpath = output.Item(0).AfterLast(' ');
+@@ -666,7 +666,12 @@ if (clientDC) delete clientDC;
+ #endif
+ //-----------------------------------------------------------------------------------------------------------------------
+ 
++#ifdef __linux__
+ #include <pty.h>
++#else
++#include <termios.h>
++#include <libutil.h>
++#endif
+ #include <errno.h>
+ #include <sys/wait.h>
+ 
+@@ -779,7 +784,7 @@ if (cmd.empty()) return ERROR_RETURN_CODE;
+ {
+ if (cmd.empty()) return ERROR_RETURN_CODE;
+ 
+-if (wxGetOsDescription().Contains(wxT("kFreeBSD"))) // The kFreeBSD forkpty hangs
++if (wxGetOsDescription().Contains(wxT("FreeBSD"))) // FreeBSD's forkpty() hangs
+   { if (GetCallerTextCtrl())
+       InformCallerOnTerminate();
+     return ERROR_RETURN_CODE;
diff --git a/x11-fm/4pane/files/patch-Mounts.cpp b/x11-fm/4pane/files/patch-Mounts.cpp
new file mode 100644
index 000000000000..b0f4b6d28554
--- /dev/null
+++ b/x11-fm/4pane/files/patch-Mounts.cpp
@@ -0,0 +1,102 @@
+--- Mounts.cpp.orig	2020-11-22 11:42:50 UTC
++++ Mounts.cpp
+@@ -866,8 +866,13 @@ for (size_t n=0; n < parent->PartitionArray->GetCount(
+   if (parent->PartitionArray->Item(n)->device == dev.BeforeFirst(' ')) // BeforeFirst in case of "/dev/sda1  $UUID/$LABEL"
+     { if (GetDataFromMtab)    // If we're unmounting, we can't rely on the PartitionArray info:  the partition may not have been mounted where fstab intended
+         { FstabMountptTxt->Clear();
++#ifdef __linux__
+           struct mntent* mnt = parent->ReadMtab(dev.BeforeFirst(' ')); // So see where it really is
+           if (mnt != NULL)   FstabMountptTxt->ChangeValue(wxString(mnt->mnt_dir, wxConvUTF8));
++#else
++          struct statfs* mnt = parent->ReadMtab(dev.BeforeFirst(' '));
++          if (mnt != NULL) FstabMountptTxt->ChangeValue(wxString(mnt->f_mntonname, wxConvUTF8));
++#endif
+           return;
+         }
+        else
+@@ -968,10 +973,18 @@ FstabMt = (InFstab ? wxString(fs->fs_file, wxConvUTF8)
+ InFstab = (fs != NULL);                                       // Store or null the data according to the result
+ FstabMt = (InFstab ? wxString(fs->fs_file, wxConvUTF8) : wxT(""));
+ 
++#ifdef __linux__
+ struct mntent* mnt = DeviceAndMountManager::ReadMtab(Image);  // Now read mtab to see if the share's already mounted
++#else
++struct statfs* mnt = DeviceAndMountManager::ReadMtab(Image);
++#endif
+ IsMounted = (mnt != NULL);
+ AlreadyMounted->Show(IsMounted); GetSizer()->Layout();        // If it is mounted, expose the wxStaticTxt that says so (and Layout, else 2.8.0 displays it in top left corner!)
++#ifdef __linux__
+ AtMountPt = (IsMounted ? wxString(mnt->mnt_dir, wxConvUTF8) : wxT(""));  // Store any mountpt, or delete any previous entry
++#else
++AtMountPt = (IsMounted ? wxString(mnt->f_mntonname, wxConvUTF8) : wxT(""));
++#endif
+ if (IsMounted)
+     MountptCombo->SetValue(AtMountPt);                        // Put any mountpt in the combobox
+   else if (InFstab)
+@@ -1209,11 +1222,19 @@ FstabMt = (InFstab ? wxString(fs->fs_file, wxConvUTF8)
+ InFstab = (fs != NULL);                                                   // Store or null the data according to the result
+ FstabMt = (InFstab ? wxString(fs->fs_file, wxConvUTF8) : wxT(""));
+ 
++#ifdef __linux__
+ struct mntent* mnt = DeviceAndMountManager::ReadMtab(device1);            // Now read mtab to see if the share's already mounted
++#else
++struct statfs* mnt = DeviceAndMountManager::ReadMtab(device1);
++#endif
+ if (mnt == NULL)  mnt = DeviceAndMountManager::ReadMtab(device2);         // Null means not found, so try again with the IP version
+ IsMounted = (mnt != NULL);
+ AlreadyMounted->Show(IsMounted); GetSizer()->Layout();                    // If it is mounted, expose the wxStaticTxt that says so (and Layout, else 2.8.0 displays it in top left corner!)
++#ifdef __linux__
+ AtMountPt = (IsMounted ? wxString(mnt->mnt_dir, wxConvUTF8) : wxT(""));   // Store any mountpt, or delete any previous entry
++#else
++AtMountPt = (IsMounted ? wxString(mnt->f_mntonname, wxConvUTF8) : wxT(""));
++#endif
+ if (IsMounted)
+     MountptCombo->SetValue(AtMountPt);
+   else if (InFstab)
+@@ -1503,10 +1524,18 @@ FstabMt = (InFstab ? wxString(fs->fs_file, wxConvUTF8)
+ InFstab = (fs != NULL);                                     // Store or null the data according to the result
+ FstabMt = (InFstab ? wxString(fs->fs_file, wxConvUTF8) : wxT(""));
+ 
++#ifdef __linux__
+ mntent* mnt = DeviceAndMountManager::ReadMtab(device);      // Now read mtab to see if the share's already mounted
++#else
++struct statfs* mnt = DeviceAndMountManager::ReadMtab(device);
++#endif
+ IsMounted = (mnt != NULL);
+ AlreadyMounted->Show(IsMounted); GetSizer()->Layout();      // If it is mounted, expose the wxStaticTxt that says so (and Layout, else 2.8.0 displays it in top left corner!)
++#ifdef __linux__
+ AtMountPt = (IsMounted ? wxString(mnt->mnt_dir, wxConvUTF8) : wxT(""));  // Store any mountpt, or delete any previous entry
++#else
++AtMountPt = (IsMounted ? wxString(mnt->f_mntonname, wxConvUTF8) : wxT(""));
++#endif
+ 
+ if (IsMounted)
+     MountptCombo->SetValue(AtMountPt);
+@@ -1736,6 +1765,7 @@ void UnMountSambaDialog::SearchForNetworkMounts()  // 
+ 
+ void UnMountSambaDialog::SearchForNetworkMounts()  // Scans mtab for established NFS & samba mounts
+ {
++#ifdef __linux__
+ FILE* fmp = setmntent (_PATH_MOUNTED, "r");                   // Get a file* to (probably) /etc/mtab
+ if (fmp==NULL) return;
+ 
+@@ -1749,6 +1779,19 @@ while (1)                                             
+       { struct PartitionStruct* newmnt = new struct PartitionStruct;
+         newmnt->device = wxString(mnt->mnt_fsname, wxConvUTF8);
+         newmnt->mountpt = wxString(mnt->mnt_dir, wxConvUTF8);
++#else
++struct statfs *fslist;
++
++int numfs = getmntinfo(&fslist, MNT_NOWAIT);
++if (numfs < 1) return;
++
++for (int i = 0; i < numfs; ++i)
++  { wxString type(fslist[i].f_fstypename, wxConvUTF8);
++    if (ParseNetworkFstype(type) != MT_invalid)
++      { struct PartitionStruct* newmnt = new struct PartitionStruct;
++        newmnt->device = wxString(fslist[i].f_mntfromname, wxConvUTF8);
++        newmnt->mountpt = wxString(fslist[i].f_mntonname, wxConvUTF8);
++#endif
+         newmnt->type = type;
+         Mntarray.Add(newmnt);                      
+       }
diff --git a/x11-fm/4pane/pkg-descr b/x11-fm/4pane/pkg-descr
new file mode 100644
index 000000000000..efd1c0290336
--- /dev/null
+++ b/x11-fm/4pane/pkg-descr
@@ -0,0 +1,6 @@
+4Pane is a multi-pane, detailed-list file manager for Unix-like systems.
+It is designed to be fully-featured without bloat, and aims for speed
+rather than visual effects.  In addition to standard file manager things,
+it offers multiple undo and redo of most operations (including deletions),
+archive management including "virtual browsing" inside archives, multiple
+renaming/duplication of files, terminal emulator, and user-defined tools.
diff --git a/x11-fm/4pane/pkg-plist b/x11-fm/4pane/pkg-plist
new file mode 100644
index 000000000000..a8a141a5d3ac
--- /dev/null
+++ b/x11-fm/4pane/pkg-plist
@@ -0,0 +1,196 @@
+bin/4Pane
+bin/4pane
+share/4Pane/bitmaps/4Pane.png
+share/4Pane/bitmaps/4PaneIcon16.xpm
+share/4Pane/bitmaps/4PaneIcon32.xpm
+share/4Pane/bitmaps/4PaneIcon40x32.xpm
+share/4Pane/bitmaps/4PaneIcon48.png
+share/4Pane/bitmaps/4PaneIcon48.xpm
+share/4Pane/bitmaps/DelTab.png
+share/4Pane/bitmaps/DnDSelectedCursor.png
+share/4Pane/bitmaps/DnDStdCursor.png
+share/4Pane/bitmaps/MyDocuments.xpm
+share/4Pane/bitmaps/NewTab.png
+share/4Pane/bitmaps/Preview.png
+share/4Pane/bitmaps/UsbMem.xpm
+share/4Pane/bitmaps/UsbMulticard.xpm
+share/4Pane/bitmaps/UsbPen.xpm
+share/4Pane/bitmaps/abiword.png
+share/4Pane/bitmaps/back.xpm
+share/4Pane/bitmaps/bm1_button.xpm
+share/4Pane/bitmaps/bm2_button.xpm
+share/4Pane/bitmaps/bm3_button.xpm
+share/4Pane/bitmaps/cdda.png
+share/4Pane/bitmaps/cdr.xpm
+share/4Pane/bitmaps/cdrom.xpm
+share/4Pane/bitmaps/chrome-chromium.png
+share/4Pane/bitmaps/clear_right.xpm
+share/4Pane/bitmaps/connect_no.xpm
+share/4Pane/bitmaps/dir_up.xpm
+share/4Pane/bitmaps/down.xpm
+share/4Pane/bitmaps/dragicon.png
+share/4Pane/bitmaps/evince.xpm
+share/4Pane/bitmaps/featherpad.png
+share/4Pane/bitmaps/fileopen.xpm
+share/4Pane/bitmaps/firefox.png
+share/4Pane/bitmaps/floppy.xpm
+share/4Pane/bitmaps/forward.xpm
+share/4Pane/bitmaps/gedit.xpm
+share/4Pane/bitmaps/gjots.png
+share/4Pane/bitmaps/gohome.xpm
+share/4Pane/bitmaps/gphoto2.png
+share/4Pane/bitmaps/harddisk-usb.xpm
+share/4Pane/bitmaps/harddisk.xpm
+share/4Pane/bitmaps/hardlink.png
+share/4Pane/bitmaps/help.png
+share/4Pane/bitmaps/iceweasel.png
+share/4Pane/bitmaps/kedit.xpm
+share/4Pane/bitmaps/kwrite.xpm
+share/4Pane/bitmaps/largedropdown.png
+share/4Pane/bitmaps/largedropdown.xpm
+share/4Pane/bitmaps/libreoffice.png
+share/4Pane/bitmaps/mate-text-editor.png
+share/4Pane/bitmaps/mousepad.png
+share/4Pane/bitmaps/mozillacrystal.png
+share/4Pane/bitmaps/mtp.png
+share/4Pane/bitmaps/new_dir.xpm
+share/4Pane/bitmaps/openoffice.png
+share/4Pane/bitmaps/palemoon.png
+share/4Pane/bitmaps/photocopier_0.png
+share/4Pane/bitmaps/photocopier_1.png
+share/4Pane/bitmaps/photocopier_10.png
+share/4Pane/bitmaps/photocopier_11.png
+share/4Pane/bitmaps/photocopier_12.png
+share/4Pane/bitmaps/photocopier_13.png
+share/4Pane/bitmaps/photocopier_14.png
+share/4Pane/bitmaps/photocopier_15.png
+share/4Pane/bitmaps/photocopier_16.png
+share/4Pane/bitmaps/photocopier_17.png
+share/4Pane/bitmaps/photocopier_18.png
+share/4Pane/bitmaps/photocopier_19.png
+share/4Pane/bitmaps/photocopier_2.png
+share/4Pane/bitmaps/photocopier_20.png
+share/4Pane/bitmaps/photocopier_21.png
+share/4Pane/bitmaps/photocopier_22.png
+share/4Pane/bitmaps/photocopier_23.png
+share/4Pane/bitmaps/photocopier_24.png
+share/4Pane/bitmaps/photocopier_25.png
+share/4Pane/bitmaps/photocopier_26.png
+share/4Pane/bitmaps/photocopier_27.png
+share/4Pane/bitmaps/photocopier_28.png
+share/4Pane/bitmaps/photocopier_29.png
+share/4Pane/bitmaps/photocopier_3.png
+share/4Pane/bitmaps/photocopier_30.png
+share/4Pane/bitmaps/photocopier_31.png
+share/4Pane/bitmaps/photocopier_32.png
+share/4Pane/bitmaps/photocopier_33.png
+share/4Pane/bitmaps/photocopier_34.png
+share/4Pane/bitmaps/photocopier_35.png
+share/4Pane/bitmaps/photocopier_36.png
+share/4Pane/bitmaps/photocopier_37.png
+share/4Pane/bitmaps/photocopier_38.png
+share/4Pane/bitmaps/photocopier_39.png
+share/4Pane/bitmaps/photocopier_4.png
+share/4Pane/bitmaps/photocopier_40.png
+share/4Pane/bitmaps/photocopier_41.png
+share/4Pane/bitmaps/photocopier_42.png
+share/4Pane/bitmaps/photocopier_43.png
+share/4Pane/bitmaps/photocopier_5.png
+share/4Pane/bitmaps/photocopier_6.png
+share/4Pane/bitmaps/photocopier_7.png
+share/4Pane/bitmaps/photocopier_8.png
+share/4Pane/bitmaps/photocopier_9.png
+share/4Pane/bitmaps/seamonkey.png
+share/4Pane/bitmaps/smalldropdown.png
+share/4Pane/bitmaps/smalldropdown.xpm
+share/4Pane/bitmaps/softlink.png
+share/4Pane/bitmaps/toparent.xpm
+share/4Pane/bitmaps/unknown.xpm
+share/4Pane/rc/4Pane.desktop
+share/4Pane/rc/configuredialogs.xrc
+share/4Pane/rc/dialogs.xrc
+share/4Pane/rc/moredialogs.xrc
+share/doc/4Pane/About.htm
+share/doc/4Pane/Archive.htm
+share/doc/4Pane/ArchiveBrowse.htm
+share/doc/4Pane/Bookmarks.htm
+share/doc/4Pane/Chapt.con
+share/doc/4Pane/Chapt.hhc
+share/doc/4Pane/Chapt.hhk
+share/doc/4Pane/Chapt.hhp
+share/doc/4Pane/Configure.htm
+share/doc/4Pane/ConfigureUserDefTools.htm
+share/doc/4Pane/ConfiguringDevices.htm
+share/doc/4Pane/ConfiguringDisplay.htm
+share/doc/4Pane/ConfiguringMisc.htm
+share/doc/4Pane/ConfiguringNetworks.htm
+share/doc/4Pane/ConfiguringShortcuts.htm
+share/doc/4Pane/ConfiguringTerminals.htm
+share/doc/4Pane/Contents.htm
+share/doc/4Pane/ContextMenu.htm
+share/doc/4Pane/Copier.png
+share/doc/4Pane/Devices.htm
+share/doc/4Pane/Display.htm
+share/doc/4Pane/DnD.htm
+share/doc/4Pane/DnDSelectedCursor.png
+share/doc/4Pane/DnDStdCursor.png
+share/doc/4Pane/Edit.htm
+share/doc/4Pane/Editors.htm
+share/doc/4Pane/Export.htm
+share/doc/4Pane/FAQ.htm
+share/doc/4Pane/Features.htm
+share/doc/4Pane/FileviewCols.htm
+share/doc/4Pane/Filter.htm
+share/doc/4Pane/Hardlink.png
+share/doc/4Pane/Introduction.htm
+share/doc/4Pane/KeyboardNavigation.htm
+share/doc/4Pane/Licence.htm
+share/doc/4Pane/Menu.htm
+share/doc/4Pane/Mount.htm
+share/doc/4Pane/Move.png
+share/doc/4Pane/MultipleRenDup.htm
+share/doc/4Pane/Open.htm
+share/doc/4Pane/OpenWith.htm
+share/doc/4Pane/Options.htm
+share/doc/4Pane/Previews.htm
+share/doc/4Pane/Properties.htm
+share/doc/4Pane/Quickstart.htm
+share/doc/4Pane/RAQ.htm
+share/doc/4Pane/RegExpHelp.htm
+share/doc/4Pane/Running.htm
+share/doc/4Pane/Softlink.png
+share/doc/4Pane/Statusbar.htm
+share/doc/4Pane/Tabs.htm
+share/doc/4Pane/TerminalEm.htm
+share/doc/4Pane/Toolbar.htm
+share/doc/4Pane/Tools.htm
+share/doc/4Pane/UnRedo.htm
+share/doc/4Pane/Using4Pane.htm
+share/doc/4Pane/View.htm
+share/doc/4Pane/back.gif
+share/doc/4Pane/forward.gif
+share/doc/4Pane/up.gif
+share/icons/hicolor/48x48/apps/4Pane.png
+share/icons/hicolor/scalable/apps/4Pane.svg
+%%NLS%%share/locale/ar/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/ca/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/da/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/de/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/el/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/es/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/et/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/fa/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/fi_FI/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/fr/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/fr_FR/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/it/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/ja/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/nl/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/pl/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/pt_BR/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/ru/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/tr/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/uk_UA/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/vi/LC_MESSAGES/4Pane.mo
+%%NLS%%share/locale/zh_CN/LC_MESSAGES/4Pane.mo
+share/metainfo/4Pane.appdata.xml
diff --git a/x11-fm/Makefile b/x11-fm/Makefile
index d83874fd104e..2520a9d95326 100644
--- a/x11-fm/Makefile
+++ b/x11-fm/Makefile
@@ -1,5 +1,6 @@
     COMMENT = X11 file managers
 
+    SUBDIR += 4pane
     SUBDIR += arqiver
     SUBDIR += caja
     SUBDIR += catseye-fm