Merge commit '5461f6700dac79e9e71e3966f8a1270706c385ba'
This commit is contained in:
commit
6ddb6b42ed
94
deps/nfde/.github/workflows/cmake.yml
vendored
94
deps/nfde/.github/workflows/cmake.yml
vendored
@ -16,21 +16,43 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Format code
|
- name: Format code
|
||||||
run: find src/ test/ -iname '*.c' -or -iname '*.cpp' -or -iname '*.m' -or -iname '*.mm' -or -iname '*.h' -or -iname '*.hpp' | xargs clang-format -i -style=file
|
# ClangFormat 14 has a bug, which seems to be fixed in ClangFormat 15. Until GitHub-hosted runners support ClangFormat 15, we will stay at ClangFormat 13.
|
||||||
|
run: find src/ test/ -iname '*.c' -or -iname '*.cpp' -or -iname '*.m' -or -iname '*.mm' -or -iname '*.h' -or -iname '*.hpp' | xargs clang-format-13 -i -style=file
|
||||||
- name: Check diff
|
- name: Check diff
|
||||||
run: git diff --exit-code
|
run: git diff --exit-code
|
||||||
|
|
||||||
build-ubuntu:
|
build-ubuntu:
|
||||||
|
|
||||||
name: Ubuntu (${{ matrix.os }}, ${{ matrix.portal.name }}, ${{ matrix.compiler.c }}, C++${{ matrix.cppstd }})
|
name: Ubuntu ${{ matrix.os.name }} - ${{ matrix.compiler.name }}, ${{ matrix.portal.name }}, ${{ matrix.autoappend.name }}, ${{ matrix.shared_lib.name }}, C++${{ matrix.cppstd }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os.label }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, ubuntu-18.04]
|
os: [ {label: ubuntu-latest, name: latest}, {label: ubuntu-20.04, name: 20.04} ]
|
||||||
portal: [ {flag: OFF, dep: libgtk-3-dev, name: GTK}, {flag: ON, dep: libdbus-1-dev, name: Portal} ] # The NFD_PORTAL setting defaults to OFF (i.e. uses GTK)
|
portal: [ {flag: OFF, dep: libgtk-3-dev, name: GTK}, {flag: ON, dep: libdbus-1-dev, name: Portal} ] # The NFD_PORTAL setting defaults to OFF (i.e. uses GTK)
|
||||||
compiler: [ {c: gcc, cpp: g++}, {c: clang, cpp: clang++} ] # The default compiler is gcc/g++
|
autoappend: [ {flag: OFF, name: NoAppendExtn} ] # By default the NFD_PORTAL mode does not append extensions, because it breaks some features of the portal
|
||||||
cppstd: [23, 11]
|
compiler: [ {c: gcc, cpp: g++, name: GCC}, {c: clang, cpp: clang++, name: Clang} ] # The default compiler is gcc/g++
|
||||||
|
cppstd: [20, 11]
|
||||||
|
shared_lib: [ {flag: OFF, name: Static} ]
|
||||||
|
include:
|
||||||
|
- os: {label: ubuntu-latest, name: latest}
|
||||||
|
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
|
||||||
|
autoappend: {flag: ON, name: AutoAppendExtn}
|
||||||
|
compiler: {c: gcc, cpp: g++, name: GCC}
|
||||||
|
cppstd: 11
|
||||||
|
shared_lib: {flag: OFF, name: Static}
|
||||||
|
- os: {label: ubuntu-latest, name: latest}
|
||||||
|
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
|
||||||
|
autoappend: {flag: ON, name: AutoAppendExtn}
|
||||||
|
compiler: {c: clang, cpp: clang++, name: Clang}
|
||||||
|
cppstd: 11
|
||||||
|
shared_lib: {flag: OFF, name: Static}
|
||||||
|
- os: {label: ubuntu-latest, name: latest}
|
||||||
|
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
|
||||||
|
autoappend: {flag: ON, name: NoAppendExtn}
|
||||||
|
compiler: {c: gcc, cpp: g++, name: GCC}
|
||||||
|
cppstd: 11
|
||||||
|
shared_lib: {flag: ON, name: Shared}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -38,60 +60,72 @@ jobs:
|
|||||||
- name: Installing Dependencies
|
- name: Installing Dependencies
|
||||||
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }}
|
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }}
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cpp }} -DCMAKE_CXX_STANDARD=${{ matrix.cppstd }} -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_PORTAL=${{ matrix.portal.flag }} -DNFD_BUILD_TESTS=ON ..
|
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cpp }} -DCMAKE_CXX_STANDARD=${{ matrix.cppstd }} -DCMAKE_C_FLAGS="-Wall -Wextra -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_PORTAL=${{ matrix.portal.flag }} -DNFD_APPEND_EXTENSION=${{ matrix.autoappend.flag }} -DBUILD_SHARED_LIBS=${{ matrix.shared_lib.flag }} -DNFD_BUILD_TESTS=ON ..
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target install
|
run: cmake --build build --target install
|
||||||
- name: Upload test binaries
|
- name: Upload test binaries
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: $${{ matrix.os }} - $${{ matrix.compiler.c }}
|
name: Ubuntu ${{ matrix.os.name }} - ${{ matrix.compiler.name }}, ${{ matrix.portal.name }}, ${{ matrix.autoappend.name }}, ${{ matrix.shared_lib.name }}, C++${{ matrix.cppstd }}
|
||||||
path: |
|
path: |
|
||||||
build/src/libnfd.a
|
build/src/*
|
||||||
build/test/test_*
|
build/test/*
|
||||||
|
|
||||||
build-macos-clang:
|
build-macos-clang:
|
||||||
|
|
||||||
name: MacOS latest - Clang
|
name: MacOS ${{ matrix.os.name }} - Clang, ${{ matrix.shared_lib.name }}
|
||||||
runs-on: macos-latest
|
runs-on: ${{ matrix.os.label }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ {label: macos-latest, name: latest}, {label: macos-11, name: 11} ]
|
||||||
|
shared_lib: [ {flag: OFF, name: Static} ]
|
||||||
|
include:
|
||||||
|
- os: {label: macos-latest, name: latest}
|
||||||
|
shared_lib: {flag: ON, name: Shared}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_BUILD_TESTS=ON ..
|
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Wall -Wextra -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DBUILD_SHARED_LIBS=${{ matrix.shared_lib.flag }} -DNFD_BUILD_TESTS=ON ..
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target install
|
run: cmake --build build --target install
|
||||||
- name: Upload test binaries
|
- name: Upload test binaries
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: MacOS latest - Clang
|
name: MacOS ${{ matrix.os.name }} - Clang, ${{ matrix.shared_lib.name }}
|
||||||
path: |
|
path: |
|
||||||
build/src/libnfd.a
|
build/src/*
|
||||||
build/test/test_*
|
build/test/*
|
||||||
|
|
||||||
build-windows-msvc:
|
build-windows-msvc:
|
||||||
|
|
||||||
name: Windows latest - MSVC
|
name: Windows latest - MSVC, ${{ matrix.shared_lib.name }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
shared_lib: [ {flag: OFF, name: Static}, {flag: ON, name: Shared} ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DNFD_BUILD_TESTS=ON ..
|
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DBUILD_SHARED_LIBS=${{ matrix.shared_lib.flag }} -DNFD_BUILD_TESTS=ON ..
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target install --config Release
|
run: cmake --build build --target install --config Release
|
||||||
- name: Upload test binaries
|
- name: Upload test binaries
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Windows latest - MSVC
|
name: Windows latest - MSVC, ${{ matrix.shared_lib.name }}
|
||||||
path: |
|
path: |
|
||||||
build/src/Release/nfd.lib
|
build/src/Release/*
|
||||||
build/test/Release/test_*
|
build/test/Release/*
|
||||||
|
|
||||||
build-windows-clang:
|
build-windows-clang:
|
||||||
|
|
||||||
name: Windows latest - Clang
|
name: Windows latest - Clang, Static
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -104,14 +138,14 @@ jobs:
|
|||||||
- name: Upload test binaries
|
- name: Upload test binaries
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Windows latest - Clang
|
name: Windows latest - Clang, Static
|
||||||
path: |
|
path: |
|
||||||
build/src/Release/nfd.lib
|
build/src/Release/*
|
||||||
build/test/Release/test_*
|
build/test/Release/*
|
||||||
|
|
||||||
build-windows-mingw:
|
build-windows-mingw:
|
||||||
|
|
||||||
name: Windows latest - MinGW
|
name: Windows latest - MinGW, Static
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
@ -130,13 +164,13 @@ jobs:
|
|||||||
mingw-w64-x86_64-gcc
|
mingw-w64-x86_64-gcc
|
||||||
mingw-w64-x86_64-cmake
|
mingw-w64-x86_64-cmake
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_BUILD_TESTS=ON ..
|
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Wall -Wextra -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_BUILD_TESTS=ON ..
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target install
|
run: cmake --build build --target install
|
||||||
- name: Upload test binaries
|
- name: Upload test binaries
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Windows latest - MinGW
|
name: Windows latest - MinGW, Static
|
||||||
path: |
|
path: |
|
||||||
build/src/libnfd.a
|
build/src/*
|
||||||
build/test/test_*
|
build/test/*
|
||||||
|
24
deps/nfde/CMakeLists.txt
vendored
24
deps/nfde/CMakeLists.txt
vendored
@ -1,5 +1,14 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(nativefiledialog-extended)
|
project(nativefiledialog-extended VERSION 1.1.1)
|
||||||
|
|
||||||
|
set(nfd_ROOT_PROJECT OFF)
|
||||||
|
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||||
|
set(nfd_ROOT_PROJECT ON)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
option(BUILD_SHARED_LIBS "Build a shared library instead of static" OFF)
|
||||||
|
option(NFD_BUILD_TESTS "Build tests for nfd" ${nfd_ROOT_PROJECT})
|
||||||
|
option(NFD_INSTALL "Generate install target for nfd" ${nfd_ROOT_PROJECT})
|
||||||
|
|
||||||
if(NOT MSVC)
|
if(NOT MSVC)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
|
||||||
@ -30,12 +39,19 @@ message("nfd Compiler: ${nfd_COMPILER}")
|
|||||||
|
|
||||||
# Use latest C++ by default (should be the best one), but let user override it
|
# Use latest C++ by default (should be the best one), but let user override it
|
||||||
if(NOT DEFINED CMAKE_CXX_STANDARD)
|
if(NOT DEFINED CMAKE_CXX_STANDARD)
|
||||||
set (CMAKE_CXX_STANDARD 23)
|
if(CMAKE_VERSION VERSION_LESS "3.8")
|
||||||
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
|
elseif(CMAKE_VERSION VERSION_LESS "3.12")
|
||||||
|
set (CMAKE_CXX_STANDARD 17)
|
||||||
|
elseif(CMAKE_VERSION VERSION_LESS "3.20")
|
||||||
|
set (CMAKE_CXX_STANDARD 20)
|
||||||
|
else()
|
||||||
|
set (CMAKE_CXX_STANDARD 23)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
option(NFD_BUILD_TESTS "Build tests for nfd" OFF)
|
|
||||||
if(${NFD_BUILD_TESTS})
|
if(${NFD_BUILD_TESTS})
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
endif()
|
endif()
|
||||||
|
34
deps/nfde/README.md
vendored
34
deps/nfde/README.md
vendored
@ -43,6 +43,8 @@ Features added in Native File Dialog Extended:
|
|||||||
|
|
||||||
There is also significant code refractoring, especially for the Windows implementation.
|
There is also significant code refractoring, especially for the Windows implementation.
|
||||||
|
|
||||||
|
The [wiki](https://github.com/btzy/nativefiledialog-extended/wiki) keeps track of known language bindings and known popular projects that depend on this library.
|
||||||
|
|
||||||
# Basic Usage
|
# Basic Usage
|
||||||
|
|
||||||
```C
|
```C
|
||||||
@ -84,9 +86,12 @@ If you are using a platform abstraction framework such as SDL or GLFW, also see
|
|||||||
|
|
||||||
# Screenshots #
|
# Screenshots #
|
||||||
|
|
||||||
![Windows 10](screens/open_win10.png?raw=true)
|
![Windows 10](screens/open_win10.png?raw=true#gh-light-mode-only)
|
||||||
![MacOS 10.13](screens/open_macos_11.0.png?raw=true)
|
![Windows 10](screens/open_win10_dark.png?raw=true#gh-dark-mode-only)
|
||||||
![GTK3 on Ubuntu 20.04](screens/open_gtk3.png?raw=true)
|
![MacOS 10.13](screens/open_macos_11.0.png?raw=true#gh-light-mode-only)
|
||||||
|
![MacOS 10.13](screens/open_macos_11.0_dark.png?raw=true#gh-dark-mode-only)
|
||||||
|
![GTK3 on Ubuntu 20.04](screens/open_gtk3.png?raw=true#gh-light-mode-only)
|
||||||
|
![GTK3 on Ubuntu 20.04](screens/open_gtk3_dark.png?raw=true#gh-dark-mode-only)
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
@ -99,6 +104,9 @@ target_link_libraries(MyProgram PRIVATE nfd)
|
|||||||
```
|
```
|
||||||
Make sure that you also have the needed [dependencies](#dependencies).
|
Make sure that you also have the needed [dependencies](#dependencies).
|
||||||
|
|
||||||
|
When included as a subproject, sample programs are not built and the install target is disabled by default.
|
||||||
|
Add `-DNFD_BUILD_TESTS=ON` to build sample programs and `-DNFD_INSTALL=ON` to enable the install target.
|
||||||
|
|
||||||
## Standalone Library
|
## Standalone Library
|
||||||
If you want to build the standalone static library,
|
If you want to build the standalone static library,
|
||||||
execute the following commands (starting from the project root directory):
|
execute the following commands (starting from the project root directory):
|
||||||
@ -114,8 +122,8 @@ and build the project (in release mode) there.
|
|||||||
If you are developing NFDe, you may want to do `-DCMAKE_BUILD_TYPE=Debug`
|
If you are developing NFDe, you may want to do `-DCMAKE_BUILD_TYPE=Debug`
|
||||||
to build a debug version of the library instead.
|
to build a debug version of the library instead.
|
||||||
|
|
||||||
If you want to build the sample programs,
|
When building as a standalone library, sample programs are built and the install target is enabled by default.
|
||||||
add `-DNFD_BUILD_TESTS=ON` (sample programs are not built by default).
|
Add `-DNFD_BUILD_TESTS=OFF` to disable building sample programs and `-DNFD_INSTALL=OFF` to disable the install target.
|
||||||
|
|
||||||
On Linux, if you want to use the Flatpak desktop portal instead of GTK, add `-DNFD_PORTAL=ON`. (Otherwise, GTK will be used.) See the "Usage" section below for more information.
|
On Linux, if you want to use the Flatpak desktop portal instead of GTK, add `-DNFD_PORTAL=ON`. (Otherwise, GTK will be used.) See the "Usage" section below for more information.
|
||||||
|
|
||||||
@ -148,10 +156,10 @@ Make sure `libgtk-3-dev` is installed on your system.
|
|||||||
Make sure `libdbus-1-dev` is installed on your system.
|
Make sure `libdbus-1-dev` is installed on your system.
|
||||||
|
|
||||||
### MacOS
|
### MacOS
|
||||||
On MacOS, add `AppKit` to the list of frameworks.
|
On MacOS, add `AppKit` and `UniformTypeIdentifiers` to the list of frameworks.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
On Windows (both MSVC and MinGW), ensure you are building against `ole32.lib` and `uuid.lib`.
|
On Windows (both MSVC and MinGW), ensure you are building against `ole32.lib`, `uuid.lib`, and `shell32.lib`.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
@ -244,11 +252,11 @@ SDL_Quit(); // Then deinitialize SDL2
|
|||||||
|
|
||||||
On Linux, you can use the portal implementation instead of GTK, which will open the "native" file chooser selected by the OS or customized by the user. The user must have `xdg-desktop-portal` and a suitable backend installed (this comes pre-installed with most common desktop distros), otherwise `NFD_ERROR` will be returned.
|
On Linux, you can use the portal implementation instead of GTK, which will open the "native" file chooser selected by the OS or customized by the user. The user must have `xdg-desktop-portal` and a suitable backend installed (this comes pre-installed with most common desktop distros), otherwise `NFD_ERROR` will be returned.
|
||||||
|
|
||||||
The portal implementation is much less battle-tested than the GTK implementation. There may be bugs — please report them on the issue tracker.
|
|
||||||
|
|
||||||
To use the portal implementation, add `-DNFD_PORTAL=ON` to the build command.
|
To use the portal implementation, add `-DNFD_PORTAL=ON` to the build command.
|
||||||
|
|
||||||
*Note: Setting a default path is not supported by the portal implementation, and any default path passed to NFDe will be ignored. This is a limitation of the portal API, so there is no way NFDe can work around it.*
|
*Note: Setting a default path is not supported by the portal implementation, and any default path passed to NFDe will be ignored. This is a limitation of the portal API, so there is no way NFDe can work around it. If this feature is something you desire, please show your interest on https://github.com/flatpak/xdg-desktop-portal/pull/874.*
|
||||||
|
|
||||||
|
*Note 2: The folder picker is only supported on org.freedesktop.portal.FileChooser interface version >= 3, which corresponds to xdg-desktop-portal version >= 1.7.1. `NFD_PickFolder()` will query the interface version at runtime, and return `NFD_ERROR` if the version is too low.
|
||||||
|
|
||||||
### What is a portal?
|
### What is a portal?
|
||||||
|
|
||||||
@ -256,6 +264,12 @@ Unlike Windows and MacOS, Linux does not have a file chooser baked into the oper
|
|||||||
|
|
||||||
Flatpak was introduced in 2015, and with it came a standardized interface to open a file chooser. Applications using this interface did not need to come with a file chooser, and could use the one provided by Flatpak. This interface became known as the desktop portal, and its use expanded to non-Flatpak applications. Now, most major desktop Linux distros come with the desktop portal installed, with file choosers that fit the theme of the distro. Users can also install a different portal backend if desired. There are currently two known backends: GTK and KDE. (XFCE does not currently seem to have a portal backend.)
|
Flatpak was introduced in 2015, and with it came a standardized interface to open a file chooser. Applications using this interface did not need to come with a file chooser, and could use the one provided by Flatpak. This interface became known as the desktop portal, and its use expanded to non-Flatpak applications. Now, most major desktop Linux distros come with the desktop portal installed, with file choosers that fit the theme of the distro. Users can also install a different portal backend if desired. There are currently two known backends: GTK and KDE. (XFCE does not currently seem to have a portal backend.)
|
||||||
|
|
||||||
|
## Platform-specific Quirks
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
|
||||||
|
- If the MacOS deployment target is ≥ 11.0, the [allowedContentTypes](https://developer.apple.com/documentation/appkit/nssavepanel/3566857-allowedcontenttypes?language=objc) property of NSSavePanel is used instead of the deprecated [allowedFileTypes](https://developer.apple.com/documentation/appkit/nssavepanel/1534419-allowedfiletypes?language=objc) property for file filters. Thus, if you are filtering by a custom file extension specific to your application, you will need to define the data type in your `Info.plist` file as per the [Apple documentation](https://developer.apple.com/documentation/uniformtypeidentifiers/defining_file_and_data_types_for_your_app). (It is possible to force NFDe to use allowedFileTypes by adding `-DNFD_USE_ALLOWEDCONTENTTYPES_IF_AVAILABLE=OFF` to your CMake build command, but this is not recommended. If you need to support older MacOS versions, you should be setting the correct deployment target instead.)
|
||||||
|
|
||||||
# Known Limitations #
|
# Known Limitations #
|
||||||
|
|
||||||
- No support for Windows XP's legacy dialogs such as `GetOpenFileName`. (There are no plans to support this; you shouldn't be still using Windows XP anyway.)
|
- No support for Windows XP's legacy dialogs such as `GetOpenFileName`. (There are no plans to support this; you shouldn't be still using Windows XP anyway.)
|
||||||
|
72
deps/nfde/src/CMakeLists.txt
vendored
72
deps/nfde/src/CMakeLists.txt
vendored
@ -25,17 +25,49 @@ if(nfd_PLATFORM STREQUAL PLATFORM_UNIX)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
|
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
|
||||||
|
# For setting the filter list, macOS introduced allowedContentTypes in version 11.0 and deprecated allowedFileTypes in 12.0.
|
||||||
|
# By default (set to ON), NFDe will use allowedContentTypes when targeting macOS >= 11.0.
|
||||||
|
# Set this option to OFF to always use allowedFileTypes regardless of the target macOS version.
|
||||||
|
# This is mainly needed for applications that are built on macOS >= 11.0 but should be able to run on lower versions
|
||||||
|
# and should not be used otherwise.
|
||||||
|
option(NFD_USE_ALLOWEDCONTENTTYPES_IF_AVAILABLE "Use allowedContentTypes for filter lists on macOS >= 11.0" ON)
|
||||||
|
|
||||||
find_library(APPKIT_LIBRARY AppKit)
|
find_library(APPKIT_LIBRARY AppKit)
|
||||||
|
if(NFD_USE_ALLOWEDCONTENTTYPES_IF_AVAILABLE)
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
check_cxx_source_compiles(
|
||||||
|
"
|
||||||
|
#include <Availability.h>
|
||||||
|
#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || !defined(__MAC_11_0) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_11_0
|
||||||
|
static_assert(false);
|
||||||
|
#endif
|
||||||
|
int main() { return 0; }
|
||||||
|
"
|
||||||
|
NFD_USE_ALLOWEDCONTENTTYPES
|
||||||
|
)
|
||||||
|
if(NFD_USE_ALLOWEDCONTENTTYPES)
|
||||||
|
find_library(UNIFORMTYPEIDENTIFIERS_LIBRARY UniformTypeIdentifiers)
|
||||||
|
if(NOT UNIFORMTYPEIDENTIFIERS_LIBRARY)
|
||||||
|
message(FATAL_ERROR "UniformTypeIdentifiers framework is not available even though we are targeting macOS >= 11.0")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
list(APPEND SOURCE_FILES nfd_cocoa.m)
|
list(APPEND SOURCE_FILES nfd_cocoa.m)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Define the library
|
# Define the library
|
||||||
add_library(${TARGET_NAME}
|
add_library(${TARGET_NAME} ${SOURCE_FILES})
|
||||||
${SOURCE_FILES})
|
|
||||||
|
if (BUILD_SHARED_LIBS)
|
||||||
|
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_EXPORT INTERFACE NFD_SHARED)
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Allow includes from include/
|
# Allow includes from include/
|
||||||
target_include_directories(${TARGET_NAME}
|
target_include_directories(${TARGET_NAME}
|
||||||
PUBLIC include/)
|
PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:include>
|
||||||
|
)
|
||||||
|
|
||||||
if(nfd_PLATFORM STREQUAL PLATFORM_UNIX)
|
if(nfd_PLATFORM STREQUAL PLATFORM_UNIX)
|
||||||
if(NOT NFD_PORTAL)
|
if(NOT NFD_PORTAL)
|
||||||
@ -45,15 +77,25 @@ if(nfd_PLATFORM STREQUAL PLATFORM_UNIX)
|
|||||||
target_include_directories(${TARGET_NAME}
|
target_include_directories(${TARGET_NAME}
|
||||||
PRIVATE ${DBUS_INCLUDE_DIRS})
|
PRIVATE ${DBUS_INCLUDE_DIRS})
|
||||||
target_link_libraries(${TARGET_NAME}
|
target_link_libraries(${TARGET_NAME}
|
||||||
PRIVATE ${DBUS_LIBRARIES})
|
PRIVATE ${DBUS_LINK_LIBRARIES})
|
||||||
target_compile_definitions(${TARGET_NAME}
|
target_compile_definitions(${TARGET_NAME}
|
||||||
PUBLIC NFD_PORTAL)
|
PUBLIC NFD_PORTAL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
option(NFD_APPEND_EXTENSION "Automatically append file extension to an extensionless selection in SaveDialog()" OFF)
|
||||||
|
if(NFD_APPEND_EXTENSION)
|
||||||
|
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_APPEND_EXTENSION)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
|
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
|
||||||
target_link_libraries(${TARGET_NAME}
|
if(NFD_USE_ALLOWEDCONTENTTYPES)
|
||||||
PRIVATE ${APPKIT_LIBRARY})
|
target_link_libraries(${TARGET_NAME} PRIVATE ${APPKIT_LIBRARY} ${UNIFORMTYPEIDENTIFIERS_LIBRARY})
|
||||||
|
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_MACOS_ALLOWEDCONTENTTYPES=1)
|
||||||
|
else()
|
||||||
|
target_link_libraries(${TARGET_NAME} PRIVATE ${APPKIT_LIBRARY})
|
||||||
|
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_MACOS_ALLOWEDCONTENTTYPES=0)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(nfd_COMPILER STREQUAL COMPILER_MSVC)
|
if(nfd_COMPILER STREQUAL COMPILER_MSVC)
|
||||||
@ -71,6 +113,20 @@ if(nfd_COMPILER STREQUAL COMPILER_GNU)
|
|||||||
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions -fno-rtti)
|
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions -fno-rtti)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADER_FILES}")
|
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||||
|
PUBLIC_HEADER "${PUBLIC_HEADER_FILES}"
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||||
|
|
||||||
install(TARGETS ${TARGET_NAME} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib PUBLIC_HEADER DESTINATION include)
|
if (NFD_INSTALL)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
install(TARGETS ${TARGET_NAME} EXPORT ${TARGET_NAME}-export
|
||||||
|
LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||||
|
)
|
||||||
|
install(EXPORT ${TARGET_NAME}-export
|
||||||
|
DESTINATION lib/cmake/${TARGET_NAME}
|
||||||
|
NAMESPACE ${TARGET_NAME}::
|
||||||
|
FILE ${TARGET_NAME}-config.cmake
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
114
deps/nfde/src/include/nfd.h
vendored
114
deps/nfde/src/include/nfd.h
vendored
@ -10,6 +10,23 @@
|
|||||||
#ifndef _NFD_H
|
#ifndef _NFD_H
|
||||||
#define _NFD_H
|
#define _NFD_H
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#if defined(NFD_EXPORT)
|
||||||
|
#define NFD_API __declspec(dllexport)
|
||||||
|
#elif defined(NFD_SHARED)
|
||||||
|
#define NFD_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#if defined(NFD_EXPORT) || defined(NFD_SHARED)
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define NFD_API __attribute__((visibility("default")))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef NFD_API
|
||||||
|
#define NFD_API
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
@ -64,60 +81,60 @@ typedef struct {
|
|||||||
|
|
||||||
/* free a file path that was returned by the dialogs */
|
/* free a file path that was returned by the dialogs */
|
||||||
/* Note: use NFD_PathSet_FreePath to free path from pathset instead of this function */
|
/* Note: use NFD_PathSet_FreePath to free path from pathset instead of this function */
|
||||||
void NFD_FreePathN(nfdnchar_t* filePath);
|
NFD_API void NFD_FreePathN(nfdnchar_t* filePath);
|
||||||
|
|
||||||
/* initialize NFD - call this for every thread that might use NFD, before calling any other NFD
|
/* initialize NFD - call this for every thread that might use NFD, before calling any other NFD
|
||||||
* functions on that thread */
|
* functions on that thread */
|
||||||
nfdresult_t NFD_Init(void);
|
NFD_API nfdresult_t NFD_Init(void);
|
||||||
|
|
||||||
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
|
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
|
||||||
void NFD_Quit(void);
|
NFD_API void NFD_Quit(void);
|
||||||
|
|
||||||
/* single file open dialog */
|
/* single file open dialog */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
||||||
* NFD_OKAY */
|
* NFD_OKAY */
|
||||||
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
||||||
/* If defaultPath is NULL, the operating system will decide */
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
|
NFD_API nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
|
||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount,
|
nfdfiltersize_t filterCount,
|
||||||
const nfdnchar_t* defaultPath);
|
const nfdnchar_t* defaultPath);
|
||||||
|
|
||||||
/* multiple file open dialog */
|
/* multiple file open dialog */
|
||||||
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
|
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
|
||||||
* returns NFD_OKAY */
|
* returns NFD_OKAY */
|
||||||
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
||||||
/* If defaultPath is NULL, the operating system will decide */
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
NFD_API nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount,
|
nfdfiltersize_t filterCount,
|
||||||
const nfdnchar_t* defaultPath);
|
const nfdnchar_t* defaultPath);
|
||||||
|
|
||||||
/* save dialog */
|
/* save dialog */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
||||||
* NFD_OKAY */
|
* NFD_OKAY */
|
||||||
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
||||||
/* If defaultPath is NULL, the operating system will decide */
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
|
NFD_API nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
|
||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount,
|
nfdfiltersize_t filterCount,
|
||||||
const nfdnchar_t* defaultPath,
|
const nfdnchar_t* defaultPath,
|
||||||
const nfdnchar_t* defaultName);
|
const nfdnchar_t* defaultName);
|
||||||
|
|
||||||
/* select folder dialog */
|
/* select folder dialog */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
||||||
* NFD_OKAY */
|
* NFD_OKAY */
|
||||||
/* If defaultPath is NULL, the operating system will decide */
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath);
|
NFD_API nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath);
|
||||||
|
|
||||||
/* Get last error -- set when nfdresult_t returns NFD_ERROR */
|
/* Get last error -- set when nfdresult_t returns NFD_ERROR */
|
||||||
/* Returns the last error that was set, or NULL if there is no error. */
|
/* Returns the last error that was set, or NULL if there is no error. */
|
||||||
/* The memory is owned by NFD and should not be freed by user code. */
|
/* The memory is owned by NFD and should not be freed by user code. */
|
||||||
/* This is *always* ASCII printable characters, so it can be interpreted as UTF-8 without any
|
/* This is *always* ASCII printable characters, so it can be interpreted as UTF-8 without any
|
||||||
* conversion. */
|
* conversion. */
|
||||||
const char* NFD_GetError(void);
|
NFD_API const char* NFD_GetError(void);
|
||||||
/* clear the error */
|
/* clear the error */
|
||||||
void NFD_ClearError(void);
|
NFD_API void NFD_ClearError(void);
|
||||||
|
|
||||||
/* path set operations */
|
/* path set operations */
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -131,36 +148,37 @@ typedef unsigned int nfdpathsetsize_t;
|
|||||||
/* Gets the number of entries stored in pathSet */
|
/* Gets the number of entries stored in pathSet */
|
||||||
/* note that some paths might be invalid (NFD_ERROR will be returned by NFD_PathSet_GetPath), so we
|
/* note that some paths might be invalid (NFD_ERROR will be returned by NFD_PathSet_GetPath), so we
|
||||||
* might not actually have this number of usable paths */
|
* might not actually have this number of usable paths */
|
||||||
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count);
|
NFD_API nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count);
|
||||||
/* Gets the UTF-8 path at offset index */
|
/* Gets the UTF-8 path at offset index */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_PathSet_FreePathN() if this function
|
/* It is the caller's responsibility to free `outPath` via NFD_PathSet_FreePathN() if this function
|
||||||
* returns NFD_OKAY */
|
* returns NFD_OKAY */
|
||||||
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
|
NFD_API nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
|
||||||
nfdpathsetsize_t index,
|
nfdpathsetsize_t index,
|
||||||
nfdnchar_t** outPath);
|
nfdnchar_t** outPath);
|
||||||
/* Free the path gotten by NFD_PathSet_GetPathN */
|
/* Free the path gotten by NFD_PathSet_GetPathN */
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define NFD_PathSet_FreePathN NFD_FreePathN
|
#define NFD_PathSet_FreePathN NFD_FreePathN
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
#define NFD_PathSet_FreePathN NFD_FreePathN
|
#define NFD_PathSet_FreePathN NFD_FreePathN
|
||||||
#else
|
#else
|
||||||
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath);
|
NFD_API void NFD_PathSet_FreePathN(const nfdnchar_t* filePath);
|
||||||
#endif // _WIN32, __APPLE__
|
#endif // _WIN32, __APPLE__
|
||||||
|
|
||||||
/* Gets an enumerator of the path set. */
|
/* Gets an enumerator of the path set. */
|
||||||
/* It is the caller's responsibility to free `enumerator` via NFD_PathSet_FreeEnum() if this
|
/* It is the caller's responsibility to free `enumerator` via NFD_PathSet_FreeEnum() if this
|
||||||
* function returns NFD_OKAY, and it should be freed before freeing the pathset. */
|
* function returns NFD_OKAY, and it should be freed before freeing the pathset. */
|
||||||
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator);
|
NFD_API nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet,
|
||||||
|
nfdpathsetenum_t* outEnumerator);
|
||||||
/* Frees an enumerator of the path set. */
|
/* Frees an enumerator of the path set. */
|
||||||
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator);
|
NFD_API void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator);
|
||||||
/* Gets the next item from the path set enumerator.
|
/* Gets the next item from the path set enumerator.
|
||||||
* If there are no more items, then *outPaths will be set to NULL. */
|
* If there are no more items, then *outPaths will be set to NULL. */
|
||||||
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePath() if this
|
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePath() if this
|
||||||
* function returns NFD_OKAY and `*outPath` is not null */
|
* function returns NFD_OKAY and `*outPath` is not null */
|
||||||
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath);
|
NFD_API nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath);
|
||||||
|
|
||||||
/* Free the pathSet */
|
/* Free the pathSet */
|
||||||
void NFD_PathSet_Free(const nfdpathset_t* pathSet);
|
NFD_API void NFD_PathSet_Free(const nfdpathset_t* pathSet);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
@ -177,50 +195,50 @@ typedef struct {
|
|||||||
/* UTF-8 compatibility functions */
|
/* UTF-8 compatibility functions */
|
||||||
|
|
||||||
/* free a file path that was returned */
|
/* free a file path that was returned */
|
||||||
void NFD_FreePathU8(nfdu8char_t* outPath);
|
NFD_API void NFD_FreePathU8(nfdu8char_t* outPath);
|
||||||
|
|
||||||
/* single file open dialog */
|
/* single file open dialog */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
* NFD_OKAY */
|
* NFD_OKAY */
|
||||||
nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath,
|
NFD_API nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath,
|
||||||
const nfdu8filteritem_t* filterList,
|
|
||||||
nfdfiltersize_t count,
|
|
||||||
const nfdu8char_t* defaultPath);
|
|
||||||
|
|
||||||
/* multiple file open dialog */
|
|
||||||
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
|
|
||||||
* returns NFD_OKAY */
|
|
||||||
nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths,
|
|
||||||
const nfdu8filteritem_t* filterList,
|
const nfdu8filteritem_t* filterList,
|
||||||
nfdfiltersize_t count,
|
nfdfiltersize_t count,
|
||||||
const nfdu8char_t* defaultPath);
|
const nfdu8char_t* defaultPath);
|
||||||
|
|
||||||
|
/* multiple file open dialog */
|
||||||
|
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
|
||||||
|
* returns NFD_OKAY */
|
||||||
|
NFD_API nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths,
|
||||||
|
const nfdu8filteritem_t* filterList,
|
||||||
|
nfdfiltersize_t count,
|
||||||
|
const nfdu8char_t* defaultPath);
|
||||||
|
|
||||||
/* save dialog */
|
/* save dialog */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
* NFD_OKAY */
|
* NFD_OKAY */
|
||||||
nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath,
|
NFD_API nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath,
|
||||||
const nfdu8filteritem_t* filterList,
|
const nfdu8filteritem_t* filterList,
|
||||||
nfdfiltersize_t count,
|
nfdfiltersize_t count,
|
||||||
const nfdu8char_t* defaultPath,
|
const nfdu8char_t* defaultPath,
|
||||||
const nfdu8char_t* defaultName);
|
const nfdu8char_t* defaultName);
|
||||||
|
|
||||||
/* select folder dialog */
|
/* select folder dialog */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
* NFD_OKAY */
|
* NFD_OKAY */
|
||||||
nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath);
|
NFD_API nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath);
|
||||||
|
|
||||||
/* Get the UTF-8 path at offset index */
|
/* Get the UTF-8 path at offset index */
|
||||||
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
* NFD_OKAY */
|
* NFD_OKAY */
|
||||||
nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet,
|
NFD_API nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet,
|
||||||
nfdpathsetsize_t index,
|
nfdpathsetsize_t index,
|
||||||
nfdu8char_t** outPath);
|
nfdu8char_t** outPath);
|
||||||
|
|
||||||
/* Gets the next item from the path set enumerator.
|
/* Gets the next item from the path set enumerator.
|
||||||
* If there are no more items, then *outPaths will be set to NULL. */
|
* If there are no more items, then *outPaths will be set to NULL. */
|
||||||
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePathU8() if this
|
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePathU8() if this
|
||||||
* function returns NFD_OKAY and `*outPath` is not null */
|
* function returns NFD_OKAY and `*outPath` is not null */
|
||||||
nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath);
|
NFD_API nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath);
|
||||||
|
|
||||||
#define NFD_PathSet_FreePathU8 NFD_FreePathU8
|
#define NFD_PathSet_FreePathU8 NFD_FreePathU8
|
||||||
|
|
||||||
|
80
deps/nfde/src/nfd_cocoa.m
vendored
80
deps/nfde/src/nfd_cocoa.m
vendored
@ -6,8 +6,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AppKit/AppKit.h>
|
#include <AppKit/AppKit.h>
|
||||||
|
#include <Availability.h>
|
||||||
#include "nfd.h"
|
#include "nfd.h"
|
||||||
|
|
||||||
|
// MacOS is deprecating the allowedFileTypes property in favour of allowedContentTypes, so we have
|
||||||
|
// to introduce this breaking change. Define NFD_MACOS_ALLOWEDCONTENTTYPES to 1 to have it set the
|
||||||
|
// allowedContentTypes property of the SavePanel or OpenPanel. Define
|
||||||
|
// NFD_MACOS_ALLOWEDCONTENTTYPES to 0 to have it set the allowedFileTypes property of the SavePanel
|
||||||
|
// or OpenPanel. If NFD_MACOS_ALLOWEDCONTENTTYPES is undefined, then it will set it to 1 if
|
||||||
|
// __MAC_OS_X_VERSION_MIN_REQUIRED >= 11.0, and 0 otherwise.
|
||||||
|
#if !defined(NFD_MACOS_ALLOWEDCONTENTTYPES)
|
||||||
|
#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || !defined(__MAC_11_0) || \
|
||||||
|
__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_11_0
|
||||||
|
#define NFD_MACOS_ALLOWEDCONTENTTYPES 0
|
||||||
|
#else
|
||||||
|
#define NFD_MACOS_ALLOWEDCONTENTTYPES 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
|
||||||
|
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
static const char* g_errorstr = NULL;
|
static const char* g_errorstr = NULL;
|
||||||
|
|
||||||
static void NFDi_SetError(const char* msg) {
|
static void NFDi_SetError(const char* msg) {
|
||||||
@ -26,10 +46,51 @@ static void NFDi_Free(void* ptr) {
|
|||||||
free(ptr);
|
free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
|
||||||
|
// Returns an NSArray of UTType representing the content types.
|
||||||
|
static NSArray* BuildAllowedContentTypes(const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount) {
|
||||||
|
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
|
||||||
|
// this is the spec to parse (we don't use the friendly name on OS X)
|
||||||
|
const nfdnchar_t* filterSpec = filterList[filterIndex].spec;
|
||||||
|
|
||||||
|
const nfdnchar_t* p_currentFilterBegin = filterSpec;
|
||||||
|
for (const nfdnchar_t* p_filterSpec = filterSpec; *p_filterSpec; ++p_filterSpec) {
|
||||||
|
if (*p_filterSpec == ',') {
|
||||||
|
// add the extension to the array
|
||||||
|
NSString* filterStr = [[NSString alloc]
|
||||||
|
initWithBytes:(const void*)p_currentFilterBegin
|
||||||
|
length:(sizeof(nfdnchar_t) * (p_filterSpec - p_currentFilterBegin))
|
||||||
|
encoding:NSUTF8StringEncoding];
|
||||||
|
UTType* filterType = [UTType typeWithFilenameExtension:filterStr
|
||||||
|
conformingToType:UTTypeData];
|
||||||
|
[filterStr release];
|
||||||
|
if (filterType) [buildFilterList addObject:filterType];
|
||||||
|
p_currentFilterBegin = p_filterSpec + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add the extension to the array
|
||||||
|
NSString* filterStr = [[NSString alloc] initWithUTF8String:p_currentFilterBegin];
|
||||||
|
UTType* filterType = [UTType typeWithFilenameExtension:filterStr
|
||||||
|
conformingToType:UTTypeData];
|
||||||
|
[filterStr release];
|
||||||
|
if (filterType) [buildFilterList addObject:filterType];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSArray* returnArray = [NSArray arrayWithArray:buildFilterList];
|
||||||
|
|
||||||
|
[buildFilterList release];
|
||||||
|
|
||||||
|
assert([returnArray count] != 0);
|
||||||
|
|
||||||
|
return returnArray;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Returns an NSArray of NSString representing the file types.
|
||||||
static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList,
|
static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount) {
|
nfdfiltersize_t filterCount) {
|
||||||
// Commas and semicolons are the same thing on this platform
|
|
||||||
|
|
||||||
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
|
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
|
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
|
||||||
@ -61,6 +122,7 @@ static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList,
|
|||||||
|
|
||||||
return returnArray;
|
return returnArray;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void AddFilterListToDialog(NSSavePanel* dialog,
|
static void AddFilterListToDialog(NSSavePanel* dialog,
|
||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
@ -71,11 +133,15 @@ static void AddFilterListToDialog(NSSavePanel* dialog,
|
|||||||
|
|
||||||
assert(filterList);
|
assert(filterList);
|
||||||
|
|
||||||
// make NSArray of file types
|
// Make NSArray of file types and set it on the dialog
|
||||||
|
// We use setAllowedFileTypes or setAllowedContentTypes depending on the deployment target
|
||||||
|
#if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
|
||||||
|
NSArray* allowedContentTypes = BuildAllowedContentTypes(filterList, filterCount);
|
||||||
|
[dialog setAllowedContentTypes:allowedContentTypes];
|
||||||
|
#else
|
||||||
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
|
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
|
||||||
|
|
||||||
// set it on the dialog
|
|
||||||
[dialog setAllowedFileTypes:allowedFileTypes];
|
[dialog setAllowedFileTypes:allowedFileTypes];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetDefaultPath(NSSavePanel* dialog, const nfdnchar_t* defaultPath) {
|
static void SetDefaultPath(NSSavePanel* dialog, const nfdnchar_t* defaultPath) {
|
||||||
@ -114,6 +180,10 @@ const char* NFD_GetError(void) {
|
|||||||
return g_errorstr;
|
return g_errorstr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NFD_ClearError(void) {
|
||||||
|
NFDi_SetError(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
void NFD_FreePathN(nfdnchar_t* filePath) {
|
void NFD_FreePathN(nfdnchar_t* filePath) {
|
||||||
NFDi_Free((void*)filePath);
|
NFDi_Free((void*)filePath);
|
||||||
}
|
}
|
||||||
|
347
deps/nfde/src/nfd_portal.cpp
vendored
347
deps/nfde/src/nfd_portal.cpp
vendored
@ -14,19 +14,26 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/random.h> // for the random token string
|
#include <unistd.h> // for access()
|
||||||
#include <unistd.h> // for access()
|
|
||||||
|
#if !defined(__has_include) || !defined(__linux__)
|
||||||
|
#include <sys/random.h> // for getrandom() - the random token string
|
||||||
|
#elif __has_include(<sys/random.h>)
|
||||||
|
#include <sys/random.h>
|
||||||
|
#else // for GLIBC < 2.25
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#define getrandom(buf, sz, flags) syscall(SYS_getrandom, buf, sz, flags)
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "nfd.h"
|
#include "nfd.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Define NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION to 0 if you don't want the file extension to be
|
Define NFD_APPEND_EXTENSION if you want the file extension to be appended when missing. Linux
|
||||||
appended when missing. Linux programs usually doesn't append the file extension, but for consistency
|
programs usually don't append the file extension, but for consistency with other OSes you might want
|
||||||
with other OSes we append it by default.
|
to append it. However, when using portals, the file overwrite prompt and the Flatpak sandbox won't
|
||||||
|
know that we appended an extension, so they will not check or whitelist the correct file. Enabling
|
||||||
|
NFD_APPEND_EXTENSION is not recommended for portals.
|
||||||
*/
|
*/
|
||||||
#ifndef NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION
|
|
||||||
#define NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -70,7 +77,10 @@ struct DBusMessage_Guard {
|
|||||||
DBusConnection* dbus_conn;
|
DBusConnection* dbus_conn;
|
||||||
/* current D-Bus error */
|
/* current D-Bus error */
|
||||||
DBusError dbus_err;
|
DBusError dbus_err;
|
||||||
/* current error (may be a pointer to the D-Bus error message above, or a pointer to some string
|
/* current non D-Bus error */
|
||||||
|
constexpr size_t OWNED_ERR_LEN = 1024;
|
||||||
|
char owned_err[OWNED_ERR_LEN]{};
|
||||||
|
/* current error (may be a pointer to dbus_err.message, owned_err, or a pointer to some string
|
||||||
* literal) */
|
* literal) */
|
||||||
const char* err_ptr = nullptr;
|
const char* err_ptr = nullptr;
|
||||||
/* the unique name of our connection, used for the Request handle; owned by D-Bus so we don't free
|
/* the unique name of our connection, used for the Request handle; owned by D-Bus so we don't free
|
||||||
@ -81,6 +91,14 @@ void NFDi_SetError(const char* msg) {
|
|||||||
err_ptr = msg;
|
err_ptr = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NFDi_SetFormattedError(const char* format, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
vsnprintf(owned_err, OWNED_ERR_LEN, format, args);
|
||||||
|
va_end(args);
|
||||||
|
err_ptr = owned_err;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* copy(const T* begin, const T* end, T* out) {
|
T* copy(const T* begin, const T* end, T* out) {
|
||||||
for (; begin != end; ++begin) {
|
for (; begin != end; ++begin) {
|
||||||
@ -112,6 +130,10 @@ constexpr const char* STR_CURRENT_FOLDER = "current_folder";
|
|||||||
constexpr const char* STR_CURRENT_FILE = "current_file";
|
constexpr const char* STR_CURRENT_FILE = "current_file";
|
||||||
constexpr const char* STR_ALL_FILES = "All files";
|
constexpr const char* STR_ALL_FILES = "All files";
|
||||||
constexpr const char* STR_ASTERISK = "*";
|
constexpr const char* STR_ASTERISK = "*";
|
||||||
|
constexpr const char* DBUS_DESTINATION = "org.freedesktop.portal.Desktop";
|
||||||
|
constexpr const char* DBUS_PATH = "/org/freedesktop/portal/desktop";
|
||||||
|
constexpr const char* DBUS_FILECHOOSER_IFACE = "org.freedesktop.portal.FileChooser";
|
||||||
|
constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request";
|
||||||
|
|
||||||
template <bool Multiple, bool Directory>
|
template <bool Multiple, bool Directory>
|
||||||
void AppendOpenFileQueryTitle(DBusMessageIter&);
|
void AppendOpenFileQueryTitle(DBusMessageIter&);
|
||||||
@ -462,7 +484,7 @@ void AppendSaveFileQueryDictEntryCurrentName(DBusMessageIter& sub_iter, const ch
|
|||||||
dbus_message_iter_close_container(&sub_iter, &sub_sub_iter);
|
dbus_message_iter_close_container(&sub_iter, &sub_sub_iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppendSaveFileQueryDictEntryCurrentFolder(DBusMessageIter& sub_iter, const char* path) {
|
void AppendOpenFileQueryDictEntryCurrentFolder(DBusMessageIter& sub_iter, const char* path) {
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
DBusMessageIter sub_sub_iter;
|
DBusMessageIter sub_sub_iter;
|
||||||
DBusMessageIter variant_iter;
|
DBusMessageIter variant_iter;
|
||||||
@ -529,7 +551,8 @@ template <bool Multiple, bool Directory>
|
|||||||
void AppendOpenFileQueryParams(DBusMessage* query,
|
void AppendOpenFileQueryParams(DBusMessage* query,
|
||||||
const char* handle_token,
|
const char* handle_token,
|
||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount) {
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath) {
|
||||||
DBusMessageIter iter;
|
DBusMessageIter iter;
|
||||||
dbus_message_iter_init_append(query, &iter);
|
dbus_message_iter_init_append(query, &iter);
|
||||||
|
|
||||||
@ -543,6 +566,7 @@ void AppendOpenFileQueryParams(DBusMessage* query,
|
|||||||
AppendOpenFileQueryDictEntryMultiple<Multiple>(sub_iter);
|
AppendOpenFileQueryDictEntryMultiple<Multiple>(sub_iter);
|
||||||
AppendOpenFileQueryDictEntryDirectory<Directory>(sub_iter);
|
AppendOpenFileQueryDictEntryDirectory<Directory>(sub_iter);
|
||||||
AppendOpenFileQueryDictEntryFilters<!Directory>(sub_iter, filterList, filterCount);
|
AppendOpenFileQueryDictEntryFilters<!Directory>(sub_iter, filterList, filterCount);
|
||||||
|
AppendOpenFileQueryDictEntryCurrentFolder(sub_iter, defaultPath);
|
||||||
dbus_message_iter_close_container(&iter, &sub_iter);
|
dbus_message_iter_close_container(&iter, &sub_iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,7 +589,7 @@ void AppendSaveFileQueryParams(DBusMessage* query,
|
|||||||
AppendOpenFileQueryDictEntryHandleToken(sub_iter, handle_token);
|
AppendOpenFileQueryDictEntryHandleToken(sub_iter, handle_token);
|
||||||
AppendSaveFileQueryDictEntryFilters(sub_iter, filterList, filterCount, defaultName);
|
AppendSaveFileQueryDictEntryFilters(sub_iter, filterList, filterCount, defaultName);
|
||||||
AppendSaveFileQueryDictEntryCurrentName(sub_iter, defaultName);
|
AppendSaveFileQueryDictEntryCurrentName(sub_iter, defaultName);
|
||||||
AppendSaveFileQueryDictEntryCurrentFolder(sub_iter, defaultPath);
|
AppendOpenFileQueryDictEntryCurrentFolder(sub_iter, defaultPath);
|
||||||
AppendSaveFileQueryDictEntryCurrentFile(sub_iter, defaultPath, defaultName);
|
AppendSaveFileQueryDictEntryCurrentFile(sub_iter, defaultPath, defaultName);
|
||||||
dbus_message_iter_close_container(&iter, &sub_iter);
|
dbus_message_iter_close_container(&iter, &sub_iter);
|
||||||
}
|
}
|
||||||
@ -649,7 +673,9 @@ nfdresult_t ReadResponseResults(DBusMessage* msg, DBusMessageIter& resultsIter)
|
|||||||
return NFD_CANCEL;
|
return NFD_CANCEL;
|
||||||
} else {
|
} else {
|
||||||
// Some error occurred
|
// Some error occurred
|
||||||
NFDi_SetError("D-Bus file dialog interaction was ended abruptly.");
|
NFDi_SetFormattedError(
|
||||||
|
"D-Bus file dialog interaction was ended abruptly with response code %u.",
|
||||||
|
resp_code);
|
||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -720,14 +746,14 @@ nfdresult_t ReadResponseUrisSingle(DBusMessage* msg, const char*& file) {
|
|||||||
const nfdresult_t res = ReadResponseUris(msg, uri_iter);
|
const nfdresult_t res = ReadResponseUris(msg, uri_iter);
|
||||||
if (res != NFD_OKAY) return res; // can be NFD_CANCEL or NFD_ERROR
|
if (res != NFD_OKAY) return res; // can be NFD_CANCEL or NFD_ERROR
|
||||||
if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) {
|
if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) {
|
||||||
NFDi_SetError("D-Bus response signal URI sub iter is not an string.");
|
NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
|
||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
dbus_message_iter_get_basic(&uri_iter, &file);
|
dbus_message_iter_get_basic(&uri_iter, &file);
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION == 1
|
#ifdef NFD_APPEND_EXTENSION
|
||||||
// Read the response URI and selected extension (in the form "*.abc" or "*") (if any). If response
|
// Read the response URI and selected extension (in the form "*.abc" or "*") (if any). If response
|
||||||
// was okay, then returns NFD_OKAY and set file and extn to them (the pointer is set to some string
|
// was okay, then returns NFD_OKAY and set file and extn to them (the pointer is set to some string
|
||||||
// owned by msg, so you should not manually free it). `file` is the user-entered file name, and
|
// owned by msg, so you should not manually free it). `file` is the user-entered file name, and
|
||||||
@ -763,47 +789,37 @@ nfdresult_t ReadResponseUrisSingleAndCurrentExtension(DBusMessage* msg,
|
|||||||
[&tmp_extn](DBusMessageIter& current_filter_iter) {
|
[&tmp_extn](DBusMessageIter& current_filter_iter) {
|
||||||
// current_filter is best_effort, so if we fail, we still return NFD_OKAY.
|
// current_filter is best_effort, so if we fail, we still return NFD_OKAY.
|
||||||
if (dbus_message_iter_get_arg_type(¤t_filter_iter) != DBUS_TYPE_STRUCT) {
|
if (dbus_message_iter_get_arg_type(¤t_filter_iter) != DBUS_TYPE_STRUCT) {
|
||||||
// NFDi_SetError("D-Bus response signal current_filter iter is not a struct.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
DBusMessageIter current_filter_struct_iter;
|
DBusMessageIter current_filter_struct_iter;
|
||||||
dbus_message_iter_recurse(¤t_filter_iter, ¤t_filter_struct_iter);
|
dbus_message_iter_recurse(¤t_filter_iter, ¤t_filter_struct_iter);
|
||||||
if (!dbus_message_iter_next(¤t_filter_struct_iter)) {
|
if (!dbus_message_iter_next(¤t_filter_struct_iter)) {
|
||||||
// NFDi_SetError("D-Bus response signal current_filter struct iter ended
|
|
||||||
// prematurely.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
if (dbus_message_iter_get_arg_type(¤t_filter_struct_iter) !=
|
if (dbus_message_iter_get_arg_type(¤t_filter_struct_iter) !=
|
||||||
DBUS_TYPE_ARRAY) {
|
DBUS_TYPE_ARRAY) {
|
||||||
// NFDi_SetError("D-Bus response signal URI sub iter is not an string.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
DBusMessageIter current_filter_array_iter;
|
DBusMessageIter current_filter_array_iter;
|
||||||
dbus_message_iter_recurse(¤t_filter_struct_iter, ¤t_filter_array_iter);
|
dbus_message_iter_recurse(¤t_filter_struct_iter, ¤t_filter_array_iter);
|
||||||
if (dbus_message_iter_get_arg_type(¤t_filter_array_iter) !=
|
if (dbus_message_iter_get_arg_type(¤t_filter_array_iter) !=
|
||||||
DBUS_TYPE_STRUCT) {
|
DBUS_TYPE_STRUCT) {
|
||||||
// NFDi_SetError("D-Bus response signal current_filter iter is not a struct.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
DBusMessageIter current_filter_extn_iter;
|
DBusMessageIter current_filter_extn_iter;
|
||||||
dbus_message_iter_recurse(¤t_filter_array_iter, ¤t_filter_extn_iter);
|
dbus_message_iter_recurse(¤t_filter_array_iter, ¤t_filter_extn_iter);
|
||||||
if (dbus_message_iter_get_arg_type(¤t_filter_extn_iter) != DBUS_TYPE_UINT32) {
|
if (dbus_message_iter_get_arg_type(¤t_filter_extn_iter) != DBUS_TYPE_UINT32) {
|
||||||
// NFDi_SetError("D-Bus response signal URI sub iter is not an string.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
dbus_uint32_t type;
|
dbus_uint32_t type;
|
||||||
dbus_message_iter_get_basic(¤t_filter_extn_iter, &type);
|
dbus_message_iter_get_basic(¤t_filter_extn_iter, &type);
|
||||||
if (type != 0) {
|
if (type != 0) {
|
||||||
// NFDi_SetError("Wrong filter type.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
if (!dbus_message_iter_next(¤t_filter_extn_iter)) {
|
if (!dbus_message_iter_next(¤t_filter_extn_iter)) {
|
||||||
// NFDi_SetError("D-Bus response signal current_filter struct iter ended
|
|
||||||
// prematurely.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
if (dbus_message_iter_get_arg_type(¤t_filter_extn_iter) != DBUS_TYPE_STRING) {
|
if (dbus_message_iter_get_arg_type(¤t_filter_extn_iter) != DBUS_TYPE_STRING) {
|
||||||
// NFDi_SetError("D-Bus response signal URI sub iter is not an string.");
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
dbus_message_iter_get_basic(¤t_filter_extn_iter, &tmp_extn);
|
dbus_message_iter_get_basic(¤t_filter_extn_iter, &tmp_extn);
|
||||||
@ -942,29 +958,95 @@ class DBusSignalSubscriptionHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Returns true if ch is in [0-9A-Za-z], false otherwise.
|
||||||
|
bool IsHex(char ch) {
|
||||||
|
return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'F') || ('a' <= ch && ch <= 'f');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the hexadecimal value contained in the char. Precondition: IsHex(ch)
|
||||||
|
char ParseHexUnchecked(char ch) {
|
||||||
|
if ('0' <= ch && ch <= '9') return ch - '0';
|
||||||
|
if ('A' <= ch && ch <= 'F') return ch - ('A' - 10);
|
||||||
|
if ('a' <= ch && ch <= 'f') return ch - ('a' - 10);
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
__builtin_unreachable();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the given file URI is decodable (i.e. not malformed), and false otherwise.
|
||||||
|
// If this function returns true, then `out` will be populated with the length of the decoded URI
|
||||||
|
// and `fileUriEnd` will point to the trailing null byte of `fileUri`. Otherwise, `out` and
|
||||||
|
// `fileUriEnd` will be unmodified.
|
||||||
|
bool TryUriDecodeLen(const char* fileUri, size_t& out, const char*& fileUriEnd) {
|
||||||
|
size_t len = 0;
|
||||||
|
while (*fileUri) {
|
||||||
|
if (*fileUri != '%') {
|
||||||
|
++fileUri;
|
||||||
|
} else {
|
||||||
|
if (*(fileUri + 1) == '\0' || *(fileUri + 2) == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!IsHex(*(fileUri + 1)) || !IsHex(*(fileUri + 2))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fileUri += 3;
|
||||||
|
}
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
out = len;
|
||||||
|
fileUriEnd = fileUri;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes the given URI and writes it to `outPath`. The caller must ensure that the given URI is
|
||||||
|
// not malformed (typically with a prior call to `TryUriDecodeLen`). This function does not write
|
||||||
|
// any trailing null character.
|
||||||
|
char* UriDecodeUnchecked(const char* fileUri, const char* fileUriEnd, char* outPath) {
|
||||||
|
while (fileUri != fileUriEnd) {
|
||||||
|
if (*fileUri != '%') {
|
||||||
|
*outPath++ = *fileUri++;
|
||||||
|
} else {
|
||||||
|
++fileUri;
|
||||||
|
const char high_nibble = ParseHexUnchecked(*fileUri++);
|
||||||
|
const char low_nibble = ParseHexUnchecked(*fileUri++);
|
||||||
|
*outPath++ = (high_nibble << 4) | low_nibble;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outPath;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr const char FILE_URI_PREFIX[] = "file://";
|
constexpr const char FILE_URI_PREFIX[] = "file://";
|
||||||
constexpr size_t FILE_URI_PREFIX_LEN = sizeof(FILE_URI_PREFIX) - 1;
|
constexpr size_t FILE_URI_PREFIX_LEN = sizeof(FILE_URI_PREFIX) - 1;
|
||||||
|
|
||||||
// If fileUri starts with "file://", strips that prefix and copies it to a new buffer, and make
|
// If fileUri starts with "file://", strips that prefix and URI-decodes the remaining part to a new
|
||||||
// outPath point to it, and returns NFD_OKAY. Otherwise, does not modify outPath and returns
|
// buffer, and make outPath point to it, and returns NFD_OKAY. Otherwise, does not modify outPath
|
||||||
// NFD_ERROR (with the correct error set)
|
// and returns NFD_ERROR (with the correct error set)
|
||||||
nfdresult_t AllocAndCopyFilePath(const char* fileUri, char*& outPath) {
|
nfdresult_t AllocAndCopyFilePath(const char* fileUri, char*& outPath) {
|
||||||
|
const char* file_uri_iter = fileUri;
|
||||||
const char* prefix_begin = FILE_URI_PREFIX;
|
const char* prefix_begin = FILE_URI_PREFIX;
|
||||||
const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN;
|
const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN;
|
||||||
for (; prefix_begin != prefix_end; ++prefix_begin, ++fileUri) {
|
for (; prefix_begin != prefix_end; ++prefix_begin, ++file_uri_iter) {
|
||||||
if (*prefix_begin != *fileUri) {
|
if (*prefix_begin != *file_uri_iter) {
|
||||||
NFDi_SetError("D-Bus freedesktop portal returned a URI that is not a file URI.");
|
NFDi_SetFormattedError(
|
||||||
|
"D-Bus freedesktop portal returned \"%s\", which is not a file URI.", fileUri);
|
||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size_t len = strlen(fileUri);
|
size_t decoded_len;
|
||||||
char* path_without_prefix = NFDi_Malloc<char>(len + 1);
|
const char* file_uri_end;
|
||||||
copy(fileUri, fileUri + (len + 1), path_without_prefix);
|
if (!TryUriDecodeLen(file_uri_iter, decoded_len, file_uri_end)) {
|
||||||
|
NFDi_SetFormattedError("D-Bus freedesktop portal returned a malformed URI \"%s\".",
|
||||||
|
fileUri);
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
char* const path_without_prefix = NFDi_Malloc<char>(decoded_len + 1);
|
||||||
|
char* const out_end = UriDecodeUnchecked(file_uri_iter, file_uri_end, path_without_prefix);
|
||||||
|
*out_end = '\0';
|
||||||
outPath = path_without_prefix;
|
outPath = path_without_prefix;
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION == 1
|
#ifdef NFD_APPEND_EXTENSION
|
||||||
bool TryGetValidExtension(const char* extn,
|
bool TryGetValidExtension(const char* extn,
|
||||||
const char*& trimmed_extn,
|
const char*& trimmed_extn,
|
||||||
const char*& trimmed_extn_end) {
|
const char*& trimmed_extn_end) {
|
||||||
@ -985,19 +1067,30 @@ bool TryGetValidExtension(const char* extn,
|
|||||||
// expected to be either in the form "*.abc" or "*", but this function will check for it, and ignore
|
// expected to be either in the form "*.abc" or "*", but this function will check for it, and ignore
|
||||||
// the extension if it is not in the correct form.
|
// the extension if it is not in the correct form.
|
||||||
nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn, char*& outPath) {
|
nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn, char*& outPath) {
|
||||||
|
const char* file_uri_iter = fileUri;
|
||||||
const char* prefix_begin = FILE_URI_PREFIX;
|
const char* prefix_begin = FILE_URI_PREFIX;
|
||||||
const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN;
|
const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN;
|
||||||
for (; prefix_begin != prefix_end; ++prefix_begin, ++fileUri) {
|
for (; prefix_begin != prefix_end; ++prefix_begin, ++file_uri_iter) {
|
||||||
if (*prefix_begin != *fileUri) {
|
if (*prefix_begin != *file_uri_iter) {
|
||||||
NFDi_SetError("D-Bus freedesktop portal returned a URI that is not a file URI.");
|
NFDi_SetFormattedError(
|
||||||
|
"D-Bus freedesktop portal returned \"%s\", which is not a file URI.", fileUri);
|
||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* file_end = fileUri;
|
size_t decoded_len;
|
||||||
for (; *file_end != '\0'; ++file_end)
|
const char* file_uri_end;
|
||||||
;
|
if (!TryUriDecodeLen(file_uri_iter, decoded_len, file_uri_end)) {
|
||||||
const char* file_it = file_end;
|
NFDi_SetFormattedError("D-Bus freedesktop portal returned a malformed URI \"%s\".",
|
||||||
|
fileUri);
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* file_it = file_uri_end;
|
||||||
|
// The following loop condition is safe because `FILE_URI_PREFIX` ends with '/',
|
||||||
|
// so we won't iterate past the beginning of the URI.
|
||||||
|
// Also in UTF-8 all non-ASCII code points are encoded using bytes 128-255 so every '.' or '/'
|
||||||
|
// is also '.' or '/' in UTF-8.
|
||||||
do {
|
do {
|
||||||
--file_it;
|
--file_it;
|
||||||
} while (*file_it != '/' && *file_it != '.');
|
} while (*file_it != '/' && *file_it != '.');
|
||||||
@ -1005,16 +1098,17 @@ nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn,
|
|||||||
const char* trimmed_extn_end; // includes the '\0'
|
const char* trimmed_extn_end; // includes the '\0'
|
||||||
if (*file_it == '.' || !TryGetValidExtension(extn, trimmed_extn, trimmed_extn_end)) {
|
if (*file_it == '.' || !TryGetValidExtension(extn, trimmed_extn, trimmed_extn_end)) {
|
||||||
// has file extension already or no valid extension in `extn`
|
// has file extension already or no valid extension in `extn`
|
||||||
++file_end; // includes the '\0'
|
char* const path_without_prefix = NFDi_Malloc<char>(decoded_len + 1);
|
||||||
char* path_without_prefix = NFDi_Malloc<char>(file_end - fileUri);
|
char* const out_end = UriDecodeUnchecked(file_uri_iter, file_uri_end, path_without_prefix);
|
||||||
copy(fileUri, file_end, path_without_prefix);
|
*out_end = '\0';
|
||||||
outPath = path_without_prefix;
|
outPath = path_without_prefix;
|
||||||
} else {
|
} else {
|
||||||
// no file extension and we have a valid extension
|
// no file extension and we have a valid extension
|
||||||
char* path_without_prefix =
|
char* const path_without_prefix =
|
||||||
NFDi_Malloc<char>((file_end - fileUri) + (trimmed_extn_end - trimmed_extn));
|
NFDi_Malloc<char>(decoded_len + (trimmed_extn_end - trimmed_extn));
|
||||||
char* out = copy(fileUri, file_end, path_without_prefix);
|
char* const out_mid = UriDecodeUnchecked(file_uri_iter, file_uri_end, path_without_prefix);
|
||||||
copy(trimmed_extn, trimmed_extn_end, out);
|
char* const out_end = copy(trimmed_extn, trimmed_extn_end, out_mid);
|
||||||
|
*out_end = '\0';
|
||||||
outPath = path_without_prefix;
|
outPath = path_without_prefix;
|
||||||
}
|
}
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
@ -1028,7 +1122,8 @@ nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn,
|
|||||||
template <bool Multiple, bool Directory>
|
template <bool Multiple, bool Directory>
|
||||||
nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg,
|
nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg,
|
||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount) {
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath) {
|
||||||
const char* handle_token_ptr;
|
const char* handle_token_ptr;
|
||||||
char* handle_obj_path = MakeUniqueObjectPath(&handle_token_ptr);
|
char* handle_obj_path = MakeUniqueObjectPath(&handle_token_ptr);
|
||||||
Free_Guard<char> handle_obj_path_guard(handle_obj_path);
|
Free_Guard<char> handle_obj_path_guard(handle_obj_path);
|
||||||
@ -1045,13 +1140,11 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg,
|
|||||||
// TODO: use XOpenDisplay()/XGetInputFocus() to find xid of window... but what should one do on
|
// TODO: use XOpenDisplay()/XGetInputFocus() to find xid of window... but what should one do on
|
||||||
// Wayland?
|
// Wayland?
|
||||||
|
|
||||||
DBusMessage* query = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
|
DBusMessage* query = dbus_message_new_method_call(
|
||||||
"/org/freedesktop/portal/desktop",
|
DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "OpenFile");
|
||||||
"org.freedesktop.portal.FileChooser",
|
|
||||||
"OpenFile");
|
|
||||||
DBusMessage_Guard query_guard(query);
|
DBusMessage_Guard query_guard(query);
|
||||||
AppendOpenFileQueryParams<Multiple, Directory>(
|
AppendOpenFileQueryParams<Multiple, Directory>(
|
||||||
query, handle_token_ptr, filterList, filterCount);
|
query, handle_token_ptr, filterList, filterCount, defaultPath);
|
||||||
|
|
||||||
DBusMessage* reply =
|
DBusMessage* reply =
|
||||||
dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err);
|
dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err);
|
||||||
@ -1090,7 +1183,7 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg,
|
|||||||
DBusMessage* msg = dbus_connection_pop_message(dbus_conn);
|
DBusMessage* msg = dbus_connection_pop_message(dbus_conn);
|
||||||
if (!msg) break;
|
if (!msg) break;
|
||||||
|
|
||||||
if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
|
if (dbus_message_is_signal(msg, DBUS_REQUEST_IFACE, "Response")) {
|
||||||
// this is the response we're looking for
|
// this is the response we're looking for
|
||||||
outMsg = msg;
|
outMsg = msg;
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
@ -1129,10 +1222,8 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg,
|
|||||||
// TODO: use XOpenDisplay()/XGetInputFocus() to find xid of window... but what should one do on
|
// TODO: use XOpenDisplay()/XGetInputFocus() to find xid of window... but what should one do on
|
||||||
// Wayland?
|
// Wayland?
|
||||||
|
|
||||||
DBusMessage* query = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
|
DBusMessage* query = dbus_message_new_method_call(
|
||||||
"/org/freedesktop/portal/desktop",
|
DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "SaveFile");
|
||||||
"org.freedesktop.portal.FileChooser",
|
|
||||||
"SaveFile");
|
|
||||||
DBusMessage_Guard query_guard(query);
|
DBusMessage_Guard query_guard(query);
|
||||||
AppendSaveFileQueryParams(
|
AppendSaveFileQueryParams(
|
||||||
query, handle_token_ptr, filterList, filterCount, defaultPath, defaultName);
|
query, handle_token_ptr, filterList, filterCount, defaultPath, defaultName);
|
||||||
@ -1174,7 +1265,7 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg,
|
|||||||
DBusMessage* msg = dbus_connection_pop_message(dbus_conn);
|
DBusMessage* msg = dbus_connection_pop_message(dbus_conn);
|
||||||
if (!msg) break;
|
if (!msg) break;
|
||||||
|
|
||||||
if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
|
if (dbus_message_is_signal(msg, DBUS_REQUEST_IFACE, "Response")) {
|
||||||
// this is the response we're looking for
|
// this is the response we're looking for
|
||||||
outMsg = msg;
|
outMsg = msg;
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
@ -1188,6 +1279,57 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg,
|
|||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_DBus_GetVersion(dbus_uint32_t& outVersion) {
|
||||||
|
DBusError err; // need a separate error object because we don't want to mess with the old one
|
||||||
|
// if it's stil set
|
||||||
|
dbus_error_init(&err);
|
||||||
|
|
||||||
|
DBusMessage* query = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
|
||||||
|
"/org/freedesktop/portal/desktop",
|
||||||
|
"org.freedesktop.DBus.Properties",
|
||||||
|
"Get");
|
||||||
|
DBusMessage_Guard query_guard(query);
|
||||||
|
{
|
||||||
|
DBusMessageIter iter;
|
||||||
|
dbus_message_iter_init_append(query, &iter);
|
||||||
|
|
||||||
|
constexpr const char* STR_INTERFACE = "org.freedesktop.portal.FileChooser";
|
||||||
|
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_INTERFACE);
|
||||||
|
constexpr const char* STR_VERSION = "version";
|
||||||
|
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessage* reply =
|
||||||
|
dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err);
|
||||||
|
if (!reply) {
|
||||||
|
dbus_error_free(&dbus_err);
|
||||||
|
dbus_move_error(&err, &dbus_err);
|
||||||
|
NFDi_SetError(dbus_err.message);
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
DBusMessage_Guard reply_guard(reply);
|
||||||
|
{
|
||||||
|
DBusMessageIter iter;
|
||||||
|
if (!dbus_message_iter_init(reply, &iter)) {
|
||||||
|
NFDi_SetError("D-Bus reply for version query is missing an argument.");
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
|
||||||
|
NFDi_SetError("D-Bus reply for version query is not a variant.");
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
DBusMessageIter variant_iter;
|
||||||
|
dbus_message_iter_recurse(&iter, &variant_iter);
|
||||||
|
if (dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_UINT32) {
|
||||||
|
NFDi_SetError("D-Bus reply for version query is not a uint32.");
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
dbus_message_iter_get_basic(&variant_iter, &outVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/* public */
|
/* public */
|
||||||
@ -1202,7 +1344,7 @@ void NFD_ClearError(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nfdresult_t NFD_Init(void) {
|
nfdresult_t NFD_Init(void) {
|
||||||
// Initialize dbus_error
|
// Initialize dbus_err
|
||||||
dbus_error_init(&dbus_err);
|
dbus_error_init(&dbus_err);
|
||||||
// Get DBus connection
|
// Get DBus connection
|
||||||
dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_err);
|
dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_err);
|
||||||
@ -1233,37 +1375,35 @@ nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
|
|||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount,
|
nfdfiltersize_t filterCount,
|
||||||
const nfdnchar_t* defaultPath) {
|
const nfdnchar_t* defaultPath) {
|
||||||
(void)defaultPath; // Default path not supported for portal backend
|
|
||||||
|
|
||||||
DBusMessage* msg;
|
DBusMessage* msg;
|
||||||
{
|
{
|
||||||
const nfdresult_t res = NFD_DBus_OpenFile<false, false>(msg, filterList, filterCount);
|
const nfdresult_t res =
|
||||||
|
NFD_DBus_OpenFile<false, false>(msg, filterList, filterCount, defaultPath);
|
||||||
if (res != NFD_OKAY) {
|
if (res != NFD_OKAY) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DBusMessage_Guard msg_guard(msg);
|
DBusMessage_Guard msg_guard(msg);
|
||||||
|
|
||||||
const char* file;
|
const char* uri;
|
||||||
{
|
{
|
||||||
const nfdresult_t res = ReadResponseUrisSingle(msg, file);
|
const nfdresult_t res = ReadResponseUrisSingle(msg, uri);
|
||||||
if (res != NFD_OKAY) {
|
if (res != NFD_OKAY) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AllocAndCopyFilePath(file, *outPath);
|
return AllocAndCopyFilePath(uri, *outPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
||||||
const nfdnfilteritem_t* filterList,
|
const nfdnfilteritem_t* filterList,
|
||||||
nfdfiltersize_t filterCount,
|
nfdfiltersize_t filterCount,
|
||||||
const nfdnchar_t* defaultPath) {
|
const nfdnchar_t* defaultPath) {
|
||||||
(void)defaultPath; // Default path not supported for portal backend
|
|
||||||
|
|
||||||
DBusMessage* msg;
|
DBusMessage* msg;
|
||||||
{
|
{
|
||||||
const nfdresult_t res = NFD_DBus_OpenFile<true, false>(msg, filterList, filterCount);
|
const nfdresult_t res =
|
||||||
|
NFD_DBus_OpenFile<true, false>(msg, filterList, filterCount, defaultPath);
|
||||||
if (res != NFD_OKAY) {
|
if (res != NFD_OKAY) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -1295,51 +1435,67 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
|
|||||||
}
|
}
|
||||||
DBusMessage_Guard msg_guard(msg);
|
DBusMessage_Guard msg_guard(msg);
|
||||||
|
|
||||||
#if NFD_PORTAL_AUTO_APPEND_FILE_EXTENSION == 1
|
#ifdef NFD_APPEND_EXTENSION
|
||||||
const char* file;
|
const char* uri;
|
||||||
const char* extn;
|
const char* extn;
|
||||||
{
|
{
|
||||||
const nfdresult_t res = ReadResponseUrisSingleAndCurrentExtension(msg, file, extn);
|
const nfdresult_t res = ReadResponseUrisSingleAndCurrentExtension(msg, uri, extn);
|
||||||
if (res != NFD_OKAY) {
|
if (res != NFD_OKAY) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AllocAndCopyFilePathWithExtn(file, extn, *outPath);
|
return AllocAndCopyFilePathWithExtn(uri, extn, *outPath);
|
||||||
#else
|
#else
|
||||||
const char* file;
|
const char* uri;
|
||||||
{
|
{
|
||||||
const nfdresult_t res = ReadResponseUrisSingle(msg, file);
|
const nfdresult_t res = ReadResponseUrisSingle(msg, uri);
|
||||||
if (res != NFD_OKAY) {
|
if (res != NFD_OKAY) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AllocAndCopyFilePath(file, *outPath);
|
return AllocAndCopyFilePath(uri, *outPath);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
|
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
|
||||||
(void)defaultPath; // Default path not supported for portal backend
|
(void)defaultPath; // Default path not supported for portal backend
|
||||||
|
|
||||||
|
{
|
||||||
|
dbus_uint32_t version;
|
||||||
|
const nfdresult_t res = NFD_DBus_GetVersion(version);
|
||||||
|
if (res != NFD_OKAY) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (version < 3) {
|
||||||
|
NFDi_SetFormattedError(
|
||||||
|
"The xdg-desktop-portal installed on this system does not support a folder picker; "
|
||||||
|
"at least version 3 of the org.freedesktop.portal.FileChooser interface is "
|
||||||
|
"required but the installed interface version is %u.",
|
||||||
|
version);
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DBusMessage* msg;
|
DBusMessage* msg;
|
||||||
{
|
{
|
||||||
const nfdresult_t res = NFD_DBus_OpenFile<false, true>(msg, nullptr, 0);
|
const nfdresult_t res = NFD_DBus_OpenFile<false, true>(msg, nullptr, 0, defaultPath);
|
||||||
if (res != NFD_OKAY) {
|
if (res != NFD_OKAY) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DBusMessage_Guard msg_guard(msg);
|
DBusMessage_Guard msg_guard(msg);
|
||||||
|
|
||||||
const char* file;
|
const char* uri;
|
||||||
{
|
{
|
||||||
const nfdresult_t res = ReadResponseUrisSingle(msg, file);
|
const nfdresult_t res = ReadResponseUrisSingle(msg, uri);
|
||||||
if (res != NFD_OKAY) {
|
if (res != NFD_OKAY) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AllocAndCopyFilePath(file, *outPath);
|
return AllocAndCopyFilePath(uri, *outPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
|
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
|
||||||
@ -1356,20 +1512,25 @@ nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
|
|||||||
DBusMessage* msg = const_cast<DBusMessage*>(static_cast<const DBusMessage*>(pathSet));
|
DBusMessage* msg = const_cast<DBusMessage*>(static_cast<const DBusMessage*>(pathSet));
|
||||||
DBusMessageIter uri_iter;
|
DBusMessageIter uri_iter;
|
||||||
ReadResponseUrisUnchecked(msg, uri_iter);
|
ReadResponseUrisUnchecked(msg, uri_iter);
|
||||||
while (index > 0) {
|
nfdpathsetsize_t rem_index = index;
|
||||||
--index;
|
while (rem_index > 0) {
|
||||||
|
--rem_index;
|
||||||
if (!dbus_message_iter_next(&uri_iter)) {
|
if (!dbus_message_iter_next(&uri_iter)) {
|
||||||
NFDi_SetError("Index out of bounds.");
|
NFDi_SetFormattedError(
|
||||||
|
"Index out of bounds; you asked for index %u but there are only %u file paths "
|
||||||
|
"available.",
|
||||||
|
index,
|
||||||
|
index - rem_index);
|
||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) {
|
if (dbus_message_iter_get_arg_type(&uri_iter) != DBUS_TYPE_STRING) {
|
||||||
NFDi_SetError("D-Bus response signal URI sub iter is not an string.");
|
NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
|
||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
const char* file;
|
const char* uri;
|
||||||
dbus_message_iter_get_basic(&uri_iter, &file);
|
dbus_message_iter_get_basic(&uri_iter, &uri);
|
||||||
return AllocAndCopyFilePath(file, *outPath);
|
return AllocAndCopyFilePath(uri, *outPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) {
|
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) {
|
||||||
@ -1402,12 +1563,12 @@ nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** out
|
|||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
if (arg_type != DBUS_TYPE_STRING) {
|
if (arg_type != DBUS_TYPE_STRING) {
|
||||||
NFDi_SetError("D-Bus response signal URI sub iter is not an string.");
|
NFDi_SetError("D-Bus response signal URI sub iter is not a string.");
|
||||||
return NFD_ERROR;
|
return NFD_ERROR;
|
||||||
}
|
}
|
||||||
const char* file;
|
const char* uri;
|
||||||
dbus_message_iter_get_basic(&uri_iter, &file);
|
dbus_message_iter_get_basic(&uri_iter, &uri);
|
||||||
const nfdresult_t res = AllocAndCopyFilePath(file, *outPath);
|
const nfdresult_t res = AllocAndCopyFilePath(uri, *outPath);
|
||||||
if (res != NFD_OKAY) return res;
|
if (res != NFD_OKAY) return res;
|
||||||
dbus_message_iter_next(&uri_iter);
|
dbus_message_iter_next(&uri_iter);
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
|
Loading…
Reference in New Issue
Block a user