summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrent McPheron <twilightinzero@gmail.com>2022-04-03 20:04:00 -0400
committerManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com>2022-12-16 16:21:23 +0100
commit576271fb04e1c2f9ba1f95c0399fef2f73af3b06 (patch)
tree3740f4878b837a1254cd3f7becc97321c1d0f31c
parent2ddb27f0f293eb0e57e32194e4f48c72614500ab (diff)
Add Zenity-based file chooser based on the KDialog one (HugLifeTiZ)
If available, it is used on Linux regardless of the current desktop because it offers free XDG portal integration, which means it picks the correct file chooser on all desktops, and allows for meaningful file selection in sandbox environments like Flatpak.
-rw-r--r--CMake/options.cmake9
-rw-r--r--configh.cmake.in9
-rw-r--r--configh.in9
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/Fl_Native_File_Chooser_GTK.cxx28
-rw-r--r--src/Fl_Native_File_Chooser_Zenity.H54
-rw-r--r--src/Fl_Native_File_Chooser_Zenity.cxx285
7 files changed, 393 insertions, 5 deletions
diff --git a/CMake/options.cmake b/CMake/options.cmake
index 25c5a181c..c5c83ae2d 100644
--- a/CMake/options.cmake
+++ b/CMake/options.cmake
@@ -830,6 +830,15 @@ endif (OPTION_FILESYSTEM_SUPPORT)
#######################################################################
#######################################################################
+option (OPTION_USE_ZENITY "Fl_Native_File_Chooser may run zenity" ON)
+if (OPTION_USE_ZENITY)
+ set (USE_ZENITY 1)
+else ()
+ set (USE_ZENITY 0)
+endif (OPTION_USE_ZENITY)
+#######################################################################
+
+#######################################################################
option (OPTION_USE_KDIALOG "Fl_Native_File_Chooser may run kdialog" ON)
if (OPTION_USE_KDIALOG)
set (USE_KDIALOG 1)
diff --git a/configh.cmake.in b/configh.cmake.in
index dde9ee40a..df211630e 100644
--- a/configh.cmake.in
+++ b/configh.cmake.in
@@ -343,7 +343,14 @@
#cmakedefine FL_CFG_NO_FILESYSTEM_SUPPORT 1
/*
- * Do we want class Fl_Native_File_Chooser to run kdialog when desktop is KDE?
+ * Do we want class Fl_Native_File_Chooser to run zenity when available?
+ */
+
+#cmakedefine01 USE_ZENITY
+
+/*
+ * Do we want class Fl_Native_File_Chooser to run kdialog when zenity is
+ * unavailable and desktop is KDE?
*/
#cmakedefine01 USE_KDIALOG
diff --git a/configh.in b/configh.in
index e45061b31..b9000f567 100644
--- a/configh.in
+++ b/configh.in
@@ -343,7 +343,14 @@
#undef FL_CFG_NO_FILESYSTEM_SUPPORT
/*
- * Do we want class Fl_Native_File_Chooser to run kdialog when desktop is KDE?
+ * Do we want class Fl_Native_File_Chooser to run zenity if available?
+ */
+
+#define USE_ZENITY 1
+
+/*
+ * Do we want class Fl_Native_File_Chooser to run kdialog when zenity is
+ * unavailable and desktop is KDE?
*/
#define USE_KDIALOG 1
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 49825d6be..f316d11de 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -215,6 +215,10 @@ if (FLTK_USE_X11 AND NOT OPTION_USE_WAYLAND)
Fl_get_key.cxx
)
+ if (OPTION_USE_ZENITY)
+ set (DRIVER_FILES ${DRIVER_FILES} Fl_Native_File_Chooser_Zenity.cxx)
+ endif (OPTION_USE_ZENITY)
+
if (OPTION_USE_KDIALOG)
set (DRIVER_FILES ${DRIVER_FILES} Fl_Native_File_Chooser_Kdialog.cxx)
endif (OPTION_USE_KDIALOG)
diff --git a/src/Fl_Native_File_Chooser_GTK.cxx b/src/Fl_Native_File_Chooser_GTK.cxx
index ff5df7293..e41c4230f 100644
--- a/src/Fl_Native_File_Chooser_GTK.cxx
+++ b/src/Fl_Native_File_Chooser_GTK.cxx
@@ -17,6 +17,9 @@
#include <config.h>
#include <FL/Fl_Native_File_Chooser.H>
+#if USE_ZENITY
+# include "Fl_Native_File_Chooser_Zenity.H"
+#endif
#if USE_KDIALOG
# include "Fl_Native_File_Chooser_Kdialog.H"
#endif
@@ -923,15 +926,34 @@ void Fl_GTK_Native_File_Chooser_Driver::probe_for_GTK_libs(void) {
#endif // HAVE_DLSYM && HAVE_DLFCN_H
Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) {
- // Use kdialog if available at run-time and if using the KDE desktop,
- // else, use GTK dialog if available at run-time
+ // Use zenity if available at run-time even if using the KDE desktop,
+ // because its portal integration means the KDE chooser will be used.
+ // Else use kdialog if available at run-time and if using the KDE
+ // desktop, else, use GTK dialog if available at run-time
// otherwise, use FLTK file chooser.
platform_fnfc = NULL;
fl_open_display();
if (Fl::option(Fl::OPTION_FNFC_USES_GTK)) {
+#if USE_ZENITY
+ if (val != BROWSE_MULTI_DIRECTORY) {
+ if (!Fl_Zenity_Native_File_Chooser_Driver::have_looked_for_zenity) {
+ // First Time here, try to find zenity
+ FILE *pipe = popen("zenity --version 2> /dev/null", "r");
+ if (pipe) {
+ char *p, line[100] = "";
+ p = fgets(line, sizeof(line), pipe);
+ if (p && strlen(line) > 0) Fl_Zenity_Native_File_Chooser_Driver::did_find_zenity = true;
+ pclose(pipe);
+ }
+ Fl_Zenity_Native_File_Chooser_Driver::have_looked_for_zenity = true;
+ }
+ // if we found zenity, we will use the Fl_Zenity_Native_File_Chooser_Driver
+ if (Fl_Zenity_Native_File_Chooser_Driver::did_find_zenity) platform_fnfc = new Fl_Zenity_Native_File_Chooser_Driver(val);
+ }
+#endif // USE_ZENITY
#if USE_KDIALOG
const char *desktop = getenv("XDG_CURRENT_DESKTOP");
- if (desktop && strcmp(desktop, "KDE") == 0 && val != BROWSE_MULTI_DIRECTORY) {
+ if (!platform_fnfc && desktop && strcmp(desktop, "KDE") == 0 && val != BROWSE_MULTI_DIRECTORY) {
if (!Fl_Kdialog_Native_File_Chooser_Driver::have_looked_for_kdialog) {
// First Time here, try to find kdialog
FILE *pipe = popen("kdialog -v 2> /dev/null", "r");
diff --git a/src/Fl_Native_File_Chooser_Zenity.H b/src/Fl_Native_File_Chooser_Zenity.H
new file mode 100644
index 000000000..90614bb4f
--- /dev/null
+++ b/src/Fl_Native_File_Chooser_Zenity.H
@@ -0,0 +1,54 @@
+//
+// FLTK native file chooser widget : Zenity version
+//
+// Copyright 2021-2022 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file. If this
+// file is missing or damaged, see the license at:
+//
+// https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+// https://www.fltk.org/bugs.php
+//
+
+#ifndef FL_ZENITY_NATIVE_FILE_CHOOSER_H
+#define FL_ZENITY_NATIVE_FILE_CHOOSER_H 1
+
+#include <FL/Fl_Native_File_Chooser.H>
+
+class FL_EXPORT Fl_Zenity_Native_File_Chooser_Driver : public Fl_Native_File_Chooser_FLTK_Driver {
+ friend class Fl_Native_File_Chooser;
+ struct fnfc_pipe_struct {
+ char *all_files;
+ int fd;
+ };
+ static void fnfc_fd_cb(int fd, fnfc_pipe_struct *data);
+ char **_pathnames;
+ int _tpathnames;
+ char *_directory;
+ char *_preset_file;
+ char *_title;
+ static bool did_find_zenity;
+ static bool have_looked_for_zenity;
+ Fl_Zenity_Native_File_Chooser_Driver(int val);
+ ~Fl_Zenity_Native_File_Chooser_Driver();
+ int count() const;
+ const char *filename() const;
+ const char *filename(int i) const;
+ int show();
+ char *parse_filter(const char *f);
+ const char *filter() const;
+ virtual void filter(const char *f);
+ int filters() const;
+ void preset_file(const char *val);
+ const char *preset_file() const;
+ void directory(const char *val);
+ const char *directory() const;
+ void title(const char *val);
+ const char *title() const;
+};
+
+#endif // FL_ZENITY_NATIVE_FILE_CHOOSER_H
diff --git a/src/Fl_Native_File_Chooser_Zenity.cxx b/src/Fl_Native_File_Chooser_Zenity.cxx
new file mode 100644
index 000000000..2699d2b27
--- /dev/null
+++ b/src/Fl_Native_File_Chooser_Zenity.cxx
@@ -0,0 +1,285 @@
+//
+// FLTK native file chooser widget : Zenity version
+//
+// Copyright 2021-2022 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file. If this
+// file is missing or damaged, see the license at:
+//
+// https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+// https://www.fltk.org/bugs.php
+//
+
+#include <config.h>
+#include <FL/Fl_Native_File_Chooser.H>
+#include "Fl_Native_File_Chooser_Zenity.H"
+#include "Fl_Window_Driver.H"
+#include "drivers/Unix/Fl_Unix_System_Driver.H"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+/* Fl_Zenity_Native_File_Chooser_Driver : file chooser based on the "zenity" command */
+
+bool Fl_Zenity_Native_File_Chooser_Driver::did_find_zenity = false;
+bool Fl_Zenity_Native_File_Chooser_Driver::have_looked_for_zenity = false;
+
+
+Fl_Zenity_Native_File_Chooser_Driver::Fl_Zenity_Native_File_Chooser_Driver(int val) : Fl_Native_File_Chooser_FLTK_Driver(val) {
+ _tpathnames = 0;
+ _pathnames = NULL;
+ _directory = NULL;
+ _preset_file = NULL;
+ _title = NULL;
+}
+
+
+Fl_Zenity_Native_File_Chooser_Driver::~Fl_Zenity_Native_File_Chooser_Driver() {
+ for (int i = 0; i < _tpathnames; i++) delete[] _pathnames[i];
+ delete[] _pathnames;
+ if (_preset_file) free(_preset_file);
+ if (_directory) free(_directory);
+ if (_title) free(_title);
+}
+
+
+void Fl_Zenity_Native_File_Chooser_Driver::fnfc_fd_cb(int fd,
+ Fl_Zenity_Native_File_Chooser_Driver::fnfc_pipe_struct *data) {
+ char tmp[FL_PATH_MAX];
+ int l = read(fd, tmp, sizeof(tmp)-1);
+ if (l > 0) {
+ tmp[l] = 0;
+ data->all_files = Fl_Native_File_Chooser_Driver::strapp(data->all_files, tmp);
+ } else {
+ data->fd = -1;
+ }
+}
+
+
+static int fnfc_dispatch(int /*event*/, Fl_Window* /*win*/) {
+ return 0;
+}
+
+
+int Fl_Zenity_Native_File_Chooser_Driver::show() {
+ const char *option;
+ switch (_btype) {
+ case Fl_Native_File_Chooser::BROWSE_MULTI_DIRECTORY: {
+ // BROWSE_MULTI_DIRECTORY is not supported by zenity, run other chooser instead
+ Fl_Native_File_Chooser fnfc(Fl_Native_File_Chooser::BROWSE_MULTI_DIRECTORY);
+ fnfc.title( title() );
+ fnfc.directory(directory());
+ fnfc.preset_file(preset_file());
+ fnfc.filter(filter());
+ fnfc.options(options());
+ int retval = fnfc.show();
+ for (int i = 0; i < _tpathnames; i++) delete[] _pathnames[i];
+ delete[] _pathnames; _pathnames = NULL;
+ _tpathnames = fnfc.count();
+ if (_tpathnames && retval == 0) {
+ _pathnames = new char*[_tpathnames];
+ for (int i = 0; i < _tpathnames; i++) {
+ _pathnames[i] = new char[strlen(fnfc.filename(i))+1];
+ strcpy(_pathnames[i], fnfc.filename(i));
+ }
+ }
+ return retval;
+ }
+ break;
+ case Fl_Native_File_Chooser::BROWSE_DIRECTORY:
+ case Fl_Native_File_Chooser::BROWSE_SAVE_DIRECTORY:
+ option = "--file-selection --directory";
+ break;
+
+ case Fl_Native_File_Chooser::BROWSE_SAVE_FILE:
+ option = "--file-selection --save";
+ break;
+
+ case Fl_Native_File_Chooser::BROWSE_MULTI_FILE:
+ option = "--file-selection --multiple";
+ break;
+
+ default:
+ option = "--file-selection";
+ }
+ char *preset = NULL;
+ if (_preset_file) {
+ preset = new char[strlen(_preset_file) + 15];
+ sprintf(preset, "--filename '%s'", _preset_file);
+ }
+ else if (_directory) {
+ // This doesn't actually seem to do anything, but supply it anyway.
+ preset = new char[strlen(_directory) + 15];
+ sprintf(preset, "--filename '%s'", _directory);
+ }
+ char *command = new char[strlen(option) + strlen(preset) + (_title?strlen(_title)+11:0) +
+ (_parsedfilt?strlen(_parsedfilt):0) + 50];
+ strcpy(command, "zenity ");
+ if (_title) {
+ sprintf(command+strlen(command), " --title '%s'", _title);
+ }
+ sprintf(command+strlen(command), " %s %s ", option, preset ? preset : "");
+ delete[] preset;
+ if (_parsedfilt) sprintf(command+strlen(command), " \"%s\" ", _parsedfilt);
+ strcat(command, "2> /dev/null"); // get rid of stderr output
+//puts(command);
+ FILE *pipe = popen(command, "r");
+ fnfc_pipe_struct data;
+ data.all_files = NULL;
+ if (pipe) {
+ data.fd = fileno(pipe);
+ Fl::add_fd(data.fd, FL_READ, (Fl_FD_Handler)fnfc_fd_cb, &data);
+ Fl_Event_Dispatch old_dispatch = Fl::event_dispatch();
+ // prevent FLTK from processing any event
+ Fl::event_dispatch(fnfc_dispatch);
+ void *control = ((Fl_Unix_System_Driver*)Fl::system_driver())->control_maximize_button(NULL);
+ // run event loop until pipe finishes
+ while (data.fd >= 0) Fl::wait();
+ Fl::remove_fd(fileno(pipe));
+ pclose(pipe);
+ // return to previous event processing by FLTK
+ Fl::event_dispatch(old_dispatch);
+ if (control) ((Fl_Unix_System_Driver*)Fl::system_driver())->control_maximize_button(control);
+ if (data.all_files) {
+ // process text received from pipe
+ if (data.all_files[strlen(data.all_files)-1] == '\n') data.all_files[strlen(data.all_files)-1] = 0;
+ for (int i = 0; i < _tpathnames; i++) delete[] _pathnames[i];
+ delete[] _pathnames;
+ char *p = data.all_files;
+ int count = 1;
+ while ((p = strchr(p+1, ' '))) count++;
+ _pathnames = new char*[count];
+ _tpathnames = 0;
+ char *q = strtok(data.all_files, " ");
+ while (q) {
+ _pathnames[_tpathnames] = new char[strlen(q)+1];
+ strcpy(_pathnames[_tpathnames], q);
+ _tpathnames++;
+ q = strtok(NULL, " ");
+ }
+ }
+ }
+ delete[] command;
+ if (_title) { free(_title); _title = NULL; }
+ if (!pipe) return -1;
+ return (data.all_files == NULL ? 1 : 0);
+}
+
+
+const char *Fl_Zenity_Native_File_Chooser_Driver::filename() const {
+ return _tpathnames >= 1 ? _pathnames[0] : NULL;
+}
+
+const char *Fl_Zenity_Native_File_Chooser_Driver::filename (int i) const {
+ return _tpathnames > i ? _pathnames[i] : NULL;
+}
+
+const char *Fl_Zenity_Native_File_Chooser_Driver::filter() const {
+ return _filter;
+}
+
+int Fl_Zenity_Native_File_Chooser_Driver::filters() const {
+ return (_nfilters ? _nfilters - 1 : 0);
+}
+
+int Fl_Zenity_Native_File_Chooser_Driver::count() const {
+ return _tpathnames;
+}
+
+char *Fl_Zenity_Native_File_Chooser_Driver::parse_filter(const char *f) {
+ //In: "*.H\n" or "*.H" Out: "(*.H)"
+ //In: "Headers\t*.H\n" Out: "Headers (*.H)"
+ //In: "Headers\t*.{H,h}\n" Out: "Headers (*.H *.h)"
+ const char *p = strchr(f, '\t');
+ if (!p) p = f - 1;
+ const char *q = strchr(f, '\n'); if (!q) q = f + strlen(f);
+ const char *r = strchr(f, '{');
+ char *developed = NULL;
+ if (r) { // with {}
+ char *lead = new char[r-p];
+ memcpy(lead, p+1, (r-p)-1); lead[(r-p)-1] = 0;
+ const char *r2 = strchr(r, '}');
+ char *ends = new char[r2-r];
+ memcpy(ends, r+1, (r2-r)-1); ends[(r2-r)-1] = 0;
+ char *ptr;
+ char *part = strtok_r(ends, ",", &ptr);
+ while (part) {
+ developed = strapp(developed, lead);
+ developed = strapp(developed, part);
+ developed = strapp(developed, " ");
+ part = strtok_r(NULL, ",", &ptr);
+ }
+ if (developed[strlen(developed)-1] == ' ') developed[strlen(developed)-1] = 0;
+ delete[] lead;
+ delete[] ends;
+ }
+ int lout = (p>f?p-f:0) + 2 + (r?strlen(developed):((q-p)-1)) + 2;
+ char *out = new char[lout]; *out = 0;
+ if (p > f) {memcpy(out, f, p-f); out[p-f] = 0; }
+ strcat(out, " (");
+ if (r) {
+ strcpy(out+strlen(out), developed);
+ strfree(developed);
+ }
+ else memcpy(out+strlen(out), p+1, (q-p));
+ strcat(out, ")");
+//puts(out);
+ return out;
+}
+
+
+void Fl_Zenity_Native_File_Chooser_Driver::filter(const char *f) {
+ _parsedfilt = strfree(_parsedfilt); // clear previous parsed filter (if any)
+ _nfilters = 0;
+ if (!f) return;
+ _filter = strdup(f);
+ char *f2 = strdup(f);
+ char *ptr;
+ char *part = strtok_r(f2, "\n", &ptr);
+ while (part) {
+ char *p = parse_filter(part);
+ _parsedfilt = strapp(_parsedfilt, p);
+ _parsedfilt = strapp(_parsedfilt, "\\n");
+ delete[] p;
+ _nfilters++;
+ part = strtok_r(NULL, "\n", &ptr);
+ }
+ free(f2);
+ _parsedfilt = strapp(_parsedfilt, "All files (*)");
+ _nfilters++;
+//puts(_parsedfilt);
+}
+
+void Fl_Zenity_Native_File_Chooser_Driver::preset_file(const char *val) {
+ if (_preset_file) free(_preset_file);
+ _preset_file = strdup(val);
+}
+
+const char *Fl_Zenity_Native_File_Chooser_Driver::preset_file() const {
+ return _preset_file;
+}
+
+void Fl_Zenity_Native_File_Chooser_Driver::directory(const char *val) {
+ if (_directory) free(_directory);
+ _directory = strdup(val);
+}
+
+const char *Fl_Zenity_Native_File_Chooser_Driver::directory() const {
+ return _directory;
+}
+
+void Fl_Zenity_Native_File_Chooser_Driver::title(const char *val)
+{
+ if (_title) free(_title);
+ _title = strdup(val);
+}
+
+const char *Fl_Zenity_Native_File_Chooser_Driver::title() const {
+ return _title;
+}