diff --git a/src/app.rs b/src/app.rs index bba161f..65b640a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1543,7 +1543,11 @@ impl App { b = b.text(item.name()).data(MounterData(key, item.clone())); let uri = item.uri(); if let Some(path) = item.path() { - b = b.data(Location::Network(uri, item.name(), Some(path))); + if item.is_remote() { + b = b.data(Location::Network(uri, item.name(), Some(path))); + } else { + b = b.data(Location::Path(path)); + } } else if !uri.is_empty() { b = b.data(Location::Network(uri, item.name(), None)); } @@ -3088,6 +3092,16 @@ impl Application for App { Message::MountResult(mounter_key, item, res) => match res { Ok(true) => { log::info!("connected to {item:?}"); + // Automatically navigate to the mounted location + if let Some(path) = item.path() { + let location = if item.is_remote() { + Location::Network(item.uri(), item.name(), Some(path)) + } else { + Location::Path(path) + }; + let message = Message::TabMessage(None, tab::Message::Location(location)); + return self.update(message); + } } Ok(false) => { log::info!("cancelled connection to {item:?}"); diff --git a/src/mounter/gvfs.rs b/src/mounter/gvfs.rs index dcb7327..1fe8257 100644 --- a/src/mounter/gvfs.rs +++ b/src/mounter/gvfs.rs @@ -33,15 +33,26 @@ fn items(monitor: &gio::VolumeMonitor, sizes: IconSizes) -> MounterItems { let mut items: MounterItems = (monitor.mounts().into_iter()) .enumerate() .map(|(i, mount)| { + let root = MountExt::root(&mount); + let is_remote = root + .query_filesystem_info( + gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE, + gio::Cancellable::NONE, + ) + .ok() + .and_then(|info| Some(info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))) + .unwrap_or(true); // Default to remote if query fails + MounterItem::Gvfs(Item { uri: mount.root().uri().into(), kind: ItemKind::Mount, index: i, name: mount.name().into(), is_mounted: true, + is_remote, icon_opt: gio_icon_to_path(&MountExt::icon(&mount), sizes.grid()), icon_symbolic_opt: gio_icon_to_path(&MountExt::symbolic_icon(&mount), 16), - path_opt: MountExt::root(&mount).path(), + path_opt: root.path(), }) }) .collect(); @@ -61,6 +72,7 @@ fn items(monitor: &gio::VolumeMonitor, sizes: IconSizes) -> MounterItems { index: i, name: volume.name().into(), is_mounted: false, + is_remote: false, icon_opt: gio_icon_to_path(&VolumeExt::icon(&volume), sizes.grid()), icon_symbolic_opt: gio_icon_to_path(&VolumeExt::symbolic_icon(&volume), 16), path_opt: None, @@ -283,6 +295,7 @@ pub struct Item { index: usize, name: String, is_mounted: bool, + is_remote: bool, icon_opt: Option, icon_symbolic_opt: Option, path_opt: Option, @@ -297,6 +310,10 @@ impl Item { self.is_mounted } + pub const fn is_remote(&self) -> bool { + self.is_remote + } + pub fn uri(&self) -> String { self.uri.clone() } @@ -406,6 +423,7 @@ impl Gvfs { let mount_op = mount_op(name.to_string(), event_tx.clone()); let event_tx = event_tx.clone(); let mounter_item = mounter_item.clone(); + let volume_for_callback = volume.clone(); VolumeExt::mount( &volume, gio::MountMountFlags::NONE, @@ -413,7 +431,29 @@ impl Gvfs { gio::Cancellable::NONE, move |res| { log::info!("mount {name}: result {res:?}"); - event_tx.send(Event::MountResult(mounter_item, match res { + // Update the mounter_item with mount information after successful mount + let mut updated_item = mounter_item.clone(); + if res.is_ok() { + if let MounterItem::Gvfs(ref mut item) = updated_item { + if let Some(mount) = volume_for_callback.get_mount() { + let root = MountExt::root(&mount); + item.path_opt = root.path(); + item.is_mounted = true; + // Query if remote + item.is_remote = root + .query_filesystem_info( + gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE, + gio::Cancellable::NONE, + ) + .ok() + .and_then(|info| { + Some(info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE)) + }) + .unwrap_or(true); + } + } + } + event_tx.send(Event::MountResult(updated_item, match res { Ok(()) => { _ = complete_tx.send(Ok(())); Ok(true) diff --git a/src/mounter/mod.rs b/src/mounter/mod.rs index caaba72..098b2b3 100644 --- a/src/mounter/mod.rs +++ b/src/mounter/mod.rs @@ -90,6 +90,14 @@ impl MounterItem { Self::None => unreachable!(), } } + + pub fn is_remote(&self) -> bool { + match self { + #[cfg(feature = "gvfs")] + Self::Gvfs(item) => item.is_remote(), + Self::None => unreachable!(), + } + } } pub type MounterItems = Vec; diff --git a/src/tab.rs b/src/tab.rs index f3d97d3..dfb94df 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -540,11 +540,34 @@ pub fn fs_kind(metadata: &Metadata) -> FsKind { let major = major_str.parse::().ok()?; let minor = minor_str.parse::().ok()?; let dev = libc::makedev(major, minor); - //TODO: make sure this list is exhaustive + // Network and distributed filesystem types + // Based on common remote filesystem types found in /proc/mounts let kind = match mount_info.fs_type.as_str() { - "cifs" | "fuse.rclone" | "fuse.sshfs" | "nfs" | "nfs4" | "smb" - | "smb2" => FsKind::Remote, + // SMB/CIFS variants + "cifs" | "smb" | "smb2" | "smbfs" => FsKind::Remote, + + // NFS variants + "nfs" | "nfs4" => FsKind::Remote, + + // FUSE-based remote filesystems + "fuse.rclone" | "fuse.sshfs" | "fuse.davfs2" | "fuse.ceph" + | "fuse.glusterfs" | "fuse.s3fs" | "fuse.goofys" | "fuse.gcsfuse" + | "fuse.afp" | "fuse.afpfs" => FsKind::Remote, + + // Other network protocols + "afs" | "coda" | "ncpfs" | "davfs" | "davfs2" | "shfs" => { + FsKind::Remote + } + + // Cluster/distributed filesystems + "ceph" | "glusterfs" | "lustre" | "gfs" | "gfs2" | "ocfs2" => { + FsKind::Remote + } + + // GVFS (GNOME Virtual File System) "fuse.gvfsd-fuse" => FsKind::Gvfs, + + // Everything else is local _ => FsKind::Local, }; Some((dev, kind)) @@ -622,7 +645,7 @@ fn display_name_for_file(path: &Path, name: &str, get_from_gvfs: bool, is_deskto ); } else if get_from_gvfs { #[cfg(feature = "gvfs")] - return Item::display_name(glib::filename_display_name(path).as_str()) + return Item::display_name(glib::filename_display_name(path).as_str()); } Item::display_name(name) }