From 25156d864f94984e6f8e3b5656e24f422abd92ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 21 Nov 2025 20:25:45 +0100 Subject: [PATCH 1/5] build: Update build options file name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gbp-Dch: Ignore Signed-off-by: Guido Günther Part-of: --- meson.build | 2 +- meson_options.txt => meson.options | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename meson_options.txt => meson.options (100%) diff --git a/meson.build b/meson.build index c94305b..e1f55ce 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'PhoshFileSelector', ['rust', 'c'], version: '0.0.5', - meson_version: '>= 1.0.0', + meson_version: '>= 1.1.0', default_options: ['warning_level=2', 'werror=false'], ) diff --git a/meson_options.txt b/meson.options similarity index 100% rename from meson_options.txt rename to meson.options -- GitLab From 7c14ebb78026ff86a836f58c48935f2795299d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 21 Nov 2025 18:05:13 +0100 Subject: [PATCH 2/5] build: Use datadir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Part-of: --- data/meson.build | 7 ++----- meson.build | 3 +++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/meson.build b/data/meson.build index 2f0db0d..fe13d56 100644 --- a/data/meson.build +++ b/data/meson.build @@ -15,7 +15,7 @@ foreach example : examples output: example[0] + '.desktop', po_dir: '../po', install: get_option('examples'), - install_dir: get_option('datadir') / 'applications', + install_dir: datadir / 'applications', type: 'desktop', ) endforeach @@ -25,10 +25,7 @@ if desktop_utils.found() test('Validate desktop file', desktop_utils, args: merged) endif -install_data( - 'mobi.phosh.FileSelector.gschema.xml', - install_dir: get_option('datadir') / 'glib-2.0' / 'schemas', -) +install_data('mobi.phosh.FileSelector.gschema.xml', install_dir: datadir / 'glib-2.0' / 'schemas') compile_schemas = find_program('glib-compile-schemas', required: false) if compile_schemas.found() diff --git a/meson.build b/meson.build index e1f55ce..50247c0 100644 --- a/meson.build +++ b/meson.build @@ -6,6 +6,9 @@ project( default_options: ['warning_level=2', 'werror=false'], ) +prefix = get_option('prefix') +datadir = prefix / get_option('datadir') + i18n = import('i18n') gnome = import('gnome') pkgconfig = import('pkgconfig') -- GitLab From b8c4d2051e46605bbd538467b3ab66ced85b77d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 21 Nov 2025 15:21:05 +0100 Subject: [PATCH 3/5] file-selector: Allow to preselect items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows us to preselect items. In case of a directory we change into it right away. Signed-off-by: Guido Günther Part-of: --- src/dir_view.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++ src/file_selector.rs | 4 +++ 2 files changed, 71 insertions(+) diff --git a/src/dir_view.rs b/src/dir_view.rs index ec39782..427a131 100644 --- a/src/dir_view.rs +++ b/src/dir_view.rs @@ -127,6 +127,8 @@ mod imp { pub debounce_id: RefCell>, pub no_thumbnails: RefCell>, pub thumbnailer_proxy: RefCell>, + + pub select_item_id: RefCell>, } #[glib::object_subclass] @@ -829,4 +831,69 @@ impl DirView { let change = gtk::SorterChange::Inverted; sorter.emit_by_name::<()>("changed", &[&change]); } + + fn select_item_real(&self, item: &gio::File) { + let imp = self.imp(); + let uri = item.uri(); + glib::g_debug!(LOG_DOMAIN, "Selecting {uri}"); + + if let Some(select_item_id) = imp.select_item_id.replace(None) { + imp.directory_list.disconnect(select_item_id); + } + + if let Some(model) = imp.single_selection.model() { + let n_items = model.n_items(); + + if n_items == 0 { + return; + } + + if let Some(name) = item.basename() { + for n in 0..n_items - 1 { + let s = model.item(n); + + if let Some(info) = s { + if info.downcast_ref::().unwrap().name() == name { + glib::g_debug!(LOG_DOMAIN, "Found {name:?}, selecting"); + imp.grid_view + .scroll_to(n, gtk::ListScrollFlags::SELECT, None); + return; + } + } + } + glib::g_warning!(LOG_DOMAIN, "Couldn't find {name:?} in folder"); + } + } else { + glib::g_warning!(LOG_DOMAIN, "Couldn't get model"); + } + } + + pub fn select_item(&self, item: &gio::File) { + let imp = self.imp(); + + if imp.display_mode.get() == DisplayMode::Loading { + glib::g_debug!(LOG_DOMAIN, "Folder content still Loading"); + + if let Some(select_item_id) = imp.select_item_id.replace(None) { + imp.directory_list.disconnect(select_item_id); + } + + let select_item_id = imp.directory_list.connect_loading_notify(clone!( + #[weak(rename_to = this)] + self, + #[strong(rename_to = toselect)] + item, + move |dirlist| { + if dirlist.is_loading() { + return; + } + glib::g_debug!(LOG_DOMAIN, "Loading finished"); + this.select_item_real(&toselect); + } + )); + imp.select_item_id.replace(Some(select_item_id)); + } else { + self.select_item_real(item); + } + } } diff --git a/src/file_selector.rs b/src/file_selector.rs index 70a5926..aa304ed 100644 --- a/src/file_selector.rs +++ b/src/file_selector.rs @@ -662,6 +662,10 @@ impl FileSelector { self.set_current_folder(file); } + + pub fn select_item(&self, item: &gio::File) { + self.imp().dir_view.select_item(item); + } } // C bindings: -- GitLab From 9db53541c70d2616524a9cacb6964bfe6077fc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Thu, 20 Nov 2025 19:43:41 +0100 Subject: [PATCH 4/5] pfs-open: Add org.freedesktop.FileManager1 interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See examples/gio_dbus_register_object/main.rs in gtk-rs ``` $ busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowFolders ass 2 file:///tmp file:///etc asdf $ busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass 1 file:///tmp asdf ``` We lack the `ShowItemProperties` method but that'll be a completely separate window. Signed-off-by: Guido Günther Part-of: --- src/examples/open/pfs_open_application.rs | 176 +++++++++++++++++++++- 1 file changed, 171 insertions(+), 5 deletions(-) diff --git a/src/examples/open/pfs_open_application.rs b/src/examples/open/pfs_open_application.rs index 8f24573..eaade11 100644 --- a/src/examples/open/pfs_open_application.rs +++ b/src/examples/open/pfs_open_application.rs @@ -8,6 +8,7 @@ use adw::prelude::*; use adw::subclass::prelude::*; +use glib_macros::clone; use gtk::{gio, glib}; use std::cell::{Cell, RefCell}; use std::process::Command; @@ -16,13 +17,76 @@ use pfs::file_selector::{FileSelector, FileSelectorMode}; use crate::config::LOG_DOMAIN; +const FILE_MANAGER1_NAME: &str = "org.freedesktop.FileManager1"; +const FILE_MANAGER1_XML: &str = r#" + + + + + + + + + + + + + +"#; + +#[derive(Debug, glib::Variant)] +struct ShowFolders { + uris: Vec, + _startup_id: String, +} + +#[derive(Debug, glib::Variant)] +struct ShowItems { + uris: Vec, + _startup_id: String, +} + +#[derive(Debug)] +enum FileManager1 { + ShowFolders(ShowFolders), + ShowItems(ShowItems), +} + mod imp { use super::*; + impl DBusMethodCall for FileManager1 { + fn parse_call( + _obj_path: &str, + _interface: Option<&str>, + method: &str, + params: glib::Variant, + ) -> Result { + match method { + "ShowFolders" => Ok(params.get::().map(Self::ShowFolders)), + "ShowItems" => Ok(params.get::().map(Self::ShowItems)), + _ => Err(glib::Error::new( + gio::DBusError::UnknownMethod, + "No such method", + )), + } + .and_then(|p| { + p.ok_or_else(|| glib::Error::new(gio::DBusError::InvalidArgs, "Invalid parameters")) + }) + } + } + #[derive(Debug, Default)] pub struct PfsOpenApplication { pub hold_guard: RefCell>, pub hold_count: Cell, + registration_id: RefCell>, + owner_id: RefCell>, } #[glib::object_subclass] @@ -53,6 +117,56 @@ mod imp { self.obj().open_directory(file); } } + + fn dbus_register( + &self, + connection: &gio::DBusConnection, + object_path: &str, + ) -> Result<(), glib::Error> { + self.parent_dbus_register(connection, object_path)?; + + if let Ok(id) = self.obj().register_object(connection) { + glib::g_debug!(LOG_DOMAIN, "Exported FileManager1 DBus interface"); + self.registration_id.replace(Some(id)); + } else { + glib::g_warning!(LOG_DOMAIN, "Failed to export FileManager1 DBus interface"); + } + + let id = gio::bus_own_name_on_connection( + connection, + FILE_MANAGER1_NAME, + gio::BusNameOwnerFlags::REPLACE | gio::BusNameOwnerFlags::ALLOW_REPLACEMENT, + |_connection, name| { + glib::g_debug!(LOG_DOMAIN, "Owned {name} DBus name"); + }, + clone!( + #[weak(rename_to = this)] + self, + move |_connection, name| { + glib::g_warning!(LOG_DOMAIN, "Lost {name} DBus name"); + this.owner_id.replace(None); + } + ), + ); + + self.owner_id.replace(Some(id)); + Ok(()) + } + + fn dbus_unregister(&self, connection: &gio::DBusConnection, object_path: &str) { + self.parent_dbus_unregister(connection, object_path); + if let Some(id) = self.registration_id.take() { + if connection.unregister_object(id).is_ok() { + glib::g_debug!(LOG_DOMAIN, "Unregistered object"); + } else { + glib::g_warning!(LOG_DOMAIN, "Could not unregister object"); + } + } + + if let Some(owner_id) = self.owner_id.replace(None) { + gio::bus_unown_name(owner_id); + } + } } impl GtkApplicationImpl for PfsOpenApplication {} @@ -73,11 +187,11 @@ impl PfsOpenApplication { .build() } - fn open_directory(&self, file: &gio::File) { + fn open_directory(&self, dir: &gio::File) -> FileSelector { let imp = self.imp(); - let uri = file.uri(); + let uri = dir.uri(); - glib::g_message!(LOG_DOMAIN, "Opening {uri:#?}"); + glib::g_message!(LOG_DOMAIN, "Opening {uri}"); if imp.hold_count.replace(imp.hold_count.get() + 1) == 0 { *self.imp().hold_guard.borrow_mut() = Some(self.hold()); @@ -85,8 +199,8 @@ impl PfsOpenApplication { let file_selector = glib::Object::builder::() .property("accept_label", gettextrs::gettext("Open")) - .property("title", "Select a File") - .property("current-folder", file) + .property("title", gettextrs::gettext("Select a File")) + .property("current-folder", dir) .build(); file_selector.connect_closure( @@ -126,5 +240,57 @@ impl PfsOpenApplication { file_selector.set_mode(FileSelectorMode::OpenFile); file_selector.present(); + + file_selector + } + + fn select_item(&self, file: &gio::File) { + if let Some(parent) = file.parent() { + let file_selector = self.open_directory(&parent); + file_selector.select_item(file); + } + } + + fn register_object( + &self, + connection: &gio::DBusConnection, + ) -> Result { + let file_manager1 = gio::DBusNodeInfo::for_xml(FILE_MANAGER1_XML) + .ok() + .and_then(|e| e.lookup_interface("org.freedesktop.FileManager1")) + .expect("FileManagaer1 interface"); + + connection + .register_object("/org/freedesktop/FileManager1", &file_manager1) + .typed_method_call::() + .invoke_and_return_future_local(glib::clone!( + #[weak_allow_none(rename_to = this)] + self.imp(), + move |_, sender, call| { + glib::g_message!(LOG_DOMAIN, "Method call from {sender:?}"); + let app = this.clone(); + async move { + match call { + FileManager1::ShowFolders(ShowFolders { uris, _startup_id }) => { + if let Some(app) = app { + for uri in &uris { + app.obj().open_directory(&gio::File::for_uri(uri)); + } + } + Ok(None) + } + FileManager1::ShowItems(ShowItems { uris, _startup_id }) => { + if let Some(app) = app { + for uri in &uris { + app.obj().select_item(&gio::File::for_uri(uri)); + } + } + Ok(None) + } + } + } + } + )) + .build() } } -- GitLab From acd15203338cefd84d57d42903670a797342efbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 21 Nov 2025 18:19:05 +0100 Subject: [PATCH 5/5] pfs-open: Add DBus service and systemd unit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows pfs-open to be used as mobi.phosh.FileManager1 implementation Closes: https://gitlab.gnome.org/World/Phosh/pfs/-/issues/16 Signed-off-by: Guido Günther Part-of: --- data/meson.build | 16 ++++++++++++++++ data/mobi.phosh.FileOpen.service.in | 4 ++++ data/systemd/meson.build | 16 ++++++++++++++++ data/systemd/mobi.phosh.FileOpen.service.in | 7 +++++++ debian/control | 4 +++- debian/pfs-utils.install | 4 +++- meson.build | 3 +++ meson.options | 4 ++++ 8 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 data/mobi.phosh.FileOpen.service.in create mode 100644 data/systemd/meson.build create mode 100644 data/systemd/mobi.phosh.FileOpen.service.in diff --git a/data/meson.build b/data/meson.build index fe13d56..63f13e9 100644 --- a/data/meson.build +++ b/data/meson.build @@ -20,6 +20,22 @@ foreach example : examples ) endforeach +filemanager1 = 'org.freedesktop.FileManager1' +app_id = examples[1][0] +serviceconf = configuration_data() +serviceconf.set('app_id', app_id) +serviceconf.set('bindir', bindir) +serviceconf.set('busname', filemanager1) +serviceconf.set('bin', examples[1][1]) +configure_file( + input: f'@app_id@.service.in', + output: f'@app_id@.service', + configuration: serviceconf, + install_dir: servicedir, +) + +subdir('systemd') + desktop_utils = find_program('desktop-file-validate', required: false) if desktop_utils.found() test('Validate desktop file', desktop_utils, args: merged) diff --git a/data/mobi.phosh.FileOpen.service.in b/data/mobi.phosh.FileOpen.service.in new file mode 100644 index 0000000..876c6f0 --- /dev/null +++ b/data/mobi.phosh.FileOpen.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=@busname@ +Exec=@bindir@/@bin@ --gapplication-service +SystemdService=@app_id@.service diff --git a/data/systemd/meson.build b/data/systemd/meson.build new file mode 100644 index 0000000..1ad45db --- /dev/null +++ b/data/systemd/meson.build @@ -0,0 +1,16 @@ +systemd_user_unit_dir = get_option('systemd_user_unit_dir') +systemd_dep = dependency('systemd', required: false) +if systemd_user_unit_dir == '' + if systemd_dep.found() + systemd_user_unit_dir = systemd_dep.get_variable('systemd_user_unit_dir') + else + systemd_user_unit_dir = prefix / libdir / 'systemd' / 'user' + endif +endif + +configure_file( + input: f'@app_id@.service.in', + output: f'@app_id@.service', + configuration: serviceconf, + install_dir: systemd_user_unit_dir, +) diff --git a/data/systemd/mobi.phosh.FileOpen.service.in b/data/systemd/mobi.phosh.FileOpen.service.in new file mode 100644 index 0000000..90a1de9 --- /dev/null +++ b/data/systemd/mobi.phosh.FileOpen.service.in @@ -0,0 +1,7 @@ +[Unit] +Description=A simple File Browser + +[Service] +Type=dbus +BusName=@busname@ +ExecStart=@bindir@/@bin@ --gapplication-service diff --git a/debian/control b/debian/control index 31429aa..d5604ab 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Build-Depends: libglib2.0-dev (>= 2.74), libgtk-4-dev (>= 4.12), meson, + systemd-dev, patchelf, rustc (>= 1.86) , rustup , @@ -25,7 +26,8 @@ Multi-Arch: foreign Description: Phosh File Chooser Utils pfs is a library providing a file choser and related tooling. . - This package contains additional utilities. + This package contains additional utilities like an implementation + of the org.freedesktop.FileManager1 interface. Package: pfs-demo Architecture: linux-any diff --git a/debian/pfs-utils.install b/debian/pfs-utils.install index 3ec9443..7a44547 100644 --- a/debian/pfs-utils.install +++ b/debian/pfs-utils.install @@ -1,3 +1,5 @@ -usr/bin/ +usr/bin/pfs-open +usr/lib/systemd/user/mobi.phosh.FileOpen.service usr/share/applications/mobi.phosh.FileOpen.desktop +usr/share/dbus-1/services/mobi.phosh.FileOpen.service usr/share/icons/hicolor/scalable/apps/mobi.phosh.FileOpen.svg diff --git a/meson.build b/meson.build index 50247c0..1dcec89 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,10 @@ project( ) prefix = get_option('prefix') +bindir = prefix / get_option('bindir') datadir = prefix / get_option('datadir') +libdir = prefix / get_option('libdir') +servicedir = datadir / 'dbus-1' / 'services' i18n = import('i18n') gnome = import('gnome') diff --git a/meson.options b/meson.options index 2d47184..ab914a9 100644 --- a/meson.options +++ b/meson.options @@ -5,3 +5,7 @@ option('examples', option('shared-lib', type: 'boolean', value: false, description: 'Build shared library') + +option('systemd_user_unit_dir', + type: 'string', value: '', + description: 'Directory for systemd user units') \ No newline at end of file -- GitLab